Skip to content

[rule based autotagging] Add Update Rule API Logic #17797

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Implement parallel shard refresh behind cluster settings ([#17782](https://github.com/opensearch-project/OpenSearch/pull/17782))
- Bump OpenSearch Core main branch to 3.0.0 ([#18039](https://github.com/opensearch-project/OpenSearch/pull/18039))
- [Rule based Auto-tagging] Add wlm `ActionFilter` ([#17791](https://github.com/opensearch-project/OpenSearch/pull/17791))
- [Rule based auto-tagging] Add update rule API ([#17797](https://github.com/opensearch-project/OpenSearch/pull/17797))
- 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/))
- Add Warm Disk Threshold Allocation Decider for Warm shards ([#18082](https://github.com/opensearch-project/OpenSearch/pull/18082))
- Add composite directory factory ([#17988](https://github.com/opensearch-project/OpenSearch/pull/17988))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,13 @@

import org.opensearch.action.support.clustermanager.AcknowledgedResponse;
import org.opensearch.core.action.ActionListener;
import org.opensearch.rule.action.CreateRuleRequest;
import org.opensearch.rule.action.CreateRuleResponse;
import org.opensearch.rule.action.DeleteRuleRequest;
import org.opensearch.rule.action.GetRuleRequest;
import org.opensearch.rule.action.GetRuleResponse;
import org.opensearch.rule.action.UpdateRuleRequest;
import org.opensearch.rule.action.UpdateRuleResponse;

/**
* Interface for a service that handles rule persistence CRUD operations.
Expand Down Expand Up @@ -37,4 +44,11 @@ public interface RulePersistenceService {
* @param listener The listener that will handle the response or failure.
*/
void deleteRule(DeleteRuleRequest request, ActionListener<AcknowledgedResponse> listener);

/**
* Update rule based on the provided request.
* @param request The request containing the details for updating the rule.
* @param listener The listener that will handle the response or failure.
*/
void updateRule(UpdateRuleRequest request, ActionListener<UpdateRuleResponse> listener);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
package org.opensearch.rule;

import org.opensearch.common.annotation.ExperimentalApi;
import org.opensearch.rule.action.GetRuleRequest;

/**
* This interface is responsible for creating query objects which storage layer can use
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
package org.opensearch.rule;

import org.opensearch.core.action.ActionListener;
import org.opensearch.rule.action.CreateRuleRequest;
import org.opensearch.rule.action.CreateRuleResponse;
import org.opensearch.rule.action.UpdateRuleRequest;
import org.opensearch.rule.action.UpdateRuleResponse;

/**
* Interface that handles rule routing logic
Expand All @@ -22,4 +26,11 @@ public interface RuleRoutingService {
* @param listener listener to handle the final response
*/
void handleCreateRuleRequest(CreateRuleRequest request, ActionListener<CreateRuleResponse> listener);

/**
* Handles a update rule request by routing it to the appropriate node.
* @param request the update rule request
* @param listener listener to handle the final response
*/
void handleUpdateRuleRequest(UpdateRuleRequest request, ActionListener<UpdateRuleResponse> listener);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@
package org.opensearch.rule;

import org.opensearch.common.annotation.ExperimentalApi;
import org.opensearch.rule.action.UpdateRuleRequest;
import org.opensearch.rule.autotagging.Attribute;
import org.opensearch.rule.autotagging.FeatureType;
import org.opensearch.rule.autotagging.Rule;

import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;

/**
* Utility class for operations related to {@link Rule} objects.
Expand All @@ -29,6 +35,24 @@ public class RuleUtils {
*/
public RuleUtils() {}

/**
* Computes a UUID-based hash string for a rule based on its key attributes.
* @param description the rule's description
* @param featureType the rule's feature type
* @param attributeMap the rule's attribute map (will use its toString representation)
* @param featureValue the rule's feature value
*/
public static String computeRuleHash(
String description,
FeatureType featureType,
Map<Attribute, Set<String>> attributeMap,
String featureValue
) {
String combined = description + "|" + featureType.getName() + "|" + attributeMap.toString() + "|" + featureValue;
UUID uuid = UUID.nameUUIDFromBytes(combined.getBytes(StandardCharsets.UTF_8));
return uuid.toString();
}

/**
* Checks if a duplicate rule exists and returns its id.
* Two rules are considered to be duplicate when meeting all the criteria below
Expand All @@ -38,12 +62,11 @@ public RuleUtils() {}
* between the current rule and the one being checked.
*
* @param rule The rule to be validated against ruleMap.
* @param ruleMap This map contains existing rules to be checked
* @param ruleList This list contains existing rules to be checked
*/
public static Optional<String> getDuplicateRuleId(Rule rule, Map<String, Rule> ruleMap) {
public static Optional<String> getDuplicateRuleId(Rule rule, List<Rule> ruleList) {
Map<Attribute, Set<String>> targetAttributeMap = rule.getAttributeMap();
for (Map.Entry<String, Rule> entry : ruleMap.entrySet()) {
Rule currRule = entry.getValue();
for (Rule currRule : ruleList) {
Map<Attribute, Set<String>> existingAttributeMap = currRule.getAttributeMap();

if (rule.getFeatureType() != currRule.getFeatureType() || targetAttributeMap.size() != existingAttributeMap.size()) {
Expand All @@ -59,9 +82,30 @@ public static Optional<String> getDuplicateRuleId(Rule rule, Map<String, Rule> r
}
}
if (allAttributesIntersect) {
return Optional.of(entry.getKey());
return Optional.of(currRule.getId());
}
}
return Optional.empty();
}

/**
* Creates an updated {@link Rule} object by applying non-null fields from the given {@link UpdateRuleRequest}
* to the original rule. Fields not provided in the request will retain their values from the original rule.
* @param originalRule the original rule to update
* @param request the request containing the new values for the rule
* @param featureType the feature type to assign to the updated rule
*/
public static Rule composeUpdatedRule(Rule originalRule, UpdateRuleRequest request, FeatureType featureType) {
String requestDescription = request.getDescription();
Map<Attribute, Set<String>> requestMap = request.getAttributeMap();
String requestLabel = request.getFeatureValue();
return new Rule(
originalRule.getId(),
requestDescription == null ? originalRule.getDescription() : requestDescription,
requestMap == null || requestMap.isEmpty() ? originalRule.getAttributeMap() : requestMap,
featureType,
requestLabel == null ? originalRule.getFeatureValue() : requestLabel,
Instant.now().toString()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* compatible open source license.
*/

package org.opensearch.rule;
package org.opensearch.rule.action;

import org.opensearch.action.ActionRequest;
import org.opensearch.action.ActionRequestValidationException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* compatible open source license.
*/

package org.opensearch.rule;
package org.opensearch.rule.action;

import org.opensearch.core.action.ActionResponse;
import org.opensearch.core.common.io.stream.StreamInput;
Expand All @@ -17,15 +17,12 @@
import org.opensearch.rule.autotagging.Rule;

import java.io.IOException;
import java.util.Map;

import static org.opensearch.rule.autotagging.Rule._ID_STRING;

/**
* Response for the create API for Rule
* Example response:
* {
* "_id":"wi6VApYBoX5wstmtU_8l",
* "id":"wi6VApYBoX5wstmtU_8l",
* "description":"description1",
* "index_pattern":["log*", "uvent*"],
* "workload_group":"poOiU851RwyLYvV5lbvv5w",
Expand All @@ -34,16 +31,13 @@
* @opensearch.experimental
*/
public class CreateRuleResponse extends ActionResponse implements ToXContent, ToXContentObject {
private final String _id;
private final Rule rule;

/**
* contructor for CreateRuleResponse
* @param id - the id for the rule created
* @param rule - the rule created
*/
public CreateRuleResponse(String id, final Rule rule) {
this._id = id;
public CreateRuleResponse(final Rule rule) {
this.rule = rule;
}

Expand All @@ -52,19 +46,17 @@ public CreateRuleResponse(String id, final Rule rule) {
* @param in - The {@link StreamInput} instance to read from.
*/
public CreateRuleResponse(StreamInput in) throws IOException {
_id = in.readString();
rule = new Rule(in);
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(_id);
rule.writeTo(out);
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
return rule.toXContent(builder, new MapParams(Map.of(_ID_STRING, _id)));
return rule.toXContent(builder, params);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* compatible open source license.
*/

package org.opensearch.rule;
package org.opensearch.rule.action;

import org.opensearch.action.ActionRequest;
import org.opensearch.action.ActionRequestValidationException;
Expand Down Expand Up @@ -80,7 +80,7 @@ public String getRuleId() {
*
* @return The feature type.
*/
public FeatureType getFeatureType() {
FeatureType getFeatureType() {
return featureType;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* compatible open source license.
*/

package org.opensearch.rule;
package org.opensearch.rule.action;

import org.opensearch.action.ActionRequest;
import org.opensearch.action.ActionRequestValidationException;
Expand Down Expand Up @@ -67,7 +67,7 @@ public GetRuleRequest(StreamInput in) throws IOException {
@Override
public ActionRequestValidationException validate() {
if (RuleValidator.isEmpty(id)) {
throw new IllegalArgumentException(Rule._ID_STRING + " cannot be empty.");
throw new IllegalArgumentException(Rule.ID_STRING + " cannot be empty.");
}
if (RuleValidator.isEmpty(searchAfter)) {
throw new IllegalArgumentException("search_after cannot be empty.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* compatible open source license.
*/

package org.opensearch.rule;
package org.opensearch.rule.action;

import org.opensearch.common.annotation.ExperimentalApi;
import org.opensearch.core.action.ActionResponse;
Expand All @@ -18,17 +18,15 @@
import org.opensearch.rule.autotagging.Rule;

import java.io.IOException;
import java.util.Map;

import static org.opensearch.rule.autotagging.Rule._ID_STRING;
import java.util.List;

/**
* Response for the get API for Rule.
* Example response:
* {
* "rules": [
* {
* "_id": "z1MJApUB0zgMcDmz-UQq",
* "id": "z1MJApUB0zgMcDmz-UQq",
* "description": "Rule for tagging workload_group_id to index123"
* "index_pattern": ["index123"],
* "workload_group": "workload_group_id",
Expand All @@ -42,15 +40,15 @@
*/
@ExperimentalApi
public class GetRuleResponse extends ActionResponse implements ToXContent, ToXContentObject {
private final Map<String, Rule> rules;
private final List<Rule> rules;
private final String searchAfter;

/**
* Constructor for GetRuleResponse
* @param rules - Rules get from the request
* @param searchAfter - The sort value used for pagination.
*/
public GetRuleResponse(final Map<String, Rule> rules, String searchAfter) {
public GetRuleResponse(final List<Rule> rules, String searchAfter) {
this.rules = rules;
this.searchAfter = searchAfter;
}
Expand All @@ -60,21 +58,21 @@ public GetRuleResponse(final Map<String, Rule> rules, String searchAfter) {
* @param in - The {@link StreamInput} instance to read from.
*/
public GetRuleResponse(StreamInput in) throws IOException {
this(in.readMap(StreamInput::readString, Rule::new), in.readOptionalString());
this(in.readList(Rule::new), in.readOptionalString());
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeMap(rules, StreamOutput::writeString, (outStream, rule) -> rule.writeTo(outStream));
out.writeList(rules);
out.writeOptionalString(searchAfter);
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.startArray("rules");
for (Map.Entry<String, Rule> entry : rules.entrySet()) {
entry.getValue().toXContent(builder, new MapParams(Map.of(_ID_STRING, entry.getKey())));
for (Rule rule : rules) {
rule.toXContent(builder, params);
}
builder.endArray();
if (searchAfter != null && !searchAfter.isEmpty()) {
Expand All @@ -87,7 +85,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
/**
* rules getter
*/
public Map<String, Rule> getRules() {
public List<Rule> getRules() {
return rules;
}
}
Loading
Loading