From 1f723d122f12e9ec6f0a92f0ac18e803826ab4c7 Mon Sep 17 00:00:00 2001 From: Richard Salac Date: Thu, 15 Jan 2026 18:41:10 +0100 Subject: [PATCH 1/7] Fix modulith service registration IT and prevent gateway and modulith to create a route for itself Signed-off-by: Richard Salac --- .github/workflows/service-registration.yml | 8 +- .../src/main/resources/bin/start.sh | 9 ++- apiml-package/src/main/resources/bin/start.sh | 8 +- apiml/src/main/resources/application.yml | 2 +- apiml/src/test/resources/application.yml | 2 +- .../src/main/resources/bin/start.sh | 9 ++- .../src/main/resources/application.yml | 2 +- .../impl/ApiMediationLayerStartupChecker.java | 3 +- .../util/service/FullApiMediationLayer.java | 77 +++++++++++-------- 9 files changed, 80 insertions(+), 40 deletions(-) diff --git a/.github/workflows/service-registration.yml b/.github/workflows/service-registration.yml index a50146be3d..287224ee4e 100644 --- a/.github/workflows/service-registration.yml +++ b/.github/workflows/service-registration.yml @@ -29,9 +29,15 @@ jobs: - name: Build with Gradle run: > ./gradlew clean build --info --scan - - name: Run startup check + + - name: Run startup check for microservices run: > ./gradlew runStartUpCheck --info --scan -Denvironment.startServices=true + + - name: Run startup check for modulith + run: > + ./gradlew runStartUpCheck --info --scan -Denvironment.startServices=true -Denvironment.modulith=true + - name: Store results uses: actions/upload-artifact@v4 if: always() diff --git a/api-catalog-package/src/main/resources/bin/start.sh b/api-catalog-package/src/main/resources/bin/start.sh index 4e96cacd41..638ef5087e 100755 --- a/api-catalog-package/src/main/resources/bin/start.sh +++ b/api-catalog-package/src/main/resources/bin/start.sh @@ -135,6 +135,13 @@ then ZOWE_CONSOLE_LOG_CHARSET=IBM-1047 fi fi + +#Set the external URL only if the variables are defined so the APIML can fallback if the property is null +EXTERNAL_URL="" +if [ -n "${externalProtocol}" ] && [ -n "${ZWE_zowe_externalDomains_0}" ] && [ -n "${ZWE_zowe_externalPort}" ]; then + EXTERNAL_URL="-Dapiml.service.externalUrl=${externalProtocol}://${ZWE_zowe_externalDomains_0}:${ZWE_zowe_externalPort}" +fi + LIBPATH="$LIBPATH":"/lib" LIBPATH="$LIBPATH":"/usr/lib" LIBPATH="$LIBPATH":"${JAVA_HOME}"/bin @@ -287,6 +294,7 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${CATALOG_CODE} ${JAVA_BIN_DIR}java \ ${ADD_OPENS} \ ${LOGBACK} \ ${JVM_SECURITY_PROPERTIES} \ + ${EXTERNAL_URL} \ -Dibm.serversocket.recover=true \ -Dfile.encoding=UTF-8 \ -Dlogging.charset.console=${ZOWE_CONSOLE_LOG_CHARSET} \ @@ -298,7 +306,6 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${CATALOG_CODE} ${JAVA_BIN_DIR}java \ -Dapiml.service.gatewayHostname=${ZWE_GATEWAY_HOST:-${ZWE_haInstance_hostname:-localhost}} \ -Dapiml.logs.location=${ZWE_zowe_logDirectory} \ -Dapiml.health.protected=${ZWE_configs_apiml_health_protected:-true} \ - -Dapiml.service.externalUrl="${externalProtocol}://${ZWE_zowe_externalDomains_0}:${ZWE_zowe_externalPort}" \ -Dapiml.discovery.staticApiDefinitionsDirectories=${ZWE_STATIC_DEFINITIONS_DIR} \ -Dapiml.security.ssl.verifySslCertificatesOfServices=${verifySslCertificatesOfServices:-false} \ -Dapiml.security.ssl.nonStrictVerifySslCertificatesOfServices=${nonStrictVerifySslCertificatesOfServices:-false} \ diff --git a/apiml-package/src/main/resources/bin/start.sh b/apiml-package/src/main/resources/bin/start.sh index 65bcee4716..3f4b42ba0f 100755 --- a/apiml-package/src/main/resources/bin/start.sh +++ b/apiml-package/src/main/resources/bin/start.sh @@ -229,6 +229,12 @@ if [ -n "${ZWE_configs_storage_vsam_name}" ]; then VSAM_FILE_NAME=//\'${ZWE_configs_storage_vsam_name:-${ZWE_components_caching_service_storage_vsam_name}}\' fi +#Set the external URL only if the variables are defined so the APIML can fallback if the property is null +EXTERNAL_URL="" +if [ -n "${externalProtocol}" ] && [ -n "${ZWE_zowe_externalDomains_0}" ] && [ -n "${ZWE_zowe_externalPort}" ]; then + EXTERNAL_URL="-Dapiml.service.externalUrl=${externalProtocol}://${ZWE_zowe_externalDomains_0}:${ZWE_zowe_externalPort}" +fi + LIBPATH="$LIBPATH":"/lib" LIBPATH="$LIBPATH":"/usr/lib" LIBPATH="$LIBPATH":"${JAVA_HOME}/bin" @@ -340,6 +346,7 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${APIML_CODE} ${JAVA_BIN_DIR}java \ ${ADD_OPENS} \ ${LOGBACK} \ ${JVM_SECURITY_PROPERTIES} \ + ${EXTERNAL_URL} \ -Dapiml.cache.storage.location=${ZWE_zowe_workspaceDirectory}/api-mediation/${ZWE_haInstance_id:-localhost} \ -Dapiml.catalog.customStyle.backgroundColor=${ZWE_components_apicatalog_apiml_catalog_customStyle_backgroundColor:-${ZWE_configs_apiml_catalog_customStyle_backgroundColor:-}} \ -Dapiml.catalog.customStyle.docLink=${ZWE_components_apicatalog_apiml_catalog_customStyle_docLink:-${ZWE_configs_apiml_catalog_customStyle_docLink:-}} \ @@ -413,7 +420,6 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${APIML_CODE} ${JAVA_BIN_DIR}java \ -Dapiml.service.apimlId=${ZWE_components_gateway_apimlId:-${ZWE_configs_apimlId:-}} \ -Dapiml.service.corsEnabled=${ZWE_components_gateway_apiml_service_corsEnabled:-${ZWE_configs_apiml_service_corsEnabled:-false}} \ -Dapiml.service.corsAllowedMethods=${ZWE_components_gateway_apiml_service_corsAllowedMethods:-${ZWE_configs_apiml_service_corsAllowedMethods:-GET,HEAD,POST,PATCH,DELETE,PUT,OPTIONS}} \ - -Dapiml.service.externalUrl="${externalProtocol}://${ZWE_zowe_externalDomains_0}:${ZWE_zowe_externalPort}" \ -Dapiml.service.forwardClientCertEnabled=${ZWE_components_gateway_apiml_security_x509_enabled:-${ZWE_configs_apiml_security_x509_enabled:-false}} \ -Dapiml.service.hostname=${ZWE_haInstance_hostname:-localhost} \ -Dapiml.service.port=${ZWE_components_gateway_port:-${ZWE_configs_port:-7554}} \ diff --git a/apiml/src/main/resources/application.yml b/apiml/src/main/resources/application.yml index f791b58f2a..9da5764648 100644 --- a/apiml/src/main/resources/application.yml +++ b/apiml/src/main/resources/application.yml @@ -189,7 +189,7 @@ apiml: timeToLive: 10000 routing: instanceIdHeader: false - ignoredServices: discovery,zaas,apicatalog,cachingservice # to disable routing to the Discovery and ZAAS service + local services on Modulith + ignoredServices: discovery,zaas,gateway,apicatalog,cachingservice # to disable routing to the Discovery and ZAAS service + local services on Modulith service: apimlId: apiml1 corsEnabled: true diff --git a/apiml/src/test/resources/application.yml b/apiml/src/test/resources/application.yml index 4cb227e8e2..908014f6a5 100644 --- a/apiml/src/test/resources/application.yml +++ b/apiml/src/test/resources/application.yml @@ -125,7 +125,7 @@ apiml: timeToLive: 10000 routing: instanceIdHeader: false - ignoredServices: discovery,zaas,apicatalog,cachingservice # to disable routing to the Discovery and ZAAS service + local services on Modulith + ignoredServices: discovery,zaas,gateway,apicatalog,cachingservice # to disable routing to the Discovery and ZAAS service + local services on Modulith service: apimlId: apiml1 corsEnabled: true diff --git a/gateway-package/src/main/resources/bin/start.sh b/gateway-package/src/main/resources/bin/start.sh index a08c5b5c3a..2aa8e64d6f 100755 --- a/gateway-package/src/main/resources/bin/start.sh +++ b/gateway-package/src/main/resources/bin/start.sh @@ -1,4 +1,5 @@ #!/bin/sh +set -x ################################################################################ # This program and the accompanying materials are made available under the terms of the @@ -191,6 +192,12 @@ else externalProtocol="http" fi +#Set the external URL only if the variables are defined so the APIML can fallback if the property is null +EXTERNAL_URL="" +if [ -n "${externalProtocol}" ] && [ -n "${ZWE_zowe_externalDomains_0}" ] && [ -n "${ZWE_zowe_externalPort}" ]; then + EXTERNAL_URL="-Dapiml.service.externalUrl=${externalProtocol}://${ZWE_zowe_externalDomains_0}:${ZWE_zowe_externalPort}" +fi + LIBPATH="$LIBPATH":"/lib" LIBPATH="$LIBPATH":"/usr/lib" LIBPATH="$LIBPATH":"${JAVA_HOME}/bin" @@ -306,6 +313,7 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${GATEWAY_CODE} ${JAVA_BIN_DIR}java \ ${ADD_OPENS} \ ${LOGBACK} \ ${JVM_SECURITY_PROPERTIES} \ + ${EXTERNAL_URL} \ -Dapiml.connection.idleConnectionTimeoutSeconds=${ZWE_configs_apiml_connection_idleConnectionTimeoutSeconds:-5} \ -Dapiml.connection.timeout=${ZWE_configs_apiml_connection_timeout:-60000} \ -Dapiml.connection.timeToLive=${ZWE_configs_apiml_connection_timeToLive:-10000} \ @@ -341,7 +349,6 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${GATEWAY_CODE} ${JAVA_BIN_DIR}java \ -Dapiml.service.apimlId=${ZWE_configs_apimlId:-} \ -Dapiml.service.corsEnabled=${ZWE_configs_apiml_service_corsEnabled:-false} \ -Dapiml.service.corsAllowedMethods=${ZWE_configs_apiml_service_corsAllowedMethods:-GET,HEAD,POST,PATCH,DELETE,PUT,OPTIONS} \ - -Dapiml.service.externalUrl="${externalProtocol}://${ZWE_zowe_externalDomains_0}:${ZWE_zowe_externalPort}" \ -Dapiml.service.forwardClientCertEnabled=${ZWE_configs_apiml_security_x509_enabled:-false} \ -Dapiml.service.hostname=${ZWE_haInstance_hostname:-localhost} \ -Dapiml.service.port=${ZWE_configs_port:-7554} \ diff --git a/gateway-service/src/main/resources/application.yml b/gateway-service/src/main/resources/application.yml index c4638be1fb..bc9b279f45 100644 --- a/gateway-service/src/main/resources/application.yml +++ b/gateway-service/src/main/resources/application.yml @@ -95,7 +95,7 @@ apiml: timeToLive: 10000 routing: instanceIdHeader: false - ignoredServices: discovery,zaas # to disable routing to the Discovery and ZAAS service + ignoredServices: discovery,zaas,gateway # to disable routing to the Discovery and ZAAS service service: apimlId: apiml1 corsEnabled: true diff --git a/integration-tests/src/test/java/org/zowe/apiml/startup/impl/ApiMediationLayerStartupChecker.java b/integration-tests/src/test/java/org/zowe/apiml/startup/impl/ApiMediationLayerStartupChecker.java index 5126a27f2f..d16e49c048 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/startup/impl/ApiMediationLayerStartupChecker.java +++ b/integration-tests/src/test/java/org/zowe/apiml/startup/impl/ApiMediationLayerStartupChecker.java @@ -39,6 +39,7 @@ import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; import static org.awaitility.Awaitility.await; +import static org.zowe.apiml.util.config.ConfigReader.IS_MODULITH_ENABLED; /** * Checks and waits until the testing environment is ready to be tested. @@ -46,8 +47,6 @@ @Slf4j public class ApiMediationLayerStartupChecker { - private static final boolean IS_MODULITH_ENABLED = Boolean.parseBoolean(System.getProperty("environment.modulith")); - private final GatewayServiceConfiguration gatewayConfiguration; private final DiscoverableClientConfiguration discoverableClientConfiguration; private final DiscoveryServiceConfiguration discoveryServiceConfiguration; diff --git a/integration-tests/src/test/java/org/zowe/apiml/util/service/FullApiMediationLayer.java b/integration-tests/src/test/java/org/zowe/apiml/util/service/FullApiMediationLayer.java index 8c1e1b6905..696fcb2095 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/util/service/FullApiMediationLayer.java +++ b/integration-tests/src/test/java/org/zowe/apiml/util/service/FullApiMediationLayer.java @@ -29,6 +29,9 @@ @Slf4j public class FullApiMediationLayer { + + final boolean IS_MODULITH_ENABLED = Boolean.parseBoolean(System.getProperty("environment.modulith")); + private RunningService discoveryService; private RunningService gatewayService; private RunningService apiCatalogService; @@ -52,14 +55,19 @@ public class FullApiMediationLayer { private FullApiMediationLayer() { env = ConfigReader.environmentConfiguration().getInstanceEnv(); + if (IS_MODULITH_ENABLED) { + prepareApiml(); + } else { + prepareGateway(); + prepareDiscovery(); + prepareCaching(); + prepareZaas(); + } + + prepareMockServices(); prepareCatalog(); prepareDiscoverableClient(); - prepareGateway(); - prepareMockServices(); - prepareDiscovery(); - prepareCaching(); - prepareZaas(); - prepareApiml(); + if (!attlsEnabled) { prepareNodeJsSampleApp(); } @@ -130,25 +138,28 @@ private void prepareDiscoverableClient() { public void start() { try { - var discoveryEnv = new HashMap<>(env); - discoveryEnv.put("ZWE_configs_port", "10011"); - discoveryService.startWithScript("discovery-package/src/main/resources/bin", discoveryEnv); - var gatewayEnv = new HashMap<>(env); - gatewayEnv.put("ZWE_configs_port", "10010"); - gatewayService.startWithScript("gateway-package/src/main/resources/bin", gatewayEnv); - var catalogEnv = new HashMap<>(env); - catalogEnv.put("ZWE_configs_port", "10014"); - apiCatalogService.startWithScript("api-catalog-package/src/main/resources/bin", catalogEnv); - var cachingEnv = new HashMap<>(env); - cachingEnv.put("ZWE_configs_port", "10016"); - cachingService.startWithScript("caching-service-package/src/main/resources/bin", cachingEnv); - var zaasEnv = new HashMap<>(env); - zaasEnv.put("ZWE_configs_port", "10023"); - zaasService.startWithScript("zaas-package/src/main/resources/bin", zaasEnv); - var apimlModulithEnv = new HashMap<>(env); - apimlModulithEnv.put("ZWE_configs_port", "10020"); - apimlModulithEnv.put("ZWE_configs_internal_discovery_port", "10021"); - apimlService.startWithScript("apiml-package/src/main/resources/bin", apimlModulithEnv); + if (IS_MODULITH_ENABLED) { + var apimlModulithEnv = new HashMap<>(env); + apimlModulithEnv.put("ZWE_configs_port", "10010"); + apimlModulithEnv.put("ZWE_configs_internal_discovery_port", "10011"); + apimlService.startWithScript("apiml-package/src/main/resources/bin", apimlModulithEnv); + } else { + var discoveryEnv = new HashMap<>(env); + discoveryEnv.put("ZWE_configs_port", "10011"); + discoveryService.startWithScript("discovery-package/src/main/resources/bin", discoveryEnv); + var gatewayEnv = new HashMap<>(env); + gatewayEnv.put("ZWE_configs_port", "10010"); + gatewayService.startWithScript("gateway-package/src/main/resources/bin", gatewayEnv); + var catalogEnv = new HashMap<>(env); + catalogEnv.put("ZWE_configs_port", "10014"); + apiCatalogService.startWithScript("api-catalog-package/src/main/resources/bin", catalogEnv); + var cachingEnv = new HashMap<>(env); + cachingEnv.put("ZWE_configs_port", "10016"); + cachingService.startWithScript("caching-service-package/src/main/resources/bin", cachingEnv); + var zaasEnv = new HashMap<>(env); + zaasEnv.put("ZWE_configs_port", "10023"); + zaasService.startWithScript("zaas-package/src/main/resources/bin", zaasEnv); + } if (!attlsEnabled) { nodeJsSampleApp = nodeJsBuilder.start(); @@ -176,18 +187,22 @@ private String formatEnv() { public void stop() { try { - discoveryService.stop(); - gatewayService.stop(); mockZosmfService.stop(); - - apiCatalogService.stop(); discoverableClientService.stop(); - cachingService.stop(); - zaasService.stop(); if (!attlsEnabled && startServices()) { nodeJsSampleApp.destroy(); } + + if (IS_MODULITH_ENABLED) { + apimlService.stop(); + } else { + discoveryService.stop(); + gatewayService.stop(); + apiCatalogService.stop(); + cachingService.stop(); + zaasService.stop(); + } } catch (Exception e) { e.printStackTrace(); } From b8a5dc7e3d3c50b5a780ec790a0c529639149619 Mon Sep 17 00:00:00 2001 From: Richard Salac Date: Tue, 20 Jan 2026 13:19:16 +0100 Subject: [PATCH 2/7] Enable route creation for gateway Signed-off-by: Richard Salac --- apiml/src/main/resources/application.yml | 2 +- apiml/src/test/resources/application.yml | 2 +- gateway-service/src/main/resources/application.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apiml/src/main/resources/application.yml b/apiml/src/main/resources/application.yml index 9da5764648..f791b58f2a 100644 --- a/apiml/src/main/resources/application.yml +++ b/apiml/src/main/resources/application.yml @@ -189,7 +189,7 @@ apiml: timeToLive: 10000 routing: instanceIdHeader: false - ignoredServices: discovery,zaas,gateway,apicatalog,cachingservice # to disable routing to the Discovery and ZAAS service + local services on Modulith + ignoredServices: discovery,zaas,apicatalog,cachingservice # to disable routing to the Discovery and ZAAS service + local services on Modulith service: apimlId: apiml1 corsEnabled: true diff --git a/apiml/src/test/resources/application.yml b/apiml/src/test/resources/application.yml index 908014f6a5..4cb227e8e2 100644 --- a/apiml/src/test/resources/application.yml +++ b/apiml/src/test/resources/application.yml @@ -125,7 +125,7 @@ apiml: timeToLive: 10000 routing: instanceIdHeader: false - ignoredServices: discovery,zaas,gateway,apicatalog,cachingservice # to disable routing to the Discovery and ZAAS service + local services on Modulith + ignoredServices: discovery,zaas,apicatalog,cachingservice # to disable routing to the Discovery and ZAAS service + local services on Modulith service: apimlId: apiml1 corsEnabled: true diff --git a/gateway-service/src/main/resources/application.yml b/gateway-service/src/main/resources/application.yml index bc9b279f45..c4638be1fb 100644 --- a/gateway-service/src/main/resources/application.yml +++ b/gateway-service/src/main/resources/application.yml @@ -95,7 +95,7 @@ apiml: timeToLive: 10000 routing: instanceIdHeader: false - ignoredServices: discovery,zaas,gateway # to disable routing to the Discovery and ZAAS service + ignoredServices: discovery,zaas # to disable routing to the Discovery and ZAAS service service: apimlId: apiml1 corsEnabled: true From 0fb8de0ee44234640847ea1f27a7bbd07d58aed0 Mon Sep 17 00:00:00 2001 From: Richard Salac Date: Tue, 20 Jan 2026 15:08:39 +0100 Subject: [PATCH 3/7] fix sonar Signed-off-by: Richard Salac --- .../java/org/zowe/apiml/util/service/FullApiMediationLayer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/src/test/java/org/zowe/apiml/util/service/FullApiMediationLayer.java b/integration-tests/src/test/java/org/zowe/apiml/util/service/FullApiMediationLayer.java index 696fcb2095..318e1dd3c8 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/util/service/FullApiMediationLayer.java +++ b/integration-tests/src/test/java/org/zowe/apiml/util/service/FullApiMediationLayer.java @@ -30,7 +30,7 @@ @Slf4j public class FullApiMediationLayer { - final boolean IS_MODULITH_ENABLED = Boolean.parseBoolean(System.getProperty("environment.modulith")); + public static final boolean IS_MODULITH_ENABLED = Boolean.parseBoolean(System.getProperty("environment.modulith")); private RunningService discoveryService; private RunningService gatewayService; From 454c776f8297488397f7d36f687adfb49938a042 Mon Sep 17 00:00:00 2001 From: Richard Salac Date: Fri, 23 Jan 2026 10:39:07 +0100 Subject: [PATCH 4/7] remove unnecessary shell variable initialization Signed-off-by: Richard Salac --- api-catalog-package/src/main/resources/bin/start.sh | 3 +-- apiml-package/src/main/resources/bin/start.sh | 1 - gateway-package/src/main/resources/bin/start.sh | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/api-catalog-package/src/main/resources/bin/start.sh b/api-catalog-package/src/main/resources/bin/start.sh index 638ef5087e..2bf1c5d300 100755 --- a/api-catalog-package/src/main/resources/bin/start.sh +++ b/api-catalog-package/src/main/resources/bin/start.sh @@ -1,5 +1,5 @@ #!/bin/sh - +set -x ################################################################################ # This program and the accompanying materials are made available under the terms of the # Eclipse Public License v2.0 which accompanies this distribution, and is available at @@ -137,7 +137,6 @@ then fi #Set the external URL only if the variables are defined so the APIML can fallback if the property is null -EXTERNAL_URL="" if [ -n "${externalProtocol}" ] && [ -n "${ZWE_zowe_externalDomains_0}" ] && [ -n "${ZWE_zowe_externalPort}" ]; then EXTERNAL_URL="-Dapiml.service.externalUrl=${externalProtocol}://${ZWE_zowe_externalDomains_0}:${ZWE_zowe_externalPort}" fi diff --git a/apiml-package/src/main/resources/bin/start.sh b/apiml-package/src/main/resources/bin/start.sh index 3f4b42ba0f..c6e826d091 100755 --- a/apiml-package/src/main/resources/bin/start.sh +++ b/apiml-package/src/main/resources/bin/start.sh @@ -230,7 +230,6 @@ if [ -n "${ZWE_configs_storage_vsam_name}" ]; then fi #Set the external URL only if the variables are defined so the APIML can fallback if the property is null -EXTERNAL_URL="" if [ -n "${externalProtocol}" ] && [ -n "${ZWE_zowe_externalDomains_0}" ] && [ -n "${ZWE_zowe_externalPort}" ]; then EXTERNAL_URL="-Dapiml.service.externalUrl=${externalProtocol}://${ZWE_zowe_externalDomains_0}:${ZWE_zowe_externalPort}" fi diff --git a/gateway-package/src/main/resources/bin/start.sh b/gateway-package/src/main/resources/bin/start.sh index 2aa8e64d6f..7626db981a 100755 --- a/gateway-package/src/main/resources/bin/start.sh +++ b/gateway-package/src/main/resources/bin/start.sh @@ -193,7 +193,6 @@ else fi #Set the external URL only if the variables are defined so the APIML can fallback if the property is null -EXTERNAL_URL="" if [ -n "${externalProtocol}" ] && [ -n "${ZWE_zowe_externalDomains_0}" ] && [ -n "${ZWE_zowe_externalPort}" ]; then EXTERNAL_URL="-Dapiml.service.externalUrl=${externalProtocol}://${ZWE_zowe_externalDomains_0}:${ZWE_zowe_externalPort}" fi From 4f7502368091861f99df46eecee498066cf967ae Mon Sep 17 00:00:00 2001 From: ac892247 Date: Thu, 18 Dec 2025 10:23:07 +0100 Subject: [PATCH 5/7] OpenTelemetry integration test validating resource attributes Signed-off-by: Richard Salac --- .github/workflows/service-registration.yml | 58 ++++++++++- .../src/main/resources/bin/start.sh | 2 +- .../src/main/resources/bin/start.sh | 1 - integration-tests/build.gradle | 12 +++ .../startup/ApiMediationLayerStartTest.java | 15 +++ .../util/categories/OpenTelemetryTest.java | 27 ++++++ .../util/service/FullApiMediationLayer.java | 4 +- .../resources/environment-configuration.yml | 7 ++ otel/README.md | 73 ++++++++++++++ otel/docker-compose.yml | 61 ++++++++++++ otel/otel-collector/config.yaml | 96 +++++++++++++++++++ otel/otel-golden/expected.yaml | 15 +++ 12 files changed, 366 insertions(+), 5 deletions(-) create mode 100644 integration-tests/src/test/java/org/zowe/apiml/util/categories/OpenTelemetryTest.java create mode 100644 otel/README.md create mode 100644 otel/docker-compose.yml create mode 100755 otel/otel-collector/config.yaml create mode 100644 otel/otel-golden/expected.yaml diff --git a/.github/workflows/service-registration.yml b/.github/workflows/service-registration.yml index 287224ee4e..0e8ee61db4 100644 --- a/.github/workflows/service-registration.yml +++ b/.github/workflows/service-registration.yml @@ -34,9 +34,64 @@ jobs: run: > ./gradlew runStartUpCheck --info --scan -Denvironment.startServices=true + - name: Start OpenTelemetry docker containers + run: | + cd otel + chmod -R 777 otel-* + docker compose up -d + + - name: Check OpenTelemetry containers + run: | + echo "Checking OpenTelemetry Golden Tester..." + curl -s http://localhost:5318/v1/metrics -H "Content-Type: application/json" -d "{}" \ + --fail \ + --retry-all-errors \ + --retry-delay 2 \ + --retry 10 + echo "" + echo "curl exit code: "$? + echo "" + + echo "Checking OpenTelemetry Collector..." + curl -s http://localhost:4318/v1/metrics -H "Content-Type: application/json" -d "{}" \ + --fail \ + --retry-all-errors \ + --retry-delay 2 \ + --retry 10 + echo "" + echo "curl exit code: "$? + - name: Run startup check for modulith run: > - ./gradlew runStartUpCheck --info --scan -Denvironment.startServices=true -Denvironment.modulith=true + ./gradlew runStartUpCheckWithOpenTelemetry --info --scan -Denvironment.startServices=true -Denvironment.modulith=true + + - name: Verify OpenTelemetry data with Golden Tester + if: always() + run: | + echo "Waiting for Golden Validator to finish..." + + # This blocks until the golden container exits (success or timeout) + EXIT_CODE=$(docker wait golden) + + # Display logs to see the diff if it failed + echo "Golden container logs:" + docker logs golden + + echo "" + + if [ "$EXIT_CODE" -ne 0 ]; then + echo "::error::OpenTelemetry data validation failed! See logs above for diff." + exit 1 + fi + echo "OpenTelemetry data validation passed!" + + - name: Stop OpenTelemetry Collector and print logs + if: always() + run: | + docker stop collector -t 60 + + echo "Collector container logs:" + docker logs collector - name: Store results uses: actions/upload-artifact@v4 @@ -45,5 +100,6 @@ jobs: name: BuildAndTest-${{ env.JOB_ID }} path: | */build/reports/** + otel/** - uses: ./.github/actions/teardown diff --git a/api-catalog-package/src/main/resources/bin/start.sh b/api-catalog-package/src/main/resources/bin/start.sh index 2bf1c5d300..8385c2f87d 100755 --- a/api-catalog-package/src/main/resources/bin/start.sh +++ b/api-catalog-package/src/main/resources/bin/start.sh @@ -1,5 +1,5 @@ #!/bin/sh -set -x + ################################################################################ # This program and the accompanying materials are made available under the terms of the # Eclipse Public License v2.0 which accompanies this distribution, and is available at diff --git a/gateway-package/src/main/resources/bin/start.sh b/gateway-package/src/main/resources/bin/start.sh index 7626db981a..2dd6810c0b 100755 --- a/gateway-package/src/main/resources/bin/start.sh +++ b/gateway-package/src/main/resources/bin/start.sh @@ -1,5 +1,4 @@ #!/bin/sh -set -x ################################################################################ # This program and the accompanying materials are made available under the terms of the diff --git a/integration-tests/build.gradle b/integration-tests/build.gradle index 35331f7680..bbbdfc4a68 100644 --- a/integration-tests/build.gradle +++ b/integration-tests/build.gradle @@ -101,6 +101,18 @@ task runStartUpCheck(type: Test) { group 'integration tests' description "Check that the API Mediation Layer is up and running" + systemProperties System.properties + useJUnitPlatform { + includeTags 'StartupCheck' + excludeTags 'OpenTelemetryTest' + } + outputs.upToDateWhen { false } +} + +task runStartUpCheckWithOpenTelemetry(type: Test) { + group 'integration tests' + description "Check that the API Mediation Layer is up and running with graceful wait for OpenTelemetry" + systemProperties System.properties useJUnitPlatform { includeTags 'StartupCheck' diff --git a/integration-tests/src/test/java/org/zowe/apiml/startup/ApiMediationLayerStartTest.java b/integration-tests/src/test/java/org/zowe/apiml/startup/ApiMediationLayerStartTest.java index 32cb4c1242..1303124d0c 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/startup/ApiMediationLayerStartTest.java +++ b/integration-tests/src/test/java/org/zowe/apiml/startup/ApiMediationLayerStartTest.java @@ -10,11 +10,15 @@ package org.zowe.apiml.startup; +import lombok.SneakyThrows; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.zowe.apiml.startup.impl.ApiMediationLayerStartupChecker; +import org.zowe.apiml.util.categories.OpenTelemetryTest; import org.zowe.apiml.util.categories.StartupCheck; +import java.time.Duration; + import static org.junit.jupiter.api.Assertions.assertTrue; @StartupCheck @@ -29,4 +33,15 @@ void setUp() { void checkApiMediationLayerStart() { assertTrue(true); } + + @Test + @OpenTelemetryTest + @SneakyThrows + void giveOpenTelemetryTimeToSendMetrics() { + //The application has to run for a while to collect and send the telemetry data + //so they can be evaluated in the OpenTelemetry Golden Tester + Thread.sleep(Duration.ofSeconds(30).toMillis()); + assertTrue(true); + } + } diff --git a/integration-tests/src/test/java/org/zowe/apiml/util/categories/OpenTelemetryTest.java b/integration-tests/src/test/java/org/zowe/apiml/util/categories/OpenTelemetryTest.java new file mode 100644 index 0000000000..a3aa8100ef --- /dev/null +++ b/integration-tests/src/test/java/org/zowe/apiml/util/categories/OpenTelemetryTest.java @@ -0,0 +1,27 @@ +/* + * This program and the accompanying materials are made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v20.html + * + * SPDX-License-Identifier: EPL-2.0 + * + * Copyright Contributors to the Zowe Project. + */ + +package org.zowe.apiml.util.categories; + +import org.junit.jupiter.api.Tag; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.TYPE; + +@Tag("OpenTelemetryTest") +@Target({ TYPE, METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface OpenTelemetryTest { +} + diff --git a/integration-tests/src/test/java/org/zowe/apiml/util/service/FullApiMediationLayer.java b/integration-tests/src/test/java/org/zowe/apiml/util/service/FullApiMediationLayer.java index 318e1dd3c8..7c19106865 100644 --- a/integration-tests/src/test/java/org/zowe/apiml/util/service/FullApiMediationLayer.java +++ b/integration-tests/src/test/java/org/zowe/apiml/util/service/FullApiMediationLayer.java @@ -24,14 +24,14 @@ import java.util.Map; import java.util.Optional; +import static org.zowe.apiml.util.config.ConfigReader.IS_MODULITH_ENABLED; + //TODO this class doesn't lend itself well to switching of configurations. //attls is integrated in a kludgy way, and deserves a rewrite @Slf4j public class FullApiMediationLayer { - public static final boolean IS_MODULITH_ENABLED = Boolean.parseBoolean(System.getProperty("environment.modulith")); - private RunningService discoveryService; private RunningService gatewayService; private RunningService apiCatalogService; diff --git a/integration-tests/src/test/resources/environment-configuration.yml b/integration-tests/src/test/resources/environment-configuration.yml index 8c061b0779..f78acba1ab 100644 --- a/integration-tests/src/test/resources/environment-configuration.yml +++ b/integration-tests/src/test/resources/environment-configuration.yml @@ -105,3 +105,10 @@ instanceEnv: ZWE_configs_apiml_security_authorization_provider: endpoint # set the value to "authentication" if you want to test the sticky session load balancing APIML_SERVICE_CUSTOMMETADATA_APIML_LB_TYPE: headerRequest + OTEL_SDK_DISABLED: false + OTEL_RESOURCE_ATTRIBUTES_DEPLOYMENT_ENVIRONMENT: dev + OTEL_RESOURCE_ATTRIBUTES_SERVICE_NAME: apiml + OTEL_RESOURCE_ATTRIBUTES_ZOS_SMF_ID: SYS1 + OTEL_RESOURCE_ATTRIBUTES_ZOS_SYSPLEX_NAME: SYSPLEX1 + OTEL_RESOURCE_ATTRIBUTES_MAINFRAME_LPAR_NAME: LPAR01 + OTEL_EXPORTER_OTLP_ENDPOINT: http://localhost:4318 \ No newline at end of file diff --git a/otel/README.md b/otel/README.md new file mode 100644 index 0000000000..f7b341baf5 --- /dev/null +++ b/otel/README.md @@ -0,0 +1,73 @@ +# OpenTelemetry containers for integration testing + +The [docker-compose.yml](docker-compose.yml) defines 2 containers: +- OpenTelemetry Collector (oallector) +- OpenTelemetry Golden Validator (golden) + +The collector is the standard OpenTelemetry Collector ([docs](https://opentelemetry.io/docs/collector/), [repo](https://github.com/open-telemetry/opentelemetry-collector-contrib)). The Golden Tester comes from the [same](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/cmd/golden) repository and validates data exported from the collector. Only metrics (in [alpha](https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#alpha) stability level) are supported as of January 2026. + +## Integration test flow + +The API mediation layer produce telemetry data, that are exported to the Collector. Then the Collector exports the data to the Golden Tester the same way the data are published to an observability stack in real deployment. The Golden Tester validates the telemetry data against a definition from yaml file. If the validation does not pass within a timeout the container exits with exit code 1. + +```mermaid +flowchart LR + apiml["APIML (modulith)"] + collector["OpenTelemetry Collector"] + collector-config{{config.yml}} + golden["OpenTelemetry Golden Tester"] + golden-config{{expected.yml}} + apiml -- sends telemetry data --> collector + subgraph docker + collector -- forwards telemetry data --> golden + collector-config -.-> collector + golden -. validates against .-> golden-config + end + +``` + +The Golden Tester validates all metrics received, which makes definition of expected data difficult as the definition needs to be exhaustive. For this reason the OpenTelemetry collector is configured to produce at most one metric for validation, check the collector configuration file [otel-collector/config.yml](otel-collector/config.yml), which is mounted to the collector docker image. + +The Golden Tester configuration is split into 2 parts: +- Configuration of the tester like timeout, ports, fields to ignore, etc. is done via cli arguments. CLI arguments for the golden binary are placed in the [docker-compose.yml](docker-compose.yml) file. The list of supported options can be found in the [golden binary sources](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/cmd/golden/internal/config.go). +- The definition of expected observability data is in [otel-golden/expected.yml](otel-golden/expected.yml). + +### Golden Tester configuration consideration +Ideally, we want to have generic docker-compose file and configuration injected via mounted configuration files or environment variables. Unfortunately, the golden binary accepts only CLI arguments (except the definition of expected data). + +Every CLI argument that requires a value is processed as 2 distinct arguments by the golden binary. Given the fact, that the [official golden docker image](https://github.com/open-telemetry/opentelemetry-collector-contrib/pkgs/container/opentelemetry-collector-contrib%2Fgolden) is build from the [scratch base](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/cmd/golden/Dockerfile), there is no shell inside the golden image that preprocess the cli arguments so the arguments are passed to the binary exactly as defined in the [docker-compose.yml](docker-compose.yml) file. + +For instance if your docker file contains: +``` + command: + - "--ignore-resource-attribute-value process.pid" +``` +The whole string is passed to the binary and thus never matches the argument in the binary resulting in the value being ignored. The argument and value must be passed as two arguments: +``` + command: [ + "--ignore-resource-attribute-value", "process.pid" + ] +``` + +When environment variables are used to pass values to the docker files, only simple values that can be used in single argument value can be used. Unfortunately, this is not usable for the `--ignore-resource-attribute-value` as they must be repeated for every single value to be ignored. + +Possible workarounds are: +- Use Docker multi-stage build to create a custom Golden Tester image with a shell. The shell parses the string arguments on white spaces and pass them as individual arguments to the binary. Then multiple arguments can be defined in an environment variable: + ``` + GOLDEN_IGNORE_FIELDS = "--ignore-resource-attribute-value service.instance.id --ignore-resource-attribute-value host.name --ignore-resource-attribute-value host.arch --ignore-resource-attribute-value process.pid" + ``` + and the variable used as a placeholder in the docker compose `command`. + +- Add the arguments to the `docker compose run` command: + ```shell + $ docker compose run --rm --service-ports golden ----ignore-resource-attribute-value service.instance.id --ignore-resource-attribute-value host.name --ignore-resource-attribute-value host.arch --ignore-resource-attribute-value process.pid + ``` +Note that `docker compose` cli arguments override the `command` value in the docker file, and the containers must be started individually in comparison to the simple `docker compose up`. + +## Local run for development +To run the docker containers locally with the same setup as used in the integration tests, just run `docker compose up` (optionally with `-d`), and then start the APIML modulith with the OpenTelemetry enabled. The signals received and exported by the collector are saved to the [otel-golden](otel-golden) folder. The Golden Tester exits after timeout reporting the result of validation in the container console/log. The timeout can be set in the [docker-compose.yml](docker-compose.yml) file. + + + + + diff --git a/otel/docker-compose.yml b/otel/docker-compose.yml new file mode 100644 index 0000000000..323c53ebbf --- /dev/null +++ b/otel/docker-compose.yml @@ -0,0 +1,61 @@ +services: + # 1. OpenTelemetry Golden Tester + golden: + image: ghcr.io/open-telemetry/opentelemetry-collector-contrib/golden:latest + container_name: golden + ports: + - "5318:4318" #For validation in pipeline + command: [ + "--otlp-http-endpoint", "0.0.0.0:4318", + "--otlp-endpoint", "0.0.0.0:4317", + "--ignore-timestamp", + "--ignore-start-timestamp", + "--timeout", "3m", + "--ignore-resource-attribute-value", "service.instance.id", + "--ignore-resource-attribute-value", "host.name", + "--ignore-resource-attribute-value", "host.arch", + "--ignore-resource-attribute-value", "process.pid", + "--ignore-resource-attribute-value", "process.command_line", + "--ignore-resource-attribute-value", "process.command_args", + "--ignore-resource-attribute-value", "process.executable.path", + "--ignore-resource-attribute-value", "process.runtime.description", + "--ignore-resource-attribute-value", "process.runtime.version", + "--ignore-resource-attribute-value", "process.runtime.name", + "--ignore-resource-attribute-value", "os.description", + "--ignore-resource-attribute-value", "os.type", + "--ignore-resource-attribute-value", "telemetry.sdk.version", + "--ignore-resource-attribute-value", "telemetry.distro.name", + "--ignore-resource-attribute-value", "telemetry.distro.version", + "--ignore-resource-attribute-value", "telemetry.sdk.language", + "--ignore-resource-attribute-value", "telemetry.sdk.name", + "--ignore-resource-attribute-value", "service.version", + "--ignore-resource-attribute-value", "service.name", + "--ignore-metric-attribute-value", "service.instance.id", + "--ignore-metric-attribute-value", "service.version", + "--ignore-metric-attribute-value", "service.name", + "--ignore-resource-metrics-order", + "--ignore-scope-metrics-order", + "--ignore-metrics-order", + "--ignore-metrics-data-points-order", + "--ignore-metric-values", + "--ignore-data-points-attributes-order", + "--ignore-scope-version", + "--expected", "/var/data/expected.yaml", +# "--write-expected" # generates the expected definition file from received data + ] + volumes: + - ./otel-golden:/var/data + + # 2. OpenTelemetry Collector + collector: + image: ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib:latest + container_name: collector + depends_on: + - golden + ports: + - "4317:4317" # OTLP gRPC vstup + - "4318:4318" # OTLP HTTP vstup + volumes: + - ./otel-collector:/etc/otel-collector + command: + - "--config=/etc/otel-collector/config.yaml" diff --git a/otel/otel-collector/config.yaml b/otel/otel-collector/config.yaml new file mode 100755 index 0000000000..9565b19ec0 --- /dev/null +++ b/otel/otel-collector/config.yaml @@ -0,0 +1,96 @@ +receivers: + # OTLP receiver to receive telemetry data from services + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 + +exporters: + # signals (received and exported) are saved to files + file/metrics: + path: /etc/otel-collector/metrics.json + append: false + format: json + file/metrics_filtered: + path: /etc/otel-collector/metrics_filtered.json + append: false + format: json + file/traces: + path: /etc/otel-collector/traces.json + append: false + format: json + file/logs: + path: /etc/otel-collector/logs.json + append: false + format: json + # debug printed to the console + debug: + verbosity: detailed + # otlp-http exporter to forward telemetry to the Golden Tester + otlphttp/golden: + endpoint: "http://golden:4318" + tls: + insecure: true + nop: {} + +# The OpenTelemetry Golden Tester validates all received data, +# which requires all data to be described in golden expected definitions file. +# The metrics are filtered so only one metric is produced for validation. +processors: + # All metrics except jvm.cpu.count are ignored. + # The metric is expected to be always produced as we run java application. + filter/keep_specific_metrics: + error_mode: ignore + metrics: + include: + match_type: strict + metric_names: + - jvm.cpu.count + + # Every metric is converted to test.metric: + # - The name reflects this is purely artificial metrics for testing + # - Is converted to gauge as the gauge carries fewer attributes which simplifies the validation + # - The value is always 1.0 so it is easy to define the expected value for validation + transform/all_in_one: + error_mode: ignore + metric_statements: + - context: metric + statements: + - set(name, "test.metric") + - set(description, "Synthetic metric for resource attribute validation carrying fixed dummy value") + - set(instrumentation_scope.name, "test") + - set(unit, "") + - convert_sum_to_gauge() + - context: datapoint + statements: + - set(value_double, 1.0) + +service: + telemetry: + metrics: + level: none # Disables generation of internal otelcol_ metrics + pipelines: + traces: + # received traces are exported to console and file only + receivers: [otlp] + processors: [] + exporters: [debug, file/traces] + # received traces are exported to console and file only + metrics: + receivers: [otlp] + processors: [] + exporters: [debug, file/metrics] + # Received metrics are filtered and transformed. For any number of received metrics, at most one will be exported + # to console, file and Golden Tester. + metrics/filtered: + receivers: [ otlp ] + processors: [ filter/keep_specific_metrics, transform/all_in_one ] + exporters: [ debug, file/metrics_filtered, otlphttp/golden ] + # received logs are exported to console and file only + logs: + receivers: [otlp] + processors: [] + exporters: [debug, file/logs] + diff --git a/otel/otel-golden/expected.yaml b/otel/otel-golden/expected.yaml new file mode 100644 index 0000000000..c4bf7a4321 --- /dev/null +++ b/otel/otel-golden/expected.yaml @@ -0,0 +1,15 @@ +resourceMetrics: + - resource: + attributes: + - key: deployment.environment + value: { stringValue: "dev" } + - key: mainframe.lpar.name + value: { stringValue: "LPAR01" } + - key: zos.smf.id + value: { stringValue: "SYS1" } + - key: zos.sysplex.name + value: { stringValue: "SYSPLEX1" } + schemaUrl: "https://opentelemetry.io/schemas/1.24.0" + scopeMetrics: + - scope: { "name": "test" } + metrics: [ { "name": "test.metric", "description": "Synthetic metric for resource attribute validation carrying fixed dummy value", "gauge": {"dataPoints": [{"asDouble": 1}]} } ] From 02faf4b5ce62d1a7bca4dd71c2081f4a2abb0245 Mon Sep 17 00:00:00 2001 From: Richard Salac Date: Thu, 22 Jan 2026 15:22:29 +0100 Subject: [PATCH 6/7] cr Signed-off-by: Richard Salac --- .github/workflows/service-registration.yml | 37 ++++++---- .../resources/environment-configuration.yml | 7 -- otel/docker-compose.yml | 74 +++++++++---------- otel/otel-collector/config.yaml | 10 +-- 4 files changed, 64 insertions(+), 64 deletions(-) diff --git a/.github/workflows/service-registration.yml b/.github/workflows/service-registration.yml index 0e8ee61db4..8bd3e0325f 100644 --- a/.github/workflows/service-registration.yml +++ b/.github/workflows/service-registration.yml @@ -43,26 +43,35 @@ jobs: - name: Check OpenTelemetry containers run: | echo "Checking OpenTelemetry Golden Tester..." - curl -s http://localhost:5318/v1/metrics -H "Content-Type: application/json" -d "{}" \ + curl -s -v -w "\n" http://localhost:5318/v1/metrics -H "Content-Type: application/json" -d "{}" \ --fail \ --retry-all-errors \ - --retry-delay 2 \ - --retry 10 - echo "" - echo "curl exit code: "$? - echo "" + --retry-delay 10 \ + --retry 3 + if [ "$?" -eq 0 ]; then + echo "OpenTelemetry Golden Tester is ready!" + fi + echo "" echo "Checking OpenTelemetry Collector..." - curl -s http://localhost:4318/v1/metrics -H "Content-Type: application/json" -d "{}" \ + curl -s -v -w "\n" /dev/null http://localhost:4318/v1/metrics -H "Content-Type: application/json" -d "{}" \ --fail \ --retry-all-errors \ - --retry-delay 2 \ - --retry 10 - echo "" - echo "curl exit code: "$? + --retry-delay 10 \ + --retry 3 + if [ $? -eq 0 ]; then + echo "OpenTelemetry Collector is ready!" + fi - name: Run startup check for modulith - run: > + run: | + export OTEL_SDK_DISABLED=false + export OTEL_RESOURCE_ATTRIBUTES_DEPLOYMENT_ENVIRONMENT=dev + export OTEL_RESOURCE_ATTRIBUTES_SERVICE_NAME=apiml + export OTEL_RESOURCE_ATTRIBUTES_ZOS_SMF_ID=SYS1 + export OTEL_RESOURCE_ATTRIBUTES_ZOS_SYSPLEX_NAME=SYSPLEX1 + export OTEL_RESOURCE_ATTRIBUTES_MAINFRAME_LPAR_NAME=LPAR01 + export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 ./gradlew runStartUpCheckWithOpenTelemetry --info --scan -Denvironment.startServices=true -Denvironment.modulith=true - name: Verify OpenTelemetry data with Golden Tester @@ -75,7 +84,7 @@ jobs: # Display logs to see the diff if it failed echo "Golden container logs:" - docker logs golden + docker logs golden 2>&1 | tee otel/otel-golden/container.log echo "" @@ -91,7 +100,7 @@ jobs: docker stop collector -t 60 echo "Collector container logs:" - docker logs collector + docker logs collector 2>&1 | tee otel/otel-collector/container.log - name: Store results uses: actions/upload-artifact@v4 diff --git a/integration-tests/src/test/resources/environment-configuration.yml b/integration-tests/src/test/resources/environment-configuration.yml index f78acba1ab..8c061b0779 100644 --- a/integration-tests/src/test/resources/environment-configuration.yml +++ b/integration-tests/src/test/resources/environment-configuration.yml @@ -105,10 +105,3 @@ instanceEnv: ZWE_configs_apiml_security_authorization_provider: endpoint # set the value to "authentication" if you want to test the sticky session load balancing APIML_SERVICE_CUSTOMMETADATA_APIML_LB_TYPE: headerRequest - OTEL_SDK_DISABLED: false - OTEL_RESOURCE_ATTRIBUTES_DEPLOYMENT_ENVIRONMENT: dev - OTEL_RESOURCE_ATTRIBUTES_SERVICE_NAME: apiml - OTEL_RESOURCE_ATTRIBUTES_ZOS_SMF_ID: SYS1 - OTEL_RESOURCE_ATTRIBUTES_ZOS_SYSPLEX_NAME: SYSPLEX1 - OTEL_RESOURCE_ATTRIBUTES_MAINFRAME_LPAR_NAME: LPAR01 - OTEL_EXPORTER_OTLP_ENDPOINT: http://localhost:4318 \ No newline at end of file diff --git a/otel/docker-compose.yml b/otel/docker-compose.yml index 323c53ebbf..d13271a22d 100644 --- a/otel/docker-compose.yml +++ b/otel/docker-compose.yml @@ -6,42 +6,42 @@ services: ports: - "5318:4318" #For validation in pipeline command: [ - "--otlp-http-endpoint", "0.0.0.0:4318", - "--otlp-endpoint", "0.0.0.0:4317", - "--ignore-timestamp", - "--ignore-start-timestamp", - "--timeout", "3m", - "--ignore-resource-attribute-value", "service.instance.id", - "--ignore-resource-attribute-value", "host.name", - "--ignore-resource-attribute-value", "host.arch", - "--ignore-resource-attribute-value", "process.pid", - "--ignore-resource-attribute-value", "process.command_line", - "--ignore-resource-attribute-value", "process.command_args", - "--ignore-resource-attribute-value", "process.executable.path", - "--ignore-resource-attribute-value", "process.runtime.description", - "--ignore-resource-attribute-value", "process.runtime.version", - "--ignore-resource-attribute-value", "process.runtime.name", - "--ignore-resource-attribute-value", "os.description", - "--ignore-resource-attribute-value", "os.type", - "--ignore-resource-attribute-value", "telemetry.sdk.version", - "--ignore-resource-attribute-value", "telemetry.distro.name", - "--ignore-resource-attribute-value", "telemetry.distro.version", - "--ignore-resource-attribute-value", "telemetry.sdk.language", - "--ignore-resource-attribute-value", "telemetry.sdk.name", - "--ignore-resource-attribute-value", "service.version", - "--ignore-resource-attribute-value", "service.name", - "--ignore-metric-attribute-value", "service.instance.id", - "--ignore-metric-attribute-value", "service.version", - "--ignore-metric-attribute-value", "service.name", - "--ignore-resource-metrics-order", - "--ignore-scope-metrics-order", - "--ignore-metrics-order", - "--ignore-metrics-data-points-order", - "--ignore-metric-values", - "--ignore-data-points-attributes-order", - "--ignore-scope-version", - "--expected", "/var/data/expected.yaml", -# "--write-expected" # generates the expected definition file from received data + "--otlp-http-endpoint", "0.0.0.0:4318", + "--otlp-endpoint", "0.0.0.0:4317", + "--ignore-timestamp", + "--ignore-start-timestamp", + "--timeout", "3m", + "--ignore-resource-attribute-value", "service.instance.id", + "--ignore-resource-attribute-value", "host.name", + "--ignore-resource-attribute-value", "host.arch", + "--ignore-resource-attribute-value", "process.pid", + "--ignore-resource-attribute-value", "process.command_line", + "--ignore-resource-attribute-value", "process.command_args", + "--ignore-resource-attribute-value", "process.executable.path", + "--ignore-resource-attribute-value", "process.runtime.description", + "--ignore-resource-attribute-value", "process.runtime.version", + "--ignore-resource-attribute-value", "process.runtime.name", + "--ignore-resource-attribute-value", "os.description", + "--ignore-resource-attribute-value", "os.type", + "--ignore-resource-attribute-value", "telemetry.sdk.version", + "--ignore-resource-attribute-value", "telemetry.distro.name", + "--ignore-resource-attribute-value", "telemetry.distro.version", + "--ignore-resource-attribute-value", "telemetry.sdk.language", + "--ignore-resource-attribute-value", "telemetry.sdk.name", + "--ignore-resource-attribute-value", "service.version", + "--ignore-resource-attribute-value", "service.name", + "--ignore-metric-attribute-value", "service.instance.id", + "--ignore-metric-attribute-value", "service.version", + "--ignore-metric-attribute-value", "service.name", + "--ignore-resource-metrics-order", + "--ignore-scope-metrics-order", + "--ignore-metrics-order", + "--ignore-metrics-data-points-order", + "--ignore-metric-values", + "--ignore-data-points-attributes-order", + "--ignore-scope-version", + "--expected", "/var/data/expected.yaml", +# "--write-expected" # generates the expected definition file from received data ] volumes: - ./otel-golden:/var/data @@ -50,8 +50,6 @@ services: collector: image: ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib:latest container_name: collector - depends_on: - - golden ports: - "4317:4317" # OTLP gRPC vstup - "4318:4318" # OTLP HTTP vstup diff --git a/otel/otel-collector/config.yaml b/otel/otel-collector/config.yaml index 9565b19ec0..42f62821e1 100755 --- a/otel/otel-collector/config.yaml +++ b/otel/otel-collector/config.yaml @@ -25,7 +25,7 @@ exporters: path: /etc/otel-collector/logs.json append: false format: json - # debug printed to the console + # debug printed to the console when added to exporters debug: verbosity: detailed # otlp-http exporter to forward telemetry to the Golden Tester @@ -76,21 +76,21 @@ service: # received traces are exported to console and file only receivers: [otlp] processors: [] - exporters: [debug, file/traces] + exporters: [file/traces] # received traces are exported to console and file only metrics: receivers: [otlp] processors: [] - exporters: [debug, file/metrics] + exporters: [file/metrics] # Received metrics are filtered and transformed. For any number of received metrics, at most one will be exported # to console, file and Golden Tester. metrics/filtered: receivers: [ otlp ] processors: [ filter/keep_specific_metrics, transform/all_in_one ] - exporters: [ debug, file/metrics_filtered, otlphttp/golden ] + exporters: [ file/metrics_filtered, otlphttp/golden ] # received logs are exported to console and file only logs: receivers: [otlp] processors: [] - exporters: [debug, file/logs] + exporters: [file/logs] From 0023bbe28046d634ce006fad20d7f77449b17133 Mon Sep 17 00:00:00 2001 From: Richard Salac Date: Tue, 3 Feb 2026 15:08:59 +0100 Subject: [PATCH 7/7] refactor otel gh action into scripts Signed-off-by: Richard Salac --- .github/workflows/service-registration.yml | 56 ++-------------------- otel/sh/start_containers.sh | 33 +++++++++++++ otel/sh/validate_and_stop.sh | 22 +++++++++ 3 files changed, 60 insertions(+), 51 deletions(-) create mode 100755 otel/sh/start_containers.sh create mode 100755 otel/sh/validate_and_stop.sh diff --git a/.github/workflows/service-registration.yml b/.github/workflows/service-registration.yml index 8bd3e0325f..e72ea7705d 100644 --- a/.github/workflows/service-registration.yml +++ b/.github/workflows/service-registration.yml @@ -34,34 +34,10 @@ jobs: run: > ./gradlew runStartUpCheck --info --scan -Denvironment.startServices=true - - name: Start OpenTelemetry docker containers + - name: Start OpenTelemetry containers run: | cd otel - chmod -R 777 otel-* - docker compose up -d - - - name: Check OpenTelemetry containers - run: | - echo "Checking OpenTelemetry Golden Tester..." - curl -s -v -w "\n" http://localhost:5318/v1/metrics -H "Content-Type: application/json" -d "{}" \ - --fail \ - --retry-all-errors \ - --retry-delay 10 \ - --retry 3 - if [ "$?" -eq 0 ]; then - echo "OpenTelemetry Golden Tester is ready!" - fi - - echo "" - echo "Checking OpenTelemetry Collector..." - curl -s -v -w "\n" /dev/null http://localhost:4318/v1/metrics -H "Content-Type: application/json" -d "{}" \ - --fail \ - --retry-all-errors \ - --retry-delay 10 \ - --retry 3 - if [ $? -eq 0 ]; then - echo "OpenTelemetry Collector is ready!" - fi + sh/start_containers.sh - name: Run startup check for modulith run: | @@ -74,33 +50,11 @@ jobs: export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 ./gradlew runStartUpCheckWithOpenTelemetry --info --scan -Denvironment.startServices=true -Denvironment.modulith=true - - name: Verify OpenTelemetry data with Golden Tester - if: always() - run: | - echo "Waiting for Golden Validator to finish..." - - # This blocks until the golden container exits (success or timeout) - EXIT_CODE=$(docker wait golden) - - # Display logs to see the diff if it failed - echo "Golden container logs:" - docker logs golden 2>&1 | tee otel/otel-golden/container.log - - echo "" - - if [ "$EXIT_CODE" -ne 0 ]; then - echo "::error::OpenTelemetry data validation failed! See logs above for diff." - exit 1 - fi - echo "OpenTelemetry data validation passed!" - - - name: Stop OpenTelemetry Collector and print logs + - name: Validate telemetry data and stop containers if: always() run: | - docker stop collector -t 60 - - echo "Collector container logs:" - docker logs collector 2>&1 | tee otel/otel-collector/container.log + cd otel + sh/validate_and_stop.sh - name: Store results uses: actions/upload-artifact@v4 diff --git a/otel/sh/start_containers.sh b/otel/sh/start_containers.sh new file mode 100755 index 0000000000..abb935696b --- /dev/null +++ b/otel/sh/start_containers.sh @@ -0,0 +1,33 @@ +#!/bin/sh + +chmod -R 777 otel-* +docker compose up -d + +echo "Checking OpenTelemetry Golden Tester..." +curl -s -v -w "\n" http://localhost:5318/v1/metrics -H "Content-Type: application/json" -d "{}" \ + --fail \ + --retry-all-errors \ + --retry-delay 10 \ + --retry 3 +if [ "$?" -eq 0 ]; then + echo "OpenTelemetry Golden Tester is ready!" +else + echo "::error::OpenTelemetry Golden Tester startup failed" + docker compose stop + exit 1 +fi + +echo "" +echo "Checking OpenTelemetry Collector..." +curl -s -v -w "\n" /dev/null http://localhost:4318/v1/metrics -H "Content-Type: application/json" -d "{}" \ + --fail \ + --retry-all-errors \ + --retry-delay 10 \ + --retry 3 +if [ $? -eq 0 ]; then + echo "OpenTelemetry Collector is ready!" +else + echo "::error::OpenTelemetry Collector startup failed" + docker compose stop + exit 1 +fi diff --git a/otel/sh/validate_and_stop.sh b/otel/sh/validate_and_stop.sh new file mode 100755 index 0000000000..782e56b25d --- /dev/null +++ b/otel/sh/validate_and_stop.sh @@ -0,0 +1,22 @@ +#!/bin/sh + +echo "Waiting for Golden Validator to finish..." +# This blocks until the golden container exits (success or timeout) +EXIT_CODE=$(docker wait golden) + +echo "Stopping collector container..." +docker stop collector -t 60 +echo "Collector container logs:" +docker logs collector 2>&1 | tee otel-collector/container.log + +# Display logs to see the diff if it failed +echo "Golden container logs:" +docker logs golden 2>&1 | tee otel-golden/container.log + +echo "" + +if [ "$EXIT_CODE" -ne 0 ]; then + echo "::error::OpenTelemetry data validation failed! See logs above for diff." + exit 1 +fi +echo "OpenTelemetry data validation passed!"