Merge branch 'master' into bug5479

This commit is contained in:
Udo Wessels
2021-06-07 13:43:52 +02:00
48 changed files with 1004 additions and 154 deletions
+4
View File
@@ -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/
+8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
+21
View File
@@ -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
View File
@@ -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"
@@ -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;
}
@@ -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);
@@ -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();
@@ -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;
}
@@ -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:
@@ -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);
@@ -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 {
@@ -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
@@ -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);
@@ -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;
@@ -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,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>
@@ -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
}
});
}
}
@@ -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);
}
}
}
@@ -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:
@@ -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 {
@@ -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
@@ -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
+39 -1
View File
@@ -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.
+1 -1
View File
@@ -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
View File
@@ -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
+31 -14
View File
@@ -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
View File
@@ -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
+2 -1
View File
@@ -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
+36
View File
@@ -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
+100 -19
View File
@@ -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