Skip to content

Commit 5af66d7

Browse files
authored
Add SLM support to xpack usage and info APIs (#48149)
* Add SLM support to xpack usage and info APIs This is a backport of #48096 This adds the missing xpack usage and info information into the `/_xpack` and `/_xpack/usage` APIs. The output now looks like: ``` GET /_xpack/usage { ... "slm" : { "available" : true, "enabled" : true, "policy_count" : 1, "policy_stats" : { "retention_runs" : 0, ... } } ``` and ``` GET /_xpack { ... "features" : { ... "slm" : { "available" : true, "enabled" : true }, ... } } ``` Relates to #43663 * Fix missing license
1 parent fa99721 commit 5af66d7

File tree

7 files changed

+227
-5
lines changed

7 files changed

+227
-5
lines changed

docs/reference/rest-api/info.asciidoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ Example response:
111111
"available" : true,
112112
"enabled" : false
113113
},
114+
"slm" : {
115+
"available" : true,
116+
"enabled" : true
117+
},
114118
"spatial" : {
115119
"available" : true,
116120
"enabled" : true

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@
3838
import org.elasticsearch.transport.Transport;
3939
import org.elasticsearch.xpack.core.action.XPackInfoAction;
4040
import org.elasticsearch.xpack.core.action.XPackUsageAction;
41+
import org.elasticsearch.xpack.core.analytics.AnalyticsFeatureSetUsage;
4142
import org.elasticsearch.xpack.core.beats.BeatsFeatureSetUsage;
4243
import org.elasticsearch.xpack.core.ccr.AutoFollowMetadata;
4344
import org.elasticsearch.xpack.core.ccr.CCRFeatureSet;
44-
import org.elasticsearch.xpack.core.analytics.AnalyticsFeatureSetUsage;
4545
import org.elasticsearch.xpack.core.deprecation.DeprecationInfoAction;
4646
import org.elasticsearch.xpack.core.enrich.action.DeleteEnrichPolicyAction;
4747
import org.elasticsearch.xpack.core.enrich.action.ExecuteEnrichPolicyAction;
@@ -152,12 +152,12 @@
152152
import org.elasticsearch.xpack.core.ml.dataframe.evaluation.softclassification.Recall;
153153
import org.elasticsearch.xpack.core.ml.dataframe.evaluation.softclassification.ScoreByThresholdResult;
154154
import org.elasticsearch.xpack.core.ml.dataframe.evaluation.softclassification.SoftClassificationMetric;
155-
import org.elasticsearch.xpack.core.ml.inference.trainedmodel.TrainedModel;
156-
import org.elasticsearch.xpack.core.ml.inference.trainedmodel.tree.Tree;
157155
import org.elasticsearch.xpack.core.ml.inference.preprocessing.FrequencyEncoding;
158156
import org.elasticsearch.xpack.core.ml.inference.preprocessing.OneHotEncoding;
159157
import org.elasticsearch.xpack.core.ml.inference.preprocessing.PreProcessor;
160158
import org.elasticsearch.xpack.core.ml.inference.preprocessing.TargetMeanEncoding;
159+
import org.elasticsearch.xpack.core.ml.inference.trainedmodel.TrainedModel;
160+
import org.elasticsearch.xpack.core.ml.inference.trainedmodel.tree.Tree;
161161
import org.elasticsearch.xpack.core.ml.job.config.JobTaskState;
162162
import org.elasticsearch.xpack.core.monitoring.MonitoringFeatureSetUsage;
163163
import org.elasticsearch.xpack.core.rollup.RollupFeatureSetUsage;
@@ -204,6 +204,7 @@
204204
import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivilege;
205205
import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivileges;
206206
import org.elasticsearch.xpack.core.security.transport.netty4.SecurityNetty4Transport;
207+
import org.elasticsearch.xpack.core.slm.SLMFeatureSetUsage;
207208
import org.elasticsearch.xpack.core.slm.SnapshotLifecycleMetadata;
208209
import org.elasticsearch.xpack.core.slm.action.DeleteSnapshotLifecycleAction;
209210
import org.elasticsearch.xpack.core.slm.action.ExecuteSnapshotLifecycleAction;
@@ -223,10 +224,10 @@
223224
import org.elasticsearch.xpack.core.transform.action.PutTransformAction;
224225
import org.elasticsearch.xpack.core.transform.action.StartTransformAction;
225226
import org.elasticsearch.xpack.core.transform.action.StopTransformAction;
226-
import org.elasticsearch.xpack.core.transform.transforms.TransformTaskParams;
227-
import org.elasticsearch.xpack.core.transform.transforms.TransformState;
228227
import org.elasticsearch.xpack.core.transform.transforms.SyncConfig;
229228
import org.elasticsearch.xpack.core.transform.transforms.TimeSyncConfig;
229+
import org.elasticsearch.xpack.core.transform.transforms.TransformState;
230+
import org.elasticsearch.xpack.core.transform.transforms.TransformTaskParams;
230231
import org.elasticsearch.xpack.core.upgrade.actions.IndexUpgradeAction;
231232
import org.elasticsearch.xpack.core.upgrade.actions.IndexUpgradeInfoAction;
232233
import org.elasticsearch.xpack.core.vectors.VectorsFeatureSetUsage;
@@ -544,6 +545,9 @@ public List<NamedWriteableRegistry.Entry> getNamedWriteables() {
544545
// ILM
545546
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.INDEX_LIFECYCLE,
546547
IndexLifecycleFeatureSetUsage::new),
548+
// SLM
549+
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, XPackField.SNAPSHOT_LIFECYCLE,
550+
SLMFeatureSetUsage::new),
547551
// ILM - Custom Metadata
548552
new NamedWriteableRegistry.Entry(MetaData.Custom.class, IndexLifecycleMetadata.TYPE, IndexLifecycleMetadata::new),
549553
new NamedWriteableRegistry.Entry(NamedDiff.class, IndexLifecycleMetadata.TYPE,

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackField.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ public final class XPackField {
3333
public static final String ROLLUP = "rollup";
3434
/** Name constant for the index lifecycle feature. */
3535
public static final String INDEX_LIFECYCLE = "ilm";
36+
/** Name constant for the snapshot lifecycle management feature. */
37+
public static final String SNAPSHOT_LIFECYCLE = "slm";
3638
/** Name constant for the CCR feature. */
3739
public static final String CCR = "ccr";
3840
/** Name constant for the transform feature. */
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
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+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.core.slm;
8+
9+
import org.elasticsearch.common.Nullable;
10+
import org.elasticsearch.common.io.stream.StreamInput;
11+
import org.elasticsearch.common.io.stream.StreamOutput;
12+
import org.elasticsearch.common.xcontent.XContentBuilder;
13+
import org.elasticsearch.xpack.core.XPackFeatureSet;
14+
import org.elasticsearch.xpack.core.XPackField;
15+
import org.elasticsearch.xpack.slm.SnapshotLifecycleStats;
16+
17+
import java.io.IOException;
18+
import java.util.Objects;
19+
20+
public class SLMFeatureSetUsage extends XPackFeatureSet.Usage {
21+
@Nullable
22+
private final SnapshotLifecycleStats slmStats;
23+
24+
public SLMFeatureSetUsage(StreamInput in) throws IOException {
25+
super(in);
26+
this.slmStats = in.readOptionalWriteable(SnapshotLifecycleStats::new);
27+
}
28+
29+
@Override
30+
public void writeTo(StreamOutput out) throws IOException {
31+
super.writeTo(out);
32+
out.writeOptionalWriteable(this.slmStats);
33+
}
34+
35+
public SLMFeatureSetUsage(boolean available, boolean enabled, @Nullable SnapshotLifecycleStats slmStats) {
36+
super(XPackField.SNAPSHOT_LIFECYCLE, available, enabled);
37+
this.slmStats = slmStats;
38+
}
39+
40+
public SnapshotLifecycleStats getStats() {
41+
return this.slmStats;
42+
}
43+
44+
@Override
45+
protected void innerXContent(XContentBuilder builder, Params params) throws IOException {
46+
super.innerXContent(builder, params);
47+
if (slmStats != null) {
48+
builder.field("policy_count", slmStats.getMetrics().size());
49+
builder.field("policy_stats", slmStats);
50+
}
51+
}
52+
53+
@Override
54+
public int hashCode() {
55+
return Objects.hash(available, enabled, slmStats);
56+
}
57+
58+
@Override
59+
public boolean equals(Object obj) {
60+
if (obj == null) {
61+
return false;
62+
}
63+
if (getClass() != obj.getClass()) {
64+
return false;
65+
}
66+
SLMFeatureSetUsage other = (SLMFeatureSetUsage) obj;
67+
return Objects.equals(available, other.available) &&
68+
Objects.equals(enabled, other.enabled) &&
69+
Objects.equals(slmStats, other.slmStats);
70+
}
71+
72+
}

x-pack/plugin/ilm/qa/multi-node/src/test/java/org/elasticsearch/xpack/slm/SnapshotLifecycleRestIT.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import static org.elasticsearch.xpack.core.slm.history.SnapshotHistoryItem.DELETE_OPERATION;
5050
import static org.elasticsearch.xpack.core.slm.history.SnapshotHistoryStore.SLM_HISTORY_INDEX_PREFIX;
5151
import static org.elasticsearch.xpack.ilm.TimeSeriesLifecycleActionsIT.getStepKeyForIndex;
52+
import static org.hamcrest.Matchers.anyOf;
5253
import static org.hamcrest.Matchers.containsString;
5354
import static org.hamcrest.Matchers.equalTo;
5455
import static org.hamcrest.Matchers.greaterThan;
@@ -422,6 +423,76 @@ public void testBasicTimeBasedRetenion() throws Exception {
422423
}
423424
}
424425

426+
@SuppressWarnings("unchecked")
427+
public void testSLMXpackInfo() {
428+
Map<String, Object> features = (Map<String, Object>) getLocation("/_xpack").get("features");
429+
assertNotNull(features);
430+
Map<String, Object> slm = (Map<String, Object>) features.get("slm");
431+
assertNotNull(slm);
432+
assertTrue((boolean) slm.get("available"));
433+
assertTrue((boolean) slm.get("enabled"));
434+
}
435+
436+
@SuppressWarnings("unchecked")
437+
public void testSLMXpackUsage() throws Exception {
438+
Map<String, Object> slm = (Map<String, Object>) getLocation("/_xpack/usage").get("slm");
439+
assertNotNull(slm);
440+
assertTrue((boolean) slm.get("available"));
441+
assertTrue((boolean) slm.get("enabled"));
442+
assertThat(slm.get("policy_count"), anyOf(equalTo(null), equalTo(0)));
443+
444+
// Create a snapshot repo
445+
initializeRepo("repo");
446+
// Create a policy with a retention period of 1 millisecond
447+
createSnapshotPolicy("policy", "snap", "1 2 3 4 5 ?", "repo", "*", true,
448+
new SnapshotRetentionConfiguration(TimeValue.timeValueMillis(1), null, null));
449+
final String snapshotName = executePolicy("policy");
450+
451+
// Check that the executed snapshot is created
452+
assertBusy(() -> {
453+
try {
454+
logger.info("--> checking for snapshot creation...");
455+
Response response = client().performRequest(new Request("GET", "/_snapshot/repo/" + snapshotName));
456+
Map<String, Object> snapshotResponseMap;
457+
try (InputStream is = response.getEntity().getContent()) {
458+
snapshotResponseMap = XContentHelper.convertToMap(XContentType.JSON.xContent(), is, true);
459+
}
460+
assertThat(snapshotResponseMap.size(), greaterThan(0));
461+
} catch (ResponseException e) {
462+
fail("expected snapshot to exist but it does not: " + EntityUtils.toString(e.getResponse().getEntity()));
463+
}
464+
});
465+
466+
// Wait for stats to be updated
467+
assertBusy(() -> {
468+
logger.info("--> checking for stats to be updated...");
469+
Map<String, Object> stats = getSLMStats();
470+
Map<String, Object> policyStats = policyStatsAsMap(stats);
471+
Map<String, Object> policyIdStats = (Map<String, Object>) policyStats.get("policy");
472+
assertNotNull(policyIdStats);
473+
});
474+
475+
slm = (Map<String, Object>) getLocation("/_xpack/usage").get("slm");
476+
assertNotNull(slm);
477+
assertTrue((boolean) slm.get("available"));
478+
assertTrue((boolean) slm.get("enabled"));
479+
assertThat("got: " + slm, slm.get("policy_count"), equalTo(1));
480+
assertNotNull(slm.get("policy_stats"));
481+
}
482+
483+
public Map<String, Object> getLocation(String path) {
484+
try {
485+
Response executeRepsonse = client().performRequest(new Request("GET", path));
486+
try (XContentParser parser = JsonXContent.jsonXContent.createParser(NamedXContentRegistry.EMPTY,
487+
DeprecationHandler.THROW_UNSUPPORTED_OPERATION, EntityUtils.toByteArray(executeRepsonse.getEntity()))) {
488+
return parser.map();
489+
}
490+
} catch (Exception e) {
491+
fail("failed to execute GET request to " + path + " - got: " + e);
492+
throw new RuntimeException(e);
493+
}
494+
}
495+
425496
/**
426497
* Execute the given policy and return the generated snapshot name
427498
*/

x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IndexLifecycle.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
import org.elasticsearch.xpack.ilm.action.TransportRetryAction;
9595
import org.elasticsearch.xpack.ilm.action.TransportStartILMAction;
9696
import org.elasticsearch.xpack.ilm.action.TransportStopILMAction;
97+
import org.elasticsearch.xpack.slm.SLMFeatureSet;
9798
import org.elasticsearch.xpack.slm.SnapshotLifecycleService;
9899
import org.elasticsearch.xpack.slm.SnapshotLifecycleTask;
99100
import org.elasticsearch.xpack.slm.SnapshotRetentionService;
@@ -158,6 +159,7 @@ public Collection<Module> createGuiceModules() {
158159
}
159160

160161
modules.add(b -> XPackPlugin.bindFeatureSet(b, IndexLifecycleFeatureSet.class));
162+
modules.add(b -> XPackPlugin.bindFeatureSet(b, SLMFeatureSet.class));
161163

162164
return modules;
163165
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
7+
package org.elasticsearch.xpack.slm;
8+
9+
import org.elasticsearch.action.ActionListener;
10+
import org.elasticsearch.cluster.ClusterState;
11+
import org.elasticsearch.cluster.service.ClusterService;
12+
import org.elasticsearch.common.Nullable;
13+
import org.elasticsearch.common.inject.Inject;
14+
import org.elasticsearch.common.settings.Settings;
15+
import org.elasticsearch.license.XPackLicenseState;
16+
import org.elasticsearch.xpack.core.XPackFeatureSet;
17+
import org.elasticsearch.xpack.core.XPackField;
18+
import org.elasticsearch.xpack.core.XPackSettings;
19+
import org.elasticsearch.xpack.core.slm.SLMFeatureSetUsage;
20+
import org.elasticsearch.xpack.core.slm.SnapshotLifecycleMetadata;
21+
22+
import java.util.Map;
23+
24+
public class SLMFeatureSet implements XPackFeatureSet {
25+
26+
private final boolean enabled;
27+
private final XPackLicenseState licenseState;
28+
private ClusterService clusterService;
29+
30+
@Inject
31+
public SLMFeatureSet(Settings settings, @Nullable XPackLicenseState licenseState, ClusterService clusterService) {
32+
this.clusterService = clusterService;
33+
this.enabled = XPackSettings.SNAPSHOT_LIFECYCLE_ENABLED.get(settings);
34+
this.licenseState = licenseState;
35+
}
36+
37+
@Override
38+
public String name() {
39+
return XPackField.SNAPSHOT_LIFECYCLE;
40+
}
41+
42+
@Override
43+
public boolean available() {
44+
return licenseState != null && licenseState.isIndexLifecycleAllowed();
45+
}
46+
47+
@Override
48+
public boolean enabled() {
49+
return enabled;
50+
}
51+
52+
@Override
53+
public Map<String, Object> nativeCodeInfo() {
54+
return null;
55+
}
56+
57+
@Override
58+
public void usage(ActionListener<Usage> listener) {
59+
final ClusterState state = clusterService.state();
60+
boolean available = licenseState.isIndexLifecycleAllowed();
61+
final SnapshotLifecycleMetadata slmMeta = state.metaData().custom(SnapshotLifecycleMetadata.TYPE);
62+
final SLMFeatureSetUsage usage = new SLMFeatureSetUsage(available, enabled,
63+
slmMeta == null ? null : slmMeta.getStats());
64+
listener.onResponse(usage);
65+
}
66+
67+
}

0 commit comments

Comments
 (0)