mirror of
https://github.com/eclipse-sailing-analytics/sailing-analytics.git
synced 2026-09-24 14:38:45 +00:00
Merge branch 'master' into bug5479
This commit is contained in:
@@ -293,6 +293,7 @@ if [[ "$@" == "release" ]]; then
|
||||
cp -v $PROJECT_HOME/java/target/stop $ACDIR/
|
||||
cp -v $PROJECT_HOME/java/target/status $ACDIR/
|
||||
cp -v $PROJECT_HOME/java/target/refreshInstance.sh $ACDIR/
|
||||
cp -v $PROJECT_HOME/java/target/stopReplicating.sh $ACDIR/
|
||||
|
||||
cp -v $PROJECT_HOME/java/target/env.sh $ACDIR/
|
||||
cp -v $PROJECT_HOME/java/target/env-default-rules.sh $ACDIR/
|
||||
@@ -822,6 +823,7 @@ if [[ "$@" == "install" ]] || [[ "$@" == "all" ]]; then
|
||||
cp -v $PROJECT_HOME/java/target/configuration/JavaSE-11.profile $ACDIR/
|
||||
|
||||
cp -v $PROJECT_HOME/java/target/refreshInstance.sh $ACDIR/
|
||||
cp -v $PROJECT_HOME/java/target/stopReplicating.sh $ACDIR/
|
||||
cp -v $PROJECT_HOME/java/target/udpmirror $ACDIR/
|
||||
cp -v $PROJECT_HOME/java/target/http2udpmirror $ACDIR
|
||||
|
||||
@@ -927,6 +929,8 @@ if [[ "$@" == "remote-deploy" ]]; then
|
||||
$SCP_CMD $PROJECT_HOME/java/target/start $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/
|
||||
$SCP_CMD $PROJECT_HOME/java/target/stop $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/
|
||||
$SCP_CMD $PROJECT_HOME/java/target/status $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/
|
||||
$SCP_CMD $PROJECT_HOME/java/target/refreshInstance.sh $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/
|
||||
$SCP_CMD $PROJECT_HOME/java/target/stopReplicating.sh $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/
|
||||
$SCP_CMD $PROJECT_HOME/java/target/udpmirror $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/
|
||||
|
||||
$SCP_CMD $PROJECT_HOME/java/target/http2udpmirror $REMOTE_SERVER_LOGIN:$REMOTE_SERVER/
|
||||
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
# Discover replicas
|
||||
IPs=""
|
||||
for i in $( cat `dirname $0`/regions.txt ); do
|
||||
echo Region: $i >&2
|
||||
IPs="${IPs} $( aws --region $i ec2 describe-instances --filters Name=tag:sailing-analytics-server,Values=tokyo2020 | jq .Reservations[].Instances[].PublicIpAddress -r )"
|
||||
done
|
||||
echo "${IPs}"
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
set -o pipefail
|
||||
logger -t sailing "Cloning security_service DB from eu-west-1 live replica set to local security_service replica set"
|
||||
logger -t sailing "Copying an existing local security_service DB to security_service_bak..."
|
||||
cd /tmp
|
||||
rm -rf /tmp/dump
|
||||
ssh ec2-user@tokyo-ssh.sapsailing.com "set -e; cd /tmp; rm -rf /tmp/dump; mongodump --host live/mongo0.internal.sapsailing.com,mongo1.internal.sapsailing.com,dbserver.internal.sapsailing.com:10203 --db security_service; tar czvpf - dump" | tar xzvpf - && logger -t sailing "mongodump finished with $?. Restoring dump of security_service DB from eu-west-1 locally..." || logger -t sailing "SEVERE: mongodump finished with $?. Aborting..."; exit 1
|
||||
echo 'use security_service_bak
|
||||
db.dropDatabase()
|
||||
db.copyDatabase("security_service", "security_service_bak")
|
||||
quit()' | mongo "mongodb://localhost/security_service_bak?replicaSet=security_service&retryWrites=true&readPreference=nearest" && logger -t sailing "Succesfull, continuing..." || logger -t sailing "SEVERE: mongo finished with $?"; exit 1
|
||||
mongorestore --drop --host security_service/localhost && logger -t sailing "mongorestore finished with $?. Done cloning security_service DB from eu-west-1 live replica set to local tokyo2020 replica set."; rm -rf /tmp/dump || logger -t sailing "SEVERE: mongorestore finished with $?. Aborting..."; rm -rf /tmp/dump; echo 'use security_service
|
||||
db.dropDatabase()
|
||||
db.copyDatabase("security_service_bak", "security_service")
|
||||
quit()' | mongo "mongodb://localhost/security_service_bak?replicaSet=security_service&retryWrites=true&readPreference=nearest"; logger -t sailing "SEVERE: Restored old backup, dropped security_service_bak"; exit 1
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
# Discover replicas
|
||||
IPs=""
|
||||
for i in $( cat `dirname $0`/regions.txt ); do
|
||||
echo Region: $i >&2
|
||||
IPs="${IPs} $( aws --region $i ec2 describe-instances --filters Name=tag:sailing-analytics-server,Values=tokyo2020 | jq .Reservations[].Instances[].PublicIpAddress -r )"
|
||||
done
|
||||
echo "${IPs}"
|
||||
@@ -0,0 +1,71 @@
|
||||
#!/bin/bash
|
||||
TARGET_GROUP_NAME=S-ded-tokyo2020
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "$0 -R <release-name> -b <replication-bearer-token> [-t <instance-type>] [-i <ami-id>] [-k <key-pair-name>]"
|
||||
echo ""
|
||||
echo "-b replication bearer token; mandatory"
|
||||
echo "-i Amazon Machine Image (AMI) ID to use to launch the instance; defaults to latest image tagged with image-type:sailing-analytics-server"
|
||||
echo "-k Key pair name, mapping to the --key-name parameter"
|
||||
echo "-R release name; must be provided to select the release, e.g., build-202106040947"
|
||||
echo "-t Instance type; defaults to ${INSTANCE_TYPE}"
|
||||
echo
|
||||
echo "Example: $0 -b 098toyw098typ9e8/87t9shytp98894y5= -R build-202106041327 -k Jan"
|
||||
echo
|
||||
echo "Will launch as many new replicas in regions $( cat `dirname $0`/regions.txt ) with the release specified with -R"
|
||||
echo "as there are currently healthy auto-replicas registered with teh S-ded-tokyo2020 target group in the region (at least one)."
|
||||
echo "which will register at the master proxy tokyo-ssh.internal.sapsailing.com:8888 and RabbitMQ at"
|
||||
echo "rabbit-ap-northeast-1.sapsailing.com:5672, then when healthy get added to target group S-ded-tokyo2020"
|
||||
echo "in that region, with all auto-replicas registered before removed from the target group."
|
||||
exit 2
|
||||
fi
|
||||
options='R:b:t:i:k:'
|
||||
while getopts $options option
|
||||
do
|
||||
case $option in
|
||||
b) BEARER_TOKEN=$OPTARG;;
|
||||
i) IMAGE_ID=$OPTARG;;
|
||||
k) KEY_NAME=$OPTARG;;
|
||||
R) RELEASE=$OPTARG;;
|
||||
t) INSTANCE_TYPE=$OPTARG;;
|
||||
\?) echo "Invalid option"
|
||||
exit 4;;
|
||||
esac
|
||||
done
|
||||
for REGION in $( cat `dirname $0`/regions.txt ); do
|
||||
export AWS_DEFAULT_REGION=${REGION}
|
||||
echo "Starting replica upgrade process for region ${REGION}"
|
||||
echo "-------------------------------------------------------"
|
||||
TARGET_GROUP_ARN=$( aws elbv2 describe-target-groups --names ${TARGET_GROUP_NAME} | jq -r '.TargetGroups[].TargetGroupArn' )
|
||||
HEALTHY_TARGETS_IN_REGION=$( aws elbv2 describe-target-health --target-group-arn ${TARGET_GROUP_ARN} | jq '.TargetHealthDescriptions | map(select(.TargetHealth.State == "healthy")) | length' )
|
||||
echo "Found ${HEALTHY_TARGETS_IN_REGION} healthy targets in target group ${TARGET_GROUP_NAME} in region."
|
||||
if [ ${HEALTHY_TARGETS_IN_REGION} = 0 ]; then
|
||||
echo "Launching at least one replica."
|
||||
HEALTHY_TARGETS_IN_REGION=1
|
||||
else
|
||||
echo "Launching ${HEALTHY_TARGETS_IN_REGION} new replica(s)."
|
||||
fi
|
||||
if [ "${REGION}" = "eu-west-1" ]; then
|
||||
MONGODB_PRIMARY="mongo0.internal.sapsailing.com:27017,mongo1.internal.sapsailing.com:27017,dbserver.internal.sapsailing.com:10203"
|
||||
MONGODB_REPLICA_SET="live"
|
||||
VPC_NAME="Default"
|
||||
else
|
||||
MONGODB_PRIMARY="localhost"
|
||||
MONGODB_REPLICA_SET="replica"
|
||||
VPC_NAME="Tokyo2020"
|
||||
fi
|
||||
echo "Using MongoDB primary ${MONGODB_PRIMARY} and replica set ${MONGODB_REPLICA_SET}"
|
||||
OPTIONS="-g ${REGION} -b ${BEARER_TOKEN} -R ${RELEASE} -p ${MONGODB_PRIMARY} -r ${MONGODB_REPLICA_SET} -v ${VPC_NAME} -c ${HEALTHY_TARGETS_IN_REGION}"
|
||||
if [ -n "${IMAGE_ID}" ]; then
|
||||
OPTIONS="${OPTIONS} -i ${IMAGE_ID}"
|
||||
fi
|
||||
if [ -n "${KEY_NAME}" ]; then
|
||||
OPTIONS="${OPTIONS} -k ${KEY_NAME}"
|
||||
fi
|
||||
if [ -n "${INSTANCE_TYPE}" ]; then
|
||||
OPTIONS="${OPTIONS} -t ${INSTANCE_TYPE}"
|
||||
fi
|
||||
echo "Invoking launch-replicas-in-region.sh with options ${OPTIONS}"
|
||||
`dirname $0`/launch-replicas-in-region.sh ${OPTIONS}
|
||||
done
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
#!/bin/bash
|
||||
INSTANCE_TYPE=c4.2xlarge
|
||||
REPLICA_SET_NAME=replica
|
||||
REPLICA_SET_PRIMARY=localhost
|
||||
KEY_NAME=Axel
|
||||
VPC=Tokyo2020
|
||||
TARGET_GROUP_NAME=S-ded-tokyo2020
|
||||
COUNT=1
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "$0 -g <AWS-region> -R <release-name> -b <replication-bearer-token> [-c <instance-count>] [-r <replica-set-name>] [-p <host>:<port>] [-t <instance-type>] [-i <ami-id>] [-k <key-pair-name>] [-v <VPC name> ]"
|
||||
echo ""
|
||||
echo "-b replication bearer token; mandatory"
|
||||
echo "-c Count; defaults to ${COUNT}"
|
||||
echo "-i Amazon Machine Image (AMI) ID to use to launch the instance; defaults to latest image tagged with image-type:sailing-analytics-server"
|
||||
echo "-g AWS Region, e.g., eu-west-1"
|
||||
echo "-k Key pair name, mapping to the --key-name parameter"
|
||||
echo "-p Primary host:port; defaults to ${REPLICA_SET_PRIMARY}"
|
||||
echo "-r Replica set name; defaults to ${REPLICA_SET_NAME}"
|
||||
echo "-R release name; must be provided to select the release, e.g., build-202106040947"
|
||||
echo "-t Instance type; defaults to ${INSTANCE_TYPE}"
|
||||
echo "-v VPC name; defaults to ${VPC}"
|
||||
echo
|
||||
echo "Example: $0 -g ap-southeast-2 -b 098toyw098typ9e8/87t9shytp98894y5= -R build-202106041327 -k Jan"
|
||||
echo
|
||||
echo "Will launch one or more (see -c) new replicas in the AWS region specified with -g with the release specified with -R"
|
||||
echo "which will register at the master proxy tokyo-ssh.internal.sapsailing.com:8888 and RabbitMQ at"
|
||||
echo "rabbit-ap-northeast-1.sapsailing.com:5672, then when healthy get added to target group S-ded-tokyo2020"
|
||||
echo "in that region, with all auto-replicas registered before removed from the target group."
|
||||
echo "Specify -r and -p if you are launching in eu-west-1 because it has a special non-default environment."
|
||||
exit 2
|
||||
fi
|
||||
|
||||
options='g:R:b:c:r:p:t:i:k:v:'
|
||||
while getopts $options option
|
||||
do
|
||||
case $option in
|
||||
b) BEARER_TOKEN=$OPTARG;;
|
||||
c) COUNT=$OPTARG;;
|
||||
g) REGION=$OPTARG;;
|
||||
i) IMAGE_ID=$OPTARG;;
|
||||
k) KEY_NAME=$OPTARG;;
|
||||
p) REPLICA_SET_PRIMARY=$OPTARG;;
|
||||
R) RELEASE=$OPTARG;;
|
||||
r) REPLICA_SET_NAME=$OPTARG;;
|
||||
t) INSTANCE_TYPE=$OPTARG;;
|
||||
v) VPC=$OPTARG;;
|
||||
\?) echo "Invalid option"
|
||||
exit 4;;
|
||||
esac
|
||||
done
|
||||
export AWS_DEFAULT_REGION=${REGION}
|
||||
if [ -z "$IMAGE_ID" ]; then
|
||||
IMAGE_ID=$( `dirname $0`/../aws-automation/getLatestImageOfType.sh sailing-analytics-server )
|
||||
fi
|
||||
SECURITY_GROUP_ID=$( aws ec2 describe-security-groups --filters Name=tag:Name,Values="Sailing Analytics App" | jq -r '.SecurityGroups[].GroupId' )
|
||||
echo "Found security group ${SECURITY_GROUP_ID} with name \"Sailing Analytics App\""
|
||||
VPC_ID=$( aws --region ${REGION} ec2 describe-vpcs --filters Name=tag:Name,Values=${VPC} | jq -r '.Vpcs[].VpcId' )
|
||||
echo "Found VPC ${VPC_ID}"
|
||||
SUBNETS=$( aws --region ${REGION} ec2 describe-subnets --filters Name=vpc-id,Values=${VPC_ID} )
|
||||
NUMBER_OF_SUBNETS=$( echo "${SUBNETS}" | jq -r '.Subnets | length' )
|
||||
TARGET_GROUP_ARN=$( aws elbv2 describe-target-groups --names ${TARGET_GROUP_NAME} | jq -r '.TargetGroups[].TargetGroupArn' )
|
||||
echo "Found target group with name ${TARGET_GROUP_NAME} and ARN ${TARGET_GROUP_ARN}"
|
||||
PRIVATE_IPS=
|
||||
INSTANCE_IDS=
|
||||
i=0
|
||||
while [ ${i} -lt ${COUNT} ]; do
|
||||
SUBNET_INDEX=$(( $RANDOM * $NUMBER_OF_SUBNETS / 32768 ))
|
||||
SUBNET_ID=$( echo "${SUBNETS}" | jq -r '.Subnets['${SUBNET_INDEX}'].SubnetId' )
|
||||
echo "Launching image with ID ${IMAGE_ID} into subnet #${SUBNET_INDEX} with ID ${SUBNET_ID} in VPC ${VPC_ID}"
|
||||
PRIVATE_IP_AND_INSTANCE_ID=$( aws --region ${REGION} ec2 run-instances --subnet-id ${SUBNET_ID} --instance-type ${INSTANCE_TYPE} --security-group-ids ${SECURITY_GROUP_ID} --image-id ${IMAGE_ID} --user-data "INSTALL_FROM_RELEASE=${RELEASE}
|
||||
SERVER_NAME=tokyo2020
|
||||
MONGODB_URI=\"mongodb://${REPLICA_SET_PRIMARY}/tokyo2020-replica?replicaSet=${REPLICA_SET_NAME}&retryWrites=true&readPreference=nearest\"
|
||||
USE_ENVIRONMENT=live-replica-server
|
||||
REPLICATION_CHANNEL=tokyo2020-replica
|
||||
REPLICATION_HOST=rabbit-ap-northeast-1.sapsailing.com
|
||||
REPLICATE_MASTER_SERVLET_HOST=tokyo-ssh.internal.sapsailing.com
|
||||
REPLICATE_MASTER_SERVLET_PORT=8888
|
||||
REPLICATE_MASTER_EXCHANGE_NAME=tokyo2020
|
||||
REPLICATE_MASTER_QUEUE_HOST=rabbit-ap-northeast-1.sapsailing.com
|
||||
REPLICATE_MASTER_BEARER_TOKEN=${BEARER_TOKEN}
|
||||
ADDITIONAL_JAVA_ARGS=\"${ADDITIONAL_JAVA_ARGS} -Dcom.sap.sse.debranding=true\"" --ebs-optimized --key-name $KEY_NAME --tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=SL Tokyo2020 (Upgrade Replica)},{Key=sailing-analytics-server,Value=tokyo2020}]" "ResourceType=volume,Tags=[{Key=Name,Value=SL Tokyo2020 (Upgrade Replica)}]" | jq -r '.Instances[].PrivateIpAddress + " " + .Instances[].InstanceId' )
|
||||
EXIT_CODE=$?
|
||||
if [ "${EXIT_CODE}" != "0" ]; then
|
||||
echo "Error launching instance. Exiting with status ${EXIT_CODE}"
|
||||
exit ${EXIT_CODE}
|
||||
fi
|
||||
PRIVATE_IP=$( echo ${PRIVATE_IP_AND_INSTANCE_ID} | awk '{print $1;}' )
|
||||
INSTANCE_ID=$( echo ${PRIVATE_IP_AND_INSTANCE_ID} | awk '{print $2;}' )
|
||||
PRIVATE_IPS="${PRIVATE_IPS} ${PRIVATE_IP}"
|
||||
if [ -z $INSTANCE_IDS ]; then
|
||||
INSTANCE_IDS="Id=${INSTANCE_ID}"
|
||||
else
|
||||
INSTANCE_IDS="${INSTANCE_IDS},Id=${INSTANCE_ID}"
|
||||
fi
|
||||
# Now wait for those instances launched to become available
|
||||
echo "Waiting for instance with private IP ${PRIVATE_IP} to become healthy..."
|
||||
while ! ssh -A -o StrictHostKeyChecking=no ec2-user@tokyo-ssh.sapsailing.com "ssh -o StrictHostKeyChecking=no root@${PRIVATE_IP} \"cd /home/sailing/servers/tokyo2020; ./status >/dev/null\""; do
|
||||
echo "${PRIVATE_IP} still not healthy. Trying again in 10s..."
|
||||
sleep 10
|
||||
done
|
||||
i=$(( i + 1 ))
|
||||
done
|
||||
OLD_VERSION_TARGET_IDS=$( aws elbv2 describe-target-health --target-group-arn ${TARGET_GROUP_ARN} | jq -r '.TargetHealthDescriptions[].Target.Id' )
|
||||
TARGET_IDS_TO_DEREGISTER=""
|
||||
for OLD_VERSION_TARGET_ID in ${OLD_VERSION_TARGET_IDS}; do
|
||||
if [ -z $TARGET_IDS_TO_DEREGISTER ]; then
|
||||
TARGET_IDS_TO_DEREGISTER="Id=${OLD_VERSION_TARGET_ID}"
|
||||
else
|
||||
TARGET_IDS_TO_DEREGISTER="${TARGET_IDS_TO_DEREGISTER},Id=${OLD_VERSION_TARGET_ID}"
|
||||
fi
|
||||
done
|
||||
echo "Registering instances ${INSTANCE_IDS} with target group ${TARGET_GROUP_NAME}"
|
||||
aws elbv2 register-targets --target-group-arn ${TARGET_GROUP_ARN} --targets ${INSTANCE_IDS}
|
||||
EXIT_CODE=$?
|
||||
if [ "${EXIT_CODE}" = "0" ]; then
|
||||
echo "Registering instances was successful."
|
||||
echo "De-registering old instances ${TARGET_IDS_TO_DEREGISTER} from target group ${TARGET_GROUP_NAME}"
|
||||
aws elbv2 deregister-targets --target-group-arn ${TARGET_GROUP_ARN} --targets ${TARGET_IDS_TO_DEREGISTER}
|
||||
else
|
||||
echo "Registering instances failed with exit code $?; not de-registering old instances."
|
||||
exit ${EXIT_CODE}
|
||||
fi
|
||||
@@ -2,6 +2,6 @@
|
||||
for i in `ps axlw | grep /usr/lib/autossh/autossh | grep -v grep | awk '{ print $3; }'`; do
|
||||
ps axlw | grep /usr/bin/ssh | grep -v grep | awk '{print $4;}' | grep -q $i
|
||||
if [ "$?" != "0" ]; then
|
||||
echo "autossh tunnel with PID $i is missing ssh process" | notify-operators "autossh tunnel with PID $i is missing ssh process"
|
||||
echo "autossh tunnel on `hostname` with PID $i is missing ssh process" | notify-operators "autossh tunnel on `hostname` with PID $i is missing ssh process"
|
||||
fi
|
||||
done
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/bash
|
||||
SERVER_DIRECTORY=$1
|
||||
LAST_UNHEALTHY_TIMESTAMP_FILE=${SERVER_DIRECTORY}/UNHEALTHY_TIMESTAMP
|
||||
cd ${SERVER_DIRECTORY}
|
||||
if ! ${SERVER_DIRECTORY}/status; then
|
||||
# Server is unhealthy; compute time difference to first unhealthy test
|
||||
current_time=$( date +%s )
|
||||
if [ -f "${LAST_UNHEALTHY_TIMESTAMP_FILE}" ]; then
|
||||
last_unhealthy=$(cat "${LAST_UNHEALTHY_TIMESTAMP_FILE}")
|
||||
if [ -z "${last_unhealthy}" ]; then
|
||||
last_unhealthy=0
|
||||
fi
|
||||
else
|
||||
last_unhealthy=0
|
||||
fi
|
||||
unhealthy_duration_in_seconds=$(( $current_time - $last_unhealthy ))
|
||||
echo "Unhealthy duration in seconds: ${unhealthy_duration_in_seconds}"
|
||||
if [ $last_unhealthy -eq 0 -o $unhealthy_duration_in_seconds -gt 300 ]; then
|
||||
logger -t sailing "Server at ${SERVER_DIRECTORY} not healthy!"
|
||||
echo "Server at ${SERVER_DIRECTORY} not healthy" | notify-operators "Server at ${SERVER_DIRECTORY} not healthy"
|
||||
echo "${current_time}" >${LAST_UNHEALTHY_TIMESTAMP_FILE}
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# Server is healthy; remove timestamp
|
||||
if [ -f "${LAST_UNHEALTHY_TIMESTAMP_FILE}" ]; then
|
||||
rm "${LAST_UNHEALTHY_TIMESTAMP_FILE}"
|
||||
fi
|
||||
fi
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
DELAY_FILE=/tmp/mongo-replica-set-delay
|
||||
NOTIFYING_THRESHOLD_SECOND_AVERAGE=3
|
||||
NOTIFYING_THRESHOLD_SECOND_AVERAGE=10
|
||||
echo "rs.printSecondaryReplicationInfo()" | \
|
||||
mongo "mongodb://localhost:10201,localhost:10202,localhost:10203/?replicaSet=tokyo2020&retryWrites=true&readPreference=nearest" |
|
||||
grep "\(behind the primary\)" | sed -e 's/^[ \t]*\([0-9]*\) secs.*$/\1/' >>${DELAY_FILE}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
eu-west-1
|
||||
ap-northeast-1
|
||||
ap-southeast-2
|
||||
us-west-1
|
||||
us-east-1
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/bin/bash
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "$0 -b <replication-bearer-token>"
|
||||
echo ""
|
||||
echo "-b replication bearer token; mandatory"
|
||||
echo
|
||||
echo "Example: $0 -b 7345983275087320/59870hfly945="
|
||||
exit 2
|
||||
fi
|
||||
options='b:'
|
||||
while getopts $options option
|
||||
do
|
||||
case $option in
|
||||
b) BEARER_TOKEN=$OPTARG;;
|
||||
\?) echo "Invalid option"
|
||||
exit 4;;
|
||||
esac
|
||||
done
|
||||
for i in `./get-replica-ips`; do
|
||||
ssh -o StrictHostKeyChecking=no root@$i "su - sailing -c \"cd /home/sailing/servers/tokyo2020; ./stopReplicating.sh ${BEARER_TOKEN}\""
|
||||
done
|
||||
@@ -0,0 +1,80 @@
|
||||
#!/bin/bash
|
||||
LAUNCH_CONFIGURATION_NAME_PATTERN="^tokyo2020-.*"
|
||||
AUTO_SCALING_GROUP_NAME_PATTERN="^tokyo2020.*"
|
||||
KEY_NAME=Axel
|
||||
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "$0 -R <release-name> [-t <instance-type>] [-i <ami-id>] [-k <key-pair-name>]"
|
||||
echo ""
|
||||
echo "-i Amazon Machine Image (AMI) ID to use to launch the instance; defaults to latest image tagged with image-type:sailing-analytics-server"
|
||||
echo "-k Key pair name, mapping to the --key-name parameter"
|
||||
echo "-R release name; must be provided to select the release, e.g., build-202106040947"
|
||||
echo "-t Instance type; defaults to ${INSTANCE_TYPE}"
|
||||
echo
|
||||
echo "Example: $0 -R build-202106041327 -k Jan"
|
||||
echo
|
||||
echo "Will upgrade the auto-scaling group tokyo2020-* in the regions from regions.txt with a new"
|
||||
echo "launch configuration that will be derived from the existing launch configuration named tokyo2020-*"
|
||||
echo "by copying it to tokyo2020-{RELEASE_NAME} while updating the INSTALL_FROM_RELEASE parameter in the"
|
||||
echo "user data to the {RELEASE_NAME}, and optionally adjuting the AMI, key pair name and instance type if specified."
|
||||
echo "Note: this will NOT terminate any instances in the target group!"
|
||||
exit 2
|
||||
fi
|
||||
options='R:b:t:i:k:'
|
||||
while getopts $options option
|
||||
do
|
||||
case $option in
|
||||
i) IMAGE_ID=$OPTARG;;
|
||||
k) KEY_NAME=$OPTARG;;
|
||||
R) RELEASE=$OPTARG;;
|
||||
t) INSTANCE_TYPE=$OPTARG;;
|
||||
\?) echo "Invalid option"
|
||||
exit 4;;
|
||||
esac
|
||||
done
|
||||
for REGION in $( cat `dirname $0`/regions.txt ); do
|
||||
export AWS_DEFAULT_REGION=${REGION}
|
||||
echo "Starting auto-scaling group upgrade process for region ${REGION}"
|
||||
echo "----------------------------------------------------------------------"
|
||||
LAUNCH_CONFIGURATION_NAME=$( aws autoscaling describe-launch-configurations | jq -r '.LaunchConfigurations | map(select(.LaunchConfigurationName | test("'${LAUNCH_CONFIGURATION_NAME_PATTERN}'")))[].LaunchConfigurationName' | sort | tail -n 1)
|
||||
echo "Found launch configuration ${LAUNCH_CONFIGURATION_NAME}"
|
||||
LAUNCH_CONFIGURATION_JSON=$( aws autoscaling describe-launch-configurations --launch-configuration-name ${LAUNCH_CONFIGURATION_NAME} | jq -r '.LaunchConfigurations[0]' )
|
||||
OLD_USER_DATA=$( echo "${LAUNCH_CONFIGURATION_JSON}" | jq -r '.UserData' | base64 -d )
|
||||
if [ -z "${IMAGE_ID}" ]; then
|
||||
REGIONAL_IMAGE_ID=$( echo "${LAUNCH_CONFIGURATION_JSON}" | jq -r '.ImageId' )
|
||||
else
|
||||
REGIONAL_IMAGE_ID=${IMAGE_ID}
|
||||
fi
|
||||
if [ -z "${INSTANCE_TYPE}" ]; then
|
||||
REGIONAL_INSTANCE_TYPE=$( echo "${LAUNCH_CONFIGURATION_JSON}" | jq -r '.InstanceType' )
|
||||
else
|
||||
REGIONAL_INSTANCE_TYPE=${INSTANCE_TYPE}
|
||||
fi
|
||||
SECURITY_GROUP=$( echo "${LAUNCH_CONFIGURATION_JSON}" | jq -r '.SecurityGroups[0]' )
|
||||
BLOCK_DEVICE_MAPPINGS="$( echo "${LAUNCH_CONFIGURATION_JSON}" | jq -r '.BlockDeviceMappings' )"
|
||||
NEW_USER_DATA=$( echo "${OLD_USER_DATA}" | sed -e 's/^INSTALL_FROM_RELEASE=.*$/INSTALL_FROM_RELEASE='${RELEASE}'/' )
|
||||
NEW_LAUNCH_CONFIGURATION_NAME=tokyo2020-${RELEASE}
|
||||
echo "Creating new launch configuration ${NEW_LAUNCH_CONFIGURATION_NAME}"
|
||||
aws autoscaling create-launch-configuration --launch-configuration-name ${NEW_LAUNCH_CONFIGURATION_NAME} --image-id ${REGIONAL_IMAGE_ID} --key-name ${KEY_NAME} --security-groups ${SECURITY_GROUP} --user-data "${NEW_USER_DATA}" --instance-type ${REGIONAL_INSTANCE_TYPE} --block-device-mappings "${BLOCK_DEVICE_MAPPINGS}"
|
||||
EXIT_CODE=$?
|
||||
if [ "${EXIT_CODE}" = "0" ]; then
|
||||
echo "Creation of launch configuration ${NEW_LAUNCH_CONFIGURATION_NAME} successful. Continuing with updating the auto-scaling group"
|
||||
AUTO_SCALING_GROUP_NAME=$( aws autoscaling describe-auto-scaling-groups | jq -r '.AutoScalingGroups | map(select(.AutoScalingGroupName | test("'${AUTO_SCALING_GROUP_NAME_PATTERN}'")))[].AutoScalingGroupName' )
|
||||
echo "Found auto-scaling group ${AUTO_SCALING_GROUP_NAME}"
|
||||
aws autoscaling update-auto-scaling-group --auto-scaling-group-name ${AUTO_SCALING_GROUP_NAME} --launch-configuration-name ${NEW_LAUNCH_CONFIGURATION_NAME}
|
||||
EXIT_CODE=$?
|
||||
if [ "${EXIT_CODE}" = "0" ]; then
|
||||
echo "Updating auto-scaling group ${AUTO_SCALING_GROUP_NAME} seems to have completed successfully."
|
||||
echo "Removing old launch configuration ${LAUNCH_CONFIGURATION_NAME}"
|
||||
aws autoscaling delete-launch-configuration --launch-configuration-name ${LAUNCH_CONFIGURATION_NAME}
|
||||
else
|
||||
echo "Attempt to update the auto-scaling group returned exit status $? which is considered an error."
|
||||
echo "Removing new launch configuration ${NEW_LAUNCH_CONFIGURATION_NAME} again"
|
||||
aws autoscaling delete-launch-configuration --launch-configuration-name ${NEW_LAUNCH_CONFIGURATION_NAME}
|
||||
exit ${EXIT_CODE}
|
||||
fi
|
||||
else
|
||||
echo "Creating the new launch configuration ${NEW_LAUNCH_CONFIGURATION_NAME} failed with exit status ${EXIT_CODE}"
|
||||
exit ${EXIT_CODE}
|
||||
fi
|
||||
done
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
#!/bin/bash
|
||||
# Upgrades the entire landscape of servers to a new release ${RELEASE}
|
||||
# The procedure works in the following steps:
|
||||
# - patch *.conf files in sap-p1-1:servers/[master|security_service] and sap-p1-2:servers/[replica|master|security_service] so
|
||||
# their INSTALL_FROM_RELEASE points to the new ${RELEASE}
|
||||
# - Install new releases to sap-p1-1:servers/[master|security_service] and sap-p1-2:servers/[replica|master|security_service]
|
||||
# - Update all launch configurations and auto-scaling groups in the cloud (update-launch-configuration.sh)
|
||||
# - Tell all replicas in the cloud to stop replicating (stop-all-cloud-replicas.sh)
|
||||
# - Tell sap-p1-2 to stop replicating
|
||||
# - on sap-p1-1:servers/master run ./stop; ./start to bring the master to the new release
|
||||
# - wait until master is healthy
|
||||
# - on sap-p1-2:servers/replica run ./stop; ./start to bring up on-site replica again
|
||||
# - launch upgraded cloud replicas and replace old replicas in target group (launch-replicas-in-all-regions.sh)
|
||||
# - terminate all instances named "SL Tokyo2020 (auto-replica)"; this should cause the auto-scaling group to launch new instances as required
|
||||
# - manually inspect the health of everything and terminate the "SL Tokyo2020 (Upgrade Replica)" instances when enough new instances
|
||||
# named "SL Tokyo2020 (auto-replica)" are available
|
||||
#
|
||||
KEY_NAME=Axel
|
||||
INSTANCE_NAME_TO_TERMINATE="SL Tokyo2020 (auto-replica)"
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "$0 -R <release-name> -b <replication-bearer-token> [-t <instance-type>] [-i <ami-id>] [-k <key-pair-name>] [-s]"
|
||||
echo ""
|
||||
echo "-b replication bearer token; mandatory"
|
||||
echo "-i Amazon Machine Image (AMI) ID to use to launch the instance; defaults to latest image tagged with image-type:sailing-analytics-server"
|
||||
echo "-k Key pair name, mapping to the --key-name parameter"
|
||||
echo "-R release name; must be provided to select the release, e.g., build-202106040947"
|
||||
echo "-t Instance type; defaults to ${INSTANCE_TYPE}"
|
||||
echo "-s Skip release download"
|
||||
echo
|
||||
echo "Example: $0 -R build-202106041327 -k Jan"
|
||||
echo
|
||||
echo "Will upgrade the auto-scaling group tokyo2020-* in the regions from regions.txt with a new"
|
||||
echo "launch configuration that will be derived from the existing launch configuration named tokyo2020-*"
|
||||
echo "by copying it to tokyo2020-{RELEASE_NAME} while updating the INSTALL_FROM_RELEASE parameter in the"
|
||||
echo "user data to the {RELEASE_NAME}, and optionally adjuting the AMI, key pair name and instance type if specified."
|
||||
echo "Note: this will NOT terminate any instances in the target group!"
|
||||
exit 2
|
||||
fi
|
||||
options='R:b:t:i:k:s'
|
||||
while getopts $options option
|
||||
do
|
||||
case $option in
|
||||
b) BEARER_TOKEN=$OPTARG;;
|
||||
i) IMAGE_ID=$OPTARG;;
|
||||
k) KEY_NAME=$OPTARG;;
|
||||
R) RELEASE=$OPTARG;;
|
||||
s) SKIP_DOWNLOAD=1;;
|
||||
t) INSTANCE_TYPE=$OPTARG;;
|
||||
\?) echo "Invalid option"
|
||||
exit 4;;
|
||||
esac
|
||||
done
|
||||
RELEASE_FILE=${RELEASE}.tar.gz
|
||||
|
||||
function patch_conf_and_install () {
|
||||
HOST=$1
|
||||
SERVER_DIR=$2
|
||||
ssh sailing@$1 "cd servers/${SERVER_DIR}; sed -i -e 's/^INSTALL_FROM_RELEASE=.*$/INSTALL_FROM_RELEASE='${RELEASE}'/' ${SERVER_DIR}.conf; rm env.sh; cat ${SERVER_DIR}.conf | /home/sailing/code/java/target/refreshInstance.sh auto-install-from-stdin"
|
||||
}
|
||||
|
||||
if [ "${SKIP_DOWNLOAD}" = "1" ]; then
|
||||
echo " * Skipping download of release file ${RELEASE_FILE}"
|
||||
else
|
||||
echo " * Downloading the release file to sap-p1-1:/home/trac/releases/${RELEASE}"
|
||||
ssh sailing@sap-p1-1 "bash --login -c 'mkdir -p /home/trac/releases/${RELEASE}; scp -P 22222 trac@localhost:releases/${RELEASE}/${RELEASE_FILE} /home/trac/releases/${RELEASE}'"
|
||||
fi
|
||||
echo " * Patching configurations on sap-p1-1 and sap-p1-2 to new release ${RELEASE} and installing"
|
||||
patch_conf_and_install sap-p1-1 master
|
||||
patch_conf_and_install sap-p1-1 security_service
|
||||
patch_conf_and_install sap-p1-2 replica
|
||||
patch_conf_and_install sap-p1-2 master
|
||||
patch_conf_and_install sap-p1-2 security_service
|
||||
echo " * Updating launch configurations and auto-scaling groups"
|
||||
OPTIONS="-R ${RELEASE}"
|
||||
if [ -n "${IMAGE_ID}" ]; then
|
||||
OPTIONS="${OPTIONS} -i ${IMAGE_ID}"
|
||||
fi
|
||||
if [ -n "${KEY_NAME}" ]; then
|
||||
OPTIONS="${OPTIONS} -k ${KEY_NAME}"
|
||||
fi
|
||||
if [ -n "${INSTANCE_TYPE}" ]; then
|
||||
OPTIONS="${OPTIONS} -t ${INSTANCE_TYPE}"
|
||||
fi
|
||||
`dirname $0`/update-launch-configuration.sh ${OPTIONS}
|
||||
EXIT_CODE=$?
|
||||
if [ "${EXIT_CODE}" != "0" ]; then
|
||||
echo "Updating launch configurations failed with exit code ${EXIT_CODE}."
|
||||
exit ${EXIT_CODE}
|
||||
fi
|
||||
echo " * Telling all cloud replicas to stop replicating"
|
||||
`dirname $0`/stop-all-cloud-replicas.sh -b ${BEARER_TOKEN}
|
||||
EXIT_CODE=$?
|
||||
if [ "${EXIT_CODE}" != "0" ]; then
|
||||
echo "Telling cloud replicas to stop replicating failed with exit code ${EXIT_CODE}"
|
||||
exit ${EXIT_CODE}
|
||||
fi
|
||||
echo " * Telling replica on sap-p1-2 to stop replicating"
|
||||
ssh sailing@sap-p1-2 "cd servers/replica; ./stopReplicating.sh ${BEARER_TOKEN}"
|
||||
EXIT_CODE=$?
|
||||
if [ "${EXIT_CODE}" != "0" ]; then
|
||||
echo "Telling sap-p1-2 replica to stop replicating failed with exit code ${EXIT_CODE}"
|
||||
exit ${EXIT_CODE}
|
||||
fi
|
||||
echo " * Re-launching master on sap-p1-1 to new release ${RELEASE} and waiting for it to become healthy"
|
||||
ssh sailing@sap-p1-1 "bash --login -c 'cd servers/master; ./stop; ./start; while ! ./status 2>/dev/null >/dev/null; do echo \"Waiting for healthy master\"; sleep 10; done'"
|
||||
EXIT_CODE=$?
|
||||
if [ "${EXIT_CODE}" != "0" ]; then
|
||||
echo "Re-launching master on sap-p1-1 failed with exit code ${EXIT_CODE}"
|
||||
exit ${EXIT_CODE}
|
||||
fi
|
||||
echo " * Re-launching replica on sap-p1-2 to new release ${RELEASE}"
|
||||
ssh sailing@sap-p1-2 "bash --login -c 'cd servers/replica; ./stop; ./start'"
|
||||
EXIT_CODE=$?
|
||||
if [ "${EXIT_CODE}" != "0" ]; then
|
||||
echo "Re-launching replica on sap-p1-2 failed with exit code ${EXIT_CODE}"
|
||||
exit ${EXIT_CODE}
|
||||
fi
|
||||
echo " * Launching upgraded replicas SL Tokyo2020 (Upgrade Replica) in the regions"
|
||||
OPTIONS="-b ${BEARER_TOKEN} -R ${RELEASE}"
|
||||
if [ -n "${IMAGE_ID}" ]; then
|
||||
OPTIONS="${OPTIONS} -i ${IMAGE_ID}"
|
||||
fi
|
||||
if [ -n "${KEY_NAME}" ]; then
|
||||
OPTIONS="${OPTIONS} -k ${KEY_NAME}"
|
||||
fi
|
||||
if [ -n "${INSTANCE_TYPE}" ]; then
|
||||
OPTIONS="${OPTIONS} -t ${INSTANCE_TYPE}"
|
||||
fi
|
||||
`dirname $0`/launch-replicas-in-all-regions.sh ${OPTIONS}
|
||||
EXIT_CODE=$?
|
||||
if [ "${EXIT_CODE}" != "0" ]; then
|
||||
echo "Lanuching replicas in the regions failed with exit code ${EXIT_CODE}"
|
||||
exit ${EXIT_CODE}
|
||||
fi
|
||||
read -p "Press ENTER to terminate all ${INSTANCE_NAME_TO_TERMINATE} instances"
|
||||
echo " * Terminating all instances named ${INSTANCE_NAME_TO_TERMINATE} to force auto-scaling group to launch and register upgraded ones"
|
||||
for REGION in $( cat `dirname $0`/regions.txt ); do
|
||||
export AWS_DEFAULT_REGION=${REGION}
|
||||
echo "Terminating instances named region ${REGION}"
|
||||
echo "-------------------------------------------------------"
|
||||
for INSTANCE_ID in $( aws ec2 describe-instances --filters Name=tag:Name,Values="${INSTANCE_NAME_TO_TERMINATE}" | jq -r '.Reservations[].Instances[].InstanceId' ); do
|
||||
echo " Terminating instance ${INSTANCE_ID}"
|
||||
aws ec2 terminate-instances --instance-ids ${INSTANCE_ID}
|
||||
EXIT_CODE=$?
|
||||
if [ "${EXIT_CODE}" != "0" ]; then
|
||||
echo "Terminating instance ${INSTANCE_ID} failed with exit code ${EXIT_CODE}"
|
||||
exit ${EXIT_CODE}
|
||||
fi
|
||||
done
|
||||
done
|
||||
echo " * DONE"
|
||||
+3
-2
@@ -112,6 +112,7 @@ import com.sap.sailing.domain.common.Wind;
|
||||
import com.sap.sailing.domain.common.WindSource;
|
||||
import com.sap.sailing.domain.common.dto.AnniversaryType;
|
||||
import com.sap.sailing.domain.common.orc.ORCCertificate;
|
||||
import com.sap.sailing.domain.common.racelog.Flags;
|
||||
import com.sap.sailing.domain.leaderboard.FlexibleLeaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.Leaderboard;
|
||||
import com.sap.sailing.domain.leaderboard.LeaderboardGroup;
|
||||
@@ -1250,8 +1251,8 @@ public class MongoObjectFactoryImpl implements MongoObjectFactory {
|
||||
Document result = new Document();
|
||||
storeRaceLogEventProperties(flagEvent, result);
|
||||
result.put(FieldNames.RACE_LOG_EVENT_CLASS.name(), RaceLogFlagEvent.class.getSimpleName());
|
||||
result.put(FieldNames.RACE_LOG_EVENT_FLAG_UPPER.name(), flagEvent.getUpperFlag().name());
|
||||
result.put(FieldNames.RACE_LOG_EVENT_FLAG_LOWER.name(), flagEvent.getLowerFlag().name());
|
||||
result.put(FieldNames.RACE_LOG_EVENT_FLAG_UPPER.name(), flagEvent.getUpperFlag()==null?Flags.NONE.name():flagEvent.getUpperFlag().name());
|
||||
result.put(FieldNames.RACE_LOG_EVENT_FLAG_LOWER.name(), flagEvent.getLowerFlag()==null?Flags.NONE.name():flagEvent.getLowerFlag().name());
|
||||
result.put(FieldNames.RACE_LOG_EVENT_FLAG_DISPLAYED.name(), String.valueOf(flagEvent.isDisplayed()));
|
||||
return result;
|
||||
}
|
||||
|
||||
+1
@@ -169,6 +169,7 @@ public class SensorFixStoreAndLoadTest {
|
||||
/* canBoatsOfCompetitorsChangePerRace */ true, CompetitorRegistrationType.CLOSED,
|
||||
/* startDate */ null, /* endDate */null, null, null, "a", null,
|
||||
/* registrationLinkSecret */ UUID.randomUUID().toString()));
|
||||
regatta.getRegatta().setControlTrackingFromStartAndFinishTimes(true);
|
||||
trackedRace = new DynamicTrackedRaceImpl(regatta, race, Collections.<Sideline> emptyList(),
|
||||
EmptyWindStore.INSTANCE, 0, 0, 0, /* useMarkPassingCalculator */ false, OneDesignRankingMetric::new,
|
||||
mock(RaceLogAndTrackedRaceResolver.class), /* trackingConnectorInfo */ null);
|
||||
|
||||
+3
-7
@@ -81,6 +81,7 @@ public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest {
|
||||
/* canBoatsOfCompetitorsChangePerRace */ true, CompetitorRegistrationType.CLOSED,
|
||||
/* startDate */ null, /* endDate */null, null, null, "a", null,
|
||||
/* registrationLinkSecret */ UUID.randomUUID().toString()));
|
||||
regatta.getRegatta().setControlTrackingFromStartAndFinishTimes(true);
|
||||
final DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race,
|
||||
Collections.<Sideline> emptyList(), EmptyWindStore.INSTANCE, 0, 0, 0,
|
||||
/* useMarkPassingCalculator */ false,
|
||||
@@ -203,6 +204,7 @@ public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest {
|
||||
/* canBoatsOfCompetitorsChangePerRace */ true, CompetitorRegistrationType.CLOSED,
|
||||
/* startDate */ null, /* endDate */null, null, null, "a", null,
|
||||
/* registrationLinkSecret */ UUID.randomUUID().toString()));
|
||||
regatta.getRegatta().setControlTrackingFromStartAndFinishTimes(true);
|
||||
final DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race,
|
||||
Collections.<Sideline> emptyList(), EmptyWindStore.INSTANCE, 0, 0, 0,
|
||||
/* useMarkPassingCalculator */ false,
|
||||
@@ -250,6 +252,7 @@ public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest {
|
||||
/* canBoatsOfCompetitorsChangePerRace */ true, CompetitorRegistrationType.CLOSED,
|
||||
/* startDate */ null, /* endDate */null, null, null, "a", null,
|
||||
/* registrationLinkSecret */ UUID.randomUUID().toString()));
|
||||
regatta.getRegatta().setControlTrackingFromStartAndFinishTimes(true);
|
||||
assertTrue(regatta.getRegatta().useStartTimeInference());
|
||||
final DynamicTrackedRaceImpl trackedRace = new DynamicTrackedRaceImpl(regatta, race,
|
||||
Collections.<Sideline> emptyList(), EmptyWindStore.INSTANCE, 0, 0, 0,
|
||||
@@ -270,7 +273,6 @@ public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest {
|
||||
});
|
||||
assertNull(trackedRace.getStartOfTracking());
|
||||
assertNull(trackedRace.getEndOfTracking());
|
||||
|
||||
// test inference from implicit startOfRace change through start mark passing update
|
||||
newStartAndEndOfTrackingNotifiedByRace[0] = null;
|
||||
newStartAndEndOfTrackingNotifiedByRace[1] = null;
|
||||
@@ -280,7 +282,6 @@ public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest {
|
||||
assertTrue(trackedRace.getStartOfTracking().before(startMarkPassingTimePoint));
|
||||
final MillisecondsTimePoint startOfRaceInRaceLog = new MillisecondsTimePoint(123456);
|
||||
assertTrue(trackedRace.getStartOfTracking().after(startOfRaceInRaceLog));
|
||||
|
||||
// test inference from finished time change by new blue flag down event
|
||||
newStartAndEndOfTrackingNotifiedByRace[0] = null;
|
||||
newStartAndEndOfTrackingNotifiedByRace[1] = null;
|
||||
@@ -288,7 +289,6 @@ public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest {
|
||||
raceLog.add(new RaceLogRaceStatusEventImpl(finishedTimePoint, finishedTimePoint, author, UUID.randomUUID(), 0, RaceLogRaceStatus.FINISHED));
|
||||
assertNotNull(newStartAndEndOfTrackingNotifiedByRace[1]);
|
||||
assertTrue(trackedRace.getEndOfTracking().after(finishedTimePoint));
|
||||
|
||||
// verify that setting a start and finished time through the race log adjusts the start/end of tracking times
|
||||
newStartAndEndOfTrackingNotifiedByRace[0] = null;
|
||||
newStartAndEndOfTrackingNotifiedByRace[1] = null;
|
||||
@@ -298,7 +298,6 @@ public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest {
|
||||
assertNotNull(trackedRace.getEndOfTracking());
|
||||
assertTrue(trackedRace.getStartOfTracking().before(startOfRaceInRaceLog));
|
||||
assertTrue(trackedRace.getEndOfTracking().after(endOfRaceInRaceLog));
|
||||
|
||||
newStartAndEndOfTrackingNotifiedByRace[0] = null;
|
||||
newStartAndEndOfTrackingNotifiedByRace[1] = null;
|
||||
final MillisecondsTimePoint manualStartOfTracking = new MillisecondsTimePoint(1111);
|
||||
@@ -310,7 +309,6 @@ public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest {
|
||||
// test values set immediately
|
||||
assertEquals(manualStartOfTracking, trackedRace.getStartOfTracking());
|
||||
assertEquals(manualEndOfTracking, trackedRace.getEndOfTracking());
|
||||
|
||||
newStartAndEndOfTrackingNotifiedByRace[0] = null;
|
||||
newStartAndEndOfTrackingNotifiedByRace[1] = null;
|
||||
final MillisecondsTimePoint newStartOfTrackingInRaceLog = new MillisecondsTimePoint(10000);
|
||||
@@ -322,7 +320,6 @@ public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest {
|
||||
// test inference via racelog
|
||||
assertEquals(newStartOfTrackingInRaceLog, trackedRace.getStartOfTracking());
|
||||
assertEquals(newEndOfTrackingInRaceLog, trackedRace.getEndOfTracking());
|
||||
|
||||
// shouldn't change anymore when setting explicitly because race log takes precedence
|
||||
newStartAndEndOfTrackingNotifiedByRace[0] = null;
|
||||
newStartAndEndOfTrackingNotifiedByRace[1] = null;
|
||||
@@ -331,7 +328,6 @@ public class TrackedRaceStartTimeInferenceTest extends AbstractGPSFixStoreTest {
|
||||
assertNull(newStartAndEndOfTrackingNotifiedByRace[1]);
|
||||
assertEquals(newStartOfTrackingInRaceLog, trackedRace.getStartOfTracking());
|
||||
assertEquals(newEndOfTrackingInRaceLog, trackedRace.getEndOfTracking());
|
||||
|
||||
// test inference when setting null in RaceLog; RaceLog should still take precedence with its null values
|
||||
newStartAndEndOfTrackingNotifiedByRace[0] = MillisecondsTimePoint.now();
|
||||
newStartAndEndOfTrackingNotifiedByRace[1] = MillisecondsTimePoint.now();
|
||||
|
||||
+3
-2
@@ -41,8 +41,9 @@ public class RaceLogImpl extends AbstractLogImpl<RaceLogEvent, RaceLogEventVisit
|
||||
lockForRead();
|
||||
try {
|
||||
// return pass id of last event, as pass is the top-level sorting criterion in RaceLogEventComparator
|
||||
if (!getUnrevokedEvents().isEmpty()) {
|
||||
return getUnrevokedEvents().last().getPassId();
|
||||
final NavigableSet<RaceLogEvent> unrevokedEvents = getUnrevokedEvents();
|
||||
if (!unrevokedEvents.isEmpty()) {
|
||||
return unrevokedEvents.last().getPassId();
|
||||
} else {
|
||||
return DefaultPassId;
|
||||
}
|
||||
|
||||
+1
@@ -206,6 +206,7 @@ public class SwissTimingReplayToDomainAdapter extends SwissTimingReplayAdapter i
|
||||
// found; in this case, create a default regatta based on the TracTrac event data
|
||||
this.regatta = effectiveRegatta == null ? domainFactory.getOrCreateDefaultRegatta(raceLogStore, regattaLogStore,
|
||||
raceIdForRaceDefinition, boatClass, trackedRegattaRegistry) : effectiveRegatta;
|
||||
this.regatta.setControlTrackingFromStartAndFinishTimes(true);
|
||||
this.trackedRegattaRegistry = trackedRegattaRegistry;
|
||||
racePerRaceIdForRaceDefinition = new HashMap<>();
|
||||
trackedRacePerRaceID = new HashMap<>();
|
||||
|
||||
@@ -100,6 +100,7 @@ public class ReachingLegTest extends TrackBasedTest {
|
||||
/*startDate*/ null, /*endDate*/ null, /* trackedRegattaRegistry */ null,
|
||||
DomainFactory.INSTANCE.createScoringScheme(ScoringSchemeType.LOW_POINT), "123", null,
|
||||
/* registrationLinkSecret */ UUID.randomUUID().toString());
|
||||
regatta.setControlTrackingFromStartAndFinishTimes(true);
|
||||
TrackedRegatta trackedRegatta = new DynamicTrackedRegattaImpl(regatta);
|
||||
List<Waypoint> waypoints = new ArrayList<Waypoint>();
|
||||
// create a two-lap upwind/downwind course:
|
||||
|
||||
+4
-4
@@ -167,11 +167,8 @@ public abstract class AbstractReceiverWithQueue<A, B, C> implements Runnable, Re
|
||||
Util.Triple<A, B, C> event = null;
|
||||
while (event == null || !isStopEvent(event)) {
|
||||
try {
|
||||
event = queue.take();
|
||||
if (!isStopEvent(event)) {
|
||||
handleEvent(event);
|
||||
}
|
||||
final Set<LoadingQueueDoneCallBack> callBacks;
|
||||
event = queue.take();
|
||||
synchronized (loadingQueueDoneCallBacks) {
|
||||
if (getSimulator() != null) {
|
||||
// when simulator is running, loading is considered finished and all callbacks will
|
||||
@@ -187,6 +184,9 @@ public abstract class AbstractReceiverWithQueue<A, B, C> implements Runnable, Re
|
||||
callBacks = loadingQueueDoneCallBacks.remove(event);
|
||||
}
|
||||
}
|
||||
if (!isStopEvent(event)) {
|
||||
handleEvent(event);
|
||||
}
|
||||
if (callBacks != null) {
|
||||
for (LoadingQueueDoneCallBack callback : callBacks) {
|
||||
callback.loadingQueueDone(this);
|
||||
|
||||
+82
-28
@@ -6,6 +6,7 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
@@ -70,6 +71,14 @@ public class RaceAndCompetitorStatusWithRaceLogReconciler {
|
||||
private OfficialCompetitorUpdateProvider officialCompetitorUpdateProvider;
|
||||
private final static Map<RaceStatusType, Flags> flagForRaceStatus;
|
||||
|
||||
/**
|
||||
* When this reconciler adds {@link RaceLogFinishPositioningConfirmedEvent} events to the race log on which it listens with its
|
||||
* {@link RaceLogListener}, in order to avoid endless recursion those events are added to this thread-safe queue. When any of these
|
||||
* events then expectedly reaches this reconciler's race log listener, it is removed from this queue again to clean up and
|
||||
* avoid leaking.
|
||||
*/
|
||||
private final ConcurrentLinkedQueue<RaceLogEvent> raceLogEventsAddedToRaceLogByMyself;
|
||||
|
||||
static {
|
||||
flagForRaceStatus = new HashMap<>();
|
||||
flagForRaceStatus.put(RaceStatusType.ABANDONED, Flags.NOVEMBER);
|
||||
@@ -85,7 +94,14 @@ public class RaceAndCompetitorStatusWithRaceLogReconciler {
|
||||
* on the type of even. Instances of this type are registered with race logs because the enclosing
|
||||
* {@link RaceAndCompetitorStatusWithRaceLogReconciler} object has to get informed about race log attachments/detachments
|
||||
* in its {@link RaceAndCompetitorStatusWithRaceLogReconciler#raceLogAttached(TrackedRace, RaceLog)} and
|
||||
* {@link RaceAndCompetitorStatusWithRaceLogReconciler#raceLogDetached(TrackedRace, RaceLog)} methods.
|
||||
* {@link RaceAndCompetitorStatusWithRaceLogReconciler#raceLogDetached(TrackedRace, RaceLog)} methods.<p>
|
||||
*
|
||||
* In order to avoid endless recursions for events added to the race log by the enclosing reconciler itself, a
|
||||
* thread-safe collection of those events added by the enclosing reconciler is maintained in
|
||||
* {@link RaceAndCompetitorStatusWithRaceLogReconciler#raceLogEventsAddedToRaceLogByMyself}. Each {@code visit}
|
||||
* method overridden by this listener must check for the event received in and remove it from that collection,
|
||||
* and in case it was contained in the collection ignore it, so as to avoid the endless recursion. See also bug 5565
|
||||
* for details.
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
@@ -113,35 +129,43 @@ public class RaceAndCompetitorStatusWithRaceLogReconciler {
|
||||
|
||||
@Override
|
||||
public void visit(RaceLogFlagEvent event) {
|
||||
reconcileRaceStatus(tractracRace, trackedRace);
|
||||
if (!raceLogEventsAddedToRaceLogByMyself.remove(event)) {
|
||||
reconcileRaceStatus(tractracRace, trackedRace);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(RaceLogPassChangeEvent event) {
|
||||
reconcileRaceStatus(tractracRace, trackedRace);
|
||||
reconcileAllCompetitors(trackedRace);
|
||||
if (!raceLogEventsAddedToRaceLogByMyself.remove(event)) {
|
||||
reconcileRaceStatus(tractracRace, trackedRace);
|
||||
reconcileAllCompetitors(trackedRace);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(RaceLogFinishPositioningConfirmedEvent event) {
|
||||
reconcileCompetitorsWithResults(event);
|
||||
if (!raceLogEventsAddedToRaceLogByMyself.remove(event)) {
|
||||
reconcileCompetitorsWithResults(event);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visit(RaceLogRevokeEvent event) {
|
||||
final RaceLogEvent revokedEvent;
|
||||
raceLog.lockForRead();
|
||||
try {
|
||||
revokedEvent = raceLog.getEventById(event.getRevokedEventId());
|
||||
} finally {
|
||||
raceLog.unlockAfterRead();
|
||||
}
|
||||
if (revokedEvent != null) {
|
||||
if (revokedEvent instanceof RaceLogFinishPositioningConfirmedEvent) {
|
||||
final RaceLogFinishPositioningConfirmedEvent revokedResultsEvent = (RaceLogFinishPositioningConfirmedEvent) revokedEvent;
|
||||
reconcileCompetitorsWithResults(revokedResultsEvent);
|
||||
} else if (revokedEvent instanceof RaceLogFlagEvent || revokedEvent instanceof RaceLogPassChangeEvent) {
|
||||
reconcileRaceStatus(tractracRace, trackedRace);
|
||||
if (!raceLogEventsAddedToRaceLogByMyself.remove(event)) {
|
||||
final RaceLogEvent revokedEvent;
|
||||
raceLog.lockForRead();
|
||||
try {
|
||||
revokedEvent = raceLog.getEventById(event.getRevokedEventId());
|
||||
} finally {
|
||||
raceLog.unlockAfterRead();
|
||||
}
|
||||
if (revokedEvent != null) {
|
||||
if (revokedEvent instanceof RaceLogFinishPositioningConfirmedEvent) {
|
||||
final RaceLogFinishPositioningConfirmedEvent revokedResultsEvent = (RaceLogFinishPositioningConfirmedEvent) revokedEvent;
|
||||
reconcileCompetitorsWithResults(revokedResultsEvent);
|
||||
} else if (revokedEvent instanceof RaceLogFlagEvent || revokedEvent instanceof RaceLogPassChangeEvent) {
|
||||
reconcileRaceStatus(tractracRace, trackedRace);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,6 +182,7 @@ public class RaceAndCompetitorStatusWithRaceLogReconciler {
|
||||
|
||||
public RaceAndCompetitorStatusWithRaceLogReconciler(DomainFactory domainFactory, RaceLogResolver raceLogResolver, IRace tractracRace) {
|
||||
super();
|
||||
this.raceLogEventsAddedToRaceLogByMyself = new ConcurrentLinkedQueue<>();
|
||||
this.domainFactory = domainFactory;
|
||||
this.raceLogResolver = raceLogResolver;
|
||||
this.tractracRace = tractracRace;
|
||||
@@ -221,27 +246,48 @@ public class RaceAndCompetitorStatusWithRaceLogReconciler {
|
||||
}
|
||||
}
|
||||
final RaceLog defaultRaceLog = getDefaultRaceLog(trackedRace);
|
||||
if (raceStatus == RaceStatusType.OFFICIAL && !ReadonlyRaceStateImpl.getOrCreate(raceLogResolver, defaultRaceLog).isResultsAreOfficial()) {
|
||||
final Runnable setResultsAreOfficial = ()->RaceStateImpl.create(raceLogResolver, defaultRaceLog, raceLogEventAuthor).setResultsAreOfficial(raceStatusUpdateTime);
|
||||
final boolean resultsAreOfficial = ReadonlyRaceStateImpl.getOrCreate(raceLogResolver, defaultRaceLog).isResultsAreOfficial();
|
||||
if (raceStatus == RaceStatusType.OFFICIAL && !resultsAreOfficial) {
|
||||
logger.info("Race status for race "+trackedRace.getName()+" is OFFICIAL with TracTrac and we have it as not official so far. Scheduling update.");
|
||||
final Runnable setResultsAreOfficial = ()->{
|
||||
logger.info("Setting race status for race "+trackedRace.getName()+" to OFFICIAL now");
|
||||
RaceStateImpl.create(raceLogResolver, defaultRaceLog, raceLogEventAuthor).setResultsAreOfficial(raceStatusUpdateTime);
|
||||
};
|
||||
if (officialCompetitorUpdateProvider != null) {
|
||||
logger.info("Enqueuing the setting of race status for race "+trackedRace.getName()+
|
||||
" to OFFICIAL until all competitor updates have been handled");
|
||||
officialCompetitorUpdateProvider.runWhenNoMoreOfficialCompetitorUpdatesPending(setResultsAreOfficial);
|
||||
} else {
|
||||
setResultsAreOfficial.run();
|
||||
}
|
||||
}
|
||||
if (abortingFlagEvent != null && !isAbortedState(raceStatus) && raceStatusUpdateTime.after(abortingFlagEvent.getLogicalTimePoint())) {
|
||||
logger.info("RaceLog considered race "+trackedRace.getName()+" as aborted ("+abortingFlagEvent+"), and the TracTrac race status "+raceStatus+
|
||||
" from "+raceStatusUpdateTime+" suggests it's not aborted. Starting a new pass.");
|
||||
startNewPass(raceStatusUpdateTime, defaultRaceLog);
|
||||
} else if (isAbortedState(raceStatus) &&
|
||||
(abortingFlagEvent == null || (!abortingFlagMatches(raceStatus, abortingFlagEvent.getUpperFlag()) &&
|
||||
raceStatusUpdateTime.after(abortingFlagEvent.getLogicalTimePoint())))) {
|
||||
defaultRaceLog.add(new RaceLogFlagEventImpl(raceStatusUpdateTime, raceLogEventAuthor, defaultRaceLog.getCurrentPassId(),
|
||||
flagForRaceStatus.get(raceStatus), /* lower flag */ null, /* is displayed */ true));
|
||||
final Flags upperFlag = flagForRaceStatus.get(raceStatus);
|
||||
logger.info("RaceLog considered race "+trackedRace.getName()+" as NOT aborted, and the TracTrac race status "+raceStatus+
|
||||
" from "+raceStatusUpdateTime+" suggests it's aborted. Adding abort flag event "+upperFlag+", starting a new pass.");
|
||||
final RaceLogFlagEventImpl flagEvent = new RaceLogFlagEventImpl(raceStatusUpdateTime, raceLogEventAuthor,
|
||||
defaultRaceLog.getCurrentPassId(), upperFlag, /* lower flag */ Flags.NONE,
|
||||
/* is displayed */ true);
|
||||
addRaceLogEventAndPreventRecursion(defaultRaceLog, flagEvent);
|
||||
startNewPass(raceStatusUpdateTime, defaultRaceLog);
|
||||
}
|
||||
}
|
||||
|
||||
private void addRaceLogEventAndPreventRecursion(RaceLog raceLog, RaceLogEvent event) {
|
||||
raceLogEventsAddedToRaceLogByMyself.add(event);
|
||||
raceLog.add(event);
|
||||
}
|
||||
|
||||
protected void startNewPass(final TimePoint timePointForStartOfNewPass, final RaceLog raceLog) {
|
||||
raceLog.add(new RaceLogPassChangeEventImpl(timePointForStartOfNewPass, raceLogEventAuthor, raceLog.getCurrentPassId() + 1));
|
||||
final RaceLogPassChangeEventImpl passChangeEvent = new RaceLogPassChangeEventImpl(timePointForStartOfNewPass,
|
||||
raceLogEventAuthor, raceLog.getCurrentPassId() + 1);
|
||||
addRaceLogEventAndPreventRecursion(raceLog, passChangeEvent);
|
||||
}
|
||||
|
||||
private boolean abortingFlagMatches(RaceStatusType raceStatus, Flags upperFlag) {
|
||||
@@ -351,8 +397,11 @@ public class RaceAndCompetitorStatusWithRaceLogReconciler {
|
||||
final int officialRank = raceCompetitor.getOfficialRank();
|
||||
final long officialFinishingTime = raceCompetitor.getOfficialFinishTime();
|
||||
final long timePointForStatusEvent = raceCompetitor.getStatusTime();
|
||||
logger.info("Received a status change for competitor " + raceCompetitor + " in race "+trackedRace.getRaceIdentifier()+": officialRank: " + officialRank
|
||||
+ ", officialFinishingTime: " + officialFinishingTime + ", statusTime: " + timePointForStatusEvent);
|
||||
logger.info("Received a status change for competitor " + raceCompetitor + " in race "+trackedRace.getRaceIdentifier()+
|
||||
": officialRank: " + officialRank+
|
||||
", officialFinishingTime: "+officialFinishingTime+
|
||||
", competitorStatus: "+raceCompetitor.getStatus()+
|
||||
", statusTime: " + timePointForStatusEvent);
|
||||
if (timePointForStatusEvent != 0) {
|
||||
// there is an official result for the competitor on TracTrac's side
|
||||
// find out if we already have this information represented in the race log(s) and if not if the TracTrac information is newer:
|
||||
@@ -367,6 +416,9 @@ public class RaceAndCompetitorStatusWithRaceLogReconciler {
|
||||
: resultFromRaceLogAndItsCreationTimePoint.getA().getFinishingTime().asMillis()) != officialFinishingTime
|
||||
|| resultFromRaceLogAndItsCreationTimePoint.getA().getMaxPointsReason() != officialMaxPointsReason)
|
||||
&& resultFromRaceLogAndItsCreationTimePoint.getB().before(officialResultTime))) {
|
||||
logger.info("Applying official competitor result because its time point "+officialResultTime
|
||||
+" is newer than the latest result for that competitor from the race log "+
|
||||
(resultFromRaceLogAndItsCreationTimePoint==null?"null":resultFromRaceLogAndItsCreationTimePoint.getB()));
|
||||
// We have received an update from TracTrac, rank or finishing time or penalty varies and is newer
|
||||
// than the last thing we see in the race log (including we may not have anything in the race
|
||||
// log for the results of that competitor at all yet).
|
||||
@@ -382,9 +434,11 @@ public class RaceAndCompetitorStatusWithRaceLogReconciler {
|
||||
final CompetitorResults resultsForRaceLog = new CompetitorResultsImpl();
|
||||
resultsForRaceLog.add(resultForRaceLog);
|
||||
final RaceLog defaultRaceLog = getDefaultRaceLog(trackedRace);
|
||||
defaultRaceLog.add(new RaceLogFinishPositioningConfirmedEventImpl(officialResultTime, officialResultTime,
|
||||
raceLogEventAuthor,
|
||||
UUID.randomUUID(), defaultRaceLog.getCurrentPassId(), resultsForRaceLog));
|
||||
final RaceLogFinishPositioningConfirmedEventImpl raceLogEvent = new RaceLogFinishPositioningConfirmedEventImpl(
|
||||
officialResultTime, officialResultTime, raceLogEventAuthor,
|
||||
UUID.randomUUID(), defaultRaceLog.getCurrentPassId(), resultsForRaceLog);
|
||||
// Avoid an endless recursion in case this event triggers this reconciler's race log listener; see bug 5565
|
||||
addRaceLogEventAndPreventRecursion(defaultRaceLog, raceLogEvent);
|
||||
logger.info("Added the following result to the race log of " + trackedRace.getRaceIdentifier()
|
||||
+ " for competitor " + raceCompetitor + ": " + resultForRaceLog);
|
||||
} else {
|
||||
|
||||
+3
-1
@@ -379,12 +379,14 @@ public class TracTracRaceTrackerImpl extends AbstractRaceTrackerImpl
|
||||
@Override public void dataSourceChanged(IRace race, DataSource oldDataSource, URI oldLiveURI, URI oldStoredURI) {}
|
||||
@Override
|
||||
public void updateRace(IRace race) {
|
||||
if (Util.equalsWithNull(race, TracTracRaceTrackerImpl.this.tractracRace)) {
|
||||
if (Util.equalsWithNull(race.getId(), TracTracRaceTrackerImpl.this.tractracRace.getId())) {
|
||||
int delayToLiveInMillis = race.getLiveDelay()*1000;
|
||||
if (getRace() != null) {
|
||||
DynamicTrackedRace trackedRace = getTrackedRegatta().getExistingTrackedRace(getRace());
|
||||
if (trackedRace != null) {
|
||||
if (reconciler != null) {
|
||||
logger.info("Handling a race status update for race "+race.getName()+" with status "+race.getStatus()+
|
||||
" and status time "+race.getStatusTime());
|
||||
// in case a race status change was the reason for this update, reconcile with the race log(s)
|
||||
reconciler.reconcileRaceStatus(race, trackedRace);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ package com.sap.sailing.domain.base;
|
||||
|
||||
|
||||
public interface RegattaListener {
|
||||
void raceAdded(Regatta regatta, RaceDefinition race);
|
||||
void raceRemoved(Regatta regatta, RaceDefinition race);
|
||||
default void raceAdded(Regatta regatta, RaceDefinition race) {}
|
||||
default void raceRemoved(Regatta regatta, RaceDefinition race) {}
|
||||
default void useStartTimeInferenceChanged(Regatta regatta, boolean newUseStartTimeInference) {}
|
||||
default void controlTrackingFromStartAndFinishTimesChanged(Regatta regatta, boolean newControlTrackingFromStartAndFinishTimes) {}
|
||||
default void autoRestartTrackingUponCompetitorSetChangeChanged(Regatta regatta, boolean newAutoRestartTrackingUponCompetitorSetChange) {}
|
||||
}
|
||||
|
||||
@@ -707,17 +707,38 @@ public class RegattaImpl extends NamedImpl implements Regatta, RaceColumnListene
|
||||
|
||||
@Override
|
||||
public void setControlTrackingFromStartAndFinishTimes(boolean controlTrackingFromStartAndFinishTimes) {
|
||||
this.controlTrackingFromStartAndFinishTimes = controlTrackingFromStartAndFinishTimes;
|
||||
if (controlTrackingFromStartAndFinishTimes != this.controlTrackingFromStartAndFinishTimes) {
|
||||
this.controlTrackingFromStartAndFinishTimes = controlTrackingFromStartAndFinishTimes;
|
||||
synchronized (regattaListeners) {
|
||||
for (RegattaListener l : regattaListeners) {
|
||||
l.controlTrackingFromStartAndFinishTimesChanged(this, controlTrackingFromStartAndFinishTimes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAutoRestartTrackingUponCompetitorSetChange(boolean autoRestartTrackingUponCompetitorSetChange) {
|
||||
this.autoRestartTrackingUponCompetitorSetChange = autoRestartTrackingUponCompetitorSetChange;
|
||||
if (autoRestartTrackingUponCompetitorSetChange != this.autoRestartTrackingUponCompetitorSetChange) {
|
||||
this.autoRestartTrackingUponCompetitorSetChange = autoRestartTrackingUponCompetitorSetChange;
|
||||
synchronized (regattaListeners) {
|
||||
for (RegattaListener l : regattaListeners) {
|
||||
l.autoRestartTrackingUponCompetitorSetChangeChanged(this, autoRestartTrackingUponCompetitorSetChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setUseStartTimeInference(boolean useStartTimeInference) {
|
||||
this.useStartTimeInference = useStartTimeInference;
|
||||
if (useStartTimeInference != this.useStartTimeInference) {
|
||||
this.useStartTimeInference = useStartTimeInference;
|
||||
synchronized (regattaListeners) {
|
||||
for (RegattaListener l : regattaListeners) {
|
||||
l.useStartTimeInferenceChanged(this, useStartTimeInference);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
-2
@@ -133,7 +133,6 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
this.courseDesignChangedListeners = new HashSet<>();
|
||||
this.startTimeChangedListeners = new HashSet<>();
|
||||
this.raceAbortedListeners = new HashSet<>();
|
||||
|
||||
gpsFixReceived = new AtomicBoolean(false);
|
||||
this.raceIsKnownToStartUpwind = race.getBoatClass().typicallyStartsUpwind();
|
||||
if (!raceIsKnownToStartUpwind) {
|
||||
@@ -144,7 +143,6 @@ DynamicTrackedRace, GPSTrackListener<Competitor, GPSFixMoving> {
|
||||
windSourcesToExclude.add(new WindSourceImpl(WindSourceType.COURSE_BASED));
|
||||
setWindSourcesToExclude(windSourcesToExclude);
|
||||
}
|
||||
|
||||
for (Competitor competitor : getRace().getCompetitors()) {
|
||||
DynamicGPSFixTrack<Competitor, GPSFixMoving> track = getTrack(competitor);
|
||||
track.addListener(this);
|
||||
|
||||
+1
-1
@@ -654,7 +654,7 @@ public class TrackedLegOfCompetitorImpl implements TrackedLegOfCompetitor {
|
||||
if (hasFinishedLeg(timePoint)) {
|
||||
// Yes, so the gap is the time period between the time points at which the leader and
|
||||
// our competitor finished this leg.
|
||||
return whenLeaderFinishedLeg.until(getMarkPassingForLegEnd().getTimePoint());
|
||||
return whenLeaderFinishedLeg.until(getMarkPassingForLegEnd().getTimePoint());
|
||||
} else {
|
||||
if (windwardSpeed == null) {
|
||||
return null;
|
||||
|
||||
+41
-2
@@ -63,6 +63,8 @@ import com.sap.sailing.domain.base.DomainFactory;
|
||||
import com.sap.sailing.domain.base.Leg;
|
||||
import com.sap.sailing.domain.base.Mark;
|
||||
import com.sap.sailing.domain.base.RaceDefinition;
|
||||
import com.sap.sailing.domain.base.Regatta;
|
||||
import com.sap.sailing.domain.base.RegattaListener;
|
||||
import com.sap.sailing.domain.base.SharedDomainFactory;
|
||||
import com.sap.sailing.domain.base.Sideline;
|
||||
import com.sap.sailing.domain.base.SpeedWithBearingWithConfidence;
|
||||
@@ -430,6 +432,28 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
|
||||
private final NamedReentrantReadWriteLock sensorTracksLock;
|
||||
|
||||
/**
|
||||
* When a regatta's {@link Regatta#useStartTimeInference()} or {@link Regatta#isControlTrackingFromStartAndFinishTimes()}
|
||||
* changes, the tracking start/end times need to be recalculated. This regatta listener handles this. It has to be
|
||||
* added to the tracked race's underlying regatta at construction time and after de-serialization (regatta listeners
|
||||
* are transient and are not serialized together with the regatta).
|
||||
*
|
||||
* @author Axel Uhl (D043530)
|
||||
*
|
||||
*/
|
||||
private class TimingUpdaterCallback implements RegattaListener {
|
||||
@Override
|
||||
public void useStartTimeInferenceChanged(Regatta regatta, boolean newUseStartTimeInference) {
|
||||
updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void controlTrackingFromStartAndFinishTimesChanged(Regatta regatta,
|
||||
boolean newControlTrackingFromStartAndFinishTimes) {
|
||||
updateStartAndEndOfTracking(/* waitForGPSFixesToLoad */ false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the tracked race with one-design ranking.
|
||||
*/
|
||||
@@ -455,6 +479,7 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
boolean useInternalMarkPassingAlgorithm, RankingMetricConstructor rankingMetricConstructor,
|
||||
RaceLogAndTrackedRaceResolver raceLogResolver, TrackingConnectorInfo trackingConnectorInfo) {
|
||||
super(race, trackedRegatta, windStore, millisecondsOverWhichToAverageWind);
|
||||
registerRegattaListener();
|
||||
this.raceLogResolver = raceLogResolver;
|
||||
this.trackingConnectorInfo = trackingConnectorInfo;
|
||||
raceStates = new WeakHashMap<>();
|
||||
@@ -893,15 +918,21 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
}
|
||||
// check for start/finished times in race log and add a few minutes on the ends
|
||||
if (!startOfTrackingFound || !endOfTrackingFound) {
|
||||
if (!startOfTrackingFound && getStartOfRace() != null) {
|
||||
if (!startOfTrackingFound && getStartOfRace() != null && getTrackedRegatta().getRegatta().isControlTrackingFromStartAndFinishTimes()) {
|
||||
startOfTracking = getStartOfRace().minus(START_TRACKING_THIS_MUCH_BEFORE_RACE_START);
|
||||
startOfTrackingFound = true;
|
||||
}
|
||||
if (!endOfTrackingFound && getFinishedTime() != null) {
|
||||
if (!endOfTrackingFound && getFinishedTime() != null && getTrackedRegatta().getRegatta().isControlTrackingFromStartAndFinishTimes()) {
|
||||
endOfTracking = getFinishedTime().plus(STOP_TRACKING_THIS_MUCH_AFTER_RACE_FINISH);
|
||||
endOfTrackingFound = true;
|
||||
}
|
||||
}
|
||||
if (!startOfTrackingFound) {
|
||||
startOfTracking = null;
|
||||
}
|
||||
if (!endOfTrackingFound) {
|
||||
endOfTracking = null;
|
||||
}
|
||||
}
|
||||
startOfTrackingChanged(oldStartOfTracking, waitForGPSFixesToLoad);
|
||||
endOfTrackingChanged(oldEndOfTracking, waitForGPSFixesToLoad);
|
||||
@@ -3740,6 +3771,14 @@ public abstract class TrackedRaceImpl extends TrackedRaceWithWindEssentials impl
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Regatta listeners are transient only; so after de-serialization we have to re-establish the regatta listener
|
||||
* that is responsible for updating tracking times when the rules for how this works have changed on the regatta.
|
||||
*/
|
||||
public void registerRegattaListener() {
|
||||
trackedRegatta.getRegatta().addRegattaListener(new TimingUpdaterCallback());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<Mark> getMarksFromRegattaLogs() {
|
||||
final Set<Mark> result = new HashSet<>();
|
||||
|
||||
+4
@@ -4,6 +4,10 @@
|
||||
<div id="mainContent">
|
||||
<h4 class="articleHeadline">What's New - SAP Sailing Analytics</h4>
|
||||
<div class="innerContent">
|
||||
<h5 class="articleSubheadline">June 2021</h5>
|
||||
<ul class="bulletList">
|
||||
<li>The advantage line now also (again) shows after the first competitor has crossed the finish line.</li>
|
||||
</ul>
|
||||
<h5 class="articleSubheadline">May 2021</h5>
|
||||
<ul class="bulletList">
|
||||
<li>Added language support for Danish (Dansk).</li>
|
||||
|
||||
-3
@@ -7,7 +7,6 @@ import com.sap.sse.gwt.adminconsole.AdminConsolePanelSupplier;
|
||||
import com.sap.sse.gwt.client.controls.filestorage.FileStoragePanel;
|
||||
|
||||
public class FileStoragePanelSupplier extends AdminConsolePanelSupplier<FileStoragePanel> {
|
||||
|
||||
private final Presenter presenter;
|
||||
|
||||
public FileStoragePanelSupplier(final Presenter presenter) {
|
||||
@@ -26,7 +25,6 @@ public class FileStoragePanelSupplier extends AdminConsolePanelSupplier<FileStor
|
||||
@Override
|
||||
public void getAsync(RunAsyncCallback callback) {
|
||||
GWT.runAsync(new RunAsyncCallback() {
|
||||
|
||||
@Override
|
||||
public void onSuccess() {
|
||||
widget = init();
|
||||
@@ -39,5 +37,4 @@ public class FileStoragePanelSupplier extends AdminConsolePanelSupplier<FileStor
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+11
-6
@@ -453,21 +453,26 @@ public class TimePanel<T extends TimePanelSettings> extends AbstractCompositeCom
|
||||
protected Date getToTime() {
|
||||
return timeRangeProvider.getToTime();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param min must not be <code>null</code>
|
||||
* @param max must not be <code>null</code>
|
||||
*/
|
||||
public void setMinMax(Date min, Date max, boolean fireEvent) {
|
||||
assert min != null && max != null;
|
||||
|
||||
|
||||
boolean changed = false;
|
||||
changed = timeSlider.setMinAndMaxValue(new Double(min.getTime()), new Double(max.getTime()), fireEvent);
|
||||
// Note (bug5568): updateMinAndMaxValue does not shrink the previously set time interval
|
||||
changed = timeSlider.updateMinAndMaxValue(new Double(min.getTime()), new Double(max.getTime()), fireEvent);
|
||||
if (changed) {
|
||||
final Double minValue = timeSlider.getMinValue();
|
||||
final Date minDate = new Date(minValue.longValue());
|
||||
final Double maxValue = timeSlider.getMaxValue();
|
||||
final Date maxDate = new Date(maxValue.longValue());
|
||||
if (!timeRangeProvider.isZoomed()) {
|
||||
timeRangeProvider.setTimeRange(min, max, this);
|
||||
timeRangeProvider.setTimeRange(minDate, maxDate, this);
|
||||
}
|
||||
|
||||
|
||||
int numSteps = timeSlider.getElement().getClientWidth();
|
||||
if (numSteps > 0) {
|
||||
timeSlider.setStepSize(numSteps, fireEvent);
|
||||
@@ -477,7 +482,7 @@ public class TimePanel<T extends TimePanelSettings> extends AbstractCompositeCom
|
||||
|
||||
// Christopher: following setCurrentValue requires stepsize to be set <> 0 (otherwise division by zero; NaN)
|
||||
if (timeSlider.getCurrentValue() == null) {
|
||||
timeSlider.setCurrentValue(new Double(min.getTime()), fireEvent);
|
||||
timeSlider.setCurrentValue(minValue, fireEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -1837,7 +1837,8 @@ public class RaceMap extends AbstractCompositeComponent<RaceMapSettings> impleme
|
||||
double rotatedBearingDeg1 = 0.0;
|
||||
double rotatedBearingDeg2 = 0.0;
|
||||
if (lastBoatFix.legType == null) {
|
||||
GWT.log("no legType to display advantage line");
|
||||
GWT.log("no legType to display advantage line; competitor was "+visibleLeaderInfo.getB().getName()+
|
||||
", fix from "+lastBoatFix.timepoint);
|
||||
} else {
|
||||
switch (lastBoatFix.legType) {
|
||||
case UPWIND:
|
||||
|
||||
+6
-1
@@ -129,7 +129,12 @@ public class QuickFlagDataFromLeaderboardDTOProvider extends AbstractQuickFlagDa
|
||||
.get(raceColumnName);
|
||||
List<LegEntryDTO> legDetailsList = raceEntryForCompetitor.legDetails;
|
||||
if (raceEntryForCompetitor != null && legDetailsList != null) {
|
||||
oneBasedLegNumber = raceEntryForCompetitor.getOneBasedCurrentLegNumber();
|
||||
final int oneBasedLegNumberCandidate = raceEntryForCompetitor.getOneBasedCurrentLegNumber();
|
||||
if (oneBasedLegNumberCandidate == legDetailsList.size() && legDetailsList.get(oneBasedLegNumberCandidate-1).finished) {
|
||||
oneBasedLegNumber = 0;
|
||||
} else {
|
||||
oneBasedLegNumber = oneBasedLegNumberCandidate;
|
||||
}
|
||||
lastLeaderboardProvidedLegNumbers = true;
|
||||
speedInKnots = sogProvider.get(row);
|
||||
} else {
|
||||
|
||||
+5
-8
@@ -1290,16 +1290,14 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
|
||||
result.raceIsKnownToStartUpwind = trackedRace.raceIsKnownToStartUpwind();
|
||||
Map<WindSource, WindTrackInfoDTO> windTrackInfoDTOs = new HashMap<WindSource, WindTrackInfoDTO>();
|
||||
result.windTrackInfoByWindSource = windTrackInfoDTOs;
|
||||
|
||||
List<WindSource> windSourcesToDeliver = new ArrayList<WindSource>();
|
||||
if (windSources != null) {
|
||||
windSourcesToDeliver.addAll(windSources);
|
||||
} else {
|
||||
windSourcesToDeliver.add(new WindSourceImpl(WindSourceType.EXPEDITION));
|
||||
windSourcesToDeliver.add(new WindSourceImpl(WindSourceType.WEB));
|
||||
}
|
||||
for (WindSource windSource : windSourcesToDeliver) {
|
||||
if(windSource.getType() == WindSourceType.WEB) {
|
||||
if (windSource.getType() == WindSourceType.WEB) {
|
||||
WindTrackInfoDTO windTrackInfoDTO = new WindTrackInfoDTO();
|
||||
windTrackInfoDTO.windFixes = new ArrayList<WindDTO>();
|
||||
final WindTrack windTrack = trackedRace.getOrCreateWindTrack(windSource);
|
||||
@@ -1310,7 +1308,7 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
|
||||
Iterator<Wind> windIter = windTrack.getRawFixes().iterator();
|
||||
while (windIter.hasNext()) {
|
||||
Wind wind = windIter.next();
|
||||
if(wind != null) {
|
||||
if (wind != null) {
|
||||
WindDTO windDTO = createWindDTO(wind, windTrack);
|
||||
windTrackInfoDTO.windFixes.add(windDTO);
|
||||
}
|
||||
@@ -1318,7 +1316,6 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
|
||||
} finally {
|
||||
windTrack.unlockAfterRead();
|
||||
}
|
||||
|
||||
windTrackInfoDTOs.put(windSource, windTrackInfoDTO);
|
||||
}
|
||||
}
|
||||
@@ -2283,8 +2280,8 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
|
||||
final List<Competitor> competitorsFromBestToWorst = trackedRace.getCompetitorsFromBestToWorst(actualTimePoint);
|
||||
for (Competitor competitor : competitorsFromBestToWorst) {
|
||||
TrackedLegOfCompetitor trackedLeg = trackedRace.getTrackedLeg(competitor, actualTimePoint);
|
||||
if (trackedLeg != null) {
|
||||
int legNumberOneBased = race.getCourse().getLegs().indexOf(trackedLeg.getLeg()) + 1;
|
||||
if (trackedLeg != null || !trackedRace.getMarkPassings(competitor).isEmpty()) {
|
||||
int legNumberOneBased = trackedLeg==null ? 0 : race.getCourse().getLegs().indexOf(trackedLeg.getLeg()) + 1;
|
||||
Boat boatOfCompetitor = trackedRace.getBoatOfCompetitor(competitor);
|
||||
QuickRankDTO quickRankDTO = new QuickRankDTO(
|
||||
baseDomainFactory.convertToCompetitorAndBoatDTO(competitor, boatOfCompetitor).getCompetitor(),
|
||||
@@ -4400,7 +4397,7 @@ public class SailingServiceImpl extends ResultCachingProxiedRemoteServiceServlet
|
||||
}
|
||||
RaceLogDTO result = null;
|
||||
final RaceLog raceLog = getService().getRaceLog(leaderboardName, raceColumnDTO.getName(), fleet.getName());
|
||||
if(raceLog != null) {
|
||||
if (raceLog != null) {
|
||||
List<RaceLogEventDTO> entries = new ArrayList<RaceLogEventDTO>();
|
||||
result = new RaceLogDTO(leaderboardName, raceColumnDTO.getName(), fleet.getName(), raceLog.getCurrentPassId(), entries);
|
||||
raceLog.lockForRead();
|
||||
|
||||
@@ -82,3 +82,5 @@ chargebeeSubscriptionPermissions = com.sap.sailing.server.gateway.subscription.c
|
||||
/rc/** = bearerTokenOrAnonymous
|
||||
/orc-certificate-import = bearerTokenOrAnonymous
|
||||
/subscription/hooks/chargebee = bearerToken,chargebeeSubscriptionPermissions
|
||||
/windStatus = bearerTokenOrAnonymous
|
||||
/windStatus/** = bearerTokenOrAnonymous
|
||||
+2
-7
@@ -3450,7 +3450,6 @@ implements RacingEventService, ClearStateTestSupport, RegattaListener, Leaderboa
|
||||
}
|
||||
}
|
||||
logoutput.append("Received " + competitorAndBoatStore.getCompetitorsCount() + " NEW competitors\n");
|
||||
|
||||
logger.info("Reading device configurations...");
|
||||
raceManagerDeviceConfigurationsById.putAll((Map<UUID, DeviceConfiguration>) ois.readObject());
|
||||
logoutput.append("Received " + raceManagerDeviceConfigurationsById.size() + " NEW configuration entries\n");
|
||||
@@ -3458,42 +3457,38 @@ implements RacingEventService, ClearStateTestSupport, RegattaListener, Leaderboa
|
||||
raceManagerDeviceConfigurationsByName.put(config.getName(), config);
|
||||
logoutput.append(String.format("%3s\n", config.getName()));
|
||||
}
|
||||
|
||||
logger.info("Reading anniversary races...");
|
||||
final Map<Integer, Pair<DetailedRaceInfo, AnniversaryType>> knownAnniversaries = (Map<Integer, Pair<DetailedRaceInfo, AnniversaryType>>) ois
|
||||
.readObject();
|
||||
anniversaryRaceDeterminator.setKnownAnniversaries(knownAnniversaries);
|
||||
logoutput.append("Received " + knownAnniversaries.size() + " anniversary races\n");
|
||||
|
||||
logger.info("Reading next anniversary...");
|
||||
final Pair<Integer, AnniversaryType> nextAnniversary = (Pair<Integer, AnniversaryType>) ois.readObject();
|
||||
anniversaryRaceDeterminator.setNextAnniversary(nextAnniversary);
|
||||
logoutput.append("Received next anniversary " + nextAnniversary + "\n");
|
||||
|
||||
logger.info("Reading race count for anniversaries...");
|
||||
final int currentRaceCount = ois.readInt();
|
||||
anniversaryRaceDeterminator.setRaceCount(currentRaceCount);
|
||||
logoutput.append("Received race count for anniversaries " + currentRaceCount + "\n");
|
||||
|
||||
logger.info("Reading remote sailing server references...");
|
||||
for (RemoteSailingServerReference remoteSailingServerReference : (Iterable<RemoteSailingServerReference>) ois
|
||||
.readObject()) {
|
||||
remoteSailingServerSet.add(remoteSailingServerReference);
|
||||
logoutput.append("Received remote sailing server reference " + remoteSailingServerReference);
|
||||
}
|
||||
|
||||
// make sure to initialize listeners correctly
|
||||
for (Regatta regatta : regattasByName.values()) {
|
||||
RegattaImpl regattaImpl = (RegattaImpl) regatta;
|
||||
regattaImpl.initializeSeriesAfterDeserialize();
|
||||
regattaImpl.addRaceColumnListener(raceLogReplicator);
|
||||
}
|
||||
// re-establish RaceLogResolver references to this RacingEventService in all TrackedRace instances
|
||||
// re-establish RaceLogResolver references to this RacingEventService and regatta listeners in all TrackedRace instances
|
||||
for (DynamicTrackedRegatta trackedRegatta : regattaTrackingCache.values()) {
|
||||
trackedRegatta.lockTrackedRacesForRead();
|
||||
try {
|
||||
for (TrackedRace trackedRace : trackedRegatta.getTrackedRaces()) {
|
||||
((TrackedRaceImpl) trackedRace).setRaceLogResolver(this);
|
||||
((TrackedRaceImpl) trackedRace).registerRegattaListener();
|
||||
}
|
||||
} finally {
|
||||
trackedRegatta.unlockTrackedRacesAfterRead();
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
<div class="mainContent">
|
||||
<h2 class="releaseHeadline">Release Notes - Administration Console</h2>
|
||||
<div class="innerContent">
|
||||
<h2 class="articleSubheadline">June 2021</h2>
|
||||
<ul class="bulletList">
|
||||
<li>Infer a tracking start/end time from race start/end time only if the regatta is marked accordingly.</li>
|
||||
</ul>
|
||||
<h2 class="articleSubheadline">May 2021</h2>
|
||||
<ul class="bulletList">
|
||||
<li>Added the RS21 boat class including logo, hull length and visualization type as a pre-defined boat class.</li>
|
||||
|
||||
@@ -248,7 +248,41 @@ public class TimeSlider extends SliderBar {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Updates the minimum and maximum value. Either value will only be updated if it does not shrink the the time
|
||||
* interval on its respective end.
|
||||
* @param minValue {@link Double} new value for minimum
|
||||
* @param maxValue {@link Double} new value for maximum
|
||||
* @param fireEvent fires the onValue change event if {@code true} and at least one value has changed
|
||||
* @return {@code true} if min or max have changed
|
||||
* @see {@link #setMinAndMaxValue(Double, Double, boolean)}
|
||||
*/
|
||||
public boolean updateMinAndMaxValue(Double minValue, Double maxValue, boolean fireEvent) {
|
||||
boolean changed = false;
|
||||
Double minLimited = minValue;
|
||||
if (minValue != null && this.minValue != null) {
|
||||
minLimited = Double.min(minValue, this.minValue);
|
||||
}
|
||||
if (!Util.equalsWithNull(minLimited, this.minValue)) {
|
||||
this.minValue = minLimited;
|
||||
changed = true;
|
||||
}
|
||||
|
||||
Double maxLimited = maxValue;
|
||||
if (maxValue != null && this.maxValue != null) {
|
||||
maxLimited = Double.max(maxValue, this.maxValue);
|
||||
}
|
||||
if (!Util.equalsWithNull(maxLimited, this.maxValue)) {
|
||||
this.maxValue = maxLimited;
|
||||
changed = true;
|
||||
}
|
||||
if (changed && !isZoomed) { // !isZoomed to replicate behavior of setMinAndMaxValue
|
||||
onMinMaxValueChanged(fireEvent);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMinMaxValueChanged(boolean fireEvent) {
|
||||
calculateTicks();
|
||||
|
||||
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
|
||||
Bundle-ManifestVersion: 2
|
||||
Bundle-Name: TracTrac Client module
|
||||
Bundle-SymbolicName: com.tractrac.clientmodule
|
||||
Bundle-Version: 3.13.7
|
||||
Bundle-Version: 3.13.10
|
||||
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
|
||||
Bundle-ClassPath: .,
|
||||
lib/TracAPI.jar
|
||||
|
||||
@@ -14,13 +14,51 @@ It contains also some files:
|
||||
- test.sh -> script that compiles the code in the src folder, creates the test.jar library and execute the code of the example.
|
||||
- Manifest.txt -> manifest used to create the test.jar file
|
||||
|
||||
********************************************
|
||||
TracAPI 3.13.10
|
||||
********************************************
|
||||
This is a final version. It keeps the backward compatibility.
|
||||
|
||||
Release date: 02/06/2021
|
||||
Build number:
|
||||
|
||||
1) Bugs
|
||||
|
||||
- ConcurrentModificationException: the callbacks used by the IRacesListener, ICompetitorsListener and the
|
||||
IRaceObjectsListener are not thread-safe (Reported by Axel Uhl, 01/06/2021)
|
||||
|
||||
********************************************
|
||||
TracAPI 3.13.9
|
||||
********************************************
|
||||
This is a final version. It keeps the backward compatibility.
|
||||
|
||||
Release date: 01/06/2021
|
||||
Build number:
|
||||
|
||||
1) Bugs
|
||||
|
||||
Compiled using a wrong version of Java
|
||||
|
||||
|
||||
********************************************
|
||||
TracAPI 3.13.8
|
||||
********************************************
|
||||
This is a final version. It keeps the backward compatibility.
|
||||
|
||||
Release date: 01/06/2021
|
||||
Build number:
|
||||
|
||||
1) Bugs
|
||||
|
||||
- The UPDATE RACE messages are always sent (Requested by Jorge Piera, 01/06/2021)
|
||||
|
||||
********************************************
|
||||
TracAPI 3.13.7
|
||||
********************************************
|
||||
This is a final version. It adds two new values to the RaceCompetitorStatusType
|
||||
|
||||
Release date: 02/03/2021
|
||||
Build number:
|
||||
Build number: 2d21ce3085f8a4c915fc9d81b09d1c0518e349c4
|
||||
|
||||
1) Bugs
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -8,6 +8,6 @@
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>com.tractrac.clientmodule</artifactId>
|
||||
<version>3.13.7</version>
|
||||
<version>3.13.10</version>
|
||||
<packaging>eclipse-plugin</packaging>
|
||||
</project>
|
||||
|
||||
+6
-6
@@ -75,12 +75,12 @@ JAVA_VERSION=$(echo "$JAVA_VERSION_OUTPUT" | sed 's/^.* version "\(.*\)\.\(.*\)\
|
||||
export JAVA_11_LOGGING_ARGS="-Xlog:gc+ergo*=trace:file=logs/gc_ergo.log:time:filecount=10,filesize=100000 -Xlog:gc*:file=logs/gc.log:time:filecount=10,filesize=100000"
|
||||
export JAVA_11_ARGS="-Dosgi.java.profile=file://`pwd`/JavaSE-11.profile --add-modules=ALL-SYSTEM -Djavax.xml.bind.JAXBContextFactory=com.sun.xml.bind.v2.ContextFactory -XX:ThreadPriorityPolicy=1 -XX:+UnlockExperimentalVMOptions -XX:+UseZGC ${JAVA_11_LOGGING_ARGS}"
|
||||
export JAVA_8_LOGGING_ARGS="-XX:+PrintAdaptiveSizePolicy -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Xloggc:logs/gc.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M"
|
||||
echo JAVA_VERSION detected: $JAVA_VERSION
|
||||
echo JAVA_VERSION detected: $JAVA_VERSION >&2
|
||||
if echo $JAVA_VERSION | grep -q "^11\."; then
|
||||
echo Java 11 detected
|
||||
echo Java 11 detected >&2
|
||||
JAVA_VERSION_SPECIFIC_ARGS=$JAVA_11_ARGS
|
||||
else
|
||||
echo Java other than 11 detected
|
||||
echo Java other than 11 detected >&2
|
||||
# options for use with SAP JVM only:
|
||||
if echo "$JAVA_VERSION_OUTPUT" | grep -q "SAP Java"; then
|
||||
ADDITIONAL_JAVA_ARGS="$ADDITIONAL_JAVA_ARGS -XX:+GCHistory -XX:GCHistoryFilename=logs/sapjvm_gc@PID.prf"
|
||||
@@ -88,12 +88,12 @@ else
|
||||
MAJOR=$( echo "$BUILD" | sed -e 's/^.*(build \([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)).*$/\1/' )
|
||||
MINOR=$( echo "$BUILD" | sed -e 's/^.*(build \([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)).*$/\2/' )
|
||||
UPDATE=$( echo "$BUILD" | sed -e 's/^.*(build \([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)).*$/\3/' )
|
||||
echo "SAP JVM $MAJOR $MINOR $UPDATE detected"
|
||||
echo "SAP JVM $MAJOR $MINOR $UPDATE detected" >&2
|
||||
if [ $MAJOR -ge 8 -a $MINOR -ge 1 -a $UPDATE -ge 45 ]; then
|
||||
echo "Update 8.1.045 or later; using Java11 GC logging options"
|
||||
echo "Update 8.1.045 or later; using Java11 GC logging options" >&2
|
||||
LOGGING_ARGS="$JAVA_11_LOGGING_ARGS"
|
||||
else
|
||||
echo "Update before 8.1.045; using Java8 GC logging options"
|
||||
echo "Update before 8.1.045; using Java8 GC logging options" >&2
|
||||
LOGGING_ARGS="$JAVA_8_LOGGING_ARGS"
|
||||
fi
|
||||
else
|
||||
|
||||
@@ -123,8 +123,19 @@ install_environment ()
|
||||
# clean up directory to really make sure that there are no files left
|
||||
rm -rf ${SERVER_HOME}/environment
|
||||
mkdir ${SERVER_HOME}/environment
|
||||
echo "Using environment https://releases.sapsailing.com/environments/$USE_ENVIRONMENT"
|
||||
wget -P environment https://releases.sapsailing.com/environments/$USE_ENVIRONMENT
|
||||
if [[ ${INSTALL_FROM_SCP_USER_AT_HOST_AND_PORT} != "" ]]; then
|
||||
SCP_PORT=$( echo ${INSTALL_FROM_SCP_USER_AT_HOST_AND_PORT} | sed -e 's/^[^:]*:\?\([0-9]*\)\?$/\1/' )
|
||||
if [ -n "${SCP_PORT}" ]; then
|
||||
SCP_PORT_OPTION="-P ${SCP_PORT}"
|
||||
fi
|
||||
SCP_HOST=$( echo ${INSTALL_FROM_SCP_USER_AT_HOST_AND_PORT} | sed -e 's/^\([^:]*\):\?\([0-9]*\)\?$/\1/' )
|
||||
echo "Using environment ${SCP_HOST}:/home/trac/releases/environments/${USE_ENVIRONMENT}"
|
||||
mkdir -p ./environment
|
||||
scp ${SCP_PORT_OPTION} ${SCP_HOST}:/home/trac/releases/environments/${USE_ENVIRONMENT} ./environment
|
||||
else
|
||||
echo "Using environment https://releases.sapsailing.com/environments/$USE_ENVIRONMENT"
|
||||
wget -P environment https://releases.sapsailing.com/environments/$USE_ENVIRONMENT
|
||||
fi
|
||||
echo "# Environment ($USE_ENVIRONMENT): START ($DATE_OF_EXECUTION)" >> $SERVER_HOME/env.sh
|
||||
cat ${SERVER_HOME}/environment/$USE_ENVIRONMENT >> $SERVER_HOME/env.sh
|
||||
echo "# Environment: END" >> ${SERVER_HOME}/env.sh
|
||||
@@ -140,19 +151,25 @@ load_from_release_file ()
|
||||
INSTALL_FROM_RELEASE="$(wget -O - https://releases.sapsailing.com/ 2>/dev/null | grep build- | tail -1 | sed -e 's/^.*\(build-[0-9]*\).*$/\1/')"
|
||||
echo "You didn't provide a release. Defaulting to latest master build https://releases.sapsailing.com/$INSTALL_FROM_RELEASE"
|
||||
fi
|
||||
if [[ ${INSTALL_FROM_RELEASE} != "" ]]; then
|
||||
if [ -n "${BUILD_COMPLETE_NOTIFY}" ]; then
|
||||
echo "Build/Deployment process has been started - it can take 5 to 20 minutes until your instance is ready. " | mail -r simon.marcel.pamies@sap.com -s "Build or Deployment of $INSTANCE_ID to $SERVER_HOME for server $SERVER_NAME starting" ${BUILD_COMPLETE_NOTIFY}
|
||||
fi
|
||||
cd ${SERVER_HOME}
|
||||
rm -f ${SERVER_HOME}/${INSTALL_FROM_RELEASE}.tar.gz*
|
||||
rm -rf *.tar.gz
|
||||
echo "Loading from release file https://releases.sapsailing.com/${INSTALL_FROM_RELEASE}/${INSTALL_FROM_RELEASE}.tar.gz"
|
||||
wget https://releases.sapsailing.com/${INSTALL_FROM_RELEASE}/${INSTALL_FROM_RELEASE}.tar.gz
|
||||
load_from_local_release_file
|
||||
else
|
||||
echo "The variable INSTALL_FROM_RELEASE has not been set therefore no release file will be installed!"
|
||||
if [ -n "${BUILD_COMPLETE_NOTIFY}" ]; then
|
||||
echo "Build/Deployment process has been started - it can take 5 to 20 minutes until your instance is ready. " | mail -r simon.marcel.pamies@sap.com -s "Build or Deployment of $INSTANCE_ID to $SERVER_HOME for server $SERVER_NAME starting" ${BUILD_COMPLETE_NOTIFY}
|
||||
fi
|
||||
RELEASE_FILE_NAME=${INSTALL_FROM_RELEASE}.tar.gz
|
||||
cd ${SERVER_HOME}
|
||||
rm -f ${SERVER_HOME}/${INSTALL_FROM_RELEASE}.tar.gz*
|
||||
rm -rf *.tar.gz
|
||||
if [[ ${INSTALL_FROM_SCP_USER_AT_HOST_AND_PORT} != "" ]]; then
|
||||
SCP_PORT=$( echo ${INSTALL_FROM_SCP_USER_AT_HOST_AND_PORT} | sed -e 's/^[^:]*:\?\([0-9]*\)\?$/\1/' )
|
||||
if [ -n "${SCP_PORT}" ]; then
|
||||
SCP_PORT_OPTION="-P ${SCP_PORT}"
|
||||
fi
|
||||
SCP_HOST=$( echo ${INSTALL_FROM_SCP_USER_AT_HOST_AND_PORT} | sed -e 's/^\([^:]*\):\?\([0-9]*\)\?$/\1/' )
|
||||
scp ${SCP_PORT_OPTION} ${SCP_HOST}:/home/trac/releases/${INSTALL_FROM_RELEASE}/${RELEASE_FILE_NAME} .
|
||||
else
|
||||
echo "Loading from release file https://releases.sapsailing.com/${INSTALL_FROM_RELEASE}/${RELEASE_FILE_NAME}"
|
||||
wget https://releases.sapsailing.com/${INSTALL_FROM_RELEASE}/${RELEASE_FILE_NAME}
|
||||
fi
|
||||
load_from_local_release_file
|
||||
}
|
||||
|
||||
load_from_local_release_file ()
|
||||
|
||||
+28
-28
@@ -1,44 +1,44 @@
|
||||
#!/bin/bash
|
||||
|
||||
# source variables
|
||||
. `pwd`/env.sh
|
||||
. `dirname $0`/env.sh
|
||||
|
||||
echo "Configuration for this server is:"
|
||||
echo ""
|
||||
echo "SERVER_NAME: $SERVER_NAME"
|
||||
echo "MEMORY: $MEMORY"
|
||||
echo "SERVER_PORT: $SERVER_PORT"
|
||||
echo "TELNET_PORT: $TELNET_PORT"
|
||||
echo "MONGODB_HOST: $MONGODB_HOST"
|
||||
echo "MONGODB_PORT: $MONGODB_PORT"
|
||||
echo "MONGODB_NAME: $MONGODB_NAME"
|
||||
echo "MONGODB_URI: $MONGODB_URI"
|
||||
echo "EXPEDITION_PORT: $EXPEDITION_PORT"
|
||||
echo "REPLICATION_HOST: $REPLICATION_HOST"
|
||||
echo "REPLICATION_CHANNEL: $REPLICATION_CHANNEL"
|
||||
echo "ADDITIONAL_ARGS: $ADDITIONAL_JAVA_ARGS"
|
||||
echo ""
|
||||
echo "INSTALL_FROM_RELEASE: $INSTALL_FROM_RELEASE"
|
||||
echo "DEPLOY_TO: $DEPLOY_TO"
|
||||
echo "BUILD_BEFORE_START: $BUILD_BEFORE_START"
|
||||
echo "USE_ENVRIONMENT: $USE_ENVIRONMENT"
|
||||
echo ""
|
||||
echo "JAVA_HOME: $JAVA_HOME"
|
||||
echo "INSTANCE_ID: $INSTANCE_ID"
|
||||
echo ""
|
||||
echo "Configuration for this server is:" >&2
|
||||
echo "" >&2
|
||||
echo "SERVER_NAME: $SERVER_NAME" >&2
|
||||
echo "MEMORY: $MEMORY" >&2
|
||||
echo "SERVER_PORT: $SERVER_PORT" >&2
|
||||
echo "TELNET_PORT: $TELNET_PORT" >&2
|
||||
echo "MONGODB_HOST: $MONGODB_HOST" >&2
|
||||
echo "MONGODB_PORT: $MONGODB_PORT" >&2
|
||||
echo "MONGODB_NAME: $MONGODB_NAME" >&2
|
||||
echo "MONGODB_URI: $MONGODB_URI" >&2
|
||||
echo "EXPEDITION_PORT: $EXPEDITION_PORT" >&2
|
||||
echo "REPLICATION_HOST: $REPLICATION_HOST" >&2
|
||||
echo "REPLICATION_CHANNEL: $REPLICATION_CHANNEL" >&2
|
||||
echo "ADDITIONAL_ARGS: $ADDITIONAL_JAVA_ARGS" >&2
|
||||
echo "" >&2
|
||||
echo "INSTALL_FROM_RELEASE: $INSTALL_FROM_RELEASE" >&2
|
||||
echo "DEPLOY_TO: $DEPLOY_TO" >&2
|
||||
echo "BUILD_BEFORE_START: $BUILD_BEFORE_START" >&2
|
||||
echo "USE_ENVRIONMENT: $USE_ENVIRONMENT" >&2
|
||||
echo "" >&2
|
||||
echo "JAVA_HOME: $JAVA_HOME" >&2
|
||||
echo "INSTANCE_ID: $INSTANCE_ID" >&2
|
||||
echo "" >&2
|
||||
|
||||
STATUS=$(curl -i http://127.0.0.1:$SERVER_PORT/gwt/status 2>/dev/null)
|
||||
RESPONSE_STATUS_CODE=$(echo $STATUS | head -n 1 | cut -d ' ' -f 2)
|
||||
|
||||
echo "AVAILABILITY_STATUS: $RESPONSE_STATUS_CODE"
|
||||
echo "AVAILABILITY_DETAILS:"
|
||||
echo "AVAILABILITY_STATUS: $RESPONSE_STATUS_CODE" >&2
|
||||
echo "AVAILABILITY_DETAILS:" >&2
|
||||
echo "$STATUS" | grep "^\{" | jq .
|
||||
|
||||
if [[ $RESPONSE_STATUS_CODE =~ 2.. ]]; then
|
||||
echo "Server $SERVER_NAME is available on $SERVER_PORT"
|
||||
echo "Server $SERVER_NAME is available on $SERVER_PORT" >&2
|
||||
exit 0
|
||||
else
|
||||
echo "Server $SERVER_NAME is not available."
|
||||
echo "Server $SERVER_NAME is not available." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#!/bin/bash
|
||||
SERVER_PORT=$( cat env.sh | grep "^SERVER_PORT=" | tail -n 1 | sed -e 's/^SERVER_PORT=//' )
|
||||
SERVER_PORT=$( cat env.sh | grep "^[ ]*SERVER_PORT=" | tail -n 1 | sed -e 's/^[ ]*SERVER_PORT=//' )
|
||||
if [ "$1" = "-h" -o "$1" = "-?" ]; then
|
||||
echo "Usage: $0 [ {bearer-token} ]"
|
||||
echo "If no {bearer-token} is provided, username and password will be requested from the user."
|
||||
else
|
||||
echo "SERVER_PORT is ${SERVER_PORT}"
|
||||
URL="http://127.0.0.1:${SERVER_PORT}/replication/replication?action=STOP_REPLICATING"
|
||||
if [ "$1" = "" ]; then
|
||||
read -p "Username: " USERNAME
|
||||
|
||||
@@ -34,6 +34,42 @@ What are pro's and con's?
|
||||
|
||||
I would like to see a somewhat detailed run-book.
|
||||
|
||||
### Results and procedure tested in Medemblik 2021
|
||||
|
||||
Check master.conf in ``/home/sailing/server/master`` on sap-p1-2 for the desired build and correct setting of the new variable ``INSTALL_FROM_SCP_USER_AT_HOST_AND_PORT``. There is a tunnel listening on 22222 which forwards the traffic through tokyo-ssh to sapsailing.com:22.
|
||||
So a valid entry would be:
|
||||
```
|
||||
INSTALL_FROM_RELEASE=build-202106012325
|
||||
INSTALL_FROM_SCP_USER_AT_HOST_AND_PORT="trac@localhost:22222"
|
||||
```
|
||||
|
||||
Now execute, this will download and extract the build:
|
||||
```
|
||||
cd /home/sailing/servers/master; rm env.sh; cat master.conf | ./refreshInstance.sh auto-install-from-stdin
|
||||
```
|
||||
|
||||
Now we stop the replica, make sure you are user ``sailing``:
|
||||
```
|
||||
/home/sailing/servers/replica/stop
|
||||
```
|
||||
|
||||
Wait for process to be stopped/killed.
|
||||
|
||||
Start the correct tunnels script by executing:
|
||||
```
|
||||
sudo /usr/local/bin/tunnels-master
|
||||
```
|
||||
|
||||
Start the master, make sure you are user ``sailing``!
|
||||
```
|
||||
/home/sailing/servers/master/start
|
||||
```
|
||||
|
||||
Check sailing log:
|
||||
```
|
||||
tail -f /home/sailing/servers/master/logs/sailing0.log.0
|
||||
```
|
||||
|
||||
## Hardware failure on secondary Lenovo P1 with the Sailing Analytics Replica
|
||||
|
||||
### Scenario
|
||||
|
||||
@@ -8,7 +8,15 @@ For the Olympic Summer Games 2020/2021 Tokyo we use a dedicated hardware set-up
|
||||
|
||||
The two laptops run Mint Linux with a fairly modern 5.4 kernel. We keep both up to date with regular ``apt-get update && apt-get upgrade`` executions. Both have an up-to-date SAP JVM 8 (see [https://tools.hana.ondemand.com/#cloud](https://tools.hana.ondemand.com/#cloud)) installed under /opt/sapjvm_8. This is the runtime VM used to run the Java application server process.
|
||||
|
||||
Furthermore, both laptops have a MongoDB 3.6 installation configured through ``/etc/apt/sources.list.d/mongodb-org-3.6.list`` containing the line ``deb http://repo.mongodb.org/apt/debian jessie/mongodb-org/3.6 main``. Their respective configuration can be found under ``/etc/mongod.conf``. The WiredTiger storage engine cache size should be limited. Currently, the following entry in ``/etc/mongod.conf`` does this:
|
||||
Furthermore, both laptops have a MongoDB 3.6 installation configured through ``/etc/apt/sources.list.d/mongodb-org-3.6.list`` containing the line ``deb http://repo.mongodb.org/apt/debian jessie/mongodb-org/3.6 main``. Their respective configuration can be found under ``/etc/mongod.conf``. The WiredTiger storage engine cache size should be limited. Currently, the following entry in ``/etc/mongod.conf`` does this.
|
||||
|
||||
RabbitMQ is part of the distribution natively, in version 3.6.10-1. It runs on both laptops. Both, RabbitMQ and MongoDB are installed as systemd service units and are launched during the boot sequence. The latest GWT version (currently 2.9.0) is installed under ``/opt/gwt-2.9.0`` in case any development work would need to be done on these machines.
|
||||
|
||||
Both machines have been configured to use 2GB of swap space at ``/swapfile``.
|
||||
|
||||
### Mongo Configuration
|
||||
|
||||
On both laptops, the ``/etc/mongod.conf`` configuration configures ``/var/lib/mongodb`` to be the storage directory, and the in-memory cache size to be 2GB:
|
||||
|
||||
```
|
||||
storage:
|
||||
@@ -20,17 +28,39 @@ storage:
|
||||
cacheSizeGB: 2
|
||||
```
|
||||
|
||||
On ``sap-p1-1`` we have a second MongoDB configuration ``/etc/mongod-security-service.conf`` which is used by the ``/lib/systemd/system/mongod-security-service.service`` unit which we created as a copy of ``/lib/systemd/system/mongod.service`` and adjusted the line
|
||||
The port is set to ``10201`` on ``sap-p1-1``:
|
||||
|
||||
```
|
||||
# network interfaces
|
||||
net:
|
||||
port: 10201
|
||||
bindIp: 0.0.0.0
|
||||
```
|
||||
|
||||
and to ``10202`` on ``sap-p1-2``:
|
||||
|
||||
```
|
||||
# network interfaces
|
||||
net:
|
||||
port: 10202
|
||||
bindIp: 0.0.0.0
|
||||
```
|
||||
|
||||
Furthermore, the replica set is configured to be ``tokyo2020`` on both:
|
||||
|
||||
```
|
||||
replication:
|
||||
oplogSizeMB: 10000
|
||||
replSetName: tokyo2020
|
||||
```
|
||||
|
||||
On both laptops we have a second MongoDB configuration ``/etc/mongod-security-service.conf`` which is used by the ``/lib/systemd/system/mongod-security-service.service`` unit which we created as a copy of ``/lib/systemd/system/mongod.service`` and adjusted the line
|
||||
|
||||
```
|
||||
ExecStart=/usr/bin/mongod --config /etc/mongod-security-service.conf
|
||||
```
|
||||
|
||||
This second database runs on the default port 27017 and is used as the target for a backup script for the ``security_service`` database. See below.
|
||||
|
||||
RabbitMQ is part of the distribution natively, in version 3.6.10-1. It runs on both laptops. Both, RabbitMQ and MongoDB are installed as systemd service units and are launched during the boot sequence. The latest GWT version (currently 2.9.0) is installed under ``/opt/gwt-2.9.0`` in case any development work would need to be done on these machines.
|
||||
|
||||
Both machines have been configured to use 2GB of swap space at ``/swapfile``.
|
||||
This second database runs as a replica set ``security_service`` on the default port 27017 and is used as the target for a backup script for the ``security_service`` database. See below. We increased the priority of the ``sap-p1-1`` node from 1 to 2.
|
||||
|
||||
### User Accounts
|
||||
|
||||
@@ -121,6 +151,8 @@ On both laptops, we maintain SSH connections to ``localhost`` with port forwards
|
||||
|
||||
Furthermore, for administrative SSH access from outside, we establish reverse port forwards from our jump host ``tokyo-ssh.sapsailing.com`` to the SSH ports on ``sap-p1-1`` (on port 18122) and ``sap-p1-2`` (on port 18222).
|
||||
|
||||
Both laptops have a forward from localhost:22222 to sapsailing.com:22 through tokyo-ssh, in order to be able to have a git remote ``ssh`` with the url ``ssh://trac@localhost:22222/home/trac/git``.
|
||||
|
||||
The port forwards vary for exceptional situations, such as when the Internet connection is not available, or when ``sap-p1-1`` that regularly runs the master process fails and we need to make ``sap-p1-2`` the new master. See below for the details of the configurations for those scenarios.
|
||||
|
||||
The tunnel configurations are established and configured using a set of scripts, each to be found under ``/usr/local/bin`` on each of the two laptops.
|
||||
@@ -271,6 +303,23 @@ The backup from sap-p1-1 to sap-p1-2 runs at 01:00 each day, and the backup from
|
||||
|
||||
### Monitoring and e-Mail Alerting
|
||||
|
||||
To be able to use ``sendmail`` to send notifications via email it needs to be installed and configured to use the AWS SES as smtp relay:
|
||||
```
|
||||
sudo apt install sendmail
|
||||
```
|
||||
|
||||
Follow the instructions on [https://docs.aws.amazon.com/ses/latest/DeveloperGuide/send-email-sendmail.html](https://docs.aws.amazon.com/ses/latest/DeveloperGuide/send-email-sendmail.html) with one exception, the content that needs to be added to ``sendmail.mc`` looks like:
|
||||
```
|
||||
define(`SMART_HOST', `email-smtp.eu-west-1.amazonaws.com')dnl
|
||||
define(`RELAY_MAILER_ARGS', `TCP $h 587')dnl
|
||||
define(`confAUTH_MECHANISMS', `LOGIN PLAIN')dnl
|
||||
FEATURE(`authinfo', `hash -o /etc/mail/authinfo.db')dnl
|
||||
MASQUERADE_AS(`sapsailing.com')dnl
|
||||
FEATURE(masquerade_envelope)dnl
|
||||
FEATURE(masquerade_entire_domain)dnl
|
||||
```
|
||||
The authentication details can be fetched from the content of ``/root/mail.properties`` of any running sailing EC2 instance.
|
||||
|
||||
Both laptops, ``sap-p1-1`` and ``sap-p1-2`` have monitoring scripts from the git folder ``configuration/on-site-scripts`` linked to ``/usr/local/bin``. These in particular include ``monitor-autossh-tunnels`` and ``monitor-mongo-replica-set-delay`` as well as a ``notify-operators`` script which contains the list of e-mail addresses to notify in case an alert occurs.
|
||||
|
||||
The ``monitor-autossh-tunnels`` script checks all running ``autossh`` processes and looks for their corresponding ``ssh`` child processes. If any of them is missing, an alert is sent using ``notify-operators``.
|
||||
@@ -299,13 +348,13 @@ For RabbitMQ we run a separate host, based on AWS Ubuntu 20. It brings the ``rab
|
||||
loopback_users = none
|
||||
```
|
||||
|
||||
which allows clients from other hosts to connect. The security groups for the RabbitMQ server are configured such that only ``172.0.0.0/8`` addresses from our VPCs can connect.
|
||||
which allows clients from other hosts to connect (note how this works differently on different version of RabbitMQ; the local laptops have to use a different syntax in their ``rabbitmq.config`` file). The security groups for the RabbitMQ server are configured such that only ``172.0.0.0/8`` addresses from our VPCs can connect.
|
||||
|
||||
The RabbitMQ management plugin is enabled using ``rabbitmq-plugins enable rabbitmq_management`` for access from localhost. This will require again an SSH tunnel to the host. The host's default user is ``ubuntu``. The RabbitMQ management plugin is active on port 15672 and accessible only from localhost or an SSH tunnel with port forward ending at this host. RabbitMQ itself listens on the default port 5672. With this set-up, RabbitMQ traffic for this event remains independent and undisturbed from any other RabbitMQ traffic from other servers in our default ``eu-west-1`` landscape, such as ``my.sapsailing.com``. The hostname pointing to the internal IP address of the RabbitMQ host is ``rabbit-ap-northeast-1.sapsailing.com`` and has a timeout of 60s.
|
||||
|
||||
An autossh tunnel is established from ``tokyo-ssh.sapsailing.com`` to ``rabbit-ap-northeast-1.sapsailing.com`` which forwards port 15673 to port 15672, thus exposing the RabbitMQ web interface which otherwise only responds to localhost. This autossh tunnel is established by a systemctl service that is described in ``/etc/systemd/system/autossh-port-forwards.service`` in ``tokyo-ssh.sapsailing.com``.
|
||||
|
||||
#### Local setup of rabbitmq
|
||||
### Local setup of rabbitmq
|
||||
|
||||
The above configuration needs also to be set on the rabbitmq installations of the P1s. The rabbitmq-server package has version 3.6.10. In that version the config file is located in ``/etc/rabbitmq/rabbitmq.config``, the entry is ``[{rabbit, [{loopback_users, []}]}].`` Further documentation for this version can be found here: [http://previous.rabbitmq.com/v3_6_x/configure.html](http://previous.rabbitmq.com/v3_6_x/configure.html)
|
||||
|
||||
@@ -327,11 +376,11 @@ The Route53 entry ``tokyo2020.sapsailing.com`` now is an alias A record pointing
|
||||
|
||||
### Application Load Balancers (ALBs) and Target Groups
|
||||
|
||||
In each region supported, two target groups with the usual settings (port 8888, health check on ``/gwt/status``, etc.) must exist: ``S-ded-tokyo2020`` (public) and ``S-ded-tokyo2020-m`` (master). An application load balancer then must be created or identified that will then have the five rules distributing traffic for ``tokyo2020.sapsailing.com`` to either the public or the master target group, furthermore a general rule in the HTTP listener for port 80 that will redirect all HTTP traffic to HTTPS permanently.
|
||||
In each region supported, a dedicated load balancer for the Global Accelerator-based event setup has been set up (``Tokyo2020ALB`` or simply ``ALB``). A single target group with the usual settings (port 8888, health check on ``/gwt/status``, etc.) must exist: ``S-ded-tokyo2020`` (public).
|
||||
|
||||
The master target group in all regions must contain an instance that forwards traffic on port 8888 to the master running on site, usually transitively through ``tokyo-ssh.sapsailing.com:8888``. Only in ap-northeast-1 the ``tokyo-ssh.sapsailing.com`` instance itself can be used as target in the ``S-ded-tokyo2020-m`` master target group. In ``eu-west-1`` the Webserver instance plays that role; it has a tmux running with the ``root`` user where an ``autossh`` connection is established to ``tokyo-ssh.sapsailing.com``, forwarding port 8888 accordingly.
|
||||
Note that no dedicated ``-m`` master target group is established. The reason is that the AWS Global Accelerator judges an ALB's health by looking at _all_ its target groups; should only a single target group not have a healthy target, the Global Accelerator considers the entire ALB unhealthy. With this, as soon as the on-site master server is unreachable, e.g., during an upgrade, all those ALBs would enter the "unhealthy" state from the Global Accelerator's perspective, and all public replicas which are still healthy would no longer receive traffic; the site would go "black." Therefore, we must ensure that the ALBs targeted by the Global Accelerator only have a single target group which only has the public replicas in that region as its targets.
|
||||
|
||||
Similar set-ups with a region-local "jump host" can be established; the jump host doesn't need much bandwidth as it is mainly used for admin requests that are to be routed straight to the master instance running on site.
|
||||
Each ALB has an HTTP and an HTTPS listener. The HTTP listener has only a single rule redirecting all traffic permanently (301) to the corresponding HTTPS request. The HTTPS listener has three rules: the ``/`` path for ``tokyo2020.sapsailing.com`` is re-directed to the Olympic event with ID ``25c65ff1-68b8-4734-a35f-75c9641e52f8``. All other traffic for ``tokyo2020.sapsailing.com`` goes to the public target group holding the regional replica(s). A default rule returns a 404 status with a static ``Not found`` text.
|
||||
|
||||
## Landscape Architecture
|
||||
|
||||
@@ -351,8 +400,8 @@ The cloud replica is not supposed to become primary, except for maybe in the unl
|
||||
|
||||
```
|
||||
tokyo2020:PRIMARY> cfg = rs.conf()
|
||||
# Then search for the member localhost:10203; let's assume, it's in cfg.members[1]:
|
||||
cfg.members[1].priority=0
|
||||
# Then search for the member localhost:10203; let's assume, it's in cfg.members[0]:
|
||||
cfg.members[0].priority=0
|
||||
rs.reconfig(cfg)
|
||||
```
|
||||
|
||||
@@ -366,9 +415,9 @@ One way to monitor the health and replication status of the replica set is runni
|
||||
grep "\(^source:\)\|\(syncedTo:\)\|\(behind the primary\)"'
|
||||
```
|
||||
|
||||
It shows the replication state and in particular the delay of the replicas. A cronjob exists for ``sailing@sap-p1-1`` which triggers ``/usr/local/bin/monitor-mongo-replica-set-delay`` every minute which will use ``/usr/local/bin/notify-operators`` in case the average replication delay for the last ten read-outs exceeds a threshold (currently 3s).
|
||||
It shows the replication state and in particular the delay of the replicas. A cronjob exists for ``sailing@sap-p1-1`` which triggers ``/usr/local/bin/monitor-mongo-replica-set-delay`` every minute which will use ``/usr/local/bin/notify-operators`` in case the average replication delay for the last ten read-outs exceeds a threshold (currently 3s). We have a cron job monitoring this (see above) and sending out alerts if things start slowing down.
|
||||
|
||||
In order to have a local copy of the ``security_service`` database, a CRON job exists for user ``sailing`` on ``sap-p1-1`` which executes the ``/usr/local/bin/clone-security-service-db`` script once per hour. See ``/home/sailing/crontab``. The script dumps ``security_service`` from the ``live`` replica set in ``eu-west-1`` to the ``/tmp/dump`` directory on ``ec2-user@tokyo-ssh.sapsailing.com`` and then sends the directory content as a ``tar.gz`` stream through SSH and restores it on the local ``mongodb://sap-p1-1:27017/security_service?replicaSet=security_service`` replica set, after copying an existing local ``security_service`` database to ``security_service_bak``. This way, even if the Internet connection dies during this cloning process, a valid copy still exists in the local ``tokyo2020`` replica set which can be copied back to ``security_service`` using the MongoDB shell command
|
||||
In order to have a local copy of the ``security_service`` database, a CRON job exists for user ``sailing`` on ``sap-p1-1`` which executes the ``/usr/local/bin/clone-security-service-db`` script once per hour. See ``/home/sailing/crontab``. The script dumps ``security_service`` from the ``live`` replica set in ``eu-west-1`` to the ``/tmp/dump`` directory on ``ec2-user@tokyo-ssh.sapsailing.com`` and then sends the directory content as a ``tar.gz`` stream through SSH and restores it on the local ``mongodb://sap-p1-1:27017,sap-p1-2/security_service?replicaSet=security_service`` replica set, after copying an existing local ``security_service`` database to ``security_service_bak``. This way, even if the Internet connection dies during this cloning process, a valid copy still exists in the local ``tokyo2020`` replica set which can be copied back to ``security_service`` using the MongoDB shell command
|
||||
|
||||
```
|
||||
db.copyDatabase("security_service_bak", "security_service")
|
||||
@@ -382,9 +431,12 @@ The master configuration is described in ``/home/sailing/servers/master/master.c
|
||||
rm env.sh; cat master.conf | ./refreshInstance.sh auto-install-from-stdin
|
||||
```
|
||||
|
||||
If the laptops cannot reach ``https://releases.sapsailing.com`` due to connectivity constraints, releases and environments can be downloaded through other channels to ``sap-p1-1:/home/trac/releases``, and the variable ``INSTALL_FROM_SCP_USER_AT_HOST_AND_PORT`` can be set to ``sailing@sap-p1-1`` to fetch the release file and environment file from there by SCP. Alternatively, ``sap-p1-2:/home/trac/releases`` may be used for the same.
|
||||
|
||||
This way, a clean new ``env.sh`` file will be produced from the config file, including the download and installation of a release. The ``master.conf`` file looks approximately like this:
|
||||
|
||||
```
|
||||
INSTALL_FROM_RELEASE=build-202106012325
|
||||
SERVER_NAME=tokyo2020
|
||||
MONGODB_URI="mongodb://localhost:10201,localhost:10202,localhost:10203/${SERVER_NAME}?replicaSet=tokyo2020&retryWrites=true&readPreference=nearest"
|
||||
# RabbitMQ in eu-west-1 (rabbit.internal.sapsailing.com) is expected to be found through SSH tunnel on localhost:5675
|
||||
@@ -414,6 +466,7 @@ The file looks like this:
|
||||
```
|
||||
# Regular operations; sap-p1-2 replicates sap-p1-1 using the rabbit-ap-northeast-1.sapsailing.com RabbitMQ in the cloud through SSH tunnel.
|
||||
# Outbound replication, though not expected to become active, goes to a local RabbitMQ
|
||||
INSTALL_FROM_RELEASE=build-202106012325
|
||||
SERVER_NAME=tokyo2020
|
||||
MONGODB_URI="mongodb://localhost:10201,localhost:10202,localhost:10203/${SERVER_NAME}-replica?replicaSet=tokyo2020&retryWrites=true&readPreference=nearest"
|
||||
# RabbitMQ in ap-northeast-1 is expected to be found locally on port 5673
|
||||
@@ -433,7 +486,7 @@ ADDITIONAL_JAVA_ARGS="${ADDITIONAL_JAVA_ARGS} -Dcom.sap.sse.debranding=true"
|
||||
Replicas in region ``eu-west-1`` can be launched using the following user data, making use of the established MongoDB live replica set in the region:
|
||||
|
||||
```
|
||||
INSTALL_FROM_RELEASE=build-202105211058
|
||||
INSTALL_FROM_RELEASE=build-202106012325
|
||||
SERVER_NAME=tokyo2020
|
||||
MONGODB_URI="mongodb://mongo0.internal.sapsailing.com,mongo1.internal.sapsailing.com,dbserver.internal.sapsailing.com:10203/tokyo2020-replica?replicaSet=live&retryWrites=true&readPreference=nearest"
|
||||
USE_ENVIRONMENT=live-replica-server
|
||||
@@ -452,7 +505,7 @@ ADDITIONAL_JAVA_ARGS="${ADDITIONAL_JAVA_ARGS} -Dcom.sap.sse.debranding=true"
|
||||
In other regions, instead an instance-local MongoDB shall be used for each replica, not interfering with each other or with other databases:
|
||||
|
||||
```
|
||||
INSTALL_FROM_RELEASE=build-202105211058
|
||||
INSTALL_FROM_RELEASE=build-202106012325
|
||||
SERVER_NAME=tokyo2020
|
||||
MONGODB_URI="mongodb://localhost/tokyo2020-replica?replicaSet=replica&retryWrites=true&readPreference=nearest"
|
||||
USE_ENVIRONMENT=live-replica-server
|
||||
@@ -484,4 +537,32 @@ Moderators who need to comment on the races shall be given more elaborate permis
|
||||
|
||||
To achieve this effect, the ``tokyo2020-server`` group has the ``sailing_viewer`` role assigned for all users, and all objects, except for the top-level ``Event`` object are owned by that group. This way, everything but the event are publicly visible.
|
||||
|
||||
The ``Event`` object is owned by ``tokyo2020-moderators``, and that group grants the ``sailing_viewer`` role only to its members, meaning only the members of that group are allowed to see the ``Event`` object.
|
||||
The ``Event`` object is owned by ``tokyo2020-moderators``, and that group grants the ``sailing_viewer`` role only to its members, meaning only the members of that group are allowed to see the ``Event`` object.
|
||||
|
||||
## Landscape Upgrade Procedure
|
||||
|
||||
update git on all replicas
|
||||
stop replication on all cloud replicas:
|
||||
```
|
||||
$ for i in `./get-replica-ips`; do ssh -o StrictHostKeyChecking=no sailing@$i "cd /home/sailing/servers/tokyo2020; /home/sailing/code/java/target/stopReplicating.sh 4qUrxMVQanLghETmM95XX3fshkHK0wNAQycuPAVNW0E="; done
|
||||
```
|
||||
|
||||
stop replication on-site
|
||||
|
||||
put release in /home/trac/
|
||||
|
||||
refresh instance stop start on sap-p1-1 / on-site master
|
||||
|
||||
after on-site master is healthy / available register cloud master forwarder to target groups, pay attention in eu-west-1 webserver instance works as cloud master forwarder
|
||||
|
||||
./stop /start on on-site replica
|
||||
|
||||
launch more like this with new user data, append (manual) to name of instance, make sure master target group has a healthy target.
|
||||
|
||||
check :8888/gwt/status , if initial load is not starting most probably registration to master has failed (check log for Exception), if so login and do stop & start
|
||||
|
||||
edit target group, deregister and register in the same window, save changes
|
||||
|
||||
terminate old auto-replica
|
||||
|
||||
wait for autoscaling group to create a new autoscaling instance, after healthy terminate manually launched instance/replica
|
||||
|
||||
Reference in New Issue
Block a user