Skip to content

Commit ed33bea

Browse files
martijnvgn1v0lg
andauthored
Adjust SyntheticSourceLicenseService (#116647)
Allow gold and platinum license to use synthetic source for a limited time. If the start time of a license is before the cut off date, then gold and platinum licenses will not fallback to stored source if synthetic source is used. Co-authored-by: Nikolaj Volgushev <[email protected]>
1 parent 78400b8 commit ed33bea

File tree

7 files changed

+562
-27
lines changed

7 files changed

+562
-27
lines changed

x-pack/plugin/logsdb/src/main/java/org/elasticsearch/xpack/logsdb/LogsDBPlugin.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import org.elasticsearch.common.settings.Setting;
1414
import org.elasticsearch.common.settings.Settings;
1515
import org.elasticsearch.index.IndexSettingProvider;
16+
import org.elasticsearch.license.LicenseService;
17+
import org.elasticsearch.license.XPackLicenseState;
1618
import org.elasticsearch.plugins.ActionPlugin;
1719
import org.elasticsearch.plugins.Plugin;
1820
import org.elasticsearch.xpack.core.XPackPlugin;
@@ -46,7 +48,8 @@ public LogsDBPlugin(Settings settings) {
4648

4749
@Override
4850
public Collection<?> createComponents(PluginServices services) {
49-
licenseService.setLicenseState(XPackPlugin.getSharedLicenseState());
51+
licenseService.setLicenseService(getLicenseService());
52+
licenseService.setLicenseState(getLicenseState());
5053
var clusterSettings = services.clusterService().getClusterSettings();
5154
// The `cluster.logsdb.enabled` setting is registered by this plugin, but its value may be updated by other plugins
5255
// before this plugin registers its settings update consumer below. This means we might miss updates that occurred earlier.
@@ -88,4 +91,12 @@ public List<Setting<?>> getSettings() {
8891
actions.add(new ActionPlugin.ActionHandler<>(XPackInfoFeatureAction.LOGSDB, LogsDBInfoTransportAction.class));
8992
return actions;
9093
}
94+
95+
protected XPackLicenseState getLicenseState() {
96+
return XPackPlugin.getSharedLicenseState();
97+
}
98+
99+
protected LicenseService getLicenseService() {
100+
return XPackPlugin.getSharedLicenseService();
101+
}
91102
}

x-pack/plugin/logsdb/src/main/java/org/elasticsearch/xpack/logsdb/SyntheticSourceIndexSettingsProvider.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,13 @@ public Settings getAdditionalIndexSettings(
8181
// This index name is used when validating component and index templates, we should skip this check in that case.
8282
// (See MetadataIndexTemplateService#validateIndexTemplateV2(...) method)
8383
boolean isTemplateValidation = "validate-index-name".equals(indexName);
84+
boolean legacyLicensedUsageOfSyntheticSourceAllowed = isLegacyLicensedUsageOfSyntheticSourceAllowed(
85+
templateIndexMode,
86+
indexName,
87+
dataStreamName
88+
);
8489
if (newIndexHasSyntheticSourceUsage(indexName, templateIndexMode, indexTemplateAndCreateRequestSettings, combinedTemplateMappings)
85-
&& syntheticSourceLicenseService.fallbackToStoredSource(isTemplateValidation)) {
90+
&& syntheticSourceLicenseService.fallbackToStoredSource(isTemplateValidation, legacyLicensedUsageOfSyntheticSourceAllowed)) {
8691
LOGGER.debug("creation of index [{}] with synthetic source without it being allowed", indexName);
8792
return Settings.builder()
8893
.put(SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), SourceFieldMapper.Mode.STORED.toString())
@@ -167,4 +172,29 @@ private IndexMetadata buildIndexMetadataForMapperService(
167172
tmpIndexMetadata.settings(finalResolvedSettings);
168173
return tmpIndexMetadata.build();
169174
}
175+
176+
/**
177+
* The GA-ed use cases in which synthetic source usage is allowed with gold or platinum license.
178+
*/
179+
boolean isLegacyLicensedUsageOfSyntheticSourceAllowed(IndexMode templateIndexMode, String indexName, String dataStreamName) {
180+
if (templateIndexMode == IndexMode.TIME_SERIES) {
181+
return true;
182+
}
183+
184+
// To allow the following patterns: profiling-metrics and profiling-events
185+
if (dataStreamName != null && dataStreamName.startsWith("profiling-")) {
186+
return true;
187+
}
188+
// To allow the following patterns: .profiling-sq-executables, .profiling-sq-leafframes and .profiling-stacktraces
189+
if (indexName.startsWith(".profiling-")) {
190+
return true;
191+
}
192+
// To allow the following patterns: metrics-apm.transaction.*, metrics-apm.service_transaction.*, metrics-apm.service_summary.*,
193+
// metrics-apm.service_destination.*, "metrics-apm.internal-* and metrics-apm.app.*
194+
if (dataStreamName != null && dataStreamName.startsWith("metrics-apm.")) {
195+
return true;
196+
}
197+
198+
return false;
199+
}
170200
}

x-pack/plugin/logsdb/src/main/java/org/elasticsearch/xpack/logsdb/SyntheticSourceLicenseService.java

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,30 @@
77

88
package org.elasticsearch.xpack.logsdb;
99

10+
import org.apache.logging.log4j.LogManager;
11+
import org.apache.logging.log4j.Logger;
1012
import org.elasticsearch.common.settings.Setting;
1113
import org.elasticsearch.common.settings.Settings;
1214
import org.elasticsearch.license.License;
15+
import org.elasticsearch.license.LicenseService;
1316
import org.elasticsearch.license.LicensedFeature;
1417
import org.elasticsearch.license.XPackLicenseState;
1518

19+
import java.time.Instant;
20+
import java.time.LocalDateTime;
21+
import java.time.ZoneOffset;
22+
1623
/**
1724
* Determines based on license and fallback setting whether synthetic source usages should fallback to stored source.
1825
*/
1926
final class SyntheticSourceLicenseService {
2027

21-
private static final String MAPPINGS_FEATURE_FAMILY = "mappings";
28+
static final String MAPPINGS_FEATURE_FAMILY = "mappings";
29+
// You can only override this property if you received explicit approval from Elastic.
30+
private static final String CUTOFF_DATE_SYS_PROP_NAME =
31+
"es.mapping.synthetic_source_fallback_to_stored_source.cutoff_date_restricted_override";
32+
private static final Logger LOGGER = LogManager.getLogger(SyntheticSourceLicenseService.class);
33+
static final long DEFAULT_CUTOFF_DATE = LocalDateTime.of(2024, 12, 12, 0, 0).toInstant(ZoneOffset.UTC).toEpochMilli();
2234

2335
/**
2436
* A setting that determines whether source mode should always be stored source. Regardless of licence.
@@ -30,39 +42,98 @@ final class SyntheticSourceLicenseService {
3042
Setting.Property.Dynamic
3143
);
3244

33-
private static final LicensedFeature.Momentary SYNTHETIC_SOURCE_FEATURE = LicensedFeature.momentary(
45+
static final LicensedFeature.Momentary SYNTHETIC_SOURCE_FEATURE = LicensedFeature.momentary(
3446
MAPPINGS_FEATURE_FAMILY,
3547
"synthetic-source",
3648
License.OperationMode.ENTERPRISE
3749
);
3850

51+
static final LicensedFeature.Momentary SYNTHETIC_SOURCE_FEATURE_LEGACY = LicensedFeature.momentary(
52+
MAPPINGS_FEATURE_FAMILY,
53+
"synthetic-source-legacy",
54+
License.OperationMode.GOLD
55+
);
56+
57+
private final long cutoffDate;
58+
private LicenseService licenseService;
3959
private XPackLicenseState licenseState;
4060
private volatile boolean syntheticSourceFallback;
4161

4262
SyntheticSourceLicenseService(Settings settings) {
43-
syntheticSourceFallback = FALLBACK_SETTING.get(settings);
63+
this(settings, System.getProperty(CUTOFF_DATE_SYS_PROP_NAME));
64+
}
65+
66+
SyntheticSourceLicenseService(Settings settings, String cutoffDate) {
67+
this.syntheticSourceFallback = FALLBACK_SETTING.get(settings);
68+
this.cutoffDate = getCutoffDate(cutoffDate);
4469
}
4570

4671
/**
4772
* @return whether synthetic source mode should fallback to stored source.
4873
*/
49-
public boolean fallbackToStoredSource(boolean isTemplateValidation) {
74+
public boolean fallbackToStoredSource(boolean isTemplateValidation, boolean legacyLicensedUsageOfSyntheticSourceAllowed) {
5075
if (syntheticSourceFallback) {
5176
return true;
5277
}
5378

79+
var licenseStateSnapshot = licenseState.copyCurrentLicenseState();
80+
if (checkFeature(SYNTHETIC_SOURCE_FEATURE, licenseStateSnapshot, isTemplateValidation)) {
81+
return false;
82+
}
83+
84+
var license = licenseService.getLicense();
85+
if (license == null) {
86+
return true;
87+
}
88+
89+
boolean beforeCutoffDate = license.startDate() <= cutoffDate;
90+
if (legacyLicensedUsageOfSyntheticSourceAllowed
91+
&& beforeCutoffDate
92+
&& checkFeature(SYNTHETIC_SOURCE_FEATURE_LEGACY, licenseStateSnapshot, isTemplateValidation)) {
93+
// platinum license will allow synthetic source with gold legacy licensed feature too.
94+
LOGGER.debug("legacy license [{}] is allowed to use synthetic source", licenseStateSnapshot.getOperationMode().description());
95+
return false;
96+
}
97+
98+
return true;
99+
}
100+
101+
private static boolean checkFeature(
102+
LicensedFeature.Momentary licensedFeature,
103+
XPackLicenseState licenseStateSnapshot,
104+
boolean isTemplateValidation
105+
) {
54106
if (isTemplateValidation) {
55-
return SYNTHETIC_SOURCE_FEATURE.checkWithoutTracking(licenseState) == false;
107+
return licensedFeature.checkWithoutTracking(licenseStateSnapshot);
56108
} else {
57-
return SYNTHETIC_SOURCE_FEATURE.check(licenseState) == false;
109+
return licensedFeature.check(licenseStateSnapshot);
58110
}
59111
}
60112

61113
void setSyntheticSourceFallback(boolean syntheticSourceFallback) {
62114
this.syntheticSourceFallback = syntheticSourceFallback;
63115
}
64116

117+
void setLicenseService(LicenseService licenseService) {
118+
this.licenseService = licenseService;
119+
}
120+
65121
void setLicenseState(XPackLicenseState licenseState) {
66122
this.licenseState = licenseState;
67123
}
124+
125+
private static long getCutoffDate(String cutoffDateAsString) {
126+
if (cutoffDateAsString != null) {
127+
long cutoffDate = LocalDateTime.parse(cutoffDateAsString).toInstant(ZoneOffset.UTC).toEpochMilli();
128+
LOGGER.warn("Configuring [{}] is only allowed with explicit approval from Elastic.", CUTOFF_DATE_SYS_PROP_NAME);
129+
LOGGER.info(
130+
"Configuring [{}] to [{}]",
131+
CUTOFF_DATE_SYS_PROP_NAME,
132+
LocalDateTime.ofInstant(Instant.ofEpochSecond(cutoffDate), ZoneOffset.UTC)
133+
);
134+
return cutoffDate;
135+
} else {
136+
return DEFAULT_CUTOFF_DATE;
137+
}
138+
}
68139
}
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.logsdb;
9+
10+
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
11+
import org.elasticsearch.common.settings.Settings;
12+
import org.elasticsearch.index.mapper.SourceFieldMapper;
13+
import org.elasticsearch.license.AbstractLicensesIntegrationTestCase;
14+
import org.elasticsearch.license.GetFeatureUsageRequest;
15+
import org.elasticsearch.license.GetFeatureUsageResponse;
16+
import org.elasticsearch.license.License;
17+
import org.elasticsearch.license.LicenseService;
18+
import org.elasticsearch.license.LicensedFeature;
19+
import org.elasticsearch.license.TransportGetFeatureUsageAction;
20+
import org.elasticsearch.license.XPackLicenseState;
21+
import org.elasticsearch.plugins.Plugin;
22+
import org.elasticsearch.test.ESIntegTestCase;
23+
import org.elasticsearch.xpack.core.LocalStateCompositeXPackPlugin;
24+
import org.hamcrest.Matcher;
25+
import org.junit.Before;
26+
27+
import java.nio.file.Path;
28+
import java.time.LocalDateTime;
29+
import java.time.ZoneOffset;
30+
import java.util.Collection;
31+
import java.util.List;
32+
33+
import static org.elasticsearch.test.ESIntegTestCase.Scope.TEST;
34+
import static org.elasticsearch.xpack.logsdb.SyntheticSourceLicenseServiceTests.createEnterpriseLicense;
35+
import static org.elasticsearch.xpack.logsdb.SyntheticSourceLicenseServiceTests.createGoldOrPlatinumLicense;
36+
import static org.hamcrest.Matchers.equalTo;
37+
import static org.hamcrest.Matchers.not;
38+
import static org.hamcrest.Matchers.nullValue;
39+
40+
@ESIntegTestCase.ClusterScope(scope = TEST, numDataNodes = 1, numClientNodes = 0, supportsDedicatedMasters = false)
41+
public class LegacyLicenceIntegrationTests extends AbstractLicensesIntegrationTestCase {
42+
43+
@Override
44+
protected Collection<Class<? extends Plugin>> nodePlugins() {
45+
return List.of(P.class);
46+
}
47+
48+
@Before
49+
public void setup() throws Exception {
50+
wipeAllLicenses();
51+
ensureGreen();
52+
License license = createGoldOrPlatinumLicense();
53+
putLicense(license);
54+
ensureGreen();
55+
}
56+
57+
public void testSyntheticSourceUsageDisallowed() {
58+
createIndexWithSyntheticSourceAndAssertExpectedType("test", "STORED");
59+
60+
assertFeatureUsage(SyntheticSourceLicenseService.SYNTHETIC_SOURCE_FEATURE_LEGACY, nullValue());
61+
assertFeatureUsage(SyntheticSourceLicenseService.SYNTHETIC_SOURCE_FEATURE, nullValue());
62+
}
63+
64+
public void testSyntheticSourceUsageWithLegacyLicense() {
65+
createIndexWithSyntheticSourceAndAssertExpectedType(".profiling-stacktraces", "synthetic");
66+
67+
assertFeatureUsage(SyntheticSourceLicenseService.SYNTHETIC_SOURCE_FEATURE_LEGACY, not(nullValue()));
68+
assertFeatureUsage(SyntheticSourceLicenseService.SYNTHETIC_SOURCE_FEATURE, nullValue());
69+
}
70+
71+
public void testSyntheticSourceUsageWithLegacyLicensePastCutoff() throws Exception {
72+
long startPastCutoff = LocalDateTime.of(2025, 11, 12, 0, 0).toInstant(ZoneOffset.UTC).toEpochMilli();
73+
putLicense(createGoldOrPlatinumLicense(startPastCutoff));
74+
ensureGreen();
75+
76+
createIndexWithSyntheticSourceAndAssertExpectedType(".profiling-stacktraces", "STORED");
77+
assertFeatureUsage(SyntheticSourceLicenseService.SYNTHETIC_SOURCE_FEATURE_LEGACY, nullValue());
78+
assertFeatureUsage(SyntheticSourceLicenseService.SYNTHETIC_SOURCE_FEATURE, nullValue());
79+
}
80+
81+
public void testSyntheticSourceUsageWithEnterpriseLicensePastCutoff() throws Exception {
82+
long startPastCutoff = LocalDateTime.of(2025, 11, 12, 0, 0).toInstant(ZoneOffset.UTC).toEpochMilli();
83+
putLicense(createEnterpriseLicense(startPastCutoff));
84+
ensureGreen();
85+
86+
createIndexWithSyntheticSourceAndAssertExpectedType(".profiling-traces", "synthetic");
87+
// also supports non-exceptional indices
88+
createIndexWithSyntheticSourceAndAssertExpectedType("test", "synthetic");
89+
assertFeatureUsage(SyntheticSourceLicenseService.SYNTHETIC_SOURCE_FEATURE_LEGACY, nullValue());
90+
assertFeatureUsage(SyntheticSourceLicenseService.SYNTHETIC_SOURCE_FEATURE, not(nullValue()));
91+
}
92+
93+
public void testSyntheticSourceUsageTracksBothLegacyAndRegularFeature() throws Exception {
94+
createIndexWithSyntheticSourceAndAssertExpectedType(".profiling-traces", "synthetic");
95+
96+
putLicense(createEnterpriseLicense());
97+
ensureGreen();
98+
99+
createIndexWithSyntheticSourceAndAssertExpectedType(".profiling-traces-v2", "synthetic");
100+
101+
assertFeatureUsage(SyntheticSourceLicenseService.SYNTHETIC_SOURCE_FEATURE_LEGACY, not(nullValue()));
102+
assertFeatureUsage(SyntheticSourceLicenseService.SYNTHETIC_SOURCE_FEATURE, not(nullValue()));
103+
}
104+
105+
private void createIndexWithSyntheticSourceAndAssertExpectedType(String indexName, String expectedType) {
106+
var settings = Settings.builder().put(SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey(), "synthetic").build();
107+
createIndex(indexName, settings);
108+
var response = admin().indices().getSettings(new GetSettingsRequest().indices(indexName)).actionGet();
109+
assertThat(
110+
response.getIndexToSettings().get(indexName).get(SourceFieldMapper.INDEX_MAPPER_SOURCE_MODE_SETTING.getKey()),
111+
equalTo(expectedType)
112+
);
113+
}
114+
115+
private List<GetFeatureUsageResponse.FeatureUsageInfo> getFeatureUsageInfo() {
116+
return client().execute(TransportGetFeatureUsageAction.TYPE, new GetFeatureUsageRequest()).actionGet().getFeatures();
117+
}
118+
119+
private void assertFeatureUsage(LicensedFeature.Momentary syntheticSourceFeature, Matcher<Object> matcher) {
120+
GetFeatureUsageResponse.FeatureUsageInfo featureUsage = getFeatureUsageInfo().stream()
121+
.filter(f -> f.getFamily().equals(SyntheticSourceLicenseService.MAPPINGS_FEATURE_FAMILY))
122+
.filter(f -> f.getName().equals(syntheticSourceFeature.getName()))
123+
.findAny()
124+
.orElse(null);
125+
assertThat(featureUsage, matcher);
126+
}
127+
128+
public static class P extends LocalStateCompositeXPackPlugin {
129+
130+
public P(final Settings settings, final Path configPath) {
131+
super(settings, configPath);
132+
plugins.add(new LogsDBPlugin(settings) {
133+
@Override
134+
protected XPackLicenseState getLicenseState() {
135+
return P.this.getLicenseState();
136+
}
137+
138+
@Override
139+
protected LicenseService getLicenseService() {
140+
return P.this.getLicenseService();
141+
}
142+
});
143+
}
144+
145+
}
146+
}

0 commit comments

Comments
 (0)