Skip to content

Commit 455d845

Browse files
[rule based autotagging] Add Delete Rule API Logic (opensearch-project#18184)
Signed-off-by: Lingxi Chen <[email protected]> Co-authored-by: Lingxi Chen <[email protected]>Signed-off-by: TJ Neuenfeldt <[email protected]>
1 parent 6019a36 commit 455d845

File tree

14 files changed

+510
-3
lines changed

14 files changed

+510
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
77
### Added
88
- Add support for linux riscv64 platform ([#18156](https://github.com/opensearch-project/OpenSearch/pull/18156))
99
- [Rule based auto-tagging] Add get rule API ([#17336](https://github.com/opensearch-project/OpenSearch/pull/17336))
10+
- [Rule based auto-tagging] Add Delete Rule API ([#18184](https://github.com/opensearch-project/OpenSearch/pull/18184))
1011
- Implement parallel shard refresh behind cluster settings ([#17782](https://github.com/opensearch-project/OpenSearch/pull/17782))
1112
- Bump OpenSearch Core main branch to 3.0.0 ([#18039](https://github.com/opensearch-project/OpenSearch/pull/18039))
1213
- Update API of Message in index to add the timestamp for lag calculation in ingestion polling ([#17977](https://github.com/opensearch-project/OpenSearch/pull/17977/))
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.rule;
10+
11+
import org.opensearch.action.ActionRequest;
12+
import org.opensearch.action.ActionRequestValidationException;
13+
import org.opensearch.common.annotation.ExperimentalApi;
14+
import org.opensearch.core.common.io.stream.StreamInput;
15+
import org.opensearch.core.common.io.stream.StreamOutput;
16+
import org.opensearch.rule.autotagging.FeatureType;
17+
18+
import java.io.IOException;
19+
20+
/**
21+
* A request to delete a Rule by ID
22+
* Example:
23+
* curl -XDELETE "localhost:9200/_rules/{featureType}/{_id}"
24+
* @opensearch.experimental
25+
*/
26+
@ExperimentalApi
27+
public class DeleteRuleRequest extends ActionRequest {
28+
private final String ruleId;
29+
private final FeatureType featureType;
30+
31+
/**
32+
* Constructs a request to delete a rule.
33+
*
34+
* @param ruleId The ID of the rule to delete.
35+
* @param featureType The feature type associated with the rule.
36+
*/
37+
public DeleteRuleRequest(String ruleId, FeatureType featureType) {
38+
this.ruleId = ruleId;
39+
this.featureType = featureType;
40+
}
41+
42+
/**
43+
* Deserialization constructor
44+
* @param in Stream input
45+
*/
46+
public DeleteRuleRequest(StreamInput in) throws IOException {
47+
super(in);
48+
this.ruleId = in.readString();
49+
this.featureType = FeatureType.from(in.readString());
50+
}
51+
52+
@Override
53+
public void writeTo(StreamOutput out) throws IOException {
54+
super.writeTo(out);
55+
out.writeString(ruleId);
56+
featureType.writeTo(out);
57+
}
58+
59+
@Override
60+
public ActionRequestValidationException validate() {
61+
if (ruleId == null || ruleId.isEmpty()) {
62+
ActionRequestValidationException validationException = new ActionRequestValidationException();
63+
validationException.addValidationError("Rule ID is missing");
64+
return validationException;
65+
}
66+
return null;
67+
}
68+
69+
/**
70+
* Returns the ID of the rule to be deleted.
71+
*
72+
* @return The rule ID.
73+
*/
74+
public String getRuleId() {
75+
return ruleId;
76+
}
77+
78+
/**
79+
* Returns the feature type associated with the rule.
80+
*
81+
* @return The feature type.
82+
*/
83+
public FeatureType getFeatureType() {
84+
return featureType;
85+
}
86+
}

modules/autotagging-commons/common/src/main/java/org/opensearch/rule/RulePersistenceService.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
package org.opensearch.rule;
1010

11+
import org.opensearch.action.support.clustermanager.AcknowledgedResponse;
1112
import org.opensearch.core.action.ActionListener;
1213

1314
/**
@@ -22,4 +23,11 @@ public interface RulePersistenceService {
2223
* @param listener The listener that will handle the response or failure.
2324
*/
2425
void getRule(GetRuleRequest request, ActionListener<GetRuleResponse> listener);
26+
27+
/**
28+
* Delete a rule based on the provided request.
29+
* @param request The request containing the ID of the rule to delete.
30+
* @param listener The listener that will handle the response or failure.
31+
*/
32+
void deleteRule(DeleteRuleRequest request, ActionListener<AcknowledgedResponse> listener);
2533
}

modules/autotagging-commons/common/src/main/java/org/opensearch/rule/service/IndexStoredRulePersistenceService.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@
1111
import org.apache.logging.log4j.LogManager;
1212
import org.apache.logging.log4j.Logger;
1313
import org.opensearch.ResourceNotFoundException;
14+
import org.opensearch.action.DocWriteResponse;
15+
import org.opensearch.action.delete.DeleteRequest;
1416
import org.opensearch.action.search.SearchRequestBuilder;
17+
import org.opensearch.action.support.clustermanager.AcknowledgedResponse;
1518
import org.opensearch.common.util.concurrent.ThreadContext;
1619
import org.opensearch.core.action.ActionListener;
20+
import org.opensearch.index.engine.DocumentMissingException;
1721
import org.opensearch.index.query.QueryBuilder;
1822
import org.opensearch.index.query.QueryBuilders;
23+
import org.opensearch.rule.DeleteRuleRequest;
1924
import org.opensearch.rule.GetRuleRequest;
2025
import org.opensearch.rule.GetRuleResponse;
2126
import org.opensearch.rule.RuleEntityParser;
@@ -132,4 +137,26 @@ void handleGetRuleResponse(List<SearchHit> hits, ActionListener<GetRuleResponse>
132137
private ThreadContext.StoredContext getContext() {
133138
return client.threadPool().getThreadContext().stashContext();
134139
}
140+
141+
@Override
142+
public void deleteRule(DeleteRuleRequest request, ActionListener<AcknowledgedResponse> listener) {
143+
try (ThreadContext.StoredContext context = getContext()) {
144+
DeleteRequest deleteRequest = new DeleteRequest(indexName).id(request.getRuleId());
145+
client.delete(deleteRequest, ActionListener.wrap(deleteResponse -> {
146+
boolean acknowledged = deleteResponse.getResult() == DocWriteResponse.Result.DELETED;
147+
if (!acknowledged) {
148+
logger.warn("Rule with ID " + request.getRuleId() + " was not found or already deleted.");
149+
}
150+
listener.onResponse(new AcknowledgedResponse(acknowledged));
151+
}, e -> {
152+
if (e instanceof DocumentMissingException) {
153+
logger.error("Rule with ID " + request.getRuleId() + " not found.");
154+
listener.onFailure(new ResourceNotFoundException("Rule with ID " + request.getRuleId() + " not found."));
155+
} else {
156+
logger.error("Failed to delete rule: {}", e.getMessage());
157+
listener.onFailure(e);
158+
}
159+
}));
160+
}
161+
}
135162
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.rule.action;
10+
11+
import org.opensearch.common.io.stream.BytesStreamOutput;
12+
import org.opensearch.core.common.io.stream.StreamInput;
13+
import org.opensearch.rule.DeleteRuleRequest;
14+
import org.opensearch.rule.utils.RuleTestUtils;
15+
import org.opensearch.test.OpenSearchTestCase;
16+
17+
import java.io.IOException;
18+
19+
import static org.opensearch.rule.action.GetRuleRequestTests._ID_ONE;
20+
21+
public class DeleteRuleRequestTests extends OpenSearchTestCase {
22+
23+
public void testSerialization() throws IOException {
24+
DeleteRuleRequest request = new DeleteRuleRequest(_ID_ONE, RuleTestUtils.MockRuleFeatureType.INSTANCE);
25+
BytesStreamOutput out = new BytesStreamOutput();
26+
request.writeTo(out);
27+
StreamInput in = out.bytes().streamInput();
28+
DeleteRuleRequest deserialized = new DeleteRuleRequest(in);
29+
assertEquals(request.getRuleId(), deserialized.getRuleId());
30+
assertEquals(request.getFeatureType(), deserialized.getFeatureType());
31+
}
32+
33+
public void testValidate_withMissingId() {
34+
DeleteRuleRequest request = new DeleteRuleRequest("", RuleTestUtils.MockRuleFeatureType.INSTANCE);
35+
assertNotNull(request.validate());
36+
}
37+
}

modules/autotagging-commons/common/src/test/java/org/opensearch/rule/service/IndexStoredRulePersistenceServiceTests.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,22 @@
1010

1111
import org.apache.lucene.search.TotalHits;
1212
import org.opensearch.ResourceNotFoundException;
13+
import org.opensearch.action.DocWriteResponse;
14+
import org.opensearch.action.delete.DeleteRequest;
1315
import org.opensearch.action.search.SearchRequestBuilder;
1416
import org.opensearch.action.search.SearchResponse;
17+
import org.opensearch.action.support.clustermanager.AcknowledgedResponse;
1518
import org.opensearch.cluster.ClusterState;
1619
import org.opensearch.cluster.metadata.Metadata;
1720
import org.opensearch.cluster.service.ClusterService;
1821
import org.opensearch.common.settings.Settings;
1922
import org.opensearch.common.util.concurrent.ThreadContext;
2023
import org.opensearch.core.action.ActionListener;
2124
import org.opensearch.core.common.bytes.BytesArray;
25+
import org.opensearch.core.index.shard.ShardId;
26+
import org.opensearch.index.engine.DocumentMissingException;
2227
import org.opensearch.index.query.QueryBuilder;
28+
import org.opensearch.rule.DeleteRuleRequest;
2329
import org.opensearch.rule.GetRuleRequest;
2430
import org.opensearch.rule.GetRuleResponse;
2531
import org.opensearch.rule.RuleEntityParser;
@@ -40,7 +46,9 @@
4046
import static org.opensearch.rule.XContentRuleParserTests.VALID_JSON;
4147
import static org.opensearch.rule.utils.RuleTestUtils.TEST_INDEX_NAME;
4248
import static org.opensearch.rule.utils.RuleTestUtils._ID_ONE;
49+
import static org.mockito.ArgumentMatchers.any;
4350
import static org.mockito.ArgumentMatchers.anyString;
51+
import static org.mockito.ArgumentMatchers.argThat;
4452
import static org.mockito.Mockito.any;
4553
import static org.mockito.Mockito.anyInt;
4654
import static org.mockito.Mockito.doAnswer;
@@ -162,4 +170,77 @@ private Client setUpMockClient(SearchRequestBuilder searchRequestBuilder) {
162170

163171
return client;
164172
}
173+
174+
public void testDeleteRule_successful() {
175+
String ruleId = "test-rule-id";
176+
DeleteRuleRequest request = new DeleteRuleRequest(ruleId, RuleTestUtils.MockRuleFeatureType.INSTANCE);
177+
178+
Client client = mock(Client.class);
179+
ThreadPool threadPool = mock(ThreadPool.class);
180+
when(client.threadPool()).thenReturn(threadPool);
181+
when(threadPool.getThreadContext()).thenReturn(new ThreadContext(Settings.EMPTY));
182+
183+
RulePersistenceService rulePersistenceService = new IndexStoredRulePersistenceService(
184+
TEST_INDEX_NAME,
185+
client,
186+
MAX_VALUES_PER_PAGE,
187+
mock(RuleEntityParser.class),
188+
mock(RuleQueryMapper.class)
189+
);
190+
191+
ArgumentCaptor<DeleteRequest> requestCaptor = ArgumentCaptor.forClass(DeleteRequest.class);
192+
ArgumentCaptor<ActionListener<org.opensearch.action.delete.DeleteResponse>> listenerCaptor = ArgumentCaptor.forClass(
193+
ActionListener.class
194+
);
195+
196+
@SuppressWarnings("unchecked")
197+
ActionListener<AcknowledgedResponse> listener = mock(ActionListener.class);
198+
199+
rulePersistenceService.deleteRule(request, listener);
200+
201+
verify(client).delete(requestCaptor.capture(), listenerCaptor.capture());
202+
assertEquals(ruleId, requestCaptor.getValue().id());
203+
204+
org.opensearch.action.delete.DeleteResponse deleteResponse = mock(org.opensearch.action.delete.DeleteResponse.class);
205+
when(deleteResponse.getResult()).thenReturn(DocWriteResponse.Result.DELETED);
206+
207+
listenerCaptor.getValue().onResponse(deleteResponse);
208+
209+
verify(listener).onResponse(argThat(AcknowledgedResponse::isAcknowledged));
210+
}
211+
212+
public void testDeleteRule_notFound() {
213+
String ruleId = "missing-rule-id";
214+
DeleteRuleRequest request = new DeleteRuleRequest(ruleId, RuleTestUtils.MockRuleFeatureType.INSTANCE);
215+
216+
Client client = mock(Client.class);
217+
ThreadPool threadPool = mock(ThreadPool.class);
218+
when(client.threadPool()).thenReturn(threadPool);
219+
when(threadPool.getThreadContext()).thenReturn(new ThreadContext(Settings.EMPTY));
220+
221+
RulePersistenceService rulePersistenceService = new IndexStoredRulePersistenceService(
222+
TEST_INDEX_NAME,
223+
client,
224+
MAX_VALUES_PER_PAGE,
225+
mock(RuleEntityParser.class),
226+
mock(RuleQueryMapper.class)
227+
);
228+
229+
ArgumentCaptor<DeleteRequest> requestCaptor = ArgumentCaptor.forClass(DeleteRequest.class);
230+
ArgumentCaptor<ActionListener<org.opensearch.action.delete.DeleteResponse>> listenerCaptor = ArgumentCaptor.forClass(
231+
ActionListener.class
232+
);
233+
234+
@SuppressWarnings("unchecked")
235+
ActionListener<AcknowledgedResponse> listener = mock(ActionListener.class);
236+
237+
rulePersistenceService.deleteRule(request, listener);
238+
239+
verify(client).delete(requestCaptor.capture(), listenerCaptor.capture());
240+
assertEquals(ruleId, requestCaptor.getValue().id());
241+
242+
listenerCaptor.getValue().onFailure(new DocumentMissingException(new ShardId(TEST_INDEX_NAME, "_na_", 0), ruleId));
243+
244+
verify(listener).onFailure(any(ResourceNotFoundException.class));
245+
}
165246
}

modules/autotagging-commons/src/main/java/org/opensearch/rule/RuleFrameworkPlugin.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,12 @@
2222
import org.opensearch.plugins.Plugin;
2323
import org.opensearch.rest.RestController;
2424
import org.opensearch.rest.RestHandler;
25+
import org.opensearch.rule.action.DeleteRuleAction;
2526
import org.opensearch.rule.action.GetRuleAction;
27+
import org.opensearch.rule.action.TransportDeleteRuleAction;
2628
import org.opensearch.rule.action.TransportGetRuleAction;
2729
import org.opensearch.rule.autotagging.AutoTaggingRegistry;
30+
import org.opensearch.rule.rest.RestDeleteRuleAction;
2831
import org.opensearch.rule.rest.RestGetRuleAction;
2932
import org.opensearch.rule.spi.RuleFrameworkExtension;
3033

@@ -50,7 +53,10 @@ public RuleFrameworkPlugin() {}
5053
public List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
5154
// We are consuming the extensions at this place to ensure that the RulePersistenceService is initialised
5255
ruleFrameworkExtensions.forEach(this::consumeFrameworkExtension);
53-
return List.of(new ActionPlugin.ActionHandler<>(GetRuleAction.INSTANCE, TransportGetRuleAction.class));
56+
return List.of(
57+
new ActionPlugin.ActionHandler<>(GetRuleAction.INSTANCE, TransportGetRuleAction.class),
58+
new ActionPlugin.ActionHandler<>(DeleteRuleAction.INSTANCE, TransportDeleteRuleAction.class)
59+
);
5460
}
5561

5662
@Override
@@ -63,7 +69,7 @@ public List<RestHandler> getRestHandlers(
6369
IndexNameExpressionResolver indexNameExpressionResolver,
6470
Supplier<DiscoveryNodes> nodesInCluster
6571
) {
66-
return List.of(new RestGetRuleAction());
72+
return List.of(new RestGetRuleAction(), new RestDeleteRuleAction());
6773
}
6874

6975
@Override
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.rule.action;
10+
11+
import org.opensearch.action.ActionType;
12+
import org.opensearch.action.support.clustermanager.AcknowledgedResponse;
13+
14+
/**
15+
* Action type for deleting a Rule
16+
* @opensearch.experimental
17+
*/
18+
public class DeleteRuleAction extends ActionType<AcknowledgedResponse> {
19+
20+
/**
21+
* An instance of DeleteRuleAction
22+
*/
23+
public static final DeleteRuleAction INSTANCE = new DeleteRuleAction();
24+
25+
/**
26+
* Name for DeleteRuleAction
27+
*/
28+
public static final String NAME = "cluster:admin/opensearch/rule/_delete";
29+
30+
/**
31+
* Default constructor
32+
*/
33+
private DeleteRuleAction() {
34+
super(NAME, AcknowledgedResponse::new);
35+
}
36+
}

0 commit comments

Comments
 (0)