- Use sequenced hostnames for cps-ncmp in docker compose based on a common template
- Use explicit port definitions for each instance instead of range (instrumentation/Grafana)
- Calculated startup delay as sequence number times 1 second when correct pattern used
- Fall back on hash code modulus 10 seconds otherwise
- clear logging which algorithm is used, why and outcome
- update kpi and endurance environment files to use correct instance names
Issue-ID:CPS-2752
Change-Id: Ie788aff60d6e9c470136b2a2051d0c5ccc173e9a
Signed-off-by: ToineSiebelink <toine.siebelink@est.tech>
* @param args Command-line arguments passed to the application (not used in this implementation).\r
*/\r
public static void main(final String[] args) {\r
- instanceStartupDelayManager.applyHostnameBasedStartupDelay();\r
+ instanceStartupDelayManager.applyHostNameBasedStartupDelay();\r
log.info("Initializing Spring Application context...");\r
SpringApplication.run(Application.class, args);\r
log.info("🚀 APPLICATION STARTED");\r
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class InstanceStartupDelayManager {
+ private static final Pattern HOST_NAME_WITH_SEQUENCE_PATTERN = Pattern.compile(".*-([\\d][\\d]?)$");
/**
- * Applies a consistent hash-based startup delay based on the host's name
- * to avoid race conditions during schema migration.
- * This method is useful in environments with multiple instances
- * (e.g., Docker Compose, Kubernetes), where simultaneous Liquibase executions
- * might result in conflicts.
- * Delay logic:
- * - A hash of the hostname is calculated.
- * - The result is used to derive a delay up to 5000 milliseconds.
- * - This provides a reasonably distributed delay across instances.
+ * Applies a consistent startup delay based on the host's name to avoid race conditions during liquibase set steps.
+ * This method is useful in environments with multiple instances.
+ * (e.g., Docker Compose, Kubernetes), where simultaneous Liquibase executions might result in conflicts.
+ * Delay calculation:
+ * - For host names that match {host-name}-{sequence-number} the delay wil be 1 second times the sequence number.
+ * - please note, the sequence number can be 2 digits at most.
+ * - For other names the delay is calculated as the hash code of that name modulus 10,000 ms i.e. up to 10,000 ms.
*/
- public void applyHostnameBasedStartupDelay() {
+ public void applyHostNameBasedStartupDelay() {
try {
- final String hostname = getHostName();
- final long startupDelayInMillis = Math.abs(hostname.hashCode() % 5_000L);
- log.info("Startup delay applied for Hostname: {} | Delay: {} ms", hostname, startupDelayInMillis);
+ final String hostName = getHostName();
+ log.info("Host name: {}", hostName);
+ final Matcher matcher = HOST_NAME_WITH_SEQUENCE_PATTERN.matcher(hostName);
+ final long startupDelayInMillis;
+ if (matcher.matches()) {
+ startupDelayInMillis = Integer.valueOf(matcher.group(1)) * 1_000L;
+ log.info("Sequenced host name detected, calculated delay = {} ms", startupDelayInMillis);
+ } else {
+ startupDelayInMillis = Math.abs(hostName.hashCode() % 10_000L);
+ log.warn("No Sequenced host name detected (<host-name>-<number>), hash-based delay = {} ms",
+ startupDelayInMillis);
+ }
haveALittleSleepInMs(startupDelayInMillis);
} catch (final InterruptedException e) {
log.warn("Sleep interrupted, re-interrupting the thread");
protected void haveALittleSleepInMs(final long timeInMs) throws InterruptedException {
TimeUnit.MILLISECONDS.sleep(timeInMs);
}
-}
\ No newline at end of file
+}
def objectUnderTest = Spy(InstanceStartupDelayManager)
- def 'Startup delay with real hostname.'() {
- given: 'a hostname is resolved'
- objectUnderTest.getHostName() >> 'hostX'
- and: 'the expected delay is based on hash code with max of 5,000 ms'
- def expectedDelay = Math.abs('hostX'.hashCode() % 5_000)
+ def 'Startup delay with sequenced host name with #scenario'() {
+ given: 'a sequenced host name'
+ objectUnderTest.getHostName() >> hostName
+ and: 'the expected delay is based on the sequence number'
+ def expectedDelay = expectedDelayInSeconds * 1_000;
when: 'startup delay is called'
- objectUnderTest.applyHostnameBasedStartupDelay()
- then: 'the system will sleep for expected time'
- 1 * objectUnderTest.haveALittleSleepInMs(expectedDelay)
+ objectUnderTest.applyHostNameBasedStartupDelay()
+ then: 'the system will sleep for expected number of seconds as defined by sequence number'
+ 1 * objectUnderTest.haveALittleSleepInMs(expectedDelay) >> { /* don't sleep for testing purposes */ }
+ where: ' following sequenced host names are used'
+ scenario | hostName || expectedDelayInSeconds
+ 'our usual host-name' | 'cps-and-ncmp-0' || 0
+ 'dash and 1 digit at end' | 'host-1' || 1
+ 'dash and 2 digits at end' | 'host-23' || 23
+ 'digits in name' | 'host-2-34' || 34
+ 'weird name ending in digits' | 't@st : - { " -56' || 56
}
- def 'Startup delay when hostname cannot be resolved.'() {
- given: 'an exception is thrown while getting the hostname'
+ def 'Startup delay with un-sequenced host name.'() {
+ given: 'a un-sequenced host name: #hostName'
+ objectUnderTest.getHostName() >> hostName
+ when: 'startup delay is called'
+ objectUnderTest.applyHostNameBasedStartupDelay()
+ then: 'the system will sleep for an expected time based on the hash'
+ 1 * objectUnderTest.haveALittleSleepInMs(expectedDelayBasedOnHashInMs) >> { /* don't sleep for testing purposes */ }
+ where: ' following un-sequenced host names are used'
+ hostName || expectedDelayBasedOnHashInMs
+ 'no_digits_at_all' || 784
+ 'digits-12-in-the-middle' || 1484
+ 'non-digit-after-digit-1a' || 753
+ 'dash-after-digit-1-' || 7256
+ 'three-digits-at-end-is-not-accepted-123' || 9941
+ }
+
+ def 'Startup delay when host name cannot be resolved.'() {
+ given: 'an exception is thrown while getting the host name'
objectUnderTest.getHostName() >> { throw new Exception('some message') }
when: 'startup delay is called'
- objectUnderTest.applyHostnameBasedStartupDelay()
+ objectUnderTest.applyHostNameBasedStartupDelay()
then: 'system will not sleep'
0 * objectUnderTest.haveALittleSleepInMs(_)
}
given: 'sleep method throws InterruptedException'
objectUnderTest.haveALittleSleepInMs(_) >> { throw new InterruptedException('some message') }
when: 'startup delay is called'
- objectUnderTest.applyHostnameBasedStartupDelay()
+ objectUnderTest.applyHostNameBasedStartupDelay()
then: 'interrupt exception is ignored'
noExceptionThrown()
}
}
+
# ============LICENSE_START===============================================
-# Copyright (C) 2024 Nordix Foundation. All rights reserved.
+# Copyright (C) 2024-2025 OpenInfra Foundation Europe. All rights reserved
# ========================================================================
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# Add more server entries here for scaling or load balancing
upstream cps-and-ncmp {
least_conn;
- server cps-and-ncmp:8080;
+ server cps-and-ncmp-0:8080;
+ server cps-and-ncmp-1:8080;
}
# Set the max allowed size of the incoming request
start_period: 30s # Ignore failed health checks for first 30 seconds, to give system time to start
# Full start up time allowed = 30 seconds start period + 3 tries * 10 seconds interval = 60 seconds
- cps-and-ncmp:
+ cps-and-ncmp-template:
image: ${DOCKER_REPO:-nexus3.onap.org:10003}/onap/cps-and-ncmp:${CPS_VERSION:-latest}
- ports:
- - ${CPS_PORT_RANGE:-8698-8699}:8080
### DEBUG: Uncomment next line to enable java debugging (ensure 'ports' aligns with 'deploy')
### - ${CPS_CORE_DEBUG_PORT:-5005}:5005-
environment:
depends_on:
- dbpostgresql
deploy:
- ### DEBUG: For easier debugging use just 1 instance (also update docker-compose/config/nginx/nginx.conf !)
- replicas: 2
+ replicas: 0
resources:
limits:
cpus: '3'
retries: 10
start_period: 60s
+ cps-and-ncmp-0:
+ extends:
+ service: cps-and-ncmp-template
+ container_name: ${CPS_INSTANCE_0_CONTAINER_NAME:-cps-and-ncmp-0}
+ deploy:
+ replicas: 1
+ hostname: cps-ncmp-0
+ ports:
+ - ${CPS_INSTANCE_0_REST_PORT:-8698}:8080
+
+ ### DEBUG: For easier debugging use just 1 instance and comment out below
+ cps-and-ncmp-1:
+ extends:
+ service: cps-and-ncmp-template
+ container_name: ${CPS_INSTANCE_1_CONTAINER_NAME:-cps-and-ncmp-1}
+ deploy:
+ replicas: 1
+ hostname: cps-ncmp-1
+ ports:
+ - ${CPS_INSTANCE_1_REST_PORT:-8699}:8080
+
nginx:
container_name: ${NGINX_CONTAINER_NAME:-nginx-loadbalancer}
image: nginx:latest
ports:
- ${CPS_CORE_PORT:-8883}:80
depends_on:
- - cps-and-ncmp
+ - cps-and-ncmp-0
+ ### DEBUG: For easier debugging use just 1 instance and comment out below
+ - cps-and-ncmp-1
volumes:
- ./config/nginx/nginx.conf:/etc/nginx/nginx.conf
- ./config/nginx/proxy_params:/etc/nginx/proxy_params
NGINX_CONTAINER_NAME=endurance-nginx-loadbalancer
CPS_CORE_PORT=8884
-CPS_PORT_RANGE=8798-8799
+
+CPS_INSTANCE_0_CONTAINER_NAME=endurance-cps-and-ncmp-0
+CPS_INSTANCE_1_CONTAINER_NAME=endurance-cps-and-ncmp-1
+
+CPS_INSTANCE_0_REST_PORT=8798
+CPS_INSTANCE_1_REST_PORT=8799
ZOOKEEPER_CONTAINER_NAME=endurance-zookeeper
ZOOKEEPER_PORT=2182
NGINX_CONTAINER_NAME=kpi-nginx-loadbalancer
CPS_CORE_PORT=8883
-CPS_PORT_RANGE=8698-8699
+
+CPS_INSTANCE_0_CONTAINER_NAME=kpi-cps-and-ncmp-0
+CPS_INSTANCE_1_CONTAINER_NAME=kpi-cps-and-ncmp-1
+
+CPS_INSTANCE_0_REST_PORT=8698
+CPS_INSTANCE_1_REST_PORT=8699
ZOOKEEPER_CONTAINER_NAME=kpi-zookeeper
ZOOKEEPER_PORT=2181