Skip to content

Commit 70b0ac9

Browse files
jackshiraziCopilot
andauthored
[dynamic control] Add policy store (#2721)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 1263b5b commit 70b0ac9

4 files changed

Lines changed: 166 additions & 0 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.dynamic.policy;
7+
8+
import java.util.ArrayList;
9+
import java.util.Collections;
10+
import java.util.LinkedHashSet;
11+
import java.util.List;
12+
import java.util.Objects;
13+
14+
/**
15+
* Holds the latest validated policy snapshot and reports whether an update changed effective
16+
* configuration.
17+
*/
18+
public final class PolicyStore {
19+
20+
private List<TelemetryPolicy> policies = Collections.emptyList();
21+
22+
/**
23+
* Replaces the stored policies when the new snapshot is not equal to the current one.
24+
*
25+
* <p>Input lists are normalized to a set of distinct policies ({@link TelemetryPolicy#equals
26+
* value equality}): duplicates are dropped and only the first occurrence of each policy is kept
27+
* (insertion order). Change detection uses set equality, so list order does not matter. That
28+
* matches telemetry policy semantics where the effective result does not depend on processing
29+
* order (see the telemetry policy OTEP, commutativity / no user-defined ordering between
30+
* policies).
31+
*
32+
* @return {@code true} if the store was updated, {@code false} if the snapshot was unchanged
33+
*/
34+
public synchronized boolean updatePolicies(List<TelemetryPolicy> newPolicies) {
35+
Objects.requireNonNull(newPolicies, "newPolicies cannot be null");
36+
LinkedHashSet<TelemetryPolicy> newPolicySet = new LinkedHashSet<>(newPolicies);
37+
if (new LinkedHashSet<>(policies).equals(newPolicySet)) {
38+
return false;
39+
}
40+
policies = new ArrayList<>(newPolicySet);
41+
return true;
42+
}
43+
44+
public synchronized List<TelemetryPolicy> getPolicies() {
45+
return Collections.unmodifiableList(new ArrayList<>(policies));
46+
}
47+
}

dynamic-control/src/main/java/io/opentelemetry/contrib/dynamic/policy/TelemetryPolicy.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@
2121
* <p>Direct instantiation of this base class is intentionally supported for type-only policy
2222
* signals (for example, to indicate policy removal/reset without policy-specific values).
2323
*
24+
* <p><b>Subclasses:</b> {@link #equals(Object)} on this class returns {@code false} when either
25+
* operand is a typed subclass (not {@code TelemetryPolicy} itself), so a type-only instance never
26+
* equals a value-carrying subclass with the same {@link #getType() type}. Each concrete subclass
27+
* <b>must</b> override both {@code equals} and {@code hashCode} consistently with its fields, and
28+
* obey the {@code equals}/{@code hashCode} contract. That is required for consumers such as {@link
29+
* io.opentelemetry.contrib.dynamic.policy.PolicyStore} that deduplicate and detect changes using
30+
* {@link Object#equals(Object)}.
31+
*
2432
* @see io.opentelemetry.contrib.dynamic.policy
2533
*/
2634
public class TelemetryPolicy {
@@ -50,4 +58,29 @@ public TelemetryPolicy(String type) {
5058
public String getType() {
5159
return type;
5260
}
61+
62+
/**
63+
* Type-only policies ({@link TelemetryPolicy} instances) do not equal typed subclasses that share
64+
* the same {@link #getType() type} string. Subclasses must override {@code equals} (and {@code
65+
* hashCode}) for value-based equality; see the class Javadoc.
66+
*/
67+
@Override
68+
public boolean equals(Object obj) {
69+
if (this == obj) {
70+
return true;
71+
}
72+
if (!(obj instanceof TelemetryPolicy)) {
73+
return false;
74+
}
75+
TelemetryPolicy that = (TelemetryPolicy) obj;
76+
if (that.getClass() != TelemetryPolicy.class || getClass() != TelemetryPolicy.class) {
77+
return false;
78+
}
79+
return type.equals(that.type);
80+
}
81+
82+
@Override
83+
public int hashCode() {
84+
return type.hashCode();
85+
}
5386
}

dynamic-control/src/main/java/io/opentelemetry/contrib/dynamic/policy/tracesampling/TraceSamplingRatePolicy.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,21 @@ public TraceSamplingRatePolicy(double probability) {
2323
public double getProbability() {
2424
return probability;
2525
}
26+
27+
@Override
28+
public boolean equals(Object obj) {
29+
if (this == obj) {
30+
return true;
31+
}
32+
if (!(obj instanceof TraceSamplingRatePolicy)) {
33+
return false;
34+
}
35+
TraceSamplingRatePolicy that = (TraceSamplingRatePolicy) obj;
36+
return Double.compare(probability, that.probability) == 0;
37+
}
38+
39+
@Override
40+
public int hashCode() {
41+
return Double.hashCode(probability);
42+
}
2643
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.dynamic.policy;
7+
8+
import static java.util.Collections.singletonList;
9+
import static org.assertj.core.api.Assertions.assertThat;
10+
11+
import io.opentelemetry.contrib.dynamic.policy.tracesampling.TraceSamplingRatePolicy;
12+
import java.util.Arrays;
13+
import java.util.Collections;
14+
import java.util.List;
15+
import org.junit.jupiter.api.Test;
16+
17+
class PolicyStoreTest {
18+
19+
@Test
20+
void updatePoliciesReturnsTrueOnFirstSet() {
21+
PolicyStore store = new PolicyStore();
22+
List<TelemetryPolicy> policies = singletonList(new TraceSamplingRatePolicy(0.5));
23+
24+
assertThat(store.updatePolicies(policies)).isTrue();
25+
assertThat(store.getPolicies()).isEqualTo(policies);
26+
}
27+
28+
@Test
29+
void updatePoliciesReturnsFalseWhenEqualContent() {
30+
PolicyStore store = new PolicyStore();
31+
assertThat(store.updatePolicies(singletonList(new TraceSamplingRatePolicy(0.5)))).isTrue();
32+
assertThat(store.updatePolicies(singletonList(new TraceSamplingRatePolicy(0.5)))).isFalse();
33+
}
34+
35+
@Test
36+
void updatePoliciesReturnsTrueWhenProbabilityChanges() {
37+
PolicyStore store = new PolicyStore();
38+
assertThat(store.updatePolicies(singletonList(new TraceSamplingRatePolicy(0.25)))).isTrue();
39+
assertThat(store.updatePolicies(singletonList(new TraceSamplingRatePolicy(0.75)))).isTrue();
40+
assertThat(store.getPolicies()).containsExactly(new TraceSamplingRatePolicy(0.75));
41+
}
42+
43+
@Test
44+
void updatePoliciesReturnsFalseWhenOnlyOrderDiffers() {
45+
PolicyStore store = new PolicyStore();
46+
List<TelemetryPolicy> first =
47+
Arrays.asList(new TraceSamplingRatePolicy(0.1), new TraceSamplingRatePolicy(0.2));
48+
List<TelemetryPolicy> reordered =
49+
Arrays.asList(new TraceSamplingRatePolicy(0.2), new TraceSamplingRatePolicy(0.1));
50+
51+
assertThat(store.updatePolicies(first)).isTrue();
52+
assertThat(store.updatePolicies(reordered)).isFalse();
53+
assertThat(store.getPolicies()).isEqualTo(first);
54+
}
55+
56+
@Test
57+
void updatePoliciesIgnoresDuplicatePoliciesInInput() {
58+
PolicyStore store = new PolicyStore();
59+
TraceSamplingRatePolicy p = new TraceSamplingRatePolicy(0.5);
60+
assertThat(store.updatePolicies(Arrays.asList(p, new TraceSamplingRatePolicy(0.5)))).isTrue();
61+
assertThat(store.getPolicies()).containsExactly(p);
62+
assertThat(store.updatePolicies(singletonList(new TraceSamplingRatePolicy(0.5)))).isFalse();
63+
}
64+
65+
@Test
66+
void getPoliciesReturnsEmptyWhenNeverUpdated() {
67+
assertThat(new PolicyStore().getPolicies()).isEqualTo(Collections.emptyList());
68+
}
69+
}

0 commit comments

Comments
 (0)