Skip to content

Commit 37d2da1

Browse files
authored
Fix issues with ignore_missing_component_templates (#95527)
1 parent 10d67cc commit 37d2da1

File tree

8 files changed

+168
-7
lines changed

8 files changed

+168
-7
lines changed

docs/changelog/95527.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 95527
2+
summary: Allow deletion of component templates that are specified in the `ignore_missing_component_templates` array
3+
area: Data streams
4+
type: bug
5+
issues: []

server/src/main/java/org/elasticsearch/cluster/metadata/ComposableIndexTemplate.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,23 @@ public List<String> composedOf() {
198198
return componentTemplates;
199199
}
200200

201+
/**
202+
* Returns the <b>required</b> component templates, i.e. such that are not allowed to be missing, as in
203+
* {@link #ignoreMissingComponentTemplates}.
204+
* @return a list of required component templates
205+
*/
206+
public List<String> getRequiredComponentTemplates() {
207+
if (componentTemplates == null) {
208+
return List.of();
209+
}
210+
if (ignoreMissingComponentTemplates == null) {
211+
return componentTemplates;
212+
}
213+
return componentTemplates.stream()
214+
.filter(componentTemplate -> ignoreMissingComponentTemplates.contains(componentTemplate) == false)
215+
.toList();
216+
}
217+
201218
@Nullable
202219
public Long priority() {
203220
return priority;

server/src/main/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateService.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ public void removeComponentTemplate(
383383
ClusterState state,
384384
final ActionListener<AcknowledgedResponse> listener
385385
) {
386-
validateNotInUse(state.metadata(), names);
386+
validateCanBeRemoved(state.metadata(), names);
387387
taskQueue.submitTask("remove-component-template [" + String.join(",", names) + "]", new TemplateClusterStateUpdateTask(listener) {
388388
@Override
389389
public ClusterState execute(ClusterState currentState) {
@@ -394,7 +394,7 @@ public ClusterState execute(ClusterState currentState) {
394394

395395
// Exposed for ReservedComponentTemplateAction
396396
public static ClusterState innerRemoveComponentTemplate(ClusterState currentState, String... names) {
397-
validateNotInUse(currentState.metadata(), names);
397+
validateCanBeRemoved(currentState.metadata(), names);
398398

399399
final Set<String> templateNames = new HashSet<>();
400400
if (names.length > 1) {
@@ -439,10 +439,11 @@ public static ClusterState innerRemoveComponentTemplate(ClusterState currentStat
439439
}
440440

441441
/**
442-
* Validates that the given component template is not used by any index
443-
* templates, throwing an error if it is still in use
442+
* Validates that the given component template can be removed, throwing an error if it cannot.
443+
* A component template should not be removed if it is <b>required</b> by any index templates,
444+
* that is- it is used AND NOT specified as {@code ignore_missing_component_templates}.
444445
*/
445-
static void validateNotInUse(Metadata metadata, String... templateNameOrWildcard) {
446+
static void validateCanBeRemoved(Metadata metadata, String... templateNameOrWildcard) {
446447
final Predicate<String> predicate;
447448
if (templateNameOrWildcard.length > 1) {
448449
predicate = name -> Arrays.asList(templateNameOrWildcard).contains(name);
@@ -456,7 +457,10 @@ static void validateNotInUse(Metadata metadata, String... templateNameOrWildcard
456457
.collect(Collectors.toSet());
457458
final Set<String> componentsBeingUsed = new HashSet<>();
458459
final List<String> templatesStillUsing = metadata.templatesV2().entrySet().stream().filter(e -> {
459-
Set<String> intersecting = Sets.intersection(new HashSet<>(e.getValue().composedOf()), matchingComponentTemplates);
460+
Set<String> intersecting = Sets.intersection(
461+
new HashSet<>(e.getValue().getRequiredComponentTemplates()),
462+
matchingComponentTemplates
463+
);
460464
if (intersecting.size() > 0) {
461465
componentsBeingUsed.addAll(intersecting);
462466
return true;

server/src/test/java/org/elasticsearch/cluster/metadata/MetadataIndexTemplateServiceTests.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1666,6 +1666,40 @@ public void testRemoveComponentTemplateInUse() throws Exception {
16661666
);
16671667
}
16681668

1669+
public void testRemoveRequiredAndNonRequiredComponents() throws Exception {
1670+
ComposableIndexTemplate composableIndexTemplate = new ComposableIndexTemplate(
1671+
Collections.singletonList("pattern"),
1672+
null,
1673+
List.of("required1", "non-required", "required2"),
1674+
null,
1675+
null,
1676+
null,
1677+
null,
1678+
null,
1679+
Collections.singletonList("non-required")
1680+
);
1681+
ComponentTemplate ct = new ComponentTemplate(new Template(null, new CompressedXContent("{}"), null), null, null);
1682+
1683+
final MetadataIndexTemplateService service = getMetadataIndexTemplateService();
1684+
ClusterState clusterState = service.addComponentTemplate(ClusterState.EMPTY_STATE, false, "required1", ct);
1685+
clusterState = service.addComponentTemplate(clusterState, false, "required2", ct);
1686+
clusterState = service.addComponentTemplate(clusterState, false, "non-required", ct);
1687+
clusterState = service.addIndexTemplateV2(clusterState, false, "composable-index-template", composableIndexTemplate);
1688+
1689+
final ClusterState cs = clusterState;
1690+
Exception e = expectThrows(IllegalArgumentException.class, () -> innerRemoveComponentTemplate(cs, "required*"));
1691+
assertThat(
1692+
e.getMessage(),
1693+
containsString(
1694+
"component templates [required1, required2] cannot be removed as they are still in use by index templates "
1695+
+ "[composable-index-template]"
1696+
)
1697+
);
1698+
1699+
// This removal should succeed
1700+
innerRemoveComponentTemplate(cs, "non-required*");
1701+
}
1702+
16691703
/**
16701704
* Tests that we check that settings/mappings/etc are valid even after template composition,
16711705
* when adding/updating a composable index template

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ private void addComposableTemplatesIfMissing(ClusterState state) {
327327
* Returns true if the cluster state contains all of the component templates needed by the composable template
328328
*/
329329
private static boolean componentTemplatesExist(ClusterState state, ComposableIndexTemplate indexTemplate) {
330-
return state.metadata().componentTemplates().keySet().containsAll(indexTemplate.composedOf());
330+
return state.metadata().componentTemplates().keySet().containsAll(indexTemplate.getRequiredComponentTemplates());
331331
}
332332

333333
private void putLegacyTemplate(final IndexTemplateConfig config, final AtomicBoolean creationCheck) {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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.stack;
9+
10+
import org.elasticsearch.client.internal.Client;
11+
import org.elasticsearch.cluster.metadata.ComposableIndexTemplate;
12+
import org.elasticsearch.cluster.service.ClusterService;
13+
import org.elasticsearch.common.settings.Settings;
14+
import org.elasticsearch.threadpool.ThreadPool;
15+
import org.elasticsearch.xcontent.NamedXContentRegistry;
16+
import org.elasticsearch.xpack.core.template.IndexTemplateConfig;
17+
18+
import java.util.Map;
19+
20+
class StackRegistryWithNonRequiredTemplates extends StackTemplateRegistry {
21+
StackRegistryWithNonRequiredTemplates(
22+
Settings nodeSettings,
23+
ClusterService clusterService,
24+
ThreadPool threadPool,
25+
Client client,
26+
NamedXContentRegistry xContentRegistry
27+
) {
28+
super(nodeSettings, clusterService, threadPool, client, xContentRegistry);
29+
}
30+
31+
@Override
32+
protected Map<String, ComposableIndexTemplate> getComposableTemplateConfigs() {
33+
return parseComposableTemplates(
34+
new IndexTemplateConfig("syslog", "/non-required-template.json", REGISTRY_VERSION, TEMPLATE_VERSION_VARIABLE)
35+
);
36+
}
37+
}

x-pack/plugin/stack/src/test/java/org/elasticsearch/xpack/stack/StackTemplateRegistryTests.java

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.elasticsearch.cluster.ClusterState;
2121
import org.elasticsearch.cluster.block.ClusterBlocks;
2222
import org.elasticsearch.cluster.metadata.ComponentTemplate;
23+
import org.elasticsearch.cluster.metadata.ComposableIndexTemplate;
2324
import org.elasticsearch.cluster.metadata.Metadata;
2425
import org.elasticsearch.cluster.node.DiscoveryNode;
2526
import org.elasticsearch.cluster.node.DiscoveryNodes;
@@ -270,6 +271,53 @@ public void testThatUnversionedOldTemplatesAreUpgraded() throws Exception {
270271
assertBusy(() -> assertThat(calledTimes.get(), equalTo(registry.getComponentTemplateConfigs().size())));
271272
}
272273

274+
public void testMissingNonRequiredTemplates() throws Exception {
275+
StackRegistryWithNonRequiredTemplates registryWithNonRequiredTemplate = new StackRegistryWithNonRequiredTemplates(
276+
Settings.EMPTY,
277+
clusterService,
278+
threadPool,
279+
client,
280+
NamedXContentRegistry.EMPTY
281+
);
282+
283+
DiscoveryNode node = new DiscoveryNode("node", ESTestCase.buildNewFakeTransportAddress(), Version.CURRENT);
284+
DiscoveryNodes nodes = DiscoveryNodes.builder().localNodeId("node").masterNodeId("node").add(node).build();
285+
286+
ClusterChangedEvent event = createClusterChangedEvent(
287+
Collections.singletonMap(StackTemplateRegistry.LOGS_SETTINGS_COMPONENT_TEMPLATE_NAME, null),
288+
nodes
289+
);
290+
291+
final AtomicInteger calledTimes = new AtomicInteger(0);
292+
client.setVerifier((action, request, listener) -> {
293+
if (action instanceof PutComponentTemplateAction) {
294+
// Ignore such
295+
return AcknowledgedResponse.TRUE;
296+
} else if (action instanceof PutLifecycleAction) {
297+
// Ignore such
298+
return AcknowledgedResponse.TRUE;
299+
} else if (action instanceof PutComposableIndexTemplateAction) {
300+
calledTimes.incrementAndGet();
301+
assertThat(request, instanceOf(PutComposableIndexTemplateAction.Request.class));
302+
PutComposableIndexTemplateAction.Request putComposableTemplateRequest = (PutComposableIndexTemplateAction.Request) request;
303+
assertThat(putComposableTemplateRequest.name(), equalTo("syslog"));
304+
ComposableIndexTemplate composableIndexTemplate = putComposableTemplateRequest.indexTemplate();
305+
assertThat(composableIndexTemplate.composedOf(), hasSize(2));
306+
assertThat(composableIndexTemplate.composedOf().get(0), equalTo("logs-settings"));
307+
assertThat(composableIndexTemplate.composedOf().get(1), equalTo("syslog@custom"));
308+
assertThat(composableIndexTemplate.getIgnoreMissingComponentTemplates(), hasSize(1));
309+
assertThat(composableIndexTemplate.getIgnoreMissingComponentTemplates().get(0), equalTo("syslog@custom"));
310+
return AcknowledgedResponse.TRUE;
311+
} else {
312+
fail("client called with unexpected request:" + request.toString());
313+
return null;
314+
}
315+
});
316+
317+
registryWithNonRequiredTemplate.clusterChanged(event);
318+
assertBusy(() -> assertThat(calledTimes.get(), equalTo(1)));
319+
}
320+
273321
@TestLogging(value = "org.elasticsearch.xpack.core.template:DEBUG", reason = "test")
274322
public void testSameOrHigherVersionTemplateNotUpgraded() {
275323
DiscoveryNode node = new DiscoveryNode("node", ESTestCase.buildNewFakeTransportAddress(), Version.CURRENT);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"index_patterns": ["syslog-*-*"],
3+
"priority": 100,
4+
"data_stream": {},
5+
"composed_of": [
6+
"logs-settings",
7+
"syslog@custom"
8+
],
9+
"ignore_missing_component_templates": ["syslog@custom"],
10+
"allow_auto_create": true,
11+
"_meta": {
12+
"description": "default logs template installed by x-pack",
13+
"managed": true
14+
},
15+
"version": ${xpack.stack.template.version}
16+
}

0 commit comments

Comments
 (0)