Skip to content

Commit 13a51f2

Browse files
authored
Distinguish LicensedFeature by family field (#116809)
This PR fixes unintentional licensed feature overlaps for features with the same name but different family fields.
1 parent 830c504 commit 13a51f2

File tree

3 files changed

+52
-2
lines changed

3 files changed

+52
-2
lines changed

docs/changelog/116809.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 116809
2+
summary: "Distinguish `LicensedFeature` by family field"
3+
area: License
4+
type: bug
5+
issues: []

x-pack/plugin/core/src/main/java/org/elasticsearch/license/LicensedFeature.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,11 @@ public boolean equals(Object o) {
136136
if (this == o) return true;
137137
if (o == null || getClass() != o.getClass()) return false;
138138
LicensedFeature that = (LicensedFeature) o;
139-
return Objects.equals(name, that.name);
139+
return Objects.equals(name, that.name) && Objects.equals(family, that.family);
140140
}
141141

142142
@Override
143143
public int hashCode() {
144-
return Objects.hash(name);
144+
return Objects.hash(name, family);
145145
}
146146
}

x-pack/plugin/core/src/test/java/org/elasticsearch/license/XPackLicenseStateTests.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import java.util.Arrays;
1616
import java.util.Map;
17+
import java.util.Set;
1718
import java.util.concurrent.atomic.AtomicInteger;
1819
import java.util.function.Predicate;
1920
import java.util.stream.Collectors;
@@ -228,6 +229,50 @@ public void testLastUsedMomentaryFeature() {
228229
assertThat(lastUsed.get(usage), equalTo(200L));
229230
}
230231

232+
public void testLastUsedMomentaryFeatureWithSameNameDifferentFamily() {
233+
LicensedFeature.Momentary featureFamilyA = LicensedFeature.momentary("familyA", "goldFeature", GOLD);
234+
LicensedFeature.Momentary featureFamilyB = LicensedFeature.momentary("familyB", "goldFeature", GOLD);
235+
236+
AtomicInteger currentTime = new AtomicInteger(100); // non zero start time
237+
XPackLicenseState licenseState = new XPackLicenseState(currentTime::get);
238+
239+
featureFamilyA.check(licenseState);
240+
featureFamilyB.check(licenseState);
241+
242+
Map<XPackLicenseState.FeatureUsage, Long> lastUsed = licenseState.getLastUsed();
243+
assertThat("feature.check tracks usage separately by family", lastUsed, aMapWithSize(2));
244+
Set<FeatureInfoWithTimestamp> actualFeatures = lastUsed.entrySet()
245+
.stream()
246+
.map(it -> new FeatureInfoWithTimestamp(it.getKey().feature().getFamily(), it.getKey().feature().getName(), it.getValue()))
247+
.collect(Collectors.toSet());
248+
assertThat(
249+
actualFeatures,
250+
containsInAnyOrder(
251+
new FeatureInfoWithTimestamp("familyA", "goldFeature", 100L),
252+
new FeatureInfoWithTimestamp("familyB", "goldFeature", 100L)
253+
)
254+
);
255+
256+
currentTime.set(200);
257+
featureFamilyB.check(licenseState);
258+
259+
lastUsed = licenseState.getLastUsed();
260+
assertThat("feature.check tracks usage separately by family", lastUsed, aMapWithSize(2));
261+
actualFeatures = lastUsed.entrySet()
262+
.stream()
263+
.map(it -> new FeatureInfoWithTimestamp(it.getKey().feature().getFamily(), it.getKey().feature().getName(), it.getValue()))
264+
.collect(Collectors.toSet());
265+
assertThat(
266+
actualFeatures,
267+
containsInAnyOrder(
268+
new FeatureInfoWithTimestamp("familyA", "goldFeature", 100L),
269+
new FeatureInfoWithTimestamp("familyB", "goldFeature", 200L)
270+
)
271+
);
272+
}
273+
274+
private record FeatureInfoWithTimestamp(String family, String featureName, Long timestamp) {}
275+
231276
public void testLastUsedPersistentFeature() {
232277
LicensedFeature.Persistent goldFeature = LicensedFeature.persistent("family", "goldFeature", GOLD);
233278
AtomicInteger currentTime = new AtomicInteger(100); // non zero start time

0 commit comments

Comments
 (0)