Skip to content

Commit ca91902

Browse files
committed
Align aggregation to spec
1 parent aacd4cc commit ca91902

2 files changed

Lines changed: 99 additions & 67 deletions

File tree

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

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,16 @@
77

88
import java.util.ArrayList;
99
import java.util.Collections;
10+
import java.util.LinkedHashMap;
1011
import java.util.LinkedHashSet;
1112
import java.util.List;
13+
import java.util.Map;
1214
import java.util.Objects;
1315
import java.util.Set;
1416
import java.util.logging.Level;
1517
import java.util.logging.Logger;
1618

17-
/**
18-
* Holds the latest validated policy snapshot and reports whether an update changed effective
19-
* configuration.
20-
*/
19+
/** Holds the latest validated policy snapshot and publishes accepted updates to implementers. */
2120
public final class PolicyStore {
2221
private static final Logger logger = Logger.getLogger(PolicyStore.class.getName());
2322

@@ -26,30 +25,25 @@ public final class PolicyStore {
2625
private long policyVersion;
2726

2827
/**
29-
* Replaces the stored policies when the new snapshot is not equal to the current one.
28+
* Replaces the stored policies with a new accepted snapshot.
3029
*
31-
* <p>Input lists are normalized to a set of distinct policies using value equality: duplicates
32-
* are dropped and only the first occurrence of each policy is kept (insertion order). Change
33-
* detection uses set equality, so list order does not matter. That matches telemetry policy
34-
* semantics where the effective result does not depend on processing order (see the telemetry
35-
* policy OTEP, commutativity / no user-defined ordering between policies).
30+
* <p>Input lists are normalized by policy identity, using policy type and policy id. Duplicate
31+
* policies with the same identity are dropped and only the first occurrence is kept in insertion
32+
* order. The store does not perform value-level no-op detection; providers can use source
33+
* hashes/versions to skip unchanged snapshots, and implementers must apply policies idempotently.
3634
*
37-
* @return {@code true} if the store was updated, {@code false} if the snapshot was unchanged
35+
* @return {@code true} after the snapshot is accepted
3836
*/
3937
public boolean updatePolicies(List<TelemetryPolicy> newPolicies) {
4038
Objects.requireNonNull(newPolicies, "newPolicies cannot be null");
41-
LinkedHashSet<TelemetryPolicy> newPolicySet = new LinkedHashSet<>(newPolicies);
39+
Map<PolicyKey, TelemetryPolicy> newPolicyMap = normalizedPolicyMap(newPolicies);
4240
List<TelemetryPolicy> policiesSnapshot;
4341
List<TelemetryPolicy> notificationSnapshot;
4442
List<RegisteredImplementer> implementersSnapshot;
4543
long snapshotVersion;
4644
synchronized (this) {
47-
if (new LinkedHashSet<>(policies).equals(newPolicySet)) {
48-
return false;
49-
}
50-
List<TelemetryPolicy> deletedPolicies =
51-
deletedPoliciesFrom(policies, new ArrayList<>(newPolicySet));
52-
policies = new ArrayList<>(newPolicySet);
45+
List<TelemetryPolicy> deletedPolicies = deletedPoliciesFrom(policies, newPolicyMap.keySet());
46+
policies = new ArrayList<>(newPolicyMap.values());
5347
policyVersion++;
5448
snapshotVersion = policyVersion;
5549
policiesSnapshot = new ArrayList<>(policies);
@@ -105,14 +99,10 @@ private static List<TelemetryPolicy> relevantPoliciesFor(
10599
}
106100

107101
private static List<TelemetryPolicy> deletedPoliciesFrom(
108-
List<TelemetryPolicy> previousPolicies, List<TelemetryPolicy> newPolicies) {
109-
Set<String> activePolicyKeys = new LinkedHashSet<>();
110-
for (TelemetryPolicy policy : newPolicies) {
111-
activePolicyKeys.add(policyKey(policy));
112-
}
102+
List<TelemetryPolicy> previousPolicies, Set<PolicyKey> activePolicyKeys) {
113103
ArrayList<TelemetryPolicy> deletedPolicies = new ArrayList<>();
114104
for (TelemetryPolicy previousPolicy : previousPolicies) {
115-
String policyKey = policyKey(previousPolicy);
105+
PolicyKey policyKey = policyKey(previousPolicy);
116106
TelemetryPolicyIdentity identity = previousPolicy.getIdentity();
117107
if (!activePolicyKeys.contains(policyKey)) {
118108
deletedPolicies.add(new DeletedTelemetryPolicy(identity, previousPolicy.getType()));
@@ -121,8 +111,21 @@ private static List<TelemetryPolicy> deletedPoliciesFrom(
121111
return deletedPolicies;
122112
}
123113

124-
private static String policyKey(TelemetryPolicy policy) {
125-
return policy.getType() + "\u0000" + policy.getIdentity().getId();
114+
private static Map<PolicyKey, TelemetryPolicy> normalizedPolicyMap(
115+
List<TelemetryPolicy> policies) {
116+
LinkedHashMap<PolicyKey, TelemetryPolicy> normalized = new LinkedHashMap<>();
117+
for (TelemetryPolicy policy : policies) {
118+
Objects.requireNonNull(policy, "newPolicies cannot contain null elements");
119+
PolicyKey key = policyKey(policy);
120+
if (!normalized.containsKey(key)) {
121+
normalized.put(key, policy);
122+
}
123+
}
124+
return normalized;
125+
}
126+
127+
private static PolicyKey policyKey(TelemetryPolicy policy) {
128+
return new PolicyKey(policy.getType(), policy.getIdentity().getId());
126129
}
127130

128131
private static void notifyImplementer(
@@ -150,4 +153,33 @@ private RegisteredImplementer(PolicyImplementer implementer) {
150153
this.implementer = implementer;
151154
}
152155
}
156+
157+
private static final class PolicyKey {
158+
private final String type;
159+
private final String id;
160+
161+
private PolicyKey(String type, String id) {
162+
this.type = Objects.requireNonNull(type, "policy type cannot be null");
163+
this.id = Objects.requireNonNull(id, "policy id cannot be null");
164+
}
165+
166+
@Override
167+
public boolean equals(Object obj) {
168+
if (this == obj) {
169+
return true;
170+
}
171+
if (!(obj instanceof PolicyKey)) {
172+
return false;
173+
}
174+
PolicyKey that = (PolicyKey) obj;
175+
return type.equals(that.type) && id.equals(that.id);
176+
}
177+
178+
@Override
179+
public int hashCode() {
180+
int result = type.hashCode();
181+
result = 31 * result + id.hashCode();
182+
return result;
183+
}
184+
}
153185
}

dynamic-control/src/test/java/io/opentelemetry/contrib/dynamic/policy/PolicyStoreTest.java

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -32,40 +32,51 @@ void updatePoliciesReturnsTrueOnFirstSet() {
3232
}
3333

3434
@Test
35-
void updatePoliciesReturnsFalseWhenEqualContent() {
35+
void updatePoliciesAcceptsRepeatedEquivalentSnapshot() {
3636
PolicyStore store = new PolicyStore();
3737
assertThat(store.updatePolicies(singletonList(new TraceSamplingRatePolicy(0.5)))).isTrue();
38-
assertThat(store.updatePolicies(singletonList(new TraceSamplingRatePolicy(0.5)))).isFalse();
38+
assertThat(store.updatePolicies(singletonList(new TraceSamplingRatePolicy(0.5)))).isTrue();
3939
}
4040

4141
@Test
4242
void updatePoliciesReturnsTrueWhenProbabilityChanges() {
4343
PolicyStore store = new PolicyStore();
4444
assertThat(store.updatePolicies(singletonList(new TraceSamplingRatePolicy(0.25)))).isTrue();
4545
assertThat(store.updatePolicies(singletonList(new TraceSamplingRatePolicy(0.75)))).isTrue();
46-
assertThat(store.getPolicies()).containsExactly(new TraceSamplingRatePolicy(0.75));
46+
assertThat(store.getPolicies()).hasSize(1);
47+
assertThat(((TraceSamplingRatePolicy) store.getPolicies().get(0)).getProbability())
48+
.isEqualTo(0.75);
4749
}
4850

4951
@Test
50-
void updatePoliciesReturnsFalseWhenOnlyOrderDiffers() {
52+
void updatePoliciesAcceptsReorderedSnapshot() {
5153
PolicyStore store = new PolicyStore();
5254
List<TelemetryPolicy> first =
53-
Arrays.asList(new TraceSamplingRatePolicy(0.1), new TraceSamplingRatePolicy(0.2));
54-
List<TelemetryPolicy> reordered =
55-
Arrays.asList(new TraceSamplingRatePolicy(0.2), new TraceSamplingRatePolicy(0.1));
55+
Arrays.asList(
56+
new TestTelemetryPolicy(
57+
new TelemetryPolicyIdentity("first-policy", "First policy"), "test-policy"),
58+
new TestTelemetryPolicy(
59+
new TelemetryPolicyIdentity("second-policy", "Second policy"), "test-policy"));
60+
List<TelemetryPolicy> reordered = Arrays.asList(first.get(1), first.get(0));
5661

5762
assertThat(store.updatePolicies(first)).isTrue();
58-
assertThat(store.updatePolicies(reordered)).isFalse();
59-
assertThat(store.getPolicies()).isEqualTo(first);
63+
assertThat(store.updatePolicies(reordered)).isTrue();
64+
assertThat(store.getPolicies()).containsExactly(reordered.get(0), reordered.get(1));
6065
}
6166

6267
@Test
63-
void updatePoliciesIgnoresDuplicatePoliciesInInput() {
68+
void updatePoliciesIgnoresDuplicatePolicyIdentitiesInInput() {
6469
PolicyStore store = new PolicyStore();
65-
TraceSamplingRatePolicy p = new TraceSamplingRatePolicy(0.5);
66-
assertThat(store.updatePolicies(Arrays.asList(p, new TraceSamplingRatePolicy(0.5)))).isTrue();
67-
assertThat(store.getPolicies()).containsExactly(p);
68-
assertThat(store.updatePolicies(singletonList(new TraceSamplingRatePolicy(0.5)))).isFalse();
70+
TelemetryPolicy first =
71+
new TestTelemetryPolicy(
72+
new TelemetryPolicyIdentity("test-policy", "Test policy"), "test-policy");
73+
TelemetryPolicy duplicate =
74+
new TestTelemetryPolicy(
75+
new TelemetryPolicyIdentity("test-policy", "Updated name"), "test-policy");
76+
77+
assertThat(store.updatePolicies(Arrays.asList(first, duplicate))).isTrue();
78+
79+
assertThat(store.getPolicies()).containsExactly(first);
6980
}
7081

7182
@Test
@@ -85,7 +96,8 @@ void registerImplementerReceivesCurrentRelevantPolicies() {
8596

8697
store.registerImplementer(implementer);
8798

88-
verify(implementer).onPoliciesChanged(singletonList(new TraceSamplingRatePolicy(0.5)));
99+
verify(implementer)
100+
.onPoliciesChanged(argThat(policies -> containsTraceSamplingProbability(policies, 0.5)));
89101
}
90102

91103
@Test
@@ -97,7 +109,8 @@ void updatePoliciesNotifiesRegisteredImplementerWithRelevantPolicies() {
97109
clearInvocations(implementer);
98110
store.updatePolicies(Arrays.asList(unrelatedPolicy(), new TraceSamplingRatePolicy(0.25)));
99111

100-
verify(implementer).onPoliciesChanged(singletonList(new TraceSamplingRatePolicy(0.25)));
112+
verify(implementer)
113+
.onPoliciesChanged(argThat(policies -> containsTraceSamplingProbability(policies, 0.25)));
101114
}
102115

103116
@Test
@@ -145,7 +158,7 @@ void updatePoliciesNotifiesDeletedPolicyForAnyIdentityBearingPolicy() {
145158
PolicyImplementer implementer = implementerFor("test-policy");
146159
TestTelemetryPolicy removedPolicy =
147160
new TestTelemetryPolicy(
148-
new TelemetryPolicyIdentity("test-policy-id", "Test policy"), "test-policy", "old");
161+
new TelemetryPolicyIdentity("test-policy-id", "Test policy"), "test-policy");
149162
store.updatePolicies(singletonList(removedPolicy));
150163
store.registerImplementer(implementer);
151164
clearInvocations(implementer);
@@ -219,18 +232,25 @@ private static PolicyImplementer implementerFor(String policyType) {
219232

220233
private static TelemetryPolicy unrelatedPolicy() {
221234
return new TestTelemetryPolicy(
222-
new TelemetryPolicyIdentity("other-policy", "Other policy"), "other-policy", "value");
235+
new TelemetryPolicyIdentity("other-policy", "Other policy"), "other-policy");
236+
}
237+
238+
private static boolean containsTraceSamplingProbability(
239+
List<TelemetryPolicy> policies, double probability) {
240+
if (policies.size() != 1 || !(policies.get(0) instanceof TraceSamplingRatePolicy)) {
241+
return false;
242+
}
243+
TraceSamplingRatePolicy policy = (TraceSamplingRatePolicy) policies.get(0);
244+
return Double.compare(policy.getProbability(), probability) == 0;
223245
}
224246

225247
private static final class TestTelemetryPolicy implements TelemetryPolicy {
226248
private final TelemetryPolicyIdentity identity;
227249
private final String type;
228-
private final String value;
229250

230-
private TestTelemetryPolicy(TelemetryPolicyIdentity identity, String type, String value) {
251+
private TestTelemetryPolicy(TelemetryPolicyIdentity identity, String type) {
231252
this.identity = identity;
232253
this.type = type;
233-
this.value = value;
234254
}
235255

236256
@Override
@@ -242,25 +262,5 @@ public TelemetryPolicyIdentity getIdentity() {
242262
public String getType() {
243263
return type;
244264
}
245-
246-
@Override
247-
public boolean equals(Object obj) {
248-
if (this == obj) {
249-
return true;
250-
}
251-
if (!(obj instanceof TestTelemetryPolicy)) {
252-
return false;
253-
}
254-
TestTelemetryPolicy that = (TestTelemetryPolicy) obj;
255-
return identity.equals(that.identity) && type.equals(that.type) && value.equals(that.value);
256-
}
257-
258-
@Override
259-
public int hashCode() {
260-
int result = identity.hashCode();
261-
result = 31 * result + type.hashCode();
262-
result = 31 * result + value.hashCode();
263-
return result;
264-
}
265265
}
266266
}

0 commit comments

Comments
 (0)