From bcee5f4d8f0d565bcf0d643e546e4591b2600663 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Wed, 14 Jan 2026 13:20:30 +0100 Subject: [PATCH 01/26] deprecate onboarding-enabler zutil, move to apiml-utility Signed-off-by: Pablo Carle --- .../org/zowe/apiml/product/zos/ZUtil.java | 64 +++++ .../zowe/apiml/product/zos/ZUtilDummy.java | 219 ++++++++++++++++++ .../impl/DefaultCustomMetadataHelper.java | 17 +- .../eurekaservice/client/impl/ZUtil.java | 5 +- .../eurekaservice/client/impl/ZUtilDummy.java | 4 + 5 files changed, 297 insertions(+), 12 deletions(-) create mode 100644 apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZUtil.java create mode 100644 apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZUtilDummy.java diff --git a/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZUtil.java b/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZUtil.java new file mode 100644 index 0000000000..98717b3144 --- /dev/null +++ b/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZUtil.java @@ -0,0 +1,64 @@ +/* + * 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.product.zos; + +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Properties; + +/* + * For API documentation see https://www.ibm.com/support/knowledgecenter/SSYKE2_8.0.0/com.ibm.java.zsecurity.api.80.doc/com.ibm.jzos/com/ibm/jzos/ZUtil.html + */ +public interface ZUtil { + + String[] environ(); + String formatStackTrace(Throwable t); + String getCodePageCurrentLocale(); + long getCpuTimeMicros(); + String getCurrentJobId(); + String getCurrentJobname(); + String getCurrentProcStepname(); + String getCurrentStepname(); + long getCurrentTimeMicros(); + String getCurrentTsoPrefix(); + String getCurrentUser(); + String getDefaultPlatformEncoding(); + String getEnv(String varName); + Properties getEnvironment(); + String getJavaVersionInfo(); + String getJzosDllVersion(); + String getJzosJarVersion(); + int getLoggingLevel(); + int getPid(); + int getPPid(); + byte[] getTodClock(); + void getTodClock(byte[] buffer); + byte[] getTodClockExtended(); + void getTodClockExtended(byte[] buffer); + void logDiagnostic(int level, String msg); + PrintStream newEncodedPrintStream(OutputStream os, boolean autoFlush); + PrintStream newEncodedPrintStream(OutputStream os, boolean autoFlush, String encoding); + PrintStream newEncodedPrintStream(OutputStream os, boolean autoFlush, String encoding, boolean enable); + void peekOSMemory(long address, byte[] bytes); + void peekOSMemory(long address, byte[] bytes, int offset, int len); + long peekOSMemory(long address, int len); + void redirectStandardStreams(); + boolean redirectStandardStreams(String requestedEncoding, boolean enableTranscoding); + void setDefaultPlatformEncoding(String encoding); + void setEnv(String varName, String varValue); + void setLoggingLevel(int level); + void smfRecord(int type, int subtype, byte[] rec); + String substituteSystemSymbols(String pattern); + String substituteSystemSymbols(String pattern, boolean warn); + void touch(); + +} + diff --git a/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZUtilDummy.java b/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZUtilDummy.java new file mode 100644 index 0000000000..30e177bc75 --- /dev/null +++ b/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZUtilDummy.java @@ -0,0 +1,219 @@ +/* + * 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.product.zos; + +import java.io.OutputStream; +import java.io.PrintStream; +import java.util.Properties; + +public class ZUtilDummy implements ZUtil { + + @Override + public String[] environ() { + return new String[0]; + } + + @Override + public String formatStackTrace(Throwable t) { + return null; + } + + @Override + public String getCodePageCurrentLocale() { + return null; + } + + @Override + public long getCpuTimeMicros() { + return 0; + } + + @Override + public String getCurrentJobId() { + return null; + } + + @Override + public String getCurrentJobname() { + return null; + } + + @Override + public String getCurrentProcStepname() { + return null; + } + + @Override + public String getCurrentStepname() { + return null; + } + + @Override + public long getCurrentTimeMicros() { + return 0; + } + + @Override + public String getCurrentTsoPrefix() { + return null; + } + + @Override + public String getCurrentUser() { + return null; + } + + @Override + public String getDefaultPlatformEncoding() { + return null; + } + + @Override + public String getEnv(String varName) { + return null; + } + + @Override + public Properties getEnvironment() { + return null; + } + + @Override + public String getJavaVersionInfo() { + return null; + } + + @Override + public String getJzosDllVersion() { + return null; + } + + @Override + public String getJzosJarVersion() { + return null; + } + + @Override + public int getLoggingLevel() { + return 0; + } + + @Override + public int getPid() { + return 0; + } + + @Override + public int getPPid() { + return 0; + } + + @Override + public byte[] getTodClock() { + return new byte[0]; + } + + @Override + public void getTodClock(byte[] buffer) { + // dummy implementation - do nothing + } + + @Override + public byte[] getTodClockExtended() { + return new byte[0]; + } + + @Override + public void getTodClockExtended(byte[] buffer) { + // dummy implementation - do nothing + } + + @Override + public void logDiagnostic(int level, String msg) { + // dummy implementation - do nothing + } + + @Override + public PrintStream newEncodedPrintStream(OutputStream os, boolean autoFlush) { + return null; + } + + @Override + public PrintStream newEncodedPrintStream(OutputStream os, boolean autoFlush, String encoding) { + return null; + } + + @Override + public PrintStream newEncodedPrintStream(OutputStream os, boolean autoFlush, String encoding, boolean enable) { + return null; + } + + @Override + public void peekOSMemory(long address, byte[] bytes) { + // dummy implementation - do nothing + } + + @Override + public void peekOSMemory(long address, byte[] bytes, int offset, int len) { + // dummy implementation - do nothing + } + + @Override + public long peekOSMemory(long address, int len) { + return 0; + } + + @Override + public void redirectStandardStreams() { + // dummy implementation - do nothing + } + + @Override + public boolean redirectStandardStreams(String requestedEncoding, boolean enableTranscoding) { + return false; + } + + @Override + public void setDefaultPlatformEncoding(String encoding) { + // dummy implementation - do nothing + } + + @Override + public void setEnv(String varName, String varValue) { + // dummy implementation - do nothing + } + + @Override + public void setLoggingLevel(int level) { + // dummy implementation - do nothing + } + + @Override + public void smfRecord(int type, int subtype, byte[] rec) { + // dummy implementation - do nothing + } + + @Override + public String substituteSystemSymbols(String pattern) { + return null; + } + + @Override + public String substituteSystemSymbols(String pattern, boolean warn) { + return null; + } + + @Override + public void touch() { + // dummy implementation - do nothing + } + +} diff --git a/onboarding-enabler-java/src/main/java/org/zowe/apiml/eurekaservice/client/impl/DefaultCustomMetadataHelper.java b/onboarding-enabler-java/src/main/java/org/zowe/apiml/eurekaservice/client/impl/DefaultCustomMetadataHelper.java index b1420032d8..365d523613 100644 --- a/onboarding-enabler-java/src/main/java/org/zowe/apiml/eurekaservice/client/impl/DefaultCustomMetadataHelper.java +++ b/onboarding-enabler-java/src/main/java/org/zowe/apiml/eurekaservice/client/impl/DefaultCustomMetadataHelper.java @@ -13,28 +13,22 @@ import lombok.AccessLevel; import lombok.Setter; import org.zowe.apiml.eurekaservice.client.config.ApiMediationServiceConfig; +import org.zowe.apiml.product.zos.ZUtilDummy; import org.zowe.apiml.util.ClassOrDefaultProxyUtils; import java.util.HashMap; import java.util.Map; -public class DefaultCustomMetadataHelper { +import static org.zowe.apiml.product.zos.ZosSystemInformation.*; - private static final String ZOS_JOB_ID = "zos.jobid"; - private static final String ZOS_JOB_NAME = "zos.jobname"; - private static final String ZOS_USER_ID = "zos.userid"; - private static final String ZOS_PID = "zos.pid"; - private static final String ZOS_SYSNAME = "zos.sysname"; - private static final String ZOS_SYSCLONE = "zos.sysclone"; - private static final String ZOS_SYSPLEX = "zos.sysplex"; - private static final String OS_NAME = "os.name"; +public class DefaultCustomMetadataHelper { @Setter(AccessLevel.PROTECTED) - private ZUtil zUtil; + private org.zowe.apiml.product.zos.ZUtil zUtil; public DefaultCustomMetadataHelper() { if (isRunningOnZos()) { - zUtil = ClassOrDefaultProxyUtils.createProxy(ZUtil.class, "com.ibm.jzos.ZUtil", ZUtilDummy::new); + zUtil = ClassOrDefaultProxyUtils.createProxy(org.zowe.apiml.product.zos.ZUtil.class, "com.ibm.jzos.ZUtil", org.zowe.apiml.product.zos.ZUtilDummy::new); } else { zUtil = new ZUtilDummy(); } @@ -75,6 +69,7 @@ public void update(Map customMetadata) { for (Map.Entry entry : defaultMetadata.entrySet()) { customMetadata.putIfAbsent(entry.getKey(), entry.getValue()); } + } } diff --git a/onboarding-enabler-java/src/main/java/org/zowe/apiml/eurekaservice/client/impl/ZUtil.java b/onboarding-enabler-java/src/main/java/org/zowe/apiml/eurekaservice/client/impl/ZUtil.java index 553f7d4712..4c13507fcc 100644 --- a/onboarding-enabler-java/src/main/java/org/zowe/apiml/eurekaservice/client/impl/ZUtil.java +++ b/onboarding-enabler-java/src/main/java/org/zowe/apiml/eurekaservice/client/impl/ZUtil.java @@ -14,9 +14,12 @@ import java.io.PrintStream; import java.util.Properties; -/* +/** * For API documentation see https://www.ibm.com/support/knowledgecenter/SSYKE2_8.0.0/com.ibm.java.zsecurity.api.80.doc/com.ibm.jzos/com/ibm/jzos/ZUtil.html + * + * @deprecated Use #{org.zowe.apiml.product.zos.ZUtil} from apiml-utility */ +@Deprecated public interface ZUtil { String[] environ(); diff --git a/onboarding-enabler-java/src/main/java/org/zowe/apiml/eurekaservice/client/impl/ZUtilDummy.java b/onboarding-enabler-java/src/main/java/org/zowe/apiml/eurekaservice/client/impl/ZUtilDummy.java index d8f52227f3..cda8503f54 100644 --- a/onboarding-enabler-java/src/main/java/org/zowe/apiml/eurekaservice/client/impl/ZUtilDummy.java +++ b/onboarding-enabler-java/src/main/java/org/zowe/apiml/eurekaservice/client/impl/ZUtilDummy.java @@ -14,6 +14,10 @@ import java.io.PrintStream; import java.util.Properties; +/** + * @deprecated Use new version in apiml-utility + */ +@Deprecated public class ZUtilDummy implements ZUtil { @Override From 4fbb00234f8bec1192bdd929dec26f4f96ba07d6 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Wed, 14 Jan 2026 15:39:15 +0100 Subject: [PATCH 02/26] initial commit Signed-off-by: Pablo Carle --- apiml-common/build.gradle | 1 + ...mlNonZosOpenTelemetryResourceProvider.java | 18 +++++ .../ApimlOpenTelemetryResourceProvider.java | 67 +++++++++++++++++++ ...ApimlZosOpenTelemetryResourceProvider.java | 18 +++++ apiml-package/src/main/resources/bin/start.sh | 19 ++++-- .../product/zos/ZosSystemInformation.java | 59 ++++++++++++++++ .../product/zos/ZosSystemInformationTest.java | 25 +++++++ apiml/build.gradle | 1 - config/local/apiml-service.yml | 22 ++++++ gradle/versions.gradle | 1 - .../impl/ApiMediationClientImplTest.java | 5 +- package.json | 1 + 12 files changed, 229 insertions(+), 8 deletions(-) create mode 100644 apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProvider.java create mode 100644 apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlOpenTelemetryResourceProvider.java create mode 100644 apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java create mode 100644 apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZosSystemInformation.java create mode 100644 apiml-utility/src/test/java/org/zowe/apiml/product/zos/ZosSystemInformationTest.java diff --git a/apiml-common/build.gradle b/apiml-common/build.gradle index fb710e2bb1..da7664af54 100644 --- a/apiml-common/build.gradle +++ b/apiml-common/build.gradle @@ -8,6 +8,7 @@ dependencies { implementation libs.spring.boot.starter.actuator implementation libs.spring.boot.starter.web implementation libs.spring.cloud.starter.eureka.client + implementation libs.opentelemetry.spring.boot.starter compileOnly libs.netty.reactor.http implementation libs.eureka.core diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProvider.java new file mode 100644 index 0000000000..f4a076c650 --- /dev/null +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProvider.java @@ -0,0 +1,18 @@ +/* + * 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.product.opentelemetry; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; + +@ConditionalOnMissingBean(ApimlZosOpenTelemetryResourceProvider.class) +public class ApimlNonZosOpenTelemetryResourceProvider { + +} diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlOpenTelemetryResourceProvider.java new file mode 100644 index 0000000000..3213b72bcd --- /dev/null +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlOpenTelemetryResourceProvider.java @@ -0,0 +1,67 @@ +/* + * 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.product.opentelemetry; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; +import io.opentelemetry.sdk.resources.Resource; + +import javax.annotation.Nonnull; + +import java.util.Map; + +public abstract class ApimlOpenTelemetryResourceProvider implements ResourceProvider { + + /* + # service attributes: link to opentelemetry semantic conventionservice.name - logical name of the service, must be same for all instances of horizontally scaled services (e.g. instances within the same HA deployment). Expected to be globally unique if namespace is not defined. + +# service.instance.id - must be unique for each instance of service.name + service.namespace pair. Automatically generated uuid is generally recommended to ensure uniqueness. + +# service.namespace - value having a meaning that helps to distinguish a group of services, e.g. lpar, owner team, etc. service.name is expected to be unique within the same namespace. + +# service.version + +# deployment attributes: linkdeployment.environment.name - dev/test/staging/production + +# z/OS attributes: linkzos.smf.id - The System Management Facility (SMF) Identifier uniquely identified a z/OS system within a SYSPLEX or mainframe environment and is used for system and performance analysis. + +# zos.sysplex.name - The name of the SYSPLEX to which the z/OS system belongs too. + +# mainframe.lpar.name - Name of the logical partition that hosts a systems with a mainframe operating system. + +# os.type - The operating system type, e.g. zos + +# os.version - The version string of the operating system. On z/OS, SHOULD be the release returned by the command d iplinfo. + +# process.command - The command used to launch the process (i.e. the command name). On z/OS, SHOULD be set to the name of the job used to start the z/OS system software. + +# process.pid - Process identifier (PID). On z/OS, SHOULD be set to the Address Space Identifier. + + */ + + abstract Map getOsAttributes(); + + @Override + public Resource createResource(@Nonnull ConfigProperties config) { + var attributesBuilder = Attributes.builder(); + + // Are the OS-specific attributes already provided by the Spring boot starter when non-zos? what about when it's on zos? + attributesBuilder.put("process.pid", "null"); // Should be Address Space Identifier (can we get it from JZOS) or simple PID if running non-zos + attributesBuilder.put("process.command", "null"); // Should be name of the Job used to start the z/OS system software (STC / BPX JOBNAME?) + attributesBuilder.put("os.version", "null"); // d iplinfo + attributesBuilder.put("os.type", "null"); // if running on zos, zos, otherwise? + attributesBuilder.put("os.type", "null"); // if running on zos, zos, otherwise? + + return Resource.create(attributesBuilder.build()); + } + +} diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java new file mode 100644 index 0000000000..27fce767fe --- /dev/null +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java @@ -0,0 +1,18 @@ +/* + * 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.product.opentelemetry; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +@ConditionalOnProperty // matching z/OS +public class ApimlZosOpenTelemetryResourceProvider { + +} diff --git a/apiml-package/src/main/resources/bin/start.sh b/apiml-package/src/main/resources/bin/start.sh index 65bcee4716..ae9df8ed01 100755 --- a/apiml-package/src/main/resources/bin/start.sh +++ b/apiml-package/src/main/resources/bin/start.sh @@ -366,8 +366,8 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${APIML_CODE} ${JAVA_BIN_DIR}java \ -Dapiml.gateway.refresh-interval-ms=${ZWE_components_gateway_gateway_registry_refreshIntervalMs:-${ZWE_configs_gateway_registry_refreshIntervalMs:-30000}} \ -Dapiml.gateway.registry.enabled=${ZWE_components_gateway_apiml_gateway_registry_enabled:-${ZWE_configs_apiml_gateway_registry_enabled:-false}} \ -Dapiml.gateway.registry.metadata-key-allow-list=${ZWE_components_gateway_gateway_registry_metadataKeyAllowList:-${ZWE_configs_gateway_registry_metadataKeyAllowList:-}} \ - -Dapiml.gateway.servicesToLimitRequestRate=${ZWE_components_gateway_apiml_gateway_servicesToLimitRequestRate:-${ZWE_configs_apiml_gateway_servicesToLimitRequestRate:-}} \ -Dapiml.gateway.servicesToDisableRetry=${ZWE_components_gateway_apiml_gateway_servicesToDisableRetry:-${ZWE_configs_apiml_gateway_servicesToDisableRetry:-}} \ + -Dapiml.gateway.servicesToLimitRequestRate=${ZWE_components_gateway_apiml_gateway_servicesToLimitRequestRate:-${ZWE_configs_apiml_gateway_servicesToLimitRequestRate:-}} \ -Dapiml.health.protected=${ZWE_components_gateway_apiml_health_protected:-${ZWE_configs_apiml_health_protected:-true}} \ -Dapiml.httpclient.ssl.enabled-protocols=${client_enabled_protocols} \ -Dapiml.internal-discovery.port=${ZWE_components_discovery_port:-${ZWE_configs_internal_discovery_port:-7553}} \ @@ -396,6 +396,8 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${APIML_CODE} ${JAVA_BIN_DIR}java \ -Dapiml.security.oidc.userInfo.uri=${ZWE_components_gateway_apiml_security_oidc_userInfo_uri:-${ZWE_configs_apiml_security_oidc_userInfo_uri:-}} \ -Dapiml.security.oidc.validationType=${ZWE_components_gateway_apiml_security_oidc_validationType:-${ZWE_configs_apiml_security_oidc_validationType:-"JWK"}} \ -Dapiml.security.personalAccessToken.enabled=${ZWE_components_gateway_apiml_security_personalAccessToken_enabled:-${ZWE_configs_apiml_security_personalAccessToken_enabled:-false}} \ + -Dapiml.security.rauditx.oidcSourceUserPaths=${ZWE_configs_apiml_security_rauditx_oidcSourceUserPaths:-${ZWE_components_gateway_apiml_security_rauditx_oidcSourceUserPaths:-sub}} \ + -Dapiml.security.rauditx.onOidcUserIsMapped=${ZWE_configs_apiml_security_rauditx_onOidcUserIsMapped:-${ZWE_components_gateway_apiml_security_rauditx_onOidcUserIsMapped:-false}} \ -Dapiml.security.saf.provider=${ZWE_components_gateway_apiml_security_saf_provider:-${ZWE_configs_apiml_security_saf_provider:-"rest"}} \ -Dapiml.security.saf.urls.authenticate=${ZWE_components_gateway_apiml_security_saf_urls_authenticate:-${ZWE_configs_apiml_security_saf_urls_authenticate:-"${internalProtocol:-https}://${ZWE_haInstance_hostname:-localhost}:${ZWE_components_gateway_port:-7554}/zss/api/v1/saf/authenticate"}} \ -Dapiml.security.saf.urls.verify=${ZWE_components_gateway_apiml_security_saf_urls_verify:-${ZWE_configs_apiml_security_saf_urls_verify:-"${internalProtocol:-https}://${ZWE_haInstance_hostname:-localhost}:${ZWE_components_gateway_port:-7554}/zss/api/v1/saf/verify"}} \ @@ -411,8 +413,8 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${APIML_CODE} ${JAVA_BIN_DIR}java \ -Dapiml.security.zosmf.applid=${ZWE_components_gateway_apiml_security_zosmf_applid:-${ZWE_configs_apiml_security_zosmf_applid:-IZUDFLT}} \ -Dapiml.service.allowEncodedSlashes=${ZWE_components_gateway_apiml_service_allowEncodedSlashes:-${ZWE_configs_apiml_service_allowEncodedSlashes:-true}} \ -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.corsEnabled=${ZWE_components_gateway_apiml_service_corsEnabled:-${ZWE_configs_apiml_service_corsEnabled:-false}} \ -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} \ @@ -437,6 +439,17 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${APIML_CODE} ${JAVA_BIN_DIR}java \ -Djgroups.tcp.diag.enabled=${ZWE_components_caching_service_storage_infinispan_jgroups_tcp_diag_enabled:-${ZWE_configs_storage_infinispan_jgroups_tcp_diag_enabled:-false}} \ -Dloader.path=${APIML_LOADER_PATH} \ -Dlogging.charset.console=${ZOWE_CONSOLE_LOG_CHARSET} \ + -Dotel.exporter.otlp.endpoint="${ZWE_configs_telemetry_exporter_endpoint:-localhost:4318}" \ + -Dotel.resource.attributes.deployment.environment="${ZWE_configs_telemetry_attributes_deployment_environment:-prod}" \ + -Dotel.resource.attributes.mainframe.lpar.name=${ZWE_configs_telemetry_attributes:-ZWE_haInstance_id} \ + -Dotel.resource.attributes.os.type="${:-zos}" \ + -Dotel.resource.attributes.service.instance.id="${:-}" \ + -Dotel.resource.attributes.service.name="${:-}" \ + -Dotel.resource.attributes.service.namespace="${:-}" \ + -Dotel.resource.attributes.service.version="${:-}" \ + -Dotel.resource.attributes.zos.smf.id="${}" \ + -Dotel.resource.attributes.zos.sysplex.name="${ZWE_configs_telemetry_attributes_deployment_sysplex_name:-}" \ + -Dotel.sdk.disabled=${false} \ -Dserver.address=${ZWE_configs_zowe_network_server_listenAddresses_0:-${ZWE_zowe_network_server_listenAddresses_0:-"0.0.0.0"}} \ -Dserver.maxConnectionsPerRoute=${ZWE_components_gateway_server_maxConnectionsPerRoute:-${ZWE_configs_server_maxConnectionsPerRoute:-100}} \ -Dserver.maxTotalConnections=${ZWE_components_gateway_server_maxTotalConnections:-${ZWE_configs_server_maxTotalConnections:-1000}} \ @@ -457,8 +470,6 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${APIML_CODE} ${JAVA_BIN_DIR}java \ -Dserver.webSocket.maxIdleTimeout=${ZWE_components_gateway_server_webSocket_maxIdleTimeout:-${ZWE_configs_server_webSocket_maxIdleTimeout:-3600000}} \ -Dspring.cloud.gateway.server.webflux.httpclient.websocket.max-frame-payload-length=${ZWE_components_gateway_server_webSocket_requestBufferSize:-${ZWE_configs_server_webSocket_requestBufferSize:-8192}} \ -Dspring.profiles.active=${ZWE_configs_spring_profiles_active:-} \ - -Dapiml.security.rauditx.onOidcUserIsMapped=${ZWE_configs_apiml_security_rauditx_onOidcUserIsMapped:-${ZWE_components_gateway_apiml_security_rauditx_onOidcUserIsMapped:-false}} \ - -Dapiml.security.rauditx.oidcSourceUserPaths=${ZWE_configs_apiml_security_rauditx_oidcSourceUserPaths:-${ZWE_components_gateway_apiml_security_rauditx_oidcSourceUserPaths:-sub}} \ -jar "${JAR_FILE}" & pid=$! diff --git a/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZosSystemInformation.java b/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZosSystemInformation.java new file mode 100644 index 0000000000..a5dd8e8b1c --- /dev/null +++ b/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZosSystemInformation.java @@ -0,0 +1,59 @@ +/* + * 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.product.zos; + +import jakarta.annotation.PostConstruct; +import org.springframework.stereotype.Component; +import org.zowe.apiml.util.ClassOrDefaultProxyUtils; + +import java.util.Map; + +@Component +public class ZosSystemInformation { + + public static final String ZOS_JOB_ID = "zos.jobid"; + public static final String ZOS_JOB_NAME = "zos.jobname"; + public static final String ZOS_USER_ID = "zos.userid"; + public static final String ZOS_PID = "zos.pid"; + public static final String ZOS_SYSNAME = "zos.sysname"; + public static final String ZOS_SYSCLONE = "zos.sysclone"; + public static final String ZOS_SYSPLEX = "zos.sysplex"; + public static final String OS_NAME = "os.name"; + + private ZUtil zUtil; + + public boolean isRunningOnZos() { + return "z/OS".equals(System.getProperty(OS_NAME)); + } + + public Map get() { + return Map.of( + ZOS_JOB_ID, zUtil.getCurrentJobId(), + ZOS_JOB_NAME, zUtil.getCurrentJobname(), + ZOS_USER_ID, zUtil.getCurrentUser(), + ZOS_PID, zUtil.getPid(), + ZOS_SYSNAME, zUtil.substituteSystemSymbols("&SYSNAME."), + ZOS_SYSCLONE, zUtil.substituteSystemSymbols("&SYSCLONE."), + ZOS_SYSPLEX, zUtil.substituteSystemSymbols("&SYSPLEX.") + ); + } + + @PostConstruct + public void afterPropertiesSet() throws Exception { + if (isRunningOnZos()) { + zUtil = ClassOrDefaultProxyUtils.createProxy(ZUtil.class, "com.ibm.jzos.ZUtil", ZUtilDummy::new); + } else { + zUtil = new ZUtilDummy(); + } + + } + +} diff --git a/apiml-utility/src/test/java/org/zowe/apiml/product/zos/ZosSystemInformationTest.java b/apiml-utility/src/test/java/org/zowe/apiml/product/zos/ZosSystemInformationTest.java new file mode 100644 index 0000000000..161c70f7d9 --- /dev/null +++ b/apiml-utility/src/test/java/org/zowe/apiml/product/zos/ZosSystemInformationTest.java @@ -0,0 +1,25 @@ +/* + * 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.product.zos; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ZosSystemInformationTest { + + @Test + void testIsRunningOnZos() { + + } + +} diff --git a/apiml/build.gradle b/apiml/build.gradle index 42237884e3..61d8e4497a 100644 --- a/apiml/build.gradle +++ b/apiml/build.gradle @@ -74,7 +74,6 @@ dependencies { implementation libs.nimbus.jose.jwt implementation libs.spring.doc.webflux.ui implementation libs.jose4j.jwt // Signing, with support for JCA with ICSF - implementation libs.opentelemetry.spring.boot.starter testImplementation(testFixtures(project(":apiml-common"))) testImplementation(testFixtures(project(":gateway-service"))) diff --git a/config/local/apiml-service.yml b/config/local/apiml-service.yml index 971f500f7c..0978cefcea 100644 --- a/config/local/apiml-service.yml +++ b/config/local/apiml-service.yml @@ -72,3 +72,25 @@ spring: httpclient: websocket: max-frame-payload-length: 3145728 + +otel: + resource: + attributes: + deployment.environment: dev + service: + name: apiml + namespace: lparX + traces: + exporter: none + metrics: + exporter: none + logs: + ## Do not use logging-otlp without additional configuration (TBD) because the open telemetry logs from console + ## will be captured again ending in an infinite logging loop and resource starvation + # exporter: logging-otlp + exporter: logging + include-trace-context: true + exporter: + otlp: + endpoint: https://localhost:4318 + diff --git a/gradle/versions.gradle b/gradle/versions.gradle index 0eac58a5c3..16626ea507 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -252,7 +252,6 @@ dependencyResolutionManagement { library('bucket4j_core', 'com.bucket4j', 'bucket4j_jdk17-core').versionRef('bucket4j') library('xstren', 'com.thoughtworks.xstream', 'xstream').versionRef('xstream') // to avoid security vulnerability CVE-2024-47072 library('opentelemetry_spring_boot_starter', 'io.opentelemetry.instrumentation','opentelemetry-spring-boot-starter').versionRef('openTelemetry-starter') - // Sample apps only library('jersey_client4', 'com.sun.jersey.contribs', 'jersey-apache-client4').versionRef('jerseySun') library('jersey_client', 'com.sun.jersey', 'jersey-client').versionRef('jerseySun') diff --git a/onboarding-enabler-java/src/test/java/org/zowe/apiml/eurekaservice/client/impl/ApiMediationClientImplTest.java b/onboarding-enabler-java/src/test/java/org/zowe/apiml/eurekaservice/client/impl/ApiMediationClientImplTest.java index 5e49e209ba..6ae53f2e5f 100644 --- a/onboarding-enabler-java/src/test/java/org/zowe/apiml/eurekaservice/client/impl/ApiMediationClientImplTest.java +++ b/onboarding-enabler-java/src/test/java/org/zowe/apiml/eurekaservice/client/impl/ApiMediationClientImplTest.java @@ -23,6 +23,7 @@ import org.zowe.apiml.eurekaservice.client.util.EurekaInstanceConfigCreator; import org.zowe.apiml.exception.MetadataValidationException; import org.zowe.apiml.exception.ServiceDefinitionException; +import org.zowe.apiml.product.zos.ZUtilDummy; import java.util.ArrayList; import java.util.Collections; @@ -209,8 +210,8 @@ void testGivenCustomMetadata_whenRegister_thenValueIsNotChanged() throws Service assertEquals("OSX", config.getCustomMetadata().get("os.name")); } - private ZUtil getZUtilZosValue() { - ZUtilDummy zutil = mock(ZUtilDummy.class); + private org.zowe.apiml.product.zos.ZUtil getZUtilZosValue() { + org.zowe.apiml.product.zos.ZUtilDummy zutil = mock(ZUtilDummy.class); doReturn("jobId").when(zutil).getCurrentJobId(); doReturn("jobName").when(zutil).getCurrentJobname(); doReturn("userId").when(zutil).getCurrentUser(); diff --git a/package.json b/package.json index 8bca2c9b6a..214df645c6 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ }, "scripts": { "api-layer-modulith": "concurrently --names \"DC,ZO,AL\" -c yellow,white,blue npm:discoverable-client npm:mock-services npm:apiml-service", + "supporting-apis": "concurrently --names \"DC,ZO\" -c yellow,white npm:discoverable-client npm:mock-services", "api-layer": "concurrently --names \"AZ,AD,AC,DC,ZO,CS,AG\" -c cyan,yellow,white,blue,green,orange,red npm:zaas-service npm:discovery-service npm:api-catalog-service npm:discoverable-client npm:mock-services npm:caching-service npm:gateway-service", "api-layer-ci": "concurrently --names \"AZ,AD,AC,DC,ZO,CS,NS,AG\" -c cyan,yellow,white,blue,green,red,orange,brown npm:zaas-service-thin npm:discovery-service-thin npm:api-catalog-service-thin npm:discoverable-client npm:mock-services npm:caching-service npm:onboarding-enabler-nodejs-sample-app npm:gateway-service", "api-layer-core": "concurrently --names \"AZ,AD,AC,AG\" -c cyan,yellow,white npm:zaas-service npm:discovery-service npm:api-catalog-service npm:gateway-service", From 2d03079782dbc2a488b4e884e04dad00767756bc Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Fri, 16 Jan 2026 13:55:20 +0100 Subject: [PATCH 03/26] wip add calculated attributes Signed-off-by: Pablo Carle --- ...mlNonZosOpenTelemetryResourceProvider.java | 13 +++- .../ApimlOpenTelemetryResourceProvider.java | 39 +--------- ...ApimlZosOpenTelemetryResourceProvider.java | 73 ++++++++++++++++++- apiml-package/src/main/resources/bin/start.sh | 15 ++-- .../zowe/apiml/product/zos/ZUtilDummy.java | 23 ++++-- config/local/apiml-service.yml | 9 ++- 6 files changed, 114 insertions(+), 58 deletions(-) diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProvider.java index f4a076c650..c76a489b02 100644 --- a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProvider.java +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProvider.java @@ -10,9 +10,20 @@ package org.zowe.apiml.product.opentelemetry; +import io.opentelemetry.api.common.Attributes; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.stereotype.Component; + +import javax.annotation.Nonnull; @ConditionalOnMissingBean(ApimlZosOpenTelemetryResourceProvider.class) -public class ApimlNonZosOpenTelemetryResourceProvider { +@Component +public class ApimlNonZosOpenTelemetryResourceProvider extends ApimlOpenTelemetryResourceProvider { + + @Override + @Nonnull + Attributes calculateAttributes() { + return Attributes.empty(); + } } diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlOpenTelemetryResourceProvider.java index 3213b72bcd..5ccdc4a7d2 100644 --- a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlOpenTelemetryResourceProvider.java +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlOpenTelemetryResourceProvider.java @@ -17,50 +17,15 @@ import javax.annotation.Nonnull; -import java.util.Map; - public abstract class ApimlOpenTelemetryResourceProvider implements ResourceProvider { - /* - # service attributes: link to opentelemetry semantic conventionservice.name - logical name of the service, must be same for all instances of horizontally scaled services (e.g. instances within the same HA deployment). Expected to be globally unique if namespace is not defined. - -# service.instance.id - must be unique for each instance of service.name + service.namespace pair. Automatically generated uuid is generally recommended to ensure uniqueness. - -# service.namespace - value having a meaning that helps to distinguish a group of services, e.g. lpar, owner team, etc. service.name is expected to be unique within the same namespace. - -# service.version - -# deployment attributes: linkdeployment.environment.name - dev/test/staging/production - -# z/OS attributes: linkzos.smf.id - The System Management Facility (SMF) Identifier uniquely identified a z/OS system within a SYSPLEX or mainframe environment and is used for system and performance analysis. - -# zos.sysplex.name - The name of the SYSPLEX to which the z/OS system belongs too. - -# mainframe.lpar.name - Name of the logical partition that hosts a systems with a mainframe operating system. - -# os.type - The operating system type, e.g. zos - -# os.version - The version string of the operating system. On z/OS, SHOULD be the release returned by the command d iplinfo. - -# process.command - The command used to launch the process (i.e. the command name). On z/OS, SHOULD be set to the name of the job used to start the z/OS system software. - -# process.pid - Process identifier (PID). On z/OS, SHOULD be set to the Address Space Identifier. - - */ - - abstract Map getOsAttributes(); + abstract @Nonnull Attributes calculateAttributes(); @Override public Resource createResource(@Nonnull ConfigProperties config) { var attributesBuilder = Attributes.builder(); - // Are the OS-specific attributes already provided by the Spring boot starter when non-zos? what about when it's on zos? - attributesBuilder.put("process.pid", "null"); // Should be Address Space Identifier (can we get it from JZOS) or simple PID if running non-zos - attributesBuilder.put("process.command", "null"); // Should be name of the Job used to start the z/OS system software (STC / BPX JOBNAME?) - attributesBuilder.put("os.version", "null"); // d iplinfo - attributesBuilder.put("os.type", "null"); // if running on zos, zos, otherwise? - attributesBuilder.put("os.type", "null"); // if running on zos, zos, otherwise? - + attributesBuilder.putAll(calculateAttributes()); return Resource.create(attributesBuilder.build()); } diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java index 27fce767fe..06eecc578a 100644 --- a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java @@ -10,9 +10,76 @@ package org.zowe.apiml.product.opentelemetry; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import io.opentelemetry.api.common.Attributes; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.zowe.apiml.product.zos.ZosSystemInformation; -@ConditionalOnProperty // matching z/OS -public class ApimlZosOpenTelemetryResourceProvider { +import javax.annotation.Nonnull; + +import java.util.Map; + +@Component +@RequiredArgsConstructor +//@ConditionalOnExpression -> Expression should depend on ZosSystemInformation.isRunningOnZos() +@Slf4j +public class ApimlZosOpenTelemetryResourceProvider extends ApimlOpenTelemetryResourceProvider { + + private final ZosSystemInformation zosSystemInformation; + + @Value("${otel.resource.attributes.deployment.environment.name}") + private String environmentName; + + @Value("${otel.resource.attributes.zos.lpar.name}") + private String lparName; + + @Value("${otel.resource.attributes.zos.lpar.override}") + private boolean lparOverride; + + @Value("${otel.resource.attributes.service.namespace}") + private String serviceNamespace; + + @Value("${otel.resource.attributes.service.name}") + private String serviceName; + + @SuppressWarnings("null") + @Override + @Nonnull + Attributes calculateAttributes() { + if (!zosSystemInformation.isRunningOnZos()) { + log.error("OpenTelemetry attributes provider running outside of z/OS"); + return Attributes.empty(); + } + var attributesBuilder = Attributes.builder(); + + var zosAttributes = zosSystemInformation.get(); + + if (lparOverride && StringUtils.isNotBlank(lparName)) { + attributesBuilder.put("mainframe.lpar.name", lparName); + } + + if (StringUtils.isBlank(serviceNamespace)) { + attributesBuilder.put("service.namespace", generateServiceNamespace(zosAttributes)); + } + + if (StringUtils.isBlank(serviceName)) { + attributesBuilder.put("service.name", generateServiceName(zosAttributes)) + } + + return attributesBuilder.build(); + } + + private String generateServiceName(Map zosAttributes) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'generateServiceName'"); + } + + private String generateServiceNamespace(Map zosAttributes) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException("Unimplemented method 'generateServiceNamespace'"); + } } diff --git a/apiml-package/src/main/resources/bin/start.sh b/apiml-package/src/main/resources/bin/start.sh index ae9df8ed01..3fd493061c 100755 --- a/apiml-package/src/main/resources/bin/start.sh +++ b/apiml-package/src/main/resources/bin/start.sh @@ -440,15 +440,14 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${APIML_CODE} ${JAVA_BIN_DIR}java \ -Dloader.path=${APIML_LOADER_PATH} \ -Dlogging.charset.console=${ZOWE_CONSOLE_LOG_CHARSET} \ -Dotel.exporter.otlp.endpoint="${ZWE_configs_telemetry_exporter_endpoint:-localhost:4318}" \ + -Dotel.resource.attributes.deployment.environment.name="${ZWE_configs_telemetry_attributes_deployment_environment_name:-}" \ -Dotel.resource.attributes.deployment.environment="${ZWE_configs_telemetry_attributes_deployment_environment:-prod}" \ - -Dotel.resource.attributes.mainframe.lpar.name=${ZWE_configs_telemetry_attributes:-ZWE_haInstance_id} \ - -Dotel.resource.attributes.os.type="${:-zos}" \ - -Dotel.resource.attributes.service.instance.id="${:-}" \ - -Dotel.resource.attributes.service.name="${:-}" \ - -Dotel.resource.attributes.service.namespace="${:-}" \ - -Dotel.resource.attributes.service.version="${:-}" \ - -Dotel.resource.attributes.zos.smf.id="${}" \ - -Dotel.resource.attributes.zos.sysplex.name="${ZWE_configs_telemetry_attributes_deployment_sysplex_name:-}" \ + -Dotel.resource.attributes.service.name="${ZWE_configs_telemetry_service_name:-${ZWE_components_gateway_apimlId:-${ZWE_configs_apimlId}}}" \ + -Dotel.resource.attributes.service.namespace="${ZWE_configs_telemetry_service_namespace:-}" \ + -Dotel.resource.attributes.zos.smf.id="${ZWE_configs_telemetry_attributes_zos_smf_id:-}" \ + -Dotel.resource.attributes.zos.sysplex.name="${ZWE_configs_telemetry_attributes_zos_sysplex_name:-}" \ + -Dotel.resource.attributes.zos.lpar.override="${ZWE_configs_telemetry_attributes_zos_lpar_override:-false}" \ + -Dotel.resource.attributes.zos.lpar.name="${ZWE_configs_telemetry_attributes_zos_lpar_name:-${ZWE_haInstance_sysname:-${ZWE_haInstance_id:-}}}" \ -Dotel.sdk.disabled=${false} \ -Dserver.address=${ZWE_configs_zowe_network_server_listenAddresses_0:-${ZWE_zowe_network_server_listenAddresses_0:-"0.0.0.0"}} \ -Dserver.maxConnectionsPerRoute=${ZWE_components_gateway_server_maxConnectionsPerRoute:-${ZWE_configs_server_maxConnectionsPerRoute:-100}} \ diff --git a/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZUtilDummy.java b/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZUtilDummy.java index 30e177bc75..2842327239 100644 --- a/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZUtilDummy.java +++ b/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZUtilDummy.java @@ -38,12 +38,12 @@ public long getCpuTimeMicros() { @Override public String getCurrentJobId() { - return null; + return "STC1111"; } @Override public String getCurrentJobname() { - return null; + return "ZWE1AG"; } @Override @@ -68,7 +68,7 @@ public String getCurrentTsoPrefix() { @Override public String getCurrentUser() { - return null; + return "ZWEUSER"; } @Override @@ -108,7 +108,7 @@ public int getLoggingLevel() { @Override public int getPid() { - return 0; + return 1234567; } @Override @@ -203,7 +203,20 @@ public void smfRecord(int type, int subtype, byte[] rec) { @Override public String substituteSystemSymbols(String pattern) { - return null; + return switch (pattern) { + case "&SYSNAME." -> { + yield "SYSNAME"; + } + case "&SYSCLONE." -> { + yield "SYSCLONE"; + } + case "&SYSPLEX." -> { + yield "SYSPLEX"; + } + default -> { + yield null; + } + }; } @Override diff --git a/config/local/apiml-service.yml b/config/local/apiml-service.yml index 0978cefcea..02b6fbe386 100644 --- a/config/local/apiml-service.yml +++ b/config/local/apiml-service.yml @@ -83,14 +83,15 @@ otel: traces: exporter: none metrics: - exporter: none + exporter: otlp logs: ## Do not use logging-otlp without additional configuration (TBD) because the open telemetry logs from console ## will be captured again ending in an infinite logging loop and resource starvation # exporter: logging-otlp - exporter: logging + exporter: otlp include-trace-context: true exporter: otlp: - endpoint: https://localhost:4318 - + endpoint: http://localhost:4318 + sdk: + disabled: false From 46fb08bf083f9618c6cd34f13e5899a77166fac0 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Fri, 16 Jan 2026 16:57:00 +0100 Subject: [PATCH 04/26] wip testing Signed-off-by: Pablo Carle --- apiml/build.gradle | 3 +- .../apiml/acceptance/OpenTelemetryTests.java | 62 +++++++++++++++++++ gradle/versions.gradle | 3 + 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java diff --git a/apiml/build.gradle b/apiml/build.gradle index 61d8e4497a..e3960265e3 100644 --- a/apiml/build.gradle +++ b/apiml/build.gradle @@ -83,6 +83,7 @@ dependencies { testImplementation libs.reactor.test testImplementation libs.rest.assured testImplementation libs.rest.assured.web.test.client + testImplementation libs.opentelemetry.sdk.testing compileOnly libs.lombok annotationProcessor libs.lombok @@ -123,7 +124,7 @@ bootRun { debugOptions { port = 5009 - suspend = false + suspend = true server = true } diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java new file mode 100644 index 0000000000..4ab6ec895d --- /dev/null +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java @@ -0,0 +1,62 @@ +/* + * 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.acceptance; + +import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; +import org.springframework.test.context.ActiveProfiles; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class OpenTelemetryTests { + + @Nested + @AcceptanceTest + @ActiveProfiles("OpenTelemetryTest") + class TestClass { + + @Autowired + private InMemorySpanExporter inMemorySpanExporter; + + @Test + void testSomething() { + // Startup should generate something already + + + } + + } + + @TestConfiguration + @Profile("OpenTelemetryTest") + class OpenTelemetryConfiguration { + + @Bean + @Primary + SpanExporter spanExporter() { + return InMemorySpanExporter.create(); + } + + @Bean + InMemorySpanExporter inMemorySpanExporter(SpanExporter spanExporter) { + return (InMemorySpanExporter) spanExporter; + } + + } + +} diff --git a/gradle/versions.gradle b/gradle/versions.gradle index 16626ea507..db99993683 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -50,6 +50,7 @@ dependencyResolutionManagement { version('jakartaServlet', '6.1.0') version('javaxAnnotation', '1.3.2') version('openTelemetry-starter', '2.23.0') + version('openTelemetry-core', '1.58.0') // Eureka requires this specific version of Jakarta JAXB bindings version('jaxbApi') { @@ -252,6 +253,8 @@ dependencyResolutionManagement { library('bucket4j_core', 'com.bucket4j', 'bucket4j_jdk17-core').versionRef('bucket4j') library('xstren', 'com.thoughtworks.xstream', 'xstream').versionRef('xstream') // to avoid security vulnerability CVE-2024-47072 library('opentelemetry_spring_boot_starter', 'io.opentelemetry.instrumentation','opentelemetry-spring-boot-starter').versionRef('openTelemetry-starter') + library('opentelemetry_sdk_testing', 'io.opentelemetry','opentelemetry-sdk-testing').versionRef('openTelemetry-core') + // Sample apps only library('jersey_client4', 'com.sun.jersey.contribs', 'jersey-apache-client4').versionRef('jerseySun') library('jersey_client', 'com.sun.jersey', 'jersey-client').versionRef('jerseySun') From 1fa20d2d6f904772b0fb888beafee532988e9c60 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Fri, 16 Jan 2026 17:57:35 +0100 Subject: [PATCH 05/26] compile issue Signed-off-by: Pablo Carle --- .../ApimlZosOpenTelemetryResourceProvider.java | 2 +- .../apiml/acceptance/OpenTelemetryTests.java | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java index 06eecc578a..29e22453fd 100644 --- a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java @@ -66,7 +66,7 @@ Attributes calculateAttributes() { } if (StringUtils.isBlank(serviceName)) { - attributesBuilder.put("service.name", generateServiceName(zosAttributes)) + attributesBuilder.put("service.name", generateServiceName(zosAttributes)); } return attributesBuilder.build(); diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java index 4ab6ec895d..a908c288f8 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java @@ -22,6 +22,10 @@ import org.springframework.context.annotation.Profile; import org.springframework.test.context.ActiveProfiles; +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + @TestInstance(TestInstance.Lifecycle.PER_CLASS) class OpenTelemetryTests { @@ -33,11 +37,24 @@ class TestClass { @Autowired private InMemorySpanExporter inMemorySpanExporter; + @SuppressWarnings("null") @Test void testSomething() { // Startup should generate something already + var result = inMemorySpanExporter.flush(); + assertTrue(result.isSuccess()); + + var data = inMemorySpanExporter.getFinishedSpanItems(); + data.stream() + .anyMatch(d -> { + var attributes = d.getAttributes(); + assertEquals("JOB1111", attributes.get(stringKey(null))); + assertEquals(d, data); + return true; + } + ); } } From 06206ef8a328ca87c201d0dd328640a09d1fa23d Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Fri, 16 Jan 2026 18:02:10 +0100 Subject: [PATCH 06/26] add defaults Signed-off-by: Pablo Carle --- .../ApimlZosOpenTelemetryResourceProvider.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java index 29e22453fd..151f4683a0 100644 --- a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java @@ -30,19 +30,19 @@ public class ApimlZosOpenTelemetryResourceProvider extends ApimlOpenTelemetryRes private final ZosSystemInformation zosSystemInformation; - @Value("${otel.resource.attributes.deployment.environment.name}") + @Value("${otel.resource.attributes.deployment.environment.name:#{null}}") private String environmentName; - @Value("${otel.resource.attributes.zos.lpar.name}") + @Value("${otel.resource.attributes.zos.lpar.name:#{null}}") private String lparName; - @Value("${otel.resource.attributes.zos.lpar.override}") + @Value("${otel.resource.attributes.zos.lpar.override:false}") private boolean lparOverride; - @Value("${otel.resource.attributes.service.namespace}") + @Value("${otel.resource.attributes.service.namespace:#{null}}") private String serviceNamespace; - @Value("${otel.resource.attributes.service.name}") + @Value("${otel.resource.attributes.service.name:#{null}}") private String serviceName; @SuppressWarnings("null") From d9d254184dabfdcfebb7251bf7b8207bed831978 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Fri, 16 Jan 2026 18:15:13 +0100 Subject: [PATCH 07/26] otel test wip Signed-off-by: Pablo Carle --- .../java/org/zowe/apiml/acceptance/OpenTelemetryTests.java | 6 ++++++ apiml/src/test/resources/application.yml | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java index a908c288f8..7b553e870f 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java @@ -21,6 +21,7 @@ import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Profile; import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; import static io.opentelemetry.api.common.AttributeKey.stringKey; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -32,6 +33,11 @@ class OpenTelemetryTests { @Nested @AcceptanceTest @ActiveProfiles("OpenTelemetryTest") + @TestPropertySource( + properties = { + "otel.sdk.disabled=false" + } + ) class TestClass { @Autowired diff --git a/apiml/src/test/resources/application.yml b/apiml/src/test/resources/application.yml index 4cb227e8e2..ec43ce0eee 100644 --- a/apiml/src/test/resources/application.yml +++ b/apiml/src/test/resources/application.yml @@ -34,6 +34,11 @@ management: base-path: /application exposure: include: "*" + +otel: + sdk: + disabled: true + apiml: catalog: serviceId: apicatalog From d0e19673cfc4f98ab2b20cf8be0304bd5ce2323a Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Fri, 16 Jan 2026 18:30:05 +0100 Subject: [PATCH 08/26] test works Signed-off-by: Pablo Carle --- .../apiml/acceptance/OpenTelemetryTests.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java index 7b553e870f..02158f164d 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java @@ -65,21 +65,21 @@ void testSomething() { } - @TestConfiguration - @Profile("OpenTelemetryTest") - class OpenTelemetryConfiguration { - - @Bean - @Primary - SpanExporter spanExporter() { - return InMemorySpanExporter.create(); - } +} - @Bean - InMemorySpanExporter inMemorySpanExporter(SpanExporter spanExporter) { - return (InMemorySpanExporter) spanExporter; - } +@TestConfiguration +@Profile("OpenTelemetryTest") +class OpenTelemetryConfiguration { + + @Bean + @Primary + SpanExporter spanExporter() { + return InMemorySpanExporter.create(); + } + @Bean + InMemorySpanExporter inMemorySpanExporter(SpanExporter spanExporter) { + return (InMemorySpanExporter) spanExporter; } } From 9fa1332c6dde0ef70960187ba109f54c199d3698 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Fri, 16 Jan 2026 18:39:05 +0100 Subject: [PATCH 09/26] test data is empty Signed-off-by: Pablo Carle --- .../java/org/zowe/apiml/acceptance/OpenTelemetryTests.java | 7 +++++-- gateway-service/src/test/resources/application.yml | 4 ++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java index 02158f164d..4446ee48ae 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java @@ -25,6 +25,7 @@ import static io.opentelemetry.api.common.AttributeKey.stringKey; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @TestInstance(TestInstance.Lifecycle.PER_CLASS) @@ -52,7 +53,8 @@ void testSomething() { assertTrue(result.isSuccess()); var data = inMemorySpanExporter.getFinishedSpanItems(); - data.stream() + assertFalse(data.isEmpty(), "No data collected"); + assertTrue(data.stream() .anyMatch(d -> { var attributes = d.getAttributes(); assertEquals("JOB1111", attributes.get(stringKey(null))); @@ -60,7 +62,8 @@ void testSomething() { return true; } - ); + ), "No data matches"); + } } diff --git a/gateway-service/src/test/resources/application.yml b/gateway-service/src/test/resources/application.yml index 0a4147ac55..df259e8d4e 100644 --- a/gateway-service/src/test/resources/application.yml +++ b/gateway-service/src/test/resources/application.yml @@ -67,6 +67,10 @@ logging: org.springframework.beans: WARN reactor.netty: DEBUG +otel: + sdk: + disabled: true + management: endpoint: gateway: From 245a277892c6ee78da8f1127e0e7b827f7b5d42d Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Wed, 21 Jan 2026 12:34:30 +0100 Subject: [PATCH 10/26] attempts to have acceptance tests working Signed-off-by: Pablo Carle --- .../apiml/acceptance/OpenTelemetryTests.java | 31 ++++++++++++++----- apiml/src/test/resources/application.yml | 10 ++++++ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java index 4446ee48ae..fd158854b6 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java @@ -10,6 +10,8 @@ package org.zowe.apiml.acceptance; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; +import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.opentelemetry.sdk.trace.export.SpanExporter; import org.junit.jupiter.api.Nested; @@ -36,7 +38,8 @@ class OpenTelemetryTests { @ActiveProfiles("OpenTelemetryTest") @TestPropertySource( properties = { - "otel.sdk.disabled=false" + "otel.sdk.disabled=false", + "otel.logs.exporter=in_memory" } ) class TestClass { @@ -44,21 +47,24 @@ class TestClass { @Autowired private InMemorySpanExporter inMemorySpanExporter; + @Autowired + private InMemoryLogRecordExporter inMemoryLogRecordExporter; + @SuppressWarnings("null") @Test void testSomething() { // Startup should generate something already - var result = inMemorySpanExporter.flush(); - assertTrue(result.isSuccess()); + var spanData = inMemorySpanExporter.getFinishedSpanItems(); + var logData = inMemoryLogRecordExporter.getFinishedLogRecordItems(); - var data = inMemorySpanExporter.getFinishedSpanItems(); - assertFalse(data.isEmpty(), "No data collected"); - assertTrue(data.stream() + assertFalse(logData.isEmpty(), "No logs collected"); + assertFalse(spanData.isEmpty(), "No data collected"); + assertTrue(spanData.stream() .anyMatch(d -> { var attributes = d.getAttributes(); assertEquals("JOB1111", attributes.get(stringKey(null))); - assertEquals(d, data); + assertEquals(d, spanData); return true; } @@ -85,4 +91,15 @@ InMemorySpanExporter inMemorySpanExporter(SpanExporter spanExporter) { return (InMemorySpanExporter) spanExporter; } + @Bean + @Primary + LogRecordExporter logRecordExporter() { + return InMemoryLogRecordExporter.create(); + } + + @Bean + InMemoryLogRecordExporter inMemoryLogRecordExporter(LogRecordExporter logRecordExporter) { + return (InMemoryLogRecordExporter) logRecordExporter; + } + } diff --git a/apiml/src/test/resources/application.yml b/apiml/src/test/resources/application.yml index ec43ce0eee..452bfca8a0 100644 --- a/apiml/src/test/resources/application.yml +++ b/apiml/src/test/resources/application.yml @@ -36,6 +36,16 @@ management: include: "*" otel: + resource: + attributes: + deployment.environment: dev + traces: + exporter: none + metrics: + exporter: none + logs: + exporter: logging + include-trace-context: true sdk: disabled: true From 014845a0659258c27e797e9c0920271e34e17904 Mon Sep 17 00:00:00 2001 From: Richard Salac Date: Thu, 22 Jan 2026 14:25:29 +0100 Subject: [PATCH 11/26] InMemoryMetricReader for tests Signed-off-by: Richard Salac --- apiml/build.gradle | 1 + .../acceptance/OpenTelemetryMetricsTests.java | 62 +++++++++++++++++++ gradle/versions.gradle | 2 + 3 files changed, 65 insertions(+) create mode 100644 apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java diff --git a/apiml/build.gradle b/apiml/build.gradle index e3960265e3..daaf7a79eb 100644 --- a/apiml/build.gradle +++ b/apiml/build.gradle @@ -84,6 +84,7 @@ dependencies { testImplementation libs.rest.assured testImplementation libs.rest.assured.web.test.client testImplementation libs.opentelemetry.sdk.testing + testImplementation libs.opentelemetry.sdk.extension.autoconfigure.spi compileOnly libs.lombok annotationProcessor libs.lombok diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java new file mode 100644 index 0000000000..728f680a38 --- /dev/null +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java @@ -0,0 +1,62 @@ +package org.zowe.apiml.acceptance; + +import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.TestConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Profile; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.TestPropertySource; + +import java.util.Collection; + +import static org.assertj.core.api.Assertions.assertThat; + +@AcceptanceTest +@ActiveProfiles("OpenTelemetryTest") +@TestPropertySource( + properties = { + "otel.sdk.disabled=false", + "otel.metrics.exporter=none", + "otel.traces.exporter=none", + "otel.logs.exporter=none", + } +) +@Import(OpenTelemetryMetricsTest.TestConfig.class) +class OpenTelemetryMetricsTest { + + @Autowired + private InMemoryMetricReader metricReader; + + @Test + void testJvmMetrics() { + Collection metrics = metricReader.collectAllMetrics(); + + if (metrics.isEmpty()) { + System.out.println("WARNING: No metrics found in reader."); + } else { + metrics.forEach(m -> System.out.println("Found metric: " + m.getName())); + } + + assertThat(metrics).isNotEmpty(); + } + + @Profile("OpenTelemetryTest") + @TestConfiguration + static class TestConfig { + @Bean + public InMemoryMetricReader inMemoryMetricReader() { + return InMemoryMetricReader.create(); + } + + @Bean + public AutoConfigurationCustomizerProvider otelCustomizer(InMemoryMetricReader reader) { + return p -> p.addMeterProviderCustomizer((meterProviderBuilder, configProperties) -> + meterProviderBuilder.registerMetricReader(reader)); + } + } +} diff --git a/gradle/versions.gradle b/gradle/versions.gradle index cdf5c02759..64e92d0689 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -254,6 +254,8 @@ dependencyResolutionManagement { library('xstren', 'com.thoughtworks.xstream', 'xstream').versionRef('xstream') // to avoid security vulnerability CVE-2024-47072 library('opentelemetry_spring_boot_starter', 'io.opentelemetry.instrumentation','opentelemetry-spring-boot-starter').versionRef('openTelemetry-starter') library('opentelemetry_sdk_testing', 'io.opentelemetry','opentelemetry-sdk-testing').versionRef('openTelemetry-core') + library('opentelemetry_sdk_extension_autoconfigure_spi', 'io.opentelemetry','opentelemetry-sdk-extension-autoconfigure-spi').versionRef('openTelemetry-core') + // Sample apps only library('jersey_client4', 'com.sun.jersey.contribs', 'jersey-apache-client4').versionRef('jerseySun') From c42575ed4a7bd7615b0f3634c9e134c03eebf417 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Thu, 22 Jan 2026 16:46:34 +0100 Subject: [PATCH 12/26] rename test, update structure Signed-off-by: Pablo Carle --- .../acceptance/OpenTelemetryMetricsTests.java | 88 +++++++++------ .../apiml/acceptance/OpenTelemetryTests.java | 105 ------------------ 2 files changed, 54 insertions(+), 139 deletions(-) delete mode 100644 apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java index 728f680a38..190397d8e0 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java @@ -1,13 +1,25 @@ +/* + * 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.acceptance; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Profile; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.TestPropertySource; @@ -16,47 +28,55 @@ import static org.assertj.core.api.Assertions.assertThat; -@AcceptanceTest -@ActiveProfiles("OpenTelemetryTest") -@TestPropertySource( - properties = { - "otel.sdk.disabled=false", - "otel.metrics.exporter=none", - "otel.traces.exporter=none", - "otel.logs.exporter=none", - } -) -@Import(OpenTelemetryMetricsTest.TestConfig.class) +@TestInstance(Lifecycle.PER_CLASS) class OpenTelemetryMetricsTest { - @Autowired - private InMemoryMetricReader metricReader; + @Nested + @AcceptanceTest + @ActiveProfiles("OpenTelemetryTest") + @TestPropertySource( + properties = { + "otel.sdk.disabled=false", + "otel.metrics.exporter=none", + "otel.traces.exporter=none", + "otel.logs.exporter=none", + } + ) + class WhenOpenTelemetryEnabled { - @Test - void testJvmMetrics() { - Collection metrics = metricReader.collectAllMetrics(); + @Autowired + private InMemoryMetricReader metricReader; - if (metrics.isEmpty()) { - System.out.println("WARNING: No metrics found in reader."); - } else { - metrics.forEach(m -> System.out.println("Found metric: " + m.getName())); - } + @Test + void testJvmMetrics() { + Collection metrics = metricReader.collectAllMetrics(); - assertThat(metrics).isNotEmpty(); - } + if (metrics.isEmpty()) { + System.out.println("WARNING: No metrics found in reader."); + } else { + metrics.forEach(m -> System.out.println("Found metric: " + m.getName())); + } - @Profile("OpenTelemetryTest") - @TestConfiguration - static class TestConfig { - @Bean - public InMemoryMetricReader inMemoryMetricReader() { - return InMemoryMetricReader.create(); + assertThat(metrics).isNotEmpty(); } - @Bean - public AutoConfigurationCustomizerProvider otelCustomizer(InMemoryMetricReader reader) { - return p -> p.addMeterProviderCustomizer((meterProviderBuilder, configProperties) -> - meterProviderBuilder.registerMetricReader(reader)); + @Profile("OpenTelemetryTest") + @TestConfiguration + static class TestConfig { + + @Bean + public InMemoryMetricReader inMemoryMetricReader() { + return InMemoryMetricReader.create(); + } + + @Bean + public AutoConfigurationCustomizerProvider otelCustomizer(InMemoryMetricReader reader) { + return p -> p.addMeterProviderCustomizer((meterProviderBuilder, configProperties) -> + meterProviderBuilder.registerMetricReader(reader)); + } + } + } + } diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java deleted file mode 100644 index fd158854b6..0000000000 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryTests.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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.acceptance; - -import io.opentelemetry.sdk.logs.export.LogRecordExporter; -import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; -import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.TestConfiguration; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; -import org.springframework.context.annotation.Profile; -import org.springframework.test.context.ActiveProfiles; -import org.springframework.test.context.TestPropertySource; - -import static io.opentelemetry.api.common.AttributeKey.stringKey; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class OpenTelemetryTests { - - @Nested - @AcceptanceTest - @ActiveProfiles("OpenTelemetryTest") - @TestPropertySource( - properties = { - "otel.sdk.disabled=false", - "otel.logs.exporter=in_memory" - } - ) - class TestClass { - - @Autowired - private InMemorySpanExporter inMemorySpanExporter; - - @Autowired - private InMemoryLogRecordExporter inMemoryLogRecordExporter; - - @SuppressWarnings("null") - @Test - void testSomething() { - // Startup should generate something already - - var spanData = inMemorySpanExporter.getFinishedSpanItems(); - var logData = inMemoryLogRecordExporter.getFinishedLogRecordItems(); - - assertFalse(logData.isEmpty(), "No logs collected"); - assertFalse(spanData.isEmpty(), "No data collected"); - assertTrue(spanData.stream() - .anyMatch(d -> { - var attributes = d.getAttributes(); - assertEquals("JOB1111", attributes.get(stringKey(null))); - assertEquals(d, spanData); - return true; - } - - ), "No data matches"); - - } - - } - -} - -@TestConfiguration -@Profile("OpenTelemetryTest") -class OpenTelemetryConfiguration { - - @Bean - @Primary - SpanExporter spanExporter() { - return InMemorySpanExporter.create(); - } - - @Bean - InMemorySpanExporter inMemorySpanExporter(SpanExporter spanExporter) { - return (InMemorySpanExporter) spanExporter; - } - - @Bean - @Primary - LogRecordExporter logRecordExporter() { - return InMemoryLogRecordExporter.create(); - } - - @Bean - InMemoryLogRecordExporter inMemoryLogRecordExporter(LogRecordExporter logRecordExporter) { - return (InMemoryLogRecordExporter) logRecordExporter; - } - -} From 07054ca58838adca9a3b1447dc5f07f7bddd5167 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Fri, 23 Jan 2026 09:51:06 +0100 Subject: [PATCH 13/26] add unit tests Signed-off-by: Pablo Carle --- ...mlNonZosOpenTelemetryResourceProvider.java | 4 +- ...ApimlZosOpenTelemetryResourceProvider.java | 37 ++++++++++++-- .../product/opentelemetry/ZosAttributes.java | 17 +++++++ ...nZosOpenTelemetryResourceProviderTest.java | 36 ++++++++++++++ ...lZosOpenTelemetryResourceProviderTest.java | 48 +++++++++++++++++++ .../acceptance/OpenTelemetryMetricsTests.java | 24 +++++----- .../StartupMessageAcceptanceTest.java | 6 ++- 7 files changed, 152 insertions(+), 20 deletions(-) create mode 100644 apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ZosAttributes.java create mode 100644 apiml-common/src/test/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProviderTest.java create mode 100644 apiml-common/src/test/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProviderTest.java diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProvider.java index c76a489b02..5bee416283 100644 --- a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProvider.java +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProvider.java @@ -11,12 +11,12 @@ package org.zowe.apiml.product.opentelemetry; import io.opentelemetry.api.common.Attributes; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import javax.annotation.Nonnull; -@ConditionalOnMissingBean(ApimlZosOpenTelemetryResourceProvider.class) +@ConditionalOnExpression("#{!T(org.zowe.apiml.product.zos.ZosSystemInformation).isRunningOnZos()}") @Component public class ApimlNonZosOpenTelemetryResourceProvider extends ApimlOpenTelemetryResourceProvider { diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java index 151f4683a0..39a42a3be1 100644 --- a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java @@ -15,16 +15,20 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.stereotype.Component; import org.zowe.apiml.product.zos.ZosSystemInformation; import javax.annotation.Nonnull; import java.util.Map; +import java.util.Optional; + +import static org.zowe.apiml.product.zos.ZosSystemInformation.*; @Component @RequiredArgsConstructor -//@ConditionalOnExpression -> Expression should depend on ZosSystemInformation.isRunningOnZos() +@ConditionalOnMissingBean(ApimlNonZosOpenTelemetryResourceProvider.class) @Slf4j public class ApimlZosOpenTelemetryResourceProvider extends ApimlOpenTelemetryResourceProvider { @@ -45,6 +49,10 @@ public class ApimlZosOpenTelemetryResourceProvider extends ApimlOpenTelemetryRes @Value("${otel.resource.attributes.service.name:#{null}}") private String serviceName; + @Value("${apiml.service.apimlId:#{null}}") + private String apimlId; + + @SuppressWarnings("null") @Override @Nonnull @@ -69,17 +77,36 @@ Attributes calculateAttributes() { attributesBuilder.put("service.name", generateServiceName(zosAttributes)); } + Optional.ofNullable(zosAttributes.get(ZOS_JOB_ID)).map(String::valueOf) + .ifPresent(zosJobId -> attributesBuilder.put("", zosJobId)); + + Optional.ofNullable(zosAttributes.get(ZOS_JOB_NAME)).map(String::valueOf) + .ifPresent(zosJobName -> attributesBuilder.put("", zosJobName)); + + Optional.ofNullable(zosAttributes.get(ZOS_USER_ID)).map(String::valueOf) + .ifPresent(zosUserId -> attributesBuilder.put("", zosUserId)); + + Optional.ofNullable(zosAttributes.get(ZOS_PID)).map(String::valueOf) + .ifPresent(zosPid -> attributesBuilder.put("", zosPid)); + + Optional.ofNullable(zosAttributes.get(ZOS_SYSNAME)).map(String::valueOf) + .ifPresent(zosSysname -> attributesBuilder.put("", zosSysname)); + + Optional.ofNullable(zosAttributes.get(ZOS_SYSCLONE)).map(String::valueOf) + .ifPresent(zosSysclone -> attributesBuilder.put("", zosSysclone)); + + Optional.ofNullable(zosAttributes.get(ZOS_SYSPLEX)).map(String::valueOf) + .ifPresent(zosSysplex -> attributesBuilder.put("", zosSysplex)); + return attributesBuilder.build(); } private String generateServiceName(Map zosAttributes) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'generateServiceName'"); + return ""; } private String generateServiceNamespace(Map zosAttributes) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'generateServiceNamespace'"); + return ""; } } diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ZosAttributes.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ZosAttributes.java new file mode 100644 index 0000000000..207cd6bc1c --- /dev/null +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ZosAttributes.java @@ -0,0 +1,17 @@ +/* + * 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.product.opentelemetry; + +public class ZosAttributes { + + public static final String ZOS_JOBNAME = ""; + +} diff --git a/apiml-common/src/test/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProviderTest.java b/apiml-common/src/test/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProviderTest.java new file mode 100644 index 0000000000..68547407a2 --- /dev/null +++ b/apiml-common/src/test/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProviderTest.java @@ -0,0 +1,36 @@ +/* + * 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.product.opentelemetry; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(MockitoExtension.class) +class ApimlNonZosOpenTelemetryResourceProviderTest { + + private ApimlNonZosOpenTelemetryResourceProvider resourceProvider; + + @BeforeEach + void setUp() { + this.resourceProvider = new ApimlNonZosOpenTelemetryResourceProvider(); + } + + @Test + void testCalculateAttributes() { + var result = resourceProvider.calculateAttributes(); + assertTrue(result.isEmpty()); + } + +} diff --git a/apiml-common/src/test/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProviderTest.java b/apiml-common/src/test/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProviderTest.java new file mode 100644 index 0000000000..d65e2cc6fd --- /dev/null +++ b/apiml-common/src/test/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProviderTest.java @@ -0,0 +1,48 @@ +/* + * 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.product.opentelemetry; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.zowe.apiml.product.zos.ZosSystemInformation; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +@ExtendWith(MockitoExtension.class) +class ApimlZosOpenTelemetryResourceProviderTest { + + @Mock + private ZosSystemInformation zosSystemInformation; + + private ApimlZosOpenTelemetryResourceProvider resourceProvider; + + @BeforeEach + void setUp() { + resourceProvider = new ApimlZosOpenTelemetryResourceProvider(zosSystemInformation); + } + + @Nested + class GivenZosAttributes { + + @Test + void testCalculateAttributes() { + var attributes = resourceProvider.calculateAttributes(); + + assertFalse(attributes.isEmpty()); + } + + } + +} diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java index 190397d8e0..bd04d6bed6 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java @@ -11,7 +11,6 @@ package org.zowe.apiml.acceptance; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; -import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -24,9 +23,9 @@ import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.TestPropertySource; -import java.util.Collection; +import javax.annotation.Nonnull; -import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertFalse; @TestInstance(Lifecycle.PER_CLASS) class OpenTelemetryMetricsTest { @@ -40,6 +39,7 @@ class OpenTelemetryMetricsTest { "otel.metrics.exporter=none", "otel.traces.exporter=none", "otel.logs.exporter=none", + "os.name=z/OS" } ) class WhenOpenTelemetryEnabled { @@ -49,15 +49,15 @@ class WhenOpenTelemetryEnabled { @Test void testJvmMetrics() { - Collection metrics = metricReader.collectAllMetrics(); + var metrics = metricReader.collectAllMetrics(); + assertFalse(metrics.isEmpty(), "No data received"); - if (metrics.isEmpty()) { - System.out.println("WARNING: No metrics found in reader."); - } else { - metrics.forEach(m -> System.out.println("Found metric: " + m.getName())); - } + metrics.forEach( + metric -> { + + } + ); - assertThat(metrics).isNotEmpty(); } @Profile("OpenTelemetryTest") @@ -65,12 +65,12 @@ void testJvmMetrics() { static class TestConfig { @Bean - public InMemoryMetricReader inMemoryMetricReader() { + InMemoryMetricReader inMemoryMetricReader() { return InMemoryMetricReader.create(); } @Bean - public AutoConfigurationCustomizerProvider otelCustomizer(InMemoryMetricReader reader) { + AutoConfigurationCustomizerProvider otelCustomizer(@Nonnull InMemoryMetricReader reader) { return p -> p.addMeterProviderCustomizer((meterProviderBuilder, configProperties) -> meterProviderBuilder.registerMetricReader(reader)); } diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/StartupMessageAcceptanceTest.java b/apiml/src/test/java/org/zowe/apiml/acceptance/StartupMessageAcceptanceTest.java index 457dcd698d..7238adf003 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/StartupMessageAcceptanceTest.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/StartupMessageAcceptanceTest.java @@ -45,7 +45,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class StartupMessageAcceptanceTest { +class StartupMessageAcceptanceTest { @AcceptanceTest @ExtendWith({MockitoExtension.class, OutputCaptureExtension.class}) @@ -78,10 +78,12 @@ void verifyStartupMessage(CapturedOutput output) { @Nested class GivenDefaultProfile extends BaseStartupTest { + @Test void whenFullyStartedUp_thenEmitMessage(CapturedOutput output) { verifyStartupMessage(output); } + } @Nested @@ -131,5 +133,7 @@ void whenFullyStartedUp_thenEmitMessage(CapturedOutput output) throws IoctlCallE verifyStartupMessage(output); } + } + } From f6dfe77c09fa2ebfa3c26d5b894b7c9ac3f7f0d3 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Fri, 23 Jan 2026 15:46:46 +0100 Subject: [PATCH 14/26] wip Signed-off-by: Pablo Carle --- ...ApimlZosOpenTelemetryResourceProvider.java | 25 +++++++++++-------- apiml-package/src/main/resources/bin/start.sh | 2 +- .../product/zos/ZosSystemInformation.java | 2 +- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java index 39a42a3be1..83c5d9fc62 100644 --- a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java @@ -11,6 +11,7 @@ package org.zowe.apiml.product.opentelemetry; import io.opentelemetry.api.common.Attributes; +import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -52,12 +53,16 @@ public class ApimlZosOpenTelemetryResourceProvider extends ApimlOpenTelemetryRes @Value("${apiml.service.apimlId:#{null}}") private String apimlId; + @PostConstruct + void afterPropertiesSet() { + log.debug("Using ZOS OpenTelemetry resource provider"); + } @SuppressWarnings("null") @Override @Nonnull Attributes calculateAttributes() { - if (!zosSystemInformation.isRunningOnZos()) { + if (!ZosSystemInformation.isRunningOnZos()) { log.error("OpenTelemetry attributes provider running outside of z/OS"); return Attributes.empty(); } @@ -78,35 +83,35 @@ Attributes calculateAttributes() { } Optional.ofNullable(zosAttributes.get(ZOS_JOB_ID)).map(String::valueOf) - .ifPresent(zosJobId -> attributesBuilder.put("", zosJobId)); + .ifPresent(zosJobId -> attributesBuilder.put("zos.jobid", zosJobId)); Optional.ofNullable(zosAttributes.get(ZOS_JOB_NAME)).map(String::valueOf) - .ifPresent(zosJobName -> attributesBuilder.put("", zosJobName)); + .ifPresent(zosJobName -> attributesBuilder.put("zos.jobname", zosJobName)); Optional.ofNullable(zosAttributes.get(ZOS_USER_ID)).map(String::valueOf) - .ifPresent(zosUserId -> attributesBuilder.put("", zosUserId)); + .ifPresent(zosUserId -> attributesBuilder.put("zos.userid", zosUserId)); Optional.ofNullable(zosAttributes.get(ZOS_PID)).map(String::valueOf) - .ifPresent(zosPid -> attributesBuilder.put("", zosPid)); + .ifPresent(zosPid -> attributesBuilder.put("zos.pid", zosPid)); Optional.ofNullable(zosAttributes.get(ZOS_SYSNAME)).map(String::valueOf) - .ifPresent(zosSysname -> attributesBuilder.put("", zosSysname)); + .ifPresent(zosSysname -> attributesBuilder.put("zos.sysname", zosSysname)); Optional.ofNullable(zosAttributes.get(ZOS_SYSCLONE)).map(String::valueOf) - .ifPresent(zosSysclone -> attributesBuilder.put("", zosSysclone)); + .ifPresent(zosSysclone -> attributesBuilder.put("zos.sysclone", zosSysclone)); Optional.ofNullable(zosAttributes.get(ZOS_SYSPLEX)).map(String::valueOf) - .ifPresent(zosSysplex -> attributesBuilder.put("", zosSysplex)); + .ifPresent(zosSysplex -> attributesBuilder.put("zos.sysplex", zosSysplex)); return attributesBuilder.build(); } private String generateServiceName(Map zosAttributes) { - return ""; + return "service_name"; } private String generateServiceNamespace(Map zosAttributes) { - return ""; + return "service_namespace"; } } diff --git a/apiml-package/src/main/resources/bin/start.sh b/apiml-package/src/main/resources/bin/start.sh index 3fd493061c..fd8acf19b0 100755 --- a/apiml-package/src/main/resources/bin/start.sh +++ b/apiml-package/src/main/resources/bin/start.sh @@ -439,7 +439,7 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${APIML_CODE} ${JAVA_BIN_DIR}java \ -Djgroups.tcp.diag.enabled=${ZWE_components_caching_service_storage_infinispan_jgroups_tcp_diag_enabled:-${ZWE_configs_storage_infinispan_jgroups_tcp_diag_enabled:-false}} \ -Dloader.path=${APIML_LOADER_PATH} \ -Dlogging.charset.console=${ZOWE_CONSOLE_LOG_CHARSET} \ - -Dotel.exporter.otlp.endpoint="${ZWE_configs_telemetry_exporter_endpoint:-localhost:4318}" \ + -Dotel.exporter.otlp.endpoint="${ZWE_configs_telemetry_exporter_endpoint:-http://localhost:4318}" \ -Dotel.resource.attributes.deployment.environment.name="${ZWE_configs_telemetry_attributes_deployment_environment_name:-}" \ -Dotel.resource.attributes.deployment.environment="${ZWE_configs_telemetry_attributes_deployment_environment:-prod}" \ -Dotel.resource.attributes.service.name="${ZWE_configs_telemetry_service_name:-${ZWE_components_gateway_apimlId:-${ZWE_configs_apimlId}}}" \ diff --git a/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZosSystemInformation.java b/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZosSystemInformation.java index a5dd8e8b1c..9a7b002e7e 100644 --- a/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZosSystemInformation.java +++ b/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZosSystemInformation.java @@ -30,7 +30,7 @@ public class ZosSystemInformation { private ZUtil zUtil; - public boolean isRunningOnZos() { + public static boolean isRunningOnZos() { return "z/OS".equals(System.getProperty(OS_NAME)); } From 94c9370c1d3d4439bb476c555c0c9be7d2ec18c3 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Tue, 27 Jan 2026 14:00:21 +0100 Subject: [PATCH 15/26] fix for acceptance test Signed-off-by: Pablo Carle --- .../zowe/apiml/acceptance/AvailabilityTest.java | 2 +- .../acceptance/StartupMessageAcceptanceTest.java | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/AvailabilityTest.java b/apiml/src/test/java/org/zowe/apiml/acceptance/AvailabilityTest.java index ef5f53826b..e3a22a0a7e 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/AvailabilityTest.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/AvailabilityTest.java @@ -31,7 +31,7 @@ @AcceptanceTest @TestInstance(TestInstance.Lifecycle.PER_CLASS) @ActiveProfiles({ "ApimlModulithAcceptanceTest", "AvailabilityTest" }) -public class AvailabilityTest extends AcceptanceTestWithBasePath { +class AvailabilityTest extends AcceptanceTestWithBasePath { @ParameterizedTest(name = "{0} is available at port {1} with status {2}") @CsvSource({ diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/StartupMessageAcceptanceTest.java b/apiml/src/test/java/org/zowe/apiml/acceptance/StartupMessageAcceptanceTest.java index 7238adf003..8b52345d66 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/StartupMessageAcceptanceTest.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/StartupMessageAcceptanceTest.java @@ -15,6 +15,8 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @@ -22,6 +24,7 @@ import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.cloud.netflix.eureka.server.event.EurekaInstanceRegisteredEvent; import org.springframework.cloud.netflix.eureka.server.event.EurekaRegistryAvailableEvent; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.util.ReflectionTestUtils; @@ -47,9 +50,8 @@ class StartupMessageAcceptanceTest { - @AcceptanceTest - @ExtendWith({MockitoExtension.class, OutputCaptureExtension.class}) abstract static class BaseStartupTest extends AcceptanceTestWithMockServices { + @Mock private InstanceInfo instanceInfo; @@ -77,6 +79,11 @@ void verifyStartupMessage(CapturedOutput output) { } @Nested + @AcceptanceTest + @TestInstance(Lifecycle.PER_CLASS) + @DirtiesContext + @ExtendWith({MockitoExtension.class, OutputCaptureExtension.class}) + @ActiveProfiles({"default"}) class GivenDefaultProfile extends BaseStartupTest { @Test @@ -87,6 +94,10 @@ void whenFullyStartedUp_thenEmitMessage(CapturedOutput output) { } @Nested + @AcceptanceTest + @TestInstance(Lifecycle.PER_CLASS) + @DirtiesContext + @ExtendWith({MockitoExtension.class, OutputCaptureExtension.class}) @ActiveProfiles({"attlsClient", "attlsServer"}) class GivenAttlsProfile extends BaseStartupTest { From 1015c3870fbaca3e14b926e4ac3ec7b70480574d Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Tue, 27 Jan 2026 15:26:50 +0100 Subject: [PATCH 16/26] unit test Signed-off-by: Pablo Carle --- ...ApimlZosOpenTelemetryResourceProvider.java | 39 ++++++++----------- ...lZosOpenTelemetryResourceProviderTest.java | 24 ++++++++++++ .../acceptance/OpenTelemetryMetricsTests.java | 2 +- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java index 83c5d9fc62..e36fdecd5b 100644 --- a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java @@ -25,7 +25,10 @@ import java.util.Map; import java.util.Optional; -import static org.zowe.apiml.product.zos.ZosSystemInformation.*; +import static org.zowe.apiml.product.zos.ZosSystemInformation.ZOS_JOB_ID; +import static org.zowe.apiml.product.zos.ZosSystemInformation.ZOS_JOB_NAME; +import static org.zowe.apiml.product.zos.ZosSystemInformation.ZOS_SYSPLEX; +import static org.zowe.apiml.product.zos.ZosSystemInformation.ZOS_USER_ID; @Component @RequiredArgsConstructor @@ -53,6 +56,9 @@ public class ApimlZosOpenTelemetryResourceProvider extends ApimlOpenTelemetryRes @Value("${apiml.service.apimlId:#{null}}") private String apimlId; + @Value("${apiml.service.port:10010}") + private int port; + @PostConstruct void afterPropertiesSet() { log.debug("Using ZOS OpenTelemetry resource provider"); @@ -62,56 +68,45 @@ void afterPropertiesSet() { @Override @Nonnull Attributes calculateAttributes() { - if (!ZosSystemInformation.isRunningOnZos()) { - log.error("OpenTelemetry attributes provider running outside of z/OS"); - return Attributes.empty(); - } var attributesBuilder = Attributes.builder(); var zosAttributes = zosSystemInformation.get(); if (lparOverride && StringUtils.isNotBlank(lparName)) { + log.debug("LPAR name override for OpenTelemetry metrics"); attributesBuilder.put("mainframe.lpar.name", lparName); } if (StringUtils.isBlank(serviceNamespace)) { + log.debug("service.namespace not provided in configuration, generating default"); attributesBuilder.put("service.namespace", generateServiceNamespace(zosAttributes)); } if (StringUtils.isBlank(serviceName)) { + log.debug("service.name not provided in configuration, generating default"); attributesBuilder.put("service.name", generateServiceName(zosAttributes)); } Optional.ofNullable(zosAttributes.get(ZOS_JOB_ID)).map(String::valueOf) - .ifPresent(zosJobId -> attributesBuilder.put("zos.jobid", zosJobId)); + .ifPresent(zosJobId -> attributesBuilder.put("process.zos.jobid", zosJobId)); Optional.ofNullable(zosAttributes.get(ZOS_JOB_NAME)).map(String::valueOf) - .ifPresent(zosJobName -> attributesBuilder.put("zos.jobname", zosJobName)); + .ifPresent(zosJobName -> attributesBuilder.put("process.zos.jobname", zosJobName)); Optional.ofNullable(zosAttributes.get(ZOS_USER_ID)).map(String::valueOf) - .ifPresent(zosUserId -> attributesBuilder.put("zos.userid", zosUserId)); - - Optional.ofNullable(zosAttributes.get(ZOS_PID)).map(String::valueOf) - .ifPresent(zosPid -> attributesBuilder.put("zos.pid", zosPid)); - - Optional.ofNullable(zosAttributes.get(ZOS_SYSNAME)).map(String::valueOf) - .ifPresent(zosSysname -> attributesBuilder.put("zos.sysname", zosSysname)); - - Optional.ofNullable(zosAttributes.get(ZOS_SYSCLONE)).map(String::valueOf) - .ifPresent(zosSysclone -> attributesBuilder.put("zos.sysclone", zosSysclone)); - - Optional.ofNullable(zosAttributes.get(ZOS_SYSPLEX)).map(String::valueOf) - .ifPresent(zosSysplex -> attributesBuilder.put("zos.sysplex", zosSysplex)); + .ifPresent(zosUserId -> attributesBuilder.put("process.zos.userid", zosUserId)); return attributesBuilder.build(); } private String generateServiceName(Map zosAttributes) { - return "service_name"; + var systemName = StringUtils.isBlank(apimlId) ? zosAttributes.get(ZOS_SYSPLEX) : apimlId; + + return systemName + ":" + port; } private String generateServiceNamespace(Map zosAttributes) { - return "service_namespace"; + return "apiml:" + generateServiceName(zosAttributes); } } diff --git a/apiml-common/src/test/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProviderTest.java b/apiml-common/src/test/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProviderTest.java index d65e2cc6fd..f6b72f7da9 100644 --- a/apiml-common/src/test/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProviderTest.java +++ b/apiml-common/src/test/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProviderTest.java @@ -16,9 +16,16 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; import org.zowe.apiml.product.zos.ZosSystemInformation; +import java.util.Map; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class ApimlZosOpenTelemetryResourceProviderTest { @@ -31,6 +38,7 @@ class ApimlZosOpenTelemetryResourceProviderTest { @BeforeEach void setUp() { resourceProvider = new ApimlZosOpenTelemetryResourceProvider(zosSystemInformation); + ReflectionTestUtils.setField(resourceProvider, "port", 10010); } @Nested @@ -38,9 +46,25 @@ class GivenZosAttributes { @Test void testCalculateAttributes() { + when(zosSystemInformation.get()).thenReturn(Map.of( + "zos.jobid", "JOB12345", + "zos.jobname", "JOBN12", + "zos.userid", "ZWEUSR", + "zos.pid", 123456, + "zos.sysname", "SYSA", + "zos.sysclone", "16", + "zos.sysplex", "PLEX1" + )); var attributes = resourceProvider.calculateAttributes(); assertFalse(attributes.isEmpty()); + assertNull(attributes.get(stringKey("mainframe.lpar.name"))); + + assertEquals("JOB12345", attributes.get(stringKey("process.zos.jobid"))); + assertEquals("JOBN12", attributes.get(stringKey("process.zos.jobname"))); + assertEquals("ZWEUSR", attributes.get(stringKey("process.zos.userid"))); + assertEquals("apiml:PLEX1:10010", attributes.get(stringKey("service.namespace"))); + assertEquals("PLEX1:10010", attributes.get(stringKey("service.name"))); } } diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java index bd04d6bed6..cf02e73fcb 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java @@ -54,7 +54,7 @@ void testJvmMetrics() { metrics.forEach( metric -> { - + System.out.println(); } ); From a8be34cd63611fe06293f19e924825b8a545c6b1 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Tue, 27 Jan 2026 17:37:43 +0100 Subject: [PATCH 17/26] complete functional test Signed-off-by: Pablo Carle --- ...mlNonZosOpenTelemetryResourceProvider.java | 2 +- .../ApimlOpenTelemetryResourceProvider.java | 2 +- ...ApimlZosOpenTelemetryResourceProvider.java | 2 +- .../acceptance/OpenTelemetryMetricsTests.java | 53 ++++++++++++++++++- gradle/versions.gradle | 1 - 5 files changed, 55 insertions(+), 5 deletions(-) diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProvider.java index 5bee416283..9cff2595b6 100644 --- a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProvider.java +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProvider.java @@ -22,7 +22,7 @@ public class ApimlNonZosOpenTelemetryResourceProvider extends ApimlOpenTelemetry @Override @Nonnull - Attributes calculateAttributes() { + public Attributes calculateAttributes() { return Attributes.empty(); } diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlOpenTelemetryResourceProvider.java index 5ccdc4a7d2..248a2c3fe7 100644 --- a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlOpenTelemetryResourceProvider.java +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlOpenTelemetryResourceProvider.java @@ -19,7 +19,7 @@ public abstract class ApimlOpenTelemetryResourceProvider implements ResourceProvider { - abstract @Nonnull Attributes calculateAttributes(); + public abstract @Nonnull Attributes calculateAttributes(); @Override public Resource createResource(@Nonnull ConfigProperties config) { diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java index e36fdecd5b..9f0244eed1 100644 --- a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java @@ -67,7 +67,7 @@ void afterPropertiesSet() { @SuppressWarnings("null") @Override @Nonnull - Attributes calculateAttributes() { + public Attributes calculateAttributes() { var attributesBuilder = Attributes.builder(); var zosAttributes = zosSystemInformation.get(); diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java index cf02e73fcb..683809ea8f 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java @@ -10,8 +10,10 @@ package org.zowe.apiml.acceptance; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -19,15 +21,22 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.TestConfiguration; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Profile; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.TestPropertySource; +import org.zowe.apiml.product.opentelemetry.ApimlOpenTelemetryResourceProvider; +import org.zowe.apiml.product.opentelemetry.ApimlZosOpenTelemetryResourceProvider; +import org.zowe.apiml.product.zos.ZosSystemInformation; import javax.annotation.Nonnull; +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; -@TestInstance(Lifecycle.PER_CLASS) class OpenTelemetryMetricsTest { @Nested @@ -42,11 +51,24 @@ class OpenTelemetryMetricsTest { "os.name=z/OS" } ) + @TestInstance(Lifecycle.PER_CLASS) + @DirtiesContext class WhenOpenTelemetryEnabled { + private static String defaultPlatform = System.getProperty("os.name"); + + static { + System.setProperty("os.name", "z/OS"); + } + @Autowired private InMemoryMetricReader metricReader; + @BeforeAll + void init() { + System.setProperty("os.name", defaultPlatform); + } + @Test void testJvmMetrics() { var metrics = metricReader.collectAllMetrics(); @@ -55,6 +77,14 @@ void testJvmMetrics() { metrics.forEach( metric -> { System.out.println(); + var attributes = metric.getResource().getAttributes(); + assertEquals("zos", attributes.get(stringKey("os.type"))); + assertNotNull(attributes.get(stringKey("process.pid"))); + assertEquals("STC1111", attributes.get(stringKey("process.zos.jobid"))); + assertEquals("ZWE1AG", attributes.get(stringKey("process.zos.jobname"))); + assertEquals("gateway", attributes.get(stringKey("service.name"))); + assertEquals("apiml:apiml1:40985", attributes.get(stringKey("service.namespace"))); + assertNotNull(attributes.get(stringKey("service.version"))); } ); @@ -75,6 +105,27 @@ AutoConfigurationCustomizerProvider otelCustomizer(@Nonnull InMemoryMetricReader meterProviderBuilder.registerMetricReader(reader)); } + @Bean + @Primary + ApimlOpenTelemetryResourceProvider apimlOpenTelemetryResourceProvider(ZosSystemInformation zosSystemInformation) { + return new TestApimlZosOpenTelemetryResourceProvider(zosSystemInformation); + } + + } + + static class TestApimlZosOpenTelemetryResourceProvider extends ApimlZosOpenTelemetryResourceProvider { + + public TestApimlZosOpenTelemetryResourceProvider(ZosSystemInformation zosSystemInformation) { + super(zosSystemInformation); + } + + @Override + public Attributes calculateAttributes() { + var attributes = super.calculateAttributes(); + System.setProperty("os.name", defaultPlatform); + return attributes; + } + } } diff --git a/gradle/versions.gradle b/gradle/versions.gradle index b4be70dfba..4c59b7ca8d 100644 --- a/gradle/versions.gradle +++ b/gradle/versions.gradle @@ -256,7 +256,6 @@ dependencyResolutionManagement { library('opentelemetry_sdk_testing', 'io.opentelemetry','opentelemetry-sdk-testing').versionRef('openTelemetry-core') library('opentelemetry_sdk_extension_autoconfigure_spi', 'io.opentelemetry','opentelemetry-sdk-extension-autoconfigure-spi').versionRef('openTelemetry-core') - // Sample apps only library('jersey_client4', 'com.sun.jersey.contribs', 'jersey-apache-client4').versionRef('jerseySun') library('jersey_client', 'com.sun.jersey', 'jersey-client').versionRef('jerseySun') From 86094cbe78efc2cd1e19c4985aa38c21f8697178 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Tue, 27 Jan 2026 17:58:32 +0100 Subject: [PATCH 18/26] remove standard attribute from test Signed-off-by: Pablo Carle --- .../org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java index 683809ea8f..16e0b04e26 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java @@ -79,7 +79,6 @@ void testJvmMetrics() { System.out.println(); var attributes = metric.getResource().getAttributes(); assertEquals("zos", attributes.get(stringKey("os.type"))); - assertNotNull(attributes.get(stringKey("process.pid"))); assertEquals("STC1111", attributes.get(stringKey("process.zos.jobid"))); assertEquals("ZWE1AG", attributes.get(stringKey("process.zos.jobname"))); assertEquals("gateway", attributes.get(stringKey("service.name"))); From 49520e1504836309eaaff591d6b5bb00e3f84c69 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Wed, 28 Jan 2026 10:37:44 +0100 Subject: [PATCH 19/26] attempt to fix otel settings start.sh Signed-off-by: Pablo Carle --- apiml-package/src/main/resources/bin/start.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/apiml-package/src/main/resources/bin/start.sh b/apiml-package/src/main/resources/bin/start.sh index e5bc19e904..625d66bc4f 100755 --- a/apiml-package/src/main/resources/bin/start.sh +++ b/apiml-package/src/main/resources/bin/start.sh @@ -333,6 +333,12 @@ if [ -n "${ZWE_java_home}" ]; then JAVA_BIN_DIR=${ZWE_java_home}/bin/ fi +if [ "$ZWE_configs_telemetry_enabled" = "true" ]; then + DISABLE_OTEL=false +else + DISABLE_OTEL=true +fi + APIML_CODE=AG SHARED_CLASSES_OPTS="-Xshareclasses:name=apiml_shared_classes,nonfatal" @@ -455,7 +461,7 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${APIML_CODE} ${JAVA_BIN_DIR}java \ -Dotel.resource.attributes.zos.sysplex.name="${ZWE_configs_telemetry_attributes_zos_sysplex_name:-}" \ -Dotel.resource.attributes.zos.lpar.override="${ZWE_configs_telemetry_attributes_zos_lpar_override:-false}" \ -Dotel.resource.attributes.zos.lpar.name="${ZWE_configs_telemetry_attributes_zos_lpar_name:-${ZWE_haInstance_sysname:-${ZWE_haInstance_id:-}}}" \ - -Dotel.sdk.disabled=${false} \ + -Dotel.sdk.disabled=${DISABLE_OTEL} \ -Dserver.address=${ZWE_configs_zowe_network_server_listenAddresses_0:-${ZWE_zowe_network_server_listenAddresses_0:-"0.0.0.0"}} \ -Dserver.maxConnectionsPerRoute=${ZWE_components_gateway_server_maxConnectionsPerRoute:-${ZWE_configs_server_maxConnectionsPerRoute:-100}} \ -Dserver.maxTotalConnections=${ZWE_components_gateway_server_maxTotalConnections:-${ZWE_configs_server_maxTotalConnections:-1000}} \ From a6671032c8cd0591347b18d72a41aff351c7fd0b Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Thu, 29 Jan 2026 15:36:40 +0100 Subject: [PATCH 20/26] add unit test Signed-off-by: Pablo Carle --- .../ApimlNonZosOpenTelemetryResourceProviderTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/apiml-common/src/test/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProviderTest.java b/apiml-common/src/test/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProviderTest.java index 68547407a2..3b90cb3860 100644 --- a/apiml-common/src/test/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProviderTest.java +++ b/apiml-common/src/test/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProviderTest.java @@ -10,12 +10,14 @@ package org.zowe.apiml.product.opentelemetry; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; @ExtendWith(MockitoExtension.class) class ApimlNonZosOpenTelemetryResourceProviderTest { @@ -33,4 +35,10 @@ void testCalculateAttributes() { assertTrue(result.isEmpty()); } + @Test + void testCreateResource() { + var result = resourceProvider.createResource(mock(ConfigProperties.class)); + assertTrue(result.getAttributes().isEmpty()); + } + } From ba4e739e2d31cf0459ee2706380f6bbfa06c5ff7 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Fri, 30 Jan 2026 10:10:26 +0100 Subject: [PATCH 21/26] fix merge issue Signed-off-by: Pablo Carle --- apiml-package/src/main/resources/bin/start.sh | 1 - apiml/build.gradle | 2 +- .../java/org/zowe/apiml/startup/ApiMediationLayerStartTest.java | 1 + .../java/org/zowe/apiml/util/service/FullApiMediationLayer.java | 1 - 4 files changed, 2 insertions(+), 3 deletions(-) diff --git a/apiml-package/src/main/resources/bin/start.sh b/apiml-package/src/main/resources/bin/start.sh index 625d66bc4f..a48644adc1 100755 --- a/apiml-package/src/main/resources/bin/start.sh +++ b/apiml-package/src/main/resources/bin/start.sh @@ -428,7 +428,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.corsAllowedMethods=${ZWE_components_gateway_apiml_service_corsAllowedMethods:-${ZWE_configs_apiml_service_corsAllowedMethods:-GET,HEAD,POST,PATCH,DELETE,PUT,OPTIONS}} \ -Dapiml.service.corsEnabled=${ZWE_components_gateway_apiml_service_corsEnabled:-${ZWE_configs_apiml_service_corsEnabled:-false}} \ - -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/build.gradle b/apiml/build.gradle index daaf7a79eb..03b4d707e4 100644 --- a/apiml/build.gradle +++ b/apiml/build.gradle @@ -125,7 +125,7 @@ bootRun { debugOptions { port = 5009 - suspend = true + suspend = false server = true } 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..db00a66e18 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 @@ -29,4 +29,5 @@ void setUp() { void checkApiMediationLayerStart() { assertTrue(true); } + } 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..c30200e118 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 @@ -51,7 +51,6 @@ public class FullApiMediationLayer { @Getter private static final FullApiMediationLayer instance = new FullApiMediationLayer(); - private FullApiMediationLayer() { env = ConfigReader.environmentConfiguration().getInstanceEnv(); From ca9d625e7bcb95ca13362e556bbd5815b5095726 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Tue, 3 Feb 2026 09:36:18 +0100 Subject: [PATCH 22/26] pr review 1 Signed-off-by: Pablo Carle --- ...ApimlZosOpenTelemetryResourceProvider.java | 12 ++++---- .../zowe/apiml/product/zos/ZUtilDummy.java | 18 ++++-------- .../product/zos/ZosSystemInformationTest.java | 28 ++++++++++++++++++- .../acceptance/OpenTelemetryMetricsTests.java | 6 ++-- config/local/apiml-service.yml | 3 +- 5 files changed, 42 insertions(+), 25 deletions(-) diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java index 9f0244eed1..3b8a64cb23 100644 --- a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java @@ -73,18 +73,20 @@ public Attributes calculateAttributes() { var zosAttributes = zosSystemInformation.get(); if (lparOverride && StringUtils.isNotBlank(lparName)) { - log.debug("LPAR name override for OpenTelemetry metrics"); + log.debug("mainframe.lpar.name override for OpenTelemetry metrics: {}", lparName); attributesBuilder.put("mainframe.lpar.name", lparName); } if (StringUtils.isBlank(serviceNamespace)) { - log.debug("service.namespace not provided in configuration, generating default"); - attributesBuilder.put("service.namespace", generateServiceNamespace(zosAttributes)); + var generatedDefaultNamespace = generateServiceNamespace(zosAttributes); + attributesBuilder.put("service.namespace", generatedDefaultNamespace); + log.debug("service.namespace not provided in configuration, using generated default {}", generatedDefaultNamespace); } if (StringUtils.isBlank(serviceName)) { - log.debug("service.name not provided in configuration, generating default"); - attributesBuilder.put("service.name", generateServiceName(zosAttributes)); + var generatedServiceName = generateServiceName(zosAttributes); + attributesBuilder.put("service.name", generatedServiceName); + log.debug("service.name not provided in configuration, using generated default {}", generatedServiceName); } Optional.ofNullable(zosAttributes.get(ZOS_JOB_ID)).map(String::valueOf) diff --git a/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZUtilDummy.java b/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZUtilDummy.java index 2842327239..c858b603ae 100644 --- a/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZUtilDummy.java +++ b/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZUtilDummy.java @@ -203,19 +203,11 @@ public void smfRecord(int type, int subtype, byte[] rec) { @Override public String substituteSystemSymbols(String pattern) { - return switch (pattern) { - case "&SYSNAME." -> { - yield "SYSNAME"; - } - case "&SYSCLONE." -> { - yield "SYSCLONE"; - } - case "&SYSPLEX." -> { - yield "SYSPLEX"; - } - default -> { - yield null; - } + return switch (pattern) { + case "&SYSNAME." -> "SYSNAME"; + case "&SYSCLONE." -> "SYSCLONE"; + case "&SYSPLEX." -> "SYSPLEX"; + default -> null; }; } diff --git a/apiml-utility/src/test/java/org/zowe/apiml/product/zos/ZosSystemInformationTest.java b/apiml-utility/src/test/java/org/zowe/apiml/product/zos/ZosSystemInformationTest.java index 161c70f7d9..e0d48abb3d 100644 --- a/apiml-utility/src/test/java/org/zowe/apiml/product/zos/ZosSystemInformationTest.java +++ b/apiml-utility/src/test/java/org/zowe/apiml/product/zos/ZosSystemInformationTest.java @@ -10,16 +10,42 @@ package org.zowe.apiml.product.zos; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class ZosSystemInformationTest { + @Mock + private ZUtilDummy zUtil; + + private ZosSystemInformation zosSystemInformation; + + @BeforeEach + void setUp() { + zosSystemInformation = new ZosSystemInformation(); + ReflectionTestUtils.setField(zosSystemInformation, "zUtil", zUtil); + } + @Test - void testIsRunningOnZos() { + void givenZos_thenGetAttributes() { + when(zUtil.getCurrentJobId()).thenReturn("JOBID"); + when(zUtil.getCurrentJobname()).thenReturn("JOBNAME"); + when(zUtil.getCurrentUser()).thenReturn("USER"); + when(zUtil.getPid()).thenReturn(123456); + when(zUtil.substituteSystemSymbols("&SYSNAME.")).thenReturn("SYSNAME"); + when(zUtil.substituteSystemSymbols("&SYSCLONE.")).thenReturn("32"); + when(zUtil.substituteSystemSymbols("&SYSPLEX.")).thenReturn("PLEX"); + var data = zosSystemInformation.get(); + assertFalse(data.isEmpty()); } } diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java index 16e0b04e26..ac58c1a77b 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java @@ -47,8 +47,7 @@ class OpenTelemetryMetricsTest { "otel.sdk.disabled=false", "otel.metrics.exporter=none", "otel.traces.exporter=none", - "otel.logs.exporter=none", - "os.name=z/OS" + "otel.logs.exporter=none" } ) @TestInstance(Lifecycle.PER_CLASS) @@ -70,13 +69,12 @@ void init() { } @Test - void testJvmMetrics() { + void whenZos_thenLogCustomAttributes() { var metrics = metricReader.collectAllMetrics(); assertFalse(metrics.isEmpty(), "No data received"); metrics.forEach( metric -> { - System.out.println(); var attributes = metric.getResource().getAttributes(); assertEquals("zos", attributes.get(stringKey("os.type"))); assertEquals("STC1111", attributes.get(stringKey("process.zos.jobid"))); diff --git a/config/local/apiml-service.yml b/config/local/apiml-service.yml index 02b6fbe386..756d16b978 100644 --- a/config/local/apiml-service.yml +++ b/config/local/apiml-service.yml @@ -87,11 +87,10 @@ otel: logs: ## Do not use logging-otlp without additional configuration (TBD) because the open telemetry logs from console ## will be captured again ending in an infinite logging loop and resource starvation - # exporter: logging-otlp exporter: otlp include-trace-context: true exporter: otlp: endpoint: http://localhost:4318 sdk: - disabled: false + disabled: true From 05b870df4d35e304259d5b15bc0af43519865cb8 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Tue, 3 Feb 2026 10:52:36 +0100 Subject: [PATCH 23/26] use attributes, add exporter settings Signed-off-by: Pablo Carle --- .../ApimlZosOpenTelemetryResourceProvider.java | 6 +++--- .../zowe/apiml/product/opentelemetry/ZosAttributes.java | 7 ++++++- apiml-package/src/main/resources/bin/start.sh | 7 +++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java index 3b8a64cb23..40928f1f45 100644 --- a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java @@ -90,13 +90,13 @@ public Attributes calculateAttributes() { } Optional.ofNullable(zosAttributes.get(ZOS_JOB_ID)).map(String::valueOf) - .ifPresent(zosJobId -> attributesBuilder.put("process.zos.jobid", zosJobId)); + .ifPresent(zosJobId -> attributesBuilder.put(ZosAttributes.ZOS_JOBID, zosJobId)); Optional.ofNullable(zosAttributes.get(ZOS_JOB_NAME)).map(String::valueOf) - .ifPresent(zosJobName -> attributesBuilder.put("process.zos.jobname", zosJobName)); + .ifPresent(zosJobName -> attributesBuilder.put(ZosAttributes.ZOS_JOBNAME, zosJobName)); Optional.ofNullable(zosAttributes.get(ZOS_USER_ID)).map(String::valueOf) - .ifPresent(zosUserId -> attributesBuilder.put("process.zos.userid", zosUserId)); + .ifPresent(zosUserId -> attributesBuilder.put(ZosAttributes.ZOS_USERID, zosUserId)); return attributesBuilder.build(); } diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ZosAttributes.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ZosAttributes.java index 207cd6bc1c..2f51821cd6 100644 --- a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ZosAttributes.java +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ZosAttributes.java @@ -10,8 +10,13 @@ package org.zowe.apiml.product.opentelemetry; +/** + * Set of attributes set by API ML + */ public class ZosAttributes { - public static final String ZOS_JOBNAME = ""; + public static final String ZOS_JOBNAME = "process.zos.jobname"; + public static final String ZOS_USERID = "process.zos.userid"; + public static final String ZOS_JOBID = "process.zos.jobid"; } diff --git a/apiml-package/src/main/resources/bin/start.sh b/apiml-package/src/main/resources/bin/start.sh index a48644adc1..de7ef97b50 100755 --- a/apiml-package/src/main/resources/bin/start.sh +++ b/apiml-package/src/main/resources/bin/start.sh @@ -452,14 +452,17 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${APIML_CODE} ${JAVA_BIN_DIR}java \ -Dloader.path=${APIML_LOADER_PATH} \ -Dlogging.charset.console=${ZOWE_CONSOLE_LOG_CHARSET} \ -Dotel.exporter.otlp.endpoint="${ZWE_configs_telemetry_exporter_endpoint:-http://localhost:4318}" \ + -Dotel.logs.exporter="${ZWE_configs_telemetry_logs_exporter:-none}" \ + -Dotel.metrics.exporter="${ZWE_configs_telemetry_metrics_exporter:-none}" \ -Dotel.resource.attributes.deployment.environment.name="${ZWE_configs_telemetry_attributes_deployment_environment_name:-}" \ -Dotel.resource.attributes.deployment.environment="${ZWE_configs_telemetry_attributes_deployment_environment:-prod}" \ -Dotel.resource.attributes.service.name="${ZWE_configs_telemetry_service_name:-${ZWE_components_gateway_apimlId:-${ZWE_configs_apimlId}}}" \ -Dotel.resource.attributes.service.namespace="${ZWE_configs_telemetry_service_namespace:-}" \ + -Dotel.resource.attributes.zos.lpar.name="${ZWE_configs_telemetry_attributes_zos_lpar_name:-${ZWE_haInstance_sysname:-${ZWE_haInstance_id:-}}}" \ + -Dotel.resource.attributes.zos.lpar.override="${ZWE_configs_telemetry_attributes_zos_lpar_override:-false}" \ -Dotel.resource.attributes.zos.smf.id="${ZWE_configs_telemetry_attributes_zos_smf_id:-}" \ -Dotel.resource.attributes.zos.sysplex.name="${ZWE_configs_telemetry_attributes_zos_sysplex_name:-}" \ - -Dotel.resource.attributes.zos.lpar.override="${ZWE_configs_telemetry_attributes_zos_lpar_override:-false}" \ - -Dotel.resource.attributes.zos.lpar.name="${ZWE_configs_telemetry_attributes_zos_lpar_name:-${ZWE_haInstance_sysname:-${ZWE_haInstance_id:-}}}" \ + -Dotel.traces.exporter="${ZWE_configs_telemetry_traces_exporter:-none}" \ -Dotel.sdk.disabled=${DISABLE_OTEL} \ -Dserver.address=${ZWE_configs_zowe_network_server_listenAddresses_0:-${ZWE_zowe_network_server_listenAddresses_0:-"0.0.0.0"}} \ -Dserver.maxConnectionsPerRoute=${ZWE_components_gateway_server_maxConnectionsPerRoute:-${ZWE_configs_server_maxConnectionsPerRoute:-100}} \ From 80874a4767b4a3ab8dd9b130937b97218ccaac97 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Tue, 3 Feb 2026 13:27:12 +0100 Subject: [PATCH 24/26] fix for blank otel properties in start.sh Signed-off-by: Pablo Carle --- ...ApimlZosOpenTelemetryResourceProvider.java | 11 ------- apiml-package/src/main/resources/bin/start.sh | 30 ++++++++++++++----- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java index 40928f1f45..c1af3a9a49 100644 --- a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java @@ -41,12 +41,6 @@ public class ApimlZosOpenTelemetryResourceProvider extends ApimlOpenTelemetryRes @Value("${otel.resource.attributes.deployment.environment.name:#{null}}") private String environmentName; - @Value("${otel.resource.attributes.zos.lpar.name:#{null}}") - private String lparName; - - @Value("${otel.resource.attributes.zos.lpar.override:false}") - private boolean lparOverride; - @Value("${otel.resource.attributes.service.namespace:#{null}}") private String serviceNamespace; @@ -72,11 +66,6 @@ public Attributes calculateAttributes() { var zosAttributes = zosSystemInformation.get(); - if (lparOverride && StringUtils.isNotBlank(lparName)) { - log.debug("mainframe.lpar.name override for OpenTelemetry metrics: {}", lparName); - attributesBuilder.put("mainframe.lpar.name", lparName); - } - if (StringUtils.isBlank(serviceNamespace)) { var generatedDefaultNamespace = generateServiceNamespace(zosAttributes); attributesBuilder.put("service.namespace", generatedDefaultNamespace); diff --git a/apiml-package/src/main/resources/bin/start.sh b/apiml-package/src/main/resources/bin/start.sh index de7ef97b50..1fcb1a352c 100755 --- a/apiml-package/src/main/resources/bin/start.sh +++ b/apiml-package/src/main/resources/bin/start.sh @@ -333,12 +333,32 @@ if [ -n "${ZWE_java_home}" ]; then JAVA_BIN_DIR=${ZWE_java_home}/bin/ fi +# OpenTelemetry if [ "$ZWE_configs_telemetry_enabled" = "true" ]; then DISABLE_OTEL=false else DISABLE_OTEL=true fi +if [ -n "${ZWE_configs_telemetry_attributes_deployment_environment}" ]; then + OTEL_ATTRIBUTES="-Dotel.resource.attributes.deployment.environment=${ZWE_configs_telemetry_attributes_deployment_environment}" +fi +if [ -n "${ZWE_configs_telemetry_service_name}" ]; then + OTEL_ATTRIBUTES="$OTEL_ATTRIBUTES -Dotel.resource.attributes.service.name=${ZWE_configs_telemetry_service_name}" +fi +if [ -n "${ZWE_configs_telemetry_service_namespace}" ]; then + OTEL_ATTRIBUTES="$OTEL_ATTRIBUTES -Dotel.resource.attributes.service.namespace=${ZWE_configs_telemetry_service_namespace}" +fi +if [ -n "${ZWE_configs_telemetry_attributes_zos_sysplex_name}" ]; then + OTEL_ATTRIBUTES="$OTEL_ATTRIBUTES -Dotel.resource.attributes.zos.sysplex.name=${ZWE_configs_telemetry_attributes_zos_sysplex_name}" +fi +if [ -n "${ZWE_configs_telemetry_attributes_zos_smf_id}" ]; then + OTEL_ATTRIBUTES="$OTEL_ATTRIBUTES -Dotel.resource.attributes.zos.smf.id=${ZWE_configs_telemetry_attributes_zos_smf_id}" +fi +if [ -n "${ZWE_configs_telemetry_attributes_mainframe_lpar_name}" ]; then + OTEL_ATTRIBUTES="$OTEL_ATTRIBUTES -Dotel.resource.attributes.mainframe.lpar.name=${ZWE_configs_telemetry_attributes_mainframe_lpar_name}" +fi + APIML_CODE=AG SHARED_CLASSES_OPTS="-Xshareclasses:name=apiml_shared_classes,nonfatal" @@ -346,12 +366,14 @@ _BPXK_AUTOCVT=OFF _BPX_JOBNAME=${ZWE_zowe_job_prefix}${APIML_CODE} ${JAVA_BIN_DIR}java \ -Xms${ZWE_configs_heap_init:-${ZWE_components_gateway_heap_init:-32}}m -Xmx${ZWE_configs_heap_max:-${ZWE_components_gateway_heap_max:-512}}m \ -XX:+ExitOnOutOfMemoryError \ + -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=5009,suspend=y \ ${QUICK_START} \ ${SHARED_CLASSES_OPTS} \ ${ADD_OPENS} \ ${LOGBACK} \ ${JVM_SECURITY_PROPERTIES} \ ${EXTERNAL_URL} \ + ${OTEL_ATTRIBUTES} \ -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:-}} \ @@ -454,14 +476,6 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${APIML_CODE} ${JAVA_BIN_DIR}java \ -Dotel.exporter.otlp.endpoint="${ZWE_configs_telemetry_exporter_endpoint:-http://localhost:4318}" \ -Dotel.logs.exporter="${ZWE_configs_telemetry_logs_exporter:-none}" \ -Dotel.metrics.exporter="${ZWE_configs_telemetry_metrics_exporter:-none}" \ - -Dotel.resource.attributes.deployment.environment.name="${ZWE_configs_telemetry_attributes_deployment_environment_name:-}" \ - -Dotel.resource.attributes.deployment.environment="${ZWE_configs_telemetry_attributes_deployment_environment:-prod}" \ - -Dotel.resource.attributes.service.name="${ZWE_configs_telemetry_service_name:-${ZWE_components_gateway_apimlId:-${ZWE_configs_apimlId}}}" \ - -Dotel.resource.attributes.service.namespace="${ZWE_configs_telemetry_service_namespace:-}" \ - -Dotel.resource.attributes.zos.lpar.name="${ZWE_configs_telemetry_attributes_zos_lpar_name:-${ZWE_haInstance_sysname:-${ZWE_haInstance_id:-}}}" \ - -Dotel.resource.attributes.zos.lpar.override="${ZWE_configs_telemetry_attributes_zos_lpar_override:-false}" \ - -Dotel.resource.attributes.zos.smf.id="${ZWE_configs_telemetry_attributes_zos_smf_id:-}" \ - -Dotel.resource.attributes.zos.sysplex.name="${ZWE_configs_telemetry_attributes_zos_sysplex_name:-}" \ -Dotel.traces.exporter="${ZWE_configs_telemetry_traces_exporter:-none}" \ -Dotel.sdk.disabled=${DISABLE_OTEL} \ -Dserver.address=${ZWE_configs_zowe_network_server_listenAddresses_0:-${ZWE_zowe_network_server_listenAddresses_0:-"0.0.0.0"}} \ From 5c70cf56335f31769f3e39c68c3ef6221cd41f97 Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Tue, 3 Feb 2026 15:46:51 +0100 Subject: [PATCH 25/26] add zos attributes Signed-off-by: Pablo Carle --- ...ApimlZosOpenTelemetryResourceProvider.java | 50 +++++++++++++++++-- apiml-package/src/main/resources/bin/start.sh | 7 +-- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java index c1af3a9a49..4723ac931b 100644 --- a/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java @@ -27,6 +27,8 @@ import static org.zowe.apiml.product.zos.ZosSystemInformation.ZOS_JOB_ID; import static org.zowe.apiml.product.zos.ZosSystemInformation.ZOS_JOB_NAME; +import static org.zowe.apiml.product.zos.ZosSystemInformation.ZOS_SYSCLONE; +import static org.zowe.apiml.product.zos.ZosSystemInformation.ZOS_SYSNAME; import static org.zowe.apiml.product.zos.ZosSystemInformation.ZOS_SYSPLEX; import static org.zowe.apiml.product.zos.ZosSystemInformation.ZOS_USER_ID; @@ -53,6 +55,15 @@ public class ApimlZosOpenTelemetryResourceProvider extends ApimlOpenTelemetryRes @Value("${apiml.service.port:10010}") private int port; + @Value("${otel.resource.attributes.zos.sysplex.name:#{null}}") + private String sysplexName; + + @Value("${otel.resource.attributes.mainframe.lpar.name:#{null}}") + private String lparName; + + @Value("${otel.resource.attributes.zos.smf.id:#{null}}") + private String smfId; + @PostConstruct void afterPropertiesSet() { log.debug("Using ZOS OpenTelemetry resource provider"); @@ -78,13 +89,46 @@ public Attributes calculateAttributes() { log.debug("service.name not provided in configuration, using generated default {}", generatedServiceName); } - Optional.ofNullable(zosAttributes.get(ZOS_JOB_ID)).map(String::valueOf) + if (StringUtils.isBlank(sysplexName)) { + var sysplexName = zosAttributes.get(ZOS_SYSPLEX); + if (sysplexName != null && StringUtils.isNotBlank(sysplexName.toString())) { + log.debug("zos.sysplex.name not provided in configuration, using system-obtained {}", sysplexName); + } else { + log.debug("zos.sysplex.name not provided in configuration. Could not determine name from system"); + } + } + + if (StringUtils.isBlank(lparName)) { + var lparName = zosAttributes.get(ZOS_SYSNAME); + if (lparName != null && StringUtils.isNotBlank(lparName.toString())) { + log.debug("mainframe.lpar.name not provided in configuration, using system-obtained {}", lparName); + } else { + log.debug("mainframe.lpar.name not provided in configuration. Could not determine name from system"); + } + } + + if (StringUtils.isBlank(smfId)) { + var smfId = zosAttributes.get(ZOS_SYSCLONE); + if (smfId != null && StringUtils.isNotBlank(smfId.toString())) { + log.debug("zos.smf.id not provided in configuration, using system-obtained {}", smfId); + } else { + log.debug("zos.smf.id not provided in configuration. Could not determine ID from system"); + } + } + + Optional.ofNullable(zosAttributes.get(ZOS_JOB_ID)) + .map(String::valueOf) + .filter(StringUtils::isNotBlank) .ifPresent(zosJobId -> attributesBuilder.put(ZosAttributes.ZOS_JOBID, zosJobId)); - Optional.ofNullable(zosAttributes.get(ZOS_JOB_NAME)).map(String::valueOf) + Optional.ofNullable(zosAttributes.get(ZOS_JOB_NAME)) + .map(String::valueOf) + .filter(StringUtils::isNotBlank) .ifPresent(zosJobName -> attributesBuilder.put(ZosAttributes.ZOS_JOBNAME, zosJobName)); - Optional.ofNullable(zosAttributes.get(ZOS_USER_ID)).map(String::valueOf) + Optional.ofNullable(zosAttributes.get(ZOS_USER_ID)) + .map(String::valueOf) + .filter(StringUtils::isNotBlank) .ifPresent(zosUserId -> attributesBuilder.put(ZosAttributes.ZOS_USERID, zosUserId)); return attributesBuilder.build(); diff --git a/apiml-package/src/main/resources/bin/start.sh b/apiml-package/src/main/resources/bin/start.sh index 1fcb1a352c..f43ad26e42 100755 --- a/apiml-package/src/main/resources/bin/start.sh +++ b/apiml-package/src/main/resources/bin/start.sh @@ -333,15 +333,15 @@ if [ -n "${ZWE_java_home}" ]; then JAVA_BIN_DIR=${ZWE_java_home}/bin/ fi -# OpenTelemetry +# Start OpenTelemetry if [ "$ZWE_configs_telemetry_enabled" = "true" ]; then DISABLE_OTEL=false else DISABLE_OTEL=true fi -if [ -n "${ZWE_configs_telemetry_attributes_deployment_environment}" ]; then - OTEL_ATTRIBUTES="-Dotel.resource.attributes.deployment.environment=${ZWE_configs_telemetry_attributes_deployment_environment}" +if [ -n "${ZWE_configs_telemetry_attributes_deployment_environment_name}" ]; then + OTEL_ATTRIBUTES="-Dotel.resource.attributes.deployment.environment.name=${ZWE_configs_telemetry_attributes_deployment_environment_name}" fi if [ -n "${ZWE_configs_telemetry_service_name}" ]; then OTEL_ATTRIBUTES="$OTEL_ATTRIBUTES -Dotel.resource.attributes.service.name=${ZWE_configs_telemetry_service_name}" @@ -358,6 +358,7 @@ fi if [ -n "${ZWE_configs_telemetry_attributes_mainframe_lpar_name}" ]; then OTEL_ATTRIBUTES="$OTEL_ATTRIBUTES -Dotel.resource.attributes.mainframe.lpar.name=${ZWE_configs_telemetry_attributes_mainframe_lpar_name}" fi +# End OpenTelemetry APIML_CODE=AG From 1991a2602eee939cc067d1f6e6dd8f8a811891af Mon Sep 17 00:00:00 2001 From: Pablo Carle Date: Wed, 4 Feb 2026 10:09:00 +0100 Subject: [PATCH 26/26] add comment Signed-off-by: Pablo Carle --- .../org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java index ac58c1a77b..baed7f8607 100644 --- a/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java @@ -119,6 +119,7 @@ public TestApimlZosOpenTelemetryResourceProvider(ZosSystemInformation zosSystemI @Override public Attributes calculateAttributes() { var attributes = super.calculateAttributes(); + // Restore os.name to test runner's platform to avoid issues with classes that are not available on z/OS (for instance mockito fails) System.setProperty("os.name", defaultPlatform); return attributes; }