• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #20254

29 Apr 2026 05:08PM UTC coverage: 88.827% (+0.01%) from 88.814%
#20254

push

github

web-flow
xds: Trust Manager fix for when SAN validation against SNI sent doesn't apply (#12775)

Fixes a bug in propagation of `autoSniSanValidationDoesNotApply` (from
PR #12422). It added an argument `autoSniSanValidationDoesNotApply` to
`SslContextProviderSupplier.updateSslContext` that sets it on the
`DynamicSslContextProvider` but because `UpstreamTlsContext` equals
wasn't implemented, it was getting replaced by a new instance and the
flag getting lost. This issue was identified when fixing an incorrect
merge caused error in `CertProviderClientSslContextProvider` that
recreated the trust manager without consideration to
`autoSniSanValidationDoesNotApply`. It ought to have caused failure in
the test
`XdsSecurityClientServerTest.tlsClientServer_autoSniValidation_noSniApplicable_usesMatcherFromCmnVdnCtx`
but it wasn't, because even though `autoSniSanValidationDoesNotApply`
was false due to not getting the propagated true value, SAN matcher
fallback was still happening because there was no server SNI sent.

With the new changes, in addition to fixing the equals method, by moving
the decision about autoSniSanValidationDoesNotApply to
TlsContextManagerImpl.findOrCreateClientSslContextProvider I have
eliminated the need to have a deferred setting of this decision via
DynamicSslContextProvider.setAutoSniSanValidationDoesNotApply called
from SslContextProviderSupplier.updateSslContext.

Summary of Changes:

   1. Enhanced `UpstreamTlsContext` (EnvoyServerProtoData.java):
* Modified Caching Behavior: Implemented full equals() and hashCode()
overrides for `UpstreamTlsContext`. Previously, it relied on the base
class which only compared the commonTlsContext, causing different SNI or
auto-validation settings to incorrectly share the same cache entry.
* Normalization: Updated constructors to normalize the sni field to an
empty string ("") if null. This prevents equality mismatches between
context objects created from different sources (e.g., test helpers vs.
Envoy proto... (continued)

36141 of 40687 relevant lines covered (88.83%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.83
/../opentelemetry/src/main/java/io/grpc/opentelemetry/OpenTelemetryMetricSink.java
1
/*
2
 * Copyright 2024 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc.opentelemetry;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20

21
import com.google.common.annotations.VisibleForTesting;
22
import com.google.common.collect.ImmutableMap;
23
import com.google.common.collect.ImmutableSet;
24
import io.grpc.CallbackMetricInstrument;
25
import io.grpc.DoubleCounterMetricInstrument;
26
import io.grpc.DoubleHistogramMetricInstrument;
27
import io.grpc.LongCounterMetricInstrument;
28
import io.grpc.LongGaugeMetricInstrument;
29
import io.grpc.LongHistogramMetricInstrument;
30
import io.grpc.LongUpDownCounterMetricInstrument;
31
import io.grpc.MetricInstrument;
32
import io.grpc.MetricSink;
33
import io.opentelemetry.api.common.Attributes;
34
import io.opentelemetry.api.common.AttributesBuilder;
35
import io.opentelemetry.api.metrics.BatchCallback;
36
import io.opentelemetry.api.metrics.DoubleCounter;
37
import io.opentelemetry.api.metrics.DoubleHistogram;
38
import io.opentelemetry.api.metrics.LongCounter;
39
import io.opentelemetry.api.metrics.LongHistogram;
40
import io.opentelemetry.api.metrics.LongUpDownCounter;
41
import io.opentelemetry.api.metrics.Meter;
42
import io.opentelemetry.api.metrics.ObservableLongMeasurement;
43
import io.opentelemetry.api.metrics.ObservableMeasurement;
44
import java.util.ArrayList;
45
import java.util.BitSet;
46
import java.util.Collections;
47
import java.util.List;
48
import java.util.Map;
49
import java.util.Set;
50
import java.util.logging.Level;
51
import java.util.logging.Logger;
52

53
final class OpenTelemetryMetricSink implements MetricSink {
54
  private static final Logger logger = Logger.getLogger(OpenTelemetryMetricSink.class.getName());
1✔
55
  private final Object lock = new Object();
1✔
56
  private final Meter openTelemetryMeter;
57
  private final Map<String, Boolean> enableMetrics;
58
  private final boolean disableDefaultMetrics;
59
  private final Set<String> optionalLabels;
60
  private volatile List<MeasuresData> measures = new ArrayList<>();
1✔
61

62
  OpenTelemetryMetricSink(Meter meter, Map<String, Boolean> enableMetrics,
63
      boolean disableDefaultMetrics, List<String> optionalLabels) {
1✔
64
    this.openTelemetryMeter = checkNotNull(meter, "meter");
1✔
65
    this.enableMetrics = ImmutableMap.copyOf(enableMetrics);
1✔
66
    this.disableDefaultMetrics = disableDefaultMetrics;
1✔
67
    this.optionalLabels = ImmutableSet.copyOf(optionalLabels);
1✔
68
  }
1✔
69

70
  @Override
71
  public Map<String, Boolean> getEnabledMetrics() {
72
    return enableMetrics;
×
73
  }
74

75
  @Override
76
  public Set<String> getOptionalLabels() {
77
    return optionalLabels;
×
78
  }
79

80
  @Override
81
  public int getMeasuresSize() {
82
    return measures.size();
1✔
83
  }
84

85
  @VisibleForTesting
86
  List<MeasuresData> getMeasures() {
87
    synchronized (lock) {
1✔
88
      return Collections.unmodifiableList(measures);
1✔
89
    }
90
  }
91

92
  @Override
93
  public void addDoubleCounter(DoubleCounterMetricInstrument metricInstrument, double value,
94
      List<String> requiredLabelValues, List<String> optionalLabelValues) {
95
    MeasuresData instrumentData = measures.get(metricInstrument.getIndex());
1✔
96
    if (instrumentData == null) {
1✔
97
      // Disabled metric
98
      return;
×
99
    }
100
    Attributes attributes = createAttributes(metricInstrument.getRequiredLabelKeys(),
1✔
101
        metricInstrument.getOptionalLabelKeys(), requiredLabelValues, optionalLabelValues,
1✔
102
        instrumentData.getOptionalLabelsBitSet());
1✔
103
    DoubleCounter counter = (DoubleCounter) instrumentData.getMeasure();
1✔
104
    counter.add(value, attributes);
1✔
105
  }
1✔
106

107
  @Override
108
  public void addLongCounter(LongCounterMetricInstrument metricInstrument, long value,
109
      List<String> requiredLabelValues, List<String> optionalLabelValues) {
110
    MeasuresData instrumentData = measures.get(metricInstrument.getIndex());
1✔
111
    if (instrumentData == null) {
1✔
112
      // Disabled metric
113
      return;
1✔
114
    }
115
    Attributes attributes = createAttributes(metricInstrument.getRequiredLabelKeys(),
1✔
116
        metricInstrument.getOptionalLabelKeys(), requiredLabelValues, optionalLabelValues,
1✔
117
        instrumentData.getOptionalLabelsBitSet());
1✔
118
    LongCounter counter = (LongCounter) instrumentData.getMeasure();
1✔
119
    counter.add(value, attributes);
1✔
120
  }
1✔
121

122
  @Override
123
  public void addLongUpDownCounter(LongUpDownCounterMetricInstrument metricInstrument, long value,
124
                                   List<String> requiredLabelValues,
125
                                   List<String> optionalLabelValues) {
126
    MeasuresData instrumentData = measures.get(metricInstrument.getIndex());
1✔
127
    if (instrumentData == null) {
1✔
128
      // Disabled metric
129
      return;
1✔
130
    }
131
    Attributes attributes = createAttributes(metricInstrument.getRequiredLabelKeys(),
1✔
132
        metricInstrument.getOptionalLabelKeys(), requiredLabelValues, optionalLabelValues,
1✔
133
        instrumentData.getOptionalLabelsBitSet());
1✔
134
    LongUpDownCounter counter = (LongUpDownCounter) instrumentData.getMeasure();
1✔
135
    counter.add(value, attributes);
1✔
136
  }
1✔
137

138
  @Override
139
  public void recordDoubleHistogram(DoubleHistogramMetricInstrument metricInstrument, double value,
140
      List<String> requiredLabelValues, List<String> optionalLabelValues) {
141
    MeasuresData instrumentData = measures.get(metricInstrument.getIndex());
1✔
142
    if (instrumentData == null) {
1✔
143
      // Disabled metric
144
      return;
1✔
145
    }
146
    Attributes attributes = createAttributes(metricInstrument.getRequiredLabelKeys(),
1✔
147
        metricInstrument.getOptionalLabelKeys(), requiredLabelValues, optionalLabelValues,
1✔
148
        instrumentData.getOptionalLabelsBitSet());
1✔
149
    DoubleHistogram histogram = (DoubleHistogram) instrumentData.getMeasure();
1✔
150
    histogram.record(value, attributes);
1✔
151
  }
1✔
152

153
  @Override
154
  public void recordLongHistogram(LongHistogramMetricInstrument metricInstrument, long value,
155
      List<String> requiredLabelValues, List<String> optionalLabelValues) {
156
    MeasuresData instrumentData = measures.get(metricInstrument.getIndex());
1✔
157
    if (instrumentData == null) {
1✔
158
      // Disabled metric
159
      return;
1✔
160
    }
161
    Attributes attributes = createAttributes(metricInstrument.getRequiredLabelKeys(),
1✔
162
        metricInstrument.getOptionalLabelKeys(), requiredLabelValues, optionalLabelValues,
1✔
163
        instrumentData.getOptionalLabelsBitSet());
1✔
164
    LongHistogram histogram = (LongHistogram) instrumentData.getMeasure();
1✔
165
    histogram.record(value, attributes);
1✔
166
  }
1✔
167

168
  @Override
169
  public void recordLongGauge(LongGaugeMetricInstrument metricInstrument, long value,
170
      List<String> requiredLabelValues, List<String> optionalLabelValues) {
171
    MeasuresData instrumentData = measures.get(metricInstrument.getIndex());
1✔
172
    if (instrumentData == null) {
1✔
173
      // Disabled metric
174
      return;
1✔
175
    }
176
    Attributes attributes = createAttributes(metricInstrument.getRequiredLabelKeys(),
1✔
177
        metricInstrument.getOptionalLabelKeys(), requiredLabelValues, optionalLabelValues,
1✔
178
        instrumentData.getOptionalLabelsBitSet());
1✔
179
    ObservableLongMeasurement gauge = (ObservableLongMeasurement) instrumentData.getMeasure();
1✔
180
    gauge.record(value, attributes);
1✔
181
  }
1✔
182

183
  @Override
184
  public Registration registerBatchCallback(Runnable callback,
185
      CallbackMetricInstrument... metricInstruments) {
186
    List<ObservableMeasurement> measurements = new ArrayList<>(metricInstruments.length);
1✔
187
    for (CallbackMetricInstrument metricInstrument: metricInstruments) {
1✔
188
      MeasuresData instrumentData = measures.get(metricInstrument.getIndex());
1✔
189
      if (instrumentData == null) {
1✔
190
        // Disabled metric
191
        continue;
1✔
192
      }
193
      if (!(instrumentData.getMeasure() instanceof ObservableMeasurement)) {
1✔
194
        logger.log(Level.FINE, "Unsupported metric instrument type : {0} {1}",
×
195
            new Object[] {metricInstrument, instrumentData.getMeasure().getClass()});
×
196
        continue;
×
197
      }
198
      measurements.add((ObservableMeasurement) instrumentData.getMeasure());
1✔
199
    }
200
    if (measurements.isEmpty()) {
1✔
201
      return () -> { };
1✔
202
    }
203
    ObservableMeasurement first = measurements.get(0);
1✔
204
    measurements.remove(0);
1✔
205
    BatchCallback closeable = openTelemetryMeter.batchCallback(
1✔
206
        callback, first, measurements.toArray(new ObservableMeasurement[0]));
1✔
207
    return closeable::close;
1✔
208
  }
209

210
  @Override
211
  public void updateMeasures(List<MetricInstrument> instruments) {
212
    synchronized (lock) {
1✔
213
      if (measures.size() >= instruments.size()) {
1✔
214
        // Already up-to-date
215
        return;
×
216
      }
217

218
      List<MeasuresData> newMeasures = new ArrayList<>(instruments.size());
1✔
219
      // Reuse existing measures
220
      newMeasures.addAll(measures);
1✔
221

222
      for (int i = measures.size(); i < instruments.size(); i++) {
1✔
223
        MetricInstrument instrument = instruments.get(i);
1✔
224
        // Check if the metric is disabled
225
        if (!shouldEnableMetric(instrument)) {
1✔
226
          // Adding null measure for disabled Metric
227
          newMeasures.add(null);
1✔
228
          continue;
1✔
229
        }
230

231
        BitSet bitSet = new BitSet(instrument.getOptionalLabelKeys().size());
1✔
232
        if (optionalLabels.isEmpty()) {
1✔
233
          // initialize an empty list
234
        } else {
235
          List<String> labels = instrument.getOptionalLabelKeys();
1✔
236
          for (int j = 0; j < labels.size(); j++) {
1✔
237
            if (optionalLabels.contains(labels.get(j))) {
1✔
238
              bitSet.set(j);
1✔
239
            }
240
          }
241
        }
242

243
        int index = instrument.getIndex();
1✔
244
        String name = instrument.getName();
1✔
245
        String unit = instrument.getUnit();
1✔
246
        String description = instrument.getDescription();
1✔
247

248
        Object openTelemetryMeasure;
249
        if (instrument instanceof DoubleCounterMetricInstrument) {
1✔
250
          openTelemetryMeasure = openTelemetryMeter.counterBuilder(name)
1✔
251
              .setUnit(unit)
1✔
252
              .setDescription(description)
1✔
253
              .ofDoubles()
1✔
254
              .build();
1✔
255
        } else if (instrument instanceof LongCounterMetricInstrument) {
1✔
256
          openTelemetryMeasure = openTelemetryMeter.counterBuilder(name)
1✔
257
              .setUnit(unit)
1✔
258
              .setDescription(description)
1✔
259
              .build();
1✔
260
        } else if (instrument instanceof DoubleHistogramMetricInstrument) {
1✔
261
          openTelemetryMeasure = openTelemetryMeter.histogramBuilder(name)
1✔
262
              .setUnit(unit)
1✔
263
              .setDescription(description)
1✔
264
              .build();
1✔
265
        } else if (instrument instanceof LongHistogramMetricInstrument) {
1✔
266
          openTelemetryMeasure = openTelemetryMeter.histogramBuilder(name)
1✔
267
              .setUnit(unit)
1✔
268
              .setDescription(description)
1✔
269
              .ofLongs()
1✔
270
              .build();
1✔
271
        } else if (instrument instanceof LongGaugeMetricInstrument) {
1✔
272
          openTelemetryMeasure = openTelemetryMeter.gaugeBuilder(name)
1✔
273
              .setUnit(unit)
1✔
274
              .setDescription(description)
1✔
275
              .ofLongs()
1✔
276
              .buildObserver();
1✔
277
        } else if (instrument instanceof LongUpDownCounterMetricInstrument) {
1✔
278
          openTelemetryMeasure = openTelemetryMeter.upDownCounterBuilder(name)
1✔
279
              .setUnit(unit)
1✔
280
              .setDescription(description)
1✔
281
              .build();
1✔
282
        } else {
283
          logger.log(Level.FINE, "Unsupported metric instrument type : {0}", instrument);
×
284
          openTelemetryMeasure = null;
×
285
        }
286
        newMeasures.add(index, new MeasuresData(bitSet, openTelemetryMeasure));
1✔
287
      }
288

289
      measures = newMeasures;
1✔
290
    }
1✔
291
  }
1✔
292

293
  private boolean shouldEnableMetric(MetricInstrument instrument) {
294
    Boolean explicitlyEnabled = enableMetrics.get(instrument.getName());
1✔
295
    if (explicitlyEnabled != null) {
1✔
296
      return explicitlyEnabled;
1✔
297
    }
298
    return instrument.isEnableByDefault() && !disableDefaultMetrics;
1✔
299
  }
300

301

302
  private Attributes createAttributes(List<String> requiredLabelKeys,
303
      List<String> optionalLabelKeys,
304
      List<String> requiredLabelValues, List<String> optionalLabelValues, BitSet bitSet) {
305
    AttributesBuilder builder = Attributes.builder();
1✔
306
    // Required Labels
307
    for (int i = 0; i < requiredLabelKeys.size(); i++) {
1✔
308
      builder.put(requiredLabelKeys.get(i), requiredLabelValues.get(i));
1✔
309
    }
310
    // Optional labels
311
    for (int i = bitSet.nextSetBit(0); i >= 0; i = bitSet.nextSetBit(i + 1)) {
1✔
312
      if (i == Integer.MAX_VALUE) {
1✔
313
        break; // or (i+1) would overflow
×
314
      }
315
      builder.put(optionalLabelKeys.get(i), optionalLabelValues.get(i));
1✔
316
    }
317
    return builder.build();
1✔
318
  }
319

320
  static final class MeasuresData {
321
    final BitSet optionalLabelsIndices;
322
    final Object measure;
323

324
    MeasuresData(BitSet optionalLabelsIndices, Object measure) {
1✔
325
      this.optionalLabelsIndices = optionalLabelsIndices;
1✔
326
      this.measure = measure;
1✔
327
    }
1✔
328

329
    public BitSet getOptionalLabelsBitSet() {
330
      return optionalLabelsIndices;
1✔
331
    }
332

333
    public Object getMeasure() {
334
      return measure;
1✔
335
    }
336
  }
337

338
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc