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..9cff2595b6 --- /dev/null +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProvider.java @@ -0,0 +1,29 @@ +/* + * 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 org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +import javax.annotation.Nonnull; + +@ConditionalOnExpression("#{!T(org.zowe.apiml.product.zos.ZosSystemInformation).isRunningOnZos()}") +@Component +public class ApimlNonZosOpenTelemetryResourceProvider extends ApimlOpenTelemetryResourceProvider { + + @Override + @Nonnull + 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 new file mode 100644 index 0000000000..248a2c3fe7 --- /dev/null +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlOpenTelemetryResourceProvider.java @@ -0,0 +1,32 @@ +/* + * 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; + +public abstract class ApimlOpenTelemetryResourceProvider implements ResourceProvider { + + public abstract @Nonnull Attributes calculateAttributes(); + + @Override + public Resource createResource(@Nonnull ConfigProperties config) { + var attributesBuilder = Attributes.builder(); + + 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 new file mode 100644 index 0000000000..4723ac931b --- /dev/null +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProvider.java @@ -0,0 +1,147 @@ +/* + * 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 jakarta.annotation.PostConstruct; +import lombok.RequiredArgsConstructor; +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.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; + +@Component +@RequiredArgsConstructor +@ConditionalOnMissingBean(ApimlNonZosOpenTelemetryResourceProvider.class) +@Slf4j +public class ApimlZosOpenTelemetryResourceProvider extends ApimlOpenTelemetryResourceProvider { + + private final ZosSystemInformation zosSystemInformation; + + @Value("${otel.resource.attributes.deployment.environment.name:#{null}}") + private String environmentName; + + @Value("${otel.resource.attributes.service.namespace:#{null}}") + private String serviceNamespace; + + @Value("${otel.resource.attributes.service.name:#{null}}") + private String serviceName; + + @Value("${apiml.service.apimlId:#{null}}") + private String apimlId; + + @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"); + } + + @SuppressWarnings("null") + @Override + @Nonnull + public Attributes calculateAttributes() { + var attributesBuilder = Attributes.builder(); + + var zosAttributes = zosSystemInformation.get(); + + if (StringUtils.isBlank(serviceNamespace)) { + 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)) { + var generatedServiceName = generateServiceName(zosAttributes); + attributesBuilder.put("service.name", generatedServiceName); + log.debug("service.name not provided in configuration, using generated default {}", generatedServiceName); + } + + 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) + .filter(StringUtils::isNotBlank) + .ifPresent(zosJobName -> attributesBuilder.put(ZosAttributes.ZOS_JOBNAME, zosJobName)); + + Optional.ofNullable(zosAttributes.get(ZOS_USER_ID)) + .map(String::valueOf) + .filter(StringUtils::isNotBlank) + .ifPresent(zosUserId -> attributesBuilder.put(ZosAttributes.ZOS_USERID, zosUserId)); + + return attributesBuilder.build(); + } + + private String generateServiceName(Map zosAttributes) { + var systemName = StringUtils.isBlank(apimlId) ? zosAttributes.get(ZOS_SYSPLEX) : apimlId; + + return systemName + ":" + port; + } + + private String generateServiceNamespace(Map zosAttributes) { + return "apiml:" + generateServiceName(zosAttributes); + } + +} 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..2f51821cd6 --- /dev/null +++ b/apiml-common/src/main/java/org/zowe/apiml/product/opentelemetry/ZosAttributes.java @@ -0,0 +1,22 @@ +/* + * 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; + +/** + * Set of attributes set by API ML + */ +public class ZosAttributes { + + 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-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..3b90cb3860 --- /dev/null +++ b/apiml-common/src/test/java/org/zowe/apiml/product/opentelemetry/ApimlNonZosOpenTelemetryResourceProviderTest.java @@ -0,0 +1,44 @@ +/* + * 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.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 { + + private ApimlNonZosOpenTelemetryResourceProvider resourceProvider; + + @BeforeEach + void setUp() { + this.resourceProvider = new ApimlNonZosOpenTelemetryResourceProvider(); + } + + @Test + void testCalculateAttributes() { + var result = resourceProvider.calculateAttributes(); + assertTrue(result.isEmpty()); + } + + @Test + void testCreateResource() { + var result = resourceProvider.createResource(mock(ConfigProperties.class)); + assertTrue(result.getAttributes().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..f6b72f7da9 --- /dev/null +++ b/apiml-common/src/test/java/org/zowe/apiml/product/opentelemetry/ApimlZosOpenTelemetryResourceProviderTest.java @@ -0,0 +1,72 @@ +/* + * 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.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 { + + @Mock + private ZosSystemInformation zosSystemInformation; + + private ApimlZosOpenTelemetryResourceProvider resourceProvider; + + @BeforeEach + void setUp() { + resourceProvider = new ApimlZosOpenTelemetryResourceProvider(zosSystemInformation); + ReflectionTestUtils.setField(resourceProvider, "port", 10010); + } + + @Nested + 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-package/src/main/resources/bin/start.sh b/apiml-package/src/main/resources/bin/start.sh index 3fcf95f04e..a46340f020 100755 --- a/apiml-package/src/main/resources/bin/start.sh +++ b/apiml-package/src/main/resources/bin/start.sh @@ -336,6 +336,33 @@ if [ -n "${ZWE_java_home}" ]; then JAVA_BIN_DIR=${ZWE_java_home}/bin/ fi +# 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_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}" +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 +# End OpenTelemetry + APIML_CODE=AG SHARED_CLASSES_OPTS="-Xshareclasses:name=apiml_shared_classes,nonfatal" @@ -343,6 +370,7 @@ _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} \ ${JAVA21_CONSOLE_ENCODING} \ @@ -350,6 +378,7 @@ _BPX_JOBNAME=${ZWE_zowe_job_prefix}${APIML_CODE} ${JAVA_BIN_DIR}java \ ${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:-}} \ @@ -376,8 +405,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}} \ @@ -407,6 +436,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"}} \ @@ -422,8 +453,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.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}} \ @@ -447,6 +478,11 @@ _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:-http://localhost:4318}" \ + -Dotel.logs.exporter="${ZWE_configs_telemetry_logs_exporter:-none}" \ + -Dotel.metrics.exporter="${ZWE_configs_telemetry_metrics_exporter:-none}" \ + -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}} \ -Dserver.maxTotalConnections=${ZWE_components_gateway_server_maxTotalConnections:-${ZWE_configs_server_maxTotalConnections:-1000}} \ @@ -467,8 +503,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/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..c858b603ae --- /dev/null +++ b/apiml-utility/src/main/java/org/zowe/apiml/product/zos/ZUtilDummy.java @@ -0,0 +1,224 @@ +/* + * 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 "STC1111"; + } + + @Override + public String getCurrentJobname() { + return "ZWE1AG"; + } + + @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 "ZWEUSER"; + } + + @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 1234567; + } + + @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 switch (pattern) { + case "&SYSNAME." -> "SYSNAME"; + case "&SYSCLONE." -> "SYSCLONE"; + case "&SYSPLEX." -> "SYSPLEX"; + default -> null; + }; + } + + @Override + public String substituteSystemSymbols(String pattern, boolean warn) { + return null; + } + + @Override + public void touch() { + // dummy implementation - do nothing + } + +} 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..9a7b002e7e --- /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 static 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..e0d48abb3d --- /dev/null +++ b/apiml-utility/src/test/java/org/zowe/apiml/product/zos/ZosSystemInformationTest.java @@ -0,0 +1,51 @@ +/* + * 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.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 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/build.gradle b/apiml/build.gradle index 42237884e3..03b4d707e4 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"))) @@ -84,6 +83,8 @@ dependencies { testImplementation libs.reactor.test 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/AvailabilityTest.java b/apiml/src/test/java/org/zowe/apiml/acceptance/AvailabilityTest.java index 610f8a8e5f..747de78a6c 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/OpenTelemetryMetricsTests.java b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java new file mode 100644 index 0000000000..baed7f8607 --- /dev/null +++ b/apiml/src/test/java/org/zowe/apiml/acceptance/OpenTelemetryMetricsTests.java @@ -0,0 +1,131 @@ +/* + * 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.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; +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.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; + +class OpenTelemetryMetricsTest { + + @Nested + @AcceptanceTest + @ActiveProfiles("OpenTelemetryTest") + @TestPropertySource( + properties = { + "otel.sdk.disabled=false", + "otel.metrics.exporter=none", + "otel.traces.exporter=none", + "otel.logs.exporter=none" + } + ) + @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 whenZos_thenLogCustomAttributes() { + var metrics = metricReader.collectAllMetrics(); + assertFalse(metrics.isEmpty(), "No data received"); + + metrics.forEach( + metric -> { + var attributes = metric.getResource().getAttributes(); + assertEquals("zos", attributes.get(stringKey("os.type"))); + 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"))); + } + ); + + } + + @Profile("OpenTelemetryTest") + @TestConfiguration + static class TestConfig { + + @Bean + InMemoryMetricReader inMemoryMetricReader() { + return InMemoryMetricReader.create(); + } + + @Bean + AutoConfigurationCustomizerProvider otelCustomizer(@Nonnull InMemoryMetricReader reader) { + return p -> p.addMeterProviderCustomizer((meterProviderBuilder, configProperties) -> + 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(); + // 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; + } + + } + + } + +} 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..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; @@ -45,11 +48,10 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class StartupMessageAcceptanceTest { +class StartupMessageAcceptanceTest { - @AcceptanceTest - @ExtendWith({MockitoExtension.class, OutputCaptureExtension.class}) abstract static class BaseStartupTest extends AcceptanceTestWithMockServices { + @Mock private InstanceInfo instanceInfo; @@ -77,14 +79,25 @@ void verifyStartupMessage(CapturedOutput output) { } @Nested + @AcceptanceTest + @TestInstance(Lifecycle.PER_CLASS) + @DirtiesContext + @ExtendWith({MockitoExtension.class, OutputCaptureExtension.class}) + @ActiveProfiles({"default"}) class GivenDefaultProfile extends BaseStartupTest { + @Test void whenFullyStartedUp_thenEmitMessage(CapturedOutput output) { verifyStartupMessage(output); } + } @Nested + @AcceptanceTest + @TestInstance(Lifecycle.PER_CLASS) + @DirtiesContext + @ExtendWith({MockitoExtension.class, OutputCaptureExtension.class}) @ActiveProfiles({"attlsClient", "attlsServer"}) class GivenAttlsProfile extends BaseStartupTest { @@ -131,5 +144,7 @@ void whenFullyStartedUp_thenEmitMessage(CapturedOutput output) throws IoctlCallE verifyStartupMessage(output); } + } + } diff --git a/apiml/src/test/resources/application.yml b/apiml/src/test/resources/application.yml index c4a88e2f63..675ca0aa2a 100644 --- a/apiml/src/test/resources/application.yml +++ b/apiml/src/test/resources/application.yml @@ -34,6 +34,21 @@ management: base-path: /application exposure: include: "*" + +otel: + resource: + attributes: + deployment.environment: dev + traces: + exporter: none + metrics: + exporter: none + logs: + exporter: logging + include-trace-context: true + sdk: + disabled: true + apiml: catalog: serviceId: apicatalog diff --git a/config/local/apiml-service.yml b/config/local/apiml-service.yml index 971f500f7c..756d16b978 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: 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: otlp + include-trace-context: true + exporter: + otlp: + endpoint: http://localhost:4318 + sdk: + disabled: true 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: diff --git a/gradle/versions.gradle b/gradle/versions.gradle index a0511a4a2a..4c59b7ca8d 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.24.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') + 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') 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(); 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 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",