This is an automated email from the ASF dual-hosted git repository.

sumitagrawl pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git


The following commit(s) were added to refs/heads/master by this push:
     new 3a450fa0da3 HDDS-13803. Client aware tracing (#10477)
3a450fa0da3 is described below

commit 3a450fa0da36acafe7facd5156b4131be71d1cfb
Author: sravani <[email protected]>
AuthorDate: Fri Jul 31 15:37:40 2026 +0530

    HDDS-13803. Client aware tracing (#10477)
---
 .../apache/hadoop/hdds/tracing/TracingConfig.java  |  16 ++
 .../apache/hadoop/hdds/tracing/TracingUtil.java    | 191 ++++++++++++++++----
 .../hadoop/hdds/tracing/TestTracingInitModes.java  | 193 +++++++++++++++++++++
 .../apache/hadoop/ozone/client/rpc/RpcClient.java  |   3 +-
 4 files changed, 371 insertions(+), 32 deletions(-)

diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/tracing/TracingConfig.java
 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/tracing/TracingConfig.java
index ddbc6754379..5fe6dfcdb86 100644
--- 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/tracing/TracingConfig.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/tracing/TracingConfig.java
@@ -49,6 +49,18 @@ public class TracingConfig extends ReconfigurableConfig {
   )
   private boolean tracingEnabled;
 
+  @Config(
+      key = "ozone.tracing.client.application-aware",
+      defaultValue = "true",
+      type = ConfigType.BOOLEAN,
+      reconfigurable = true,
+      tags = { ConfigTag.OZONE, ConfigTag.HDDS },
+      description = "Only effective when ozone.tracing.enabled=false. When 
true, Ozone will "
+          + "continue an application-supplied trace (via GlobalOpenTelemetry 
or a wire-propagated "
+          + "context) as child spans, but will NOT start a new root trace on 
its own."
+  )
+  private boolean applicationAware = true;
+
   @Config(
       key = "ozone.tracing.endpoint",
       defaultValue = "",
@@ -83,6 +95,10 @@ public boolean isTracingEnabled() {
     return tracingEnabled;
   }
 
+  public boolean isApplicationAware() {
+    return applicationAware;
+  }
+
   @PostConstruct
   public void validate() {
     if (tracingEndpoint.isEmpty()) {
diff --git 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/tracing/TracingUtil.java
 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/tracing/TracingUtil.java
index d25817a7a7d..c5624c3c214 100644
--- 
a/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/tracing/TracingUtil.java
+++ 
b/hadoop-hdds/common/src/main/java/org/apache/hadoop/hdds/tracing/TracingUtil.java
@@ -17,6 +17,7 @@
 
 package org.apache.hadoop.hdds.tracing;
 
+import io.opentelemetry.api.GlobalOpenTelemetry;
 import io.opentelemetry.api.OpenTelemetry;
 import io.opentelemetry.api.common.AttributeKey;
 import io.opentelemetry.api.common.Attributes;
@@ -26,12 +27,14 @@
 import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator;
 import io.opentelemetry.context.Context;
 import io.opentelemetry.context.Scope;
+import io.opentelemetry.context.propagation.ContextPropagators;
 import io.opentelemetry.context.propagation.TextMapGetter;
 import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
 import io.opentelemetry.sdk.OpenTelemetrySdk;
 import io.opentelemetry.sdk.resources.Resource;
 import io.opentelemetry.sdk.trace.SdkTracerProvider;
 import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
+import io.opentelemetry.sdk.trace.export.SpanExporter;
 import io.opentelemetry.sdk.trace.samplers.Sampler;
 import java.lang.reflect.Proxy;
 import java.util.Collections;
@@ -53,9 +56,12 @@ public final class TracingUtil {
   private static final String NULL_SPAN_AS_STRING = "";
 
   private static volatile boolean isInit = false;
+  private static volatile boolean tracingEnabled;
+  private static volatile boolean applicationAware;
   private static Tracer tracer = OpenTelemetry.noop().getTracer("noop");
   private static volatile SdkTracerProvider sdkTracerProvider;
   private static BatchSpanProcessor batchSpanProcessor;
+  public static final String GLOBAL_TRACER_NAME = "ozone";
 
   private TracingUtil() {
   }
@@ -65,14 +71,20 @@ private TracingUtil() {
    */
   public static synchronized void initTracing(
       String serviceName, TracingConfig tracingConfig) {
-    if (!tracingConfig.isTracingEnabled() || isInit) {
+    initTracing(serviceName, tracingConfig, false);
+  }
+
+  private static synchronized void initTracing(
+      String serviceName, TracingConfig tracingConfig, boolean isReconfig) {
+    if (isInit) {
       return;
     }
 
     try {
-      initialize(serviceName, tracingConfig);
+      initialize(serviceName, tracingConfig, isReconfig);
       isInit = true;
-      LOG.info("Initialized tracing service: {}", serviceName);
+      LOG.info("Initialized tracing service: {} (enabled={}, 
applicationAware={})",
+          serviceName, tracingEnabled, applicationAware);
     } catch (Exception e) {
       LOG.error("Failed to initialize tracing", e);
     }
@@ -94,7 +106,7 @@ public static synchronized void initTracing(
   public static synchronized void reconfigureTracing(
       String serviceName, TracingConfig tracingConfig) {
     shutdownTracing();
-    initTracing(serviceName, tracingConfig);
+    initTracing(serviceName, tracingConfig, true);
   }
 
   /**
@@ -129,23 +141,107 @@ public static <R, E extends Exception> R execute(
     }
   }
 
-  private static void shutdownTracing() {
-    if (sdkTracerProvider == null) {
-      return;
-    }
+  static void shutdownTracing() {
     try {
-      sdkTracerProvider.shutdown().join(10L, TimeUnit.SECONDS);
+      if (sdkTracerProvider != null) {
+        sdkTracerProvider.shutdown().join(10L, TimeUnit.SECONDS);
+      }
     } catch (Exception e) {
       LOG.warn("Tracing shutdown failed", e);
     } finally {
       sdkTracerProvider = null;
       batchSpanProcessor = null;
       tracer = OpenTelemetry.noop().getTracer("noop");
+      tracingEnabled = false;
+      applicationAware = false;
       isInit = false;
     }
   }
 
-  private static void initialize(String serviceName, TracingConfig 
tracingConfig) {
+  private static void initialize(String serviceName, TracingConfig cfg, 
boolean isReconfig) {
+    tracingEnabled = cfg.isTracingEnabled();
+    applicationAware = cfg.isApplicationAware();
+
+    if (!tracingEnabled && !applicationAware) {
+      tracer = OpenTelemetry.noop().getTracer(GLOBAL_TRACER_NAME);
+      return;
+    }
+
+    // Server reconfiguration reprioritizes Ozone's SDK over any adopted 
global,
+    // and re-registers the global name and tracer.
+    if (isReconfig && tracingEnabled) {
+      initOzoneSdk(serviceName, cfg, true);
+      return;
+    }
+
+    // Global first: adopt an application-registered GlobalOpenTelemetry when 
present.
+    if (GlobalOpenTelemetry.isSet() && 
isRealGlobal(GlobalOpenTelemetry.get())) {
+      tracer = GlobalOpenTelemetry.get().getTracer(GLOBAL_TRACER_NAME);
+      LOG.info("Tracing: adopted application GlobalOpenTelemetry");
+      return;
+    }
+
+    // No app-supplied global — build Ozone's SDK and always register it as 
the JVM global,
+    // so any co-resident library observes the same tracer whenever tracing is 
valid.
+    initOzoneSdk(serviceName, cfg, true);
+  }
+
+  private static void initOzoneSdk(String serviceName, TracingConfig cfg, 
boolean registerGlobal) {
+    SdkTracerProvider tracerProvider = buildSdkTracerProvider(serviceName, 
cfg);
+    try {
+      OpenTelemetrySdk sdk;
+      if (registerGlobal) {
+        sdk = OpenTelemetrySdk.builder()
+            .setTracerProvider(tracerProvider)
+            
.setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance()))
+            .build();
+        if (!GlobalOpenTelemetry.isSet() || 
!isRealGlobal(GlobalOpenTelemetry.get())) {
+          GlobalOpenTelemetry.set(sdk);
+        }
+        tracer = GlobalOpenTelemetry.get().getTracer(GLOBAL_TRACER_NAME);
+      } else {
+        sdk = OpenTelemetrySdk.builder()
+            .setTracerProvider(tracerProvider)
+            .build();
+        tracer = sdk.getTracer(GLOBAL_TRACER_NAME);
+      }
+      sdkTracerProvider = tracerProvider;
+    } catch (RuntimeException e) {
+      tracerProvider.shutdown();
+      batchSpanProcessor = null;
+      throw e;
+    }
+  }
+
+  /**
+   * Distinguish an application-registered GlobalOpenTelemetry from the OTel 
built-in noop.
+   * OpenTelemetry.noop() returns a singleton, so identity comparison is 
sufficient.
+   */
+  private static boolean isRealGlobal(OpenTelemetry global) {
+    return global != null && global != OpenTelemetry.noop();
+  }
+
+  /**
+   * Whether to wrap the delegate in a JDK tracing proxy.
+   * Fully enabled: always wrap. App-aware: wrap only when parent span is valid
+   */
+  private static boolean shouldCreateTracingProxy(ConfigurationSource conf) {
+    TracingConfig tc = conf.getObject(TracingConfig.class);
+    if (tc.isTracingEnabled()) {
+      return true;
+    }
+    if (!tc.isApplicationAware() || !hasUsableTracer()) {
+      return false;
+    }
+    return Span.current().getSpanContext().isValid();
+  }
+
+  /**
+   * Build the SdkTracerProvider using the configured OTLP endpoint and 
sampler.
+   * Extracted so both enabled and application-aware modes share 
exporter/sampler setup.
+   */
+  private static SdkTracerProvider buildSdkTracerProvider(
+      String serviceName, TracingConfig tracingConfig) {
     //Fetch and log the right tracing parameters based on config, environment 
variable and default value priority.
     String otelEndPoint = tracingConfig.getTracingEndpoint();
     double samplerRatio = tracingConfig.getTraceSamplerRatio();
@@ -156,7 +252,7 @@ private static void initialize(String serviceName, 
TracingConfig tracingConfig)
     Map<String, LoopSampler> spanMap = 
parseSpanSamplingConfig(spanSamplingConfig);
 
     Resource resource = 
Resource.create(Attributes.of(AttributeKey.stringKey("service.name"), 
serviceName));
-    OtlpGrpcSpanExporter spanExporter = OtlpGrpcSpanExporter.builder()
+    SpanExporter spanExporter = OtlpGrpcSpanExporter.builder()
         .setEndpoint(otelEndPoint)
         .build();
 
@@ -172,36 +268,30 @@ private static void initialize(String serviceName, 
TracingConfig tracingConfig)
       sampler = new SpanSampler(rootSampler, spanMap);
     }
 
-    SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
+    return SdkTracerProvider.builder()
         .addSpanProcessor(batchSpanProcessor)
         .setResource(resource)
         .setSampler(sampler)
         .build();
+  }
 
-    try {
-      OpenTelemetry openTelemetry = OpenTelemetrySdk.builder()
-          .setTracerProvider(tracerProvider)
-          .build();
-      tracer = openTelemetry.getTracer(serviceName);
-      sdkTracerProvider = tracerProvider;
-    } catch (RuntimeException e) {
-      tracerProvider.shutdown();
-      batchSpanProcessor = null;
-      throw e;
-    }
+  private static boolean canStartSpanWithoutParent() {
+    return tracingEnabled;
   }
 
   /**
    * Export the active tracing span as a string.
+   * When tracing is disabled, not initialized, or no valid span is in scope,
+   * {@link Span#current()} returns an invalid span; there is nothing to 
encode and this
+   * method returns an empty string. Callers must accept that as "no context 
to propagate".
    *
-   * @return encoded tracing context.
+   * @return encoded W3C trace context, or empty string if there is no valid 
active span.
    */
   public static String exportCurrentSpan() {
     Span currentSpan = Span.current();
     if (!currentSpan.getSpanContext().isValid()) {
       return NULL_SPAN_AS_STRING;
     }
-
     StringBuilder builder = new StringBuilder();
     W3CTraceContextPropagator propagator = 
W3CTraceContextPropagator.getInstance();
     propagator.inject(Context.current(), builder,
@@ -211,13 +301,23 @@ public static String exportCurrentSpan() {
 
   /**
    * Create a new scope and use the imported span as the parent.
+   * Short-circuits to an invalid span when there is no usable tracer:
+   *   - tracing was never initialized (tracer is still the noop), or
+   *   - application-aware mode is on but no app-supplied SDK was adopted 
(sdkTracerProvider == null
+   *     and the current tracer is the noop).
    *
    * @param name          name of the newly created scope
    * @param encodedParent Encoded parent span (could be null or empty)
    * @return Tracing scope.
    */
   public static Span importAndCreateSpan(String name, String encodedParent) {
+    if (!hasUsableTracer()) {
+      return Span.getInvalid();
+    }
     if (encodedParent == null || encodedParent.isEmpty()) {
+      if (!canStartSpanWithoutParent()) {
+        return Span.getInvalid();
+      }
       return tracer.spanBuilder(name).setNoParent().startSpan();
     }
 
@@ -228,6 +328,17 @@ public static Span importAndCreateSpan(String name, String 
encodedParent) {
         .startSpan();
   }
 
+  /**
+   * True when the current tracer can actually build spans — an Ozone-owned 
SDK is configured,
+   * or an adopted GlobalOpenTelemetry provides a non-noop tracer.
+   */
+  private static boolean hasUsableTracer() {
+    if (sdkTracerProvider != null) {
+      return true;
+    }
+    return GlobalOpenTelemetry.isSet() && 
isRealGlobal(GlobalOpenTelemetry.get());
+  }
+
   /**
    * Creates a proxy of the implementation and trace all the method calls.
    *
@@ -241,7 +352,7 @@ public static Span importAndCreateSpan(String name, String 
encodedParent) {
    */
   public static <T> T createProxy(
       T delegate, Class<T> itf, ConfigurationSource conf) {
-    if (!isTracingEnabled(conf)) {
+    if (!shouldCreateTracingProxy(conf)) {
       return delegate;
     }
     Class<?> aClass = delegate.getClass();
@@ -254,6 +365,20 @@ public static boolean isTracingEnabled(ConfigurationSource 
conf) {
     return conf.getObject(TracingConfig.class).isTracingEnabled();
   }
 
+  /**
+   * Returns true when tracing may actually produce spans:
+   *   - fully enabled (ozone.tracing.enabled=true), or
+   *   - application-aware AND an SDK is configured (either Ozone-owned or an 
adopted global);
+   *     without an SDK, application-aware is a passthrough that would emit 
noop spans anyway.
+   */
+  public static boolean isTracingActive(ConfigurationSource conf) {
+    TracingConfig tc = conf.getObject(TracingConfig.class);
+    if (tc.isTracingEnabled()) {
+      return true;
+    }
+    return tc.isApplicationAware() && hasUsableTracer();
+  }
+
   /**
    * Function to parse span sampling config. The input is in the form 
<span_name>:<sample_rate>.
    * The sample rate must be a number between 0 and 1. Any value other than 
that will LOG an error.
@@ -419,7 +544,8 @@ private void parse(String carrier) {
 
   /**
    * Creates a new span, using the current context as a parent if valid;
-   * otherwise, creates a root span.
+   * Otherwise starts a root span only when {@code ozone.tracing.enabled=true};
+   * if not, returns an invalid span so application-aware mode never starts a 
new trace.
    */
   private static Span buildSpan(String spanName) {
     Context currentContext = Context.current();
@@ -427,9 +553,11 @@ private static Span buildSpan(String spanName) {
 
     if (parentSpan.getSpanContext().isValid()) {
       return 
tracer.spanBuilder(spanName).setParent(currentContext).startSpan();
-    } else {
-      return tracer.spanBuilder(spanName).setNoParent().startSpan();
     }
+    if (!canStartSpanWithoutParent()) {
+      return Span.getInvalid();
+    }
+    return tracer.spanBuilder(spanName).setNoParent().startSpan();
   }
 
   /**
@@ -451,7 +579,7 @@ public String get(Function<String, String> carrier, String 
key) {
 
   public static TraceCloseable createActivatedSpanFromW3cHttpHeaders(
       String spanName, Function<String, String> getHeader, ConfigurationSource 
conf) {
-    if (conf == null || !isTracingEnabled(conf)) {
+    if (conf == null || !isTracingActive(conf)) {
       return () -> { };
     }
 
@@ -459,6 +587,9 @@ public static TraceCloseable 
createActivatedSpanFromW3cHttpHeaders(
         .extract(Context.current(), getHeader, new HttpHeaderGetter());
 
     if (!Span.fromContext(remote).getSpanContext().isValid()) {
+      if (!canStartSpanWithoutParent()) {
+        return () -> { };
+      }
       return createActivatedSpan(spanName);
     }
 
diff --git 
a/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/tracing/TestTracingInitModes.java
 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/tracing/TestTracingInitModes.java
new file mode 100644
index 00000000000..ae06a82a0d4
--- /dev/null
+++ 
b/hadoop-hdds/common/src/test/java/org/apache/hadoop/hdds/tracing/TestTracingInitModes.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.hdds.tracing;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.opentelemetry.api.OpenTelemetry;
+import io.opentelemetry.api.trace.Span;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.sdk.OpenTelemetrySdk;
+import io.opentelemetry.sdk.trace.SdkTracerProvider;
+import org.apache.hadoop.hdds.conf.InMemoryConfigurationForTesting;
+import org.apache.hadoop.hdds.conf.MutableConfigurationSource;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests tracing init for enabled, application-aware configs.
+ */
+public class TestTracingInitModes {
+
+  /** Reset tracing state before each test. */
+  @BeforeEach
+  public void resetGlobalState() {
+    TracingUtil.shutdownTracing();
+    GlobalOpenTelemetry.resetForTest();
+  }
+
+  /** Tear down tracing state after each test. */
+  @AfterEach
+  public void cleanup() {
+    TracingUtil.shutdownTracing();
+    GlobalOpenTelemetry.resetForTest();
+  }
+
+  /**
+   * Puts a real GlobalOpenTelemetry in place with no exporter, so tests stay 
offline.
+   */
+  private static void installNoExportGlobalOpenTelemetry() {
+    SdkTracerProvider provider = SdkTracerProvider.builder().build();
+    OpenTelemetrySdk sdk = 
OpenTelemetrySdk.builder().setTracerProvider(provider).build();
+    GlobalOpenTelemetry.set(sdk);
+  }
+
+  /** Builds in-memory config with enabled and application-aware flags. */
+  private static MutableConfigurationSource config(boolean enabled, boolean 
applicationAware) {
+    MutableConfigurationSource conf = new InMemoryConfigurationForTesting();
+    conf.setBoolean("ozone.tracing.enabled", enabled);
+    conf.setBoolean("ozone.tracing.client.application-aware", 
applicationAware);
+    return conf;
+  }
+
+  /**
+   * With tracing enabled, Ozone can start its own root span.
+   */
+  @Test
+  public void testEnabledModeStartsRootSpans() {
+    installNoExportGlobalOpenTelemetry();
+    MutableConfigurationSource conf = config(true, true);
+    TracingUtil.initTracing("enabled-svc", conf);
+    assertTrue(TracingUtil.isTracingActive(conf));
+
+    try (TracingUtil.TraceCloseable ignored = 
TracingUtil.createActivatedSpan("root")) {
+      assertTrue(Span.current().getSpanContext().isValid(),
+          "Enabled tracing should produce a valid root span");
+    }
+  }
+
+  /** app-aware, no app tracer: active but no root span without a parent. */
+  @Test
+  public void testApplicationAwareWithoutGlobalDoesNotStartRoot() {
+    installNoExportGlobalOpenTelemetry();
+    MutableConfigurationSource conf = config(false, true);
+    TracingUtil.initTracing("app-aware-svc", conf);
+    assertTrue(TracingUtil.isTracingActive(conf));
+
+    try (TracingUtil.TraceCloseable ignored = 
TracingUtil.createActivatedSpan("root")) {
+      assertFalse(Span.current().getSpanContext().isValid(),
+          "Application-aware mode must NOT manufacture a root span");
+    }
+  }
+
+  /** app-aware + W3C parent on the wire: child span is created. */
+  @Test
+  public void testApplicationAwareExtendsExtractedContext() {
+    SdkTracerProvider provider = SdkTracerProvider.builder().build();
+    OpenTelemetrySdk external = 
OpenTelemetrySdk.builder().setTracerProvider(provider).build();
+
+    Span external1 = 
external.getTracer("external").spanBuilder("external-root").startSpan();
+    String parentCarrier;
+    try (Scope ignored = external1.makeCurrent()) {
+      parentCarrier = TracingUtil.exportCurrentSpan();
+    } finally {
+      external1.end();
+    }
+    provider.shutdown();
+    assertFalse(parentCarrier.isEmpty(), "exported carrier should be 
non-empty");
+
+    GlobalOpenTelemetry.resetForTest();
+    installNoExportGlobalOpenTelemetry();
+
+    MutableConfigurationSource conf = config(false, true);
+    TracingUtil.initTracing("app-aware-extract", conf);
+    assertTrue(TracingUtil.isTracingActive(conf));
+
+    Span child = TracingUtil.importAndCreateSpan("child", parentCarrier);
+    try (Scope ignored = child.makeCurrent()) {
+      assertTrue(child.getSpanContext().isValid(),
+          "Application-aware mode should honor a wire-propagated parent 
context");
+    } finally {
+      child.end();
+    }
+  }
+
+  /** app-aware + GlobalOpenTelemetry set: still no root span without a 
parent. */
+  @Test
+  public void testApplicationAwareAdoptsGlobalTracer() {
+    SdkTracerProvider provider = SdkTracerProvider.builder().build();
+    OpenTelemetrySdk appGlobal = 
OpenTelemetrySdk.builder().setTracerProvider(provider).build();
+    GlobalOpenTelemetry.set(appGlobal);
+
+    MutableConfigurationSource conf = config(false, true);
+    TracingUtil.initTracing("adopt-global", conf);
+    assertTrue(TracingUtil.isTracingActive(conf));
+
+    try (TracingUtil.TraceCloseable ignored = 
TracingUtil.createActivatedSpan("root")) {
+      assertFalse(Span.current().getSpanContext().isValid(),
+          "Application-aware (with adopted global) must NOT manufacture a root 
span");
+    }
+    provider.shutdown();
+  }
+
+  /** Both flags false: tracing is off. */
+  @Test
+  public void testOffModeIsInactive() {
+    MutableConfigurationSource conf = config(false, false);
+    TracingUtil.initTracing("off-svc", conf);
+    assertFalse(TracingUtil.isTracingActive(conf),
+        "With both flags false, tracing must be inactive");
+
+    try (TracingUtil.TraceCloseable ignored = 
TracingUtil.createActivatedSpan("root")) {
+      assertFalse(Span.current().getSpanContext().isValid(),
+          "Inactive tracing must not produce a valid span");
+    }
+  }
+
+  /** Reconfig from app-aware to enabled activates tracing. */
+  @Test
+  public void testReconfigureFromAppAwareToEnabled() {
+    installNoExportGlobalOpenTelemetry();
+    MutableConfigurationSource conf = config(false, true);
+    TracingUtil.initTracing("reconfig", conf);
+    assertTrue(TracingUtil.isTracingActive(conf));
+
+    try (TracingUtil.TraceCloseable ignored = 
TracingUtil.createActivatedSpan("root")) {
+      assertFalse(Span.current().getSpanContext().isValid(),
+          "Application-aware mode must not manufacture a root span");
+    }
+
+    MutableConfigurationSource newConf = config(true, true);
+    TracingUtil.reconfigureTracing("reconfig", 
newConf.getObject(TracingConfig.class));
+    assertTrue(TracingUtil.isTracingActive(newConf),
+        "After reconfigure to enabled, tracing must be active");
+  }
+
+  /** OpenTelemetry.noop() is a singleton — used to detect a real app global. 
*/
+  @Test
+  public void testNoopSingletonIdentity() {
+    assertEquals(OpenTelemetry.noop(), OpenTelemetry.noop());
+    assertNotEquals(OpenTelemetry.noop(),
+        
OpenTelemetrySdk.builder().setTracerProvider(SdkTracerProvider.builder().build()).build());
+  }
+}
diff --git 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
index 62ef6de4f63..893fc90138c 100644
--- 
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
+++ 
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/rpc/RpcClient.java
@@ -240,6 +240,7 @@ public RpcClient(ConfigurationSource conf, String 
omServiceId)
       throws IOException {
     Objects.requireNonNull(conf, "conf == null");
     this.conf = conf;
+    TracingUtil.initTracing("client", conf);
     this.ugi = UserGroupInformation.getCurrentUser();
     replicationConfigValidator =
         this.conf.getObject(ReplicationConfigValidator.class);
@@ -338,8 +339,6 @@ public void onRemoval(
         OZONE_CLIENT_SERVER_DEFAULTS_VALIDITY_PERIOD_MS,
         OZONE_CLIENT_SERVER_DEFAULTS_VALIDITY_PERIOD_MS_DEFAULT,
         TimeUnit.MILLISECONDS);
-
-    TracingUtil.initTracing("client", conf);
   }
 
   public XceiverClientFactory getXceiverClientManager() {


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to