diff --git a/CHANGELOG.md b/CHANGELOG.md index c08995720da81..31daa2aabdeb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased 3.x] ### Added +- [Rule based auto-tagging] Add get rule API ([#17336](https://github.com/opensearch-project/OpenSearch/pull/17336)) - Add multi-threaded writer support in pull-based ingestion ([#17912](https://github.com/opensearch-project/OpenSearch/pull/17912)) - Unset discovery nodes for every transport node actions request ([#17682](https://github.com/opensearch-project/OpenSearch/pull/17682)) diff --git a/modules/autotagging-commons/build.gradle b/modules/autotagging-commons/build.gradle new file mode 100644 index 0000000000000..47ee9ff75b980 --- /dev/null +++ b/modules/autotagging-commons/build.gradle @@ -0,0 +1,21 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + + +opensearchplugin { + description = 'OpenSearch Rule Framework plugin' + classname = 'org.opensearch.rule.RuleFrameworkPlugin' +} + +dependencies { + api project("spi") + api project("common") + testImplementation(project(":test:framework")) { + exclude group: 'org.opensearch', module: 'opensearch-core' + } +} diff --git a/libs/autotagging-commons/build.gradle b/modules/autotagging-commons/common/build.gradle similarity index 67% rename from libs/autotagging-commons/build.gradle rename to modules/autotagging-commons/common/build.gradle index cf3a75440c299..0dffb80015647 100644 --- a/libs/autotagging-commons/build.gradle +++ b/modules/autotagging-commons/common/build.gradle @@ -6,15 +6,22 @@ * compatible open source license. */ +apply plugin: 'opensearch.build' +apply plugin: 'opensearch.publish' + +description = 'OpenSearch Rule framework common constructs which spi and module shares' + dependencies { api 'org.apache.commons:commons-collections4:4.4' - api project(":server") + implementation project(":libs:opensearch-common") + compileOnly project(":server") testImplementation(project(":test:framework")) { exclude group: 'org.opensearch', module: 'opensearch-core' } } + tasks.named("dependencyLicenses").configure { mapping from: /commons-collections.*/, to: 'commons-collections' } diff --git a/libs/autotagging-commons/licenses/commons-collections-LICENSE.txt b/modules/autotagging-commons/common/licenses/commons-collections-LICENSE.txt similarity index 100% rename from libs/autotagging-commons/licenses/commons-collections-LICENSE.txt rename to modules/autotagging-commons/common/licenses/commons-collections-LICENSE.txt diff --git a/libs/autotagging-commons/licenses/commons-collections-NOTICE.txt b/modules/autotagging-commons/common/licenses/commons-collections-NOTICE.txt similarity index 100% rename from libs/autotagging-commons/licenses/commons-collections-NOTICE.txt rename to modules/autotagging-commons/common/licenses/commons-collections-NOTICE.txt diff --git a/libs/autotagging-commons/licenses/commons-collections4-4.4.jar.sha1 b/modules/autotagging-commons/common/licenses/commons-collections4-4.4.jar.sha1 similarity index 100% rename from libs/autotagging-commons/licenses/commons-collections4-4.4.jar.sha1 rename to modules/autotagging-commons/common/licenses/commons-collections4-4.4.jar.sha1 diff --git a/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/GetRuleRequest.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/GetRuleRequest.java new file mode 100644 index 0000000000000..9cdebb782edc9 --- /dev/null +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/GetRuleRequest.java @@ -0,0 +1,116 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.common.annotation.ExperimentalApi; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.rule.autotagging.Attribute; +import org.opensearch.rule.autotagging.FeatureType; +import org.opensearch.rule.autotagging.Rule; +import org.opensearch.rule.autotagging.RuleValidator; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * A request for get Rule + * Example Request: + * The endpoint "localhost:9200/_wlm/rule" is specific to the Workload Management feature to manage rules + * curl -X GET "localhost:9200/_wlm/rule" - get all rules + * curl -X GET "localhost:9200/_wlm/rule/{_id}" - get single rule by id + * curl -X GET "localhost:9200/_wlm/rule?index_pattern=a,b" - get all rules containing attribute index_pattern as a or b + * @opensearch.experimental + */ +@ExperimentalApi +public class GetRuleRequest extends ActionRequest { + private final String id; + private final Map> attributeFilters; + private final String searchAfter; + private final FeatureType featureType; + + /** + * Constructor for GetRuleRequest + * @param id - Rule id to get + * @param attributeFilters - Rules will be filtered based on the attribute-value mappings. + * @param searchAfter - The sort value used for pagination. + * @param featureType - The feature type related to rule. + */ + public GetRuleRequest(String id, Map> attributeFilters, String searchAfter, FeatureType featureType) { + this.id = id; + this.attributeFilters = attributeFilters; + this.searchAfter = searchAfter; + this.featureType = featureType; + } + + /** + * Constructs a GetRuleRequest from a StreamInput for deserialization. + * @param in - The {@link StreamInput} instance to read from. + */ + public GetRuleRequest(StreamInput in) throws IOException { + super(in); + id = in.readOptionalString(); + featureType = FeatureType.from(in); + attributeFilters = in.readMap(i -> Attribute.from(i, featureType), i -> new HashSet<>(i.readStringList())); + searchAfter = in.readOptionalString(); + } + + @Override + public ActionRequestValidationException validate() { + if (RuleValidator.isEmpty(id)) { + throw new IllegalArgumentException(Rule._ID_STRING + " cannot be empty."); + } + if (RuleValidator.isEmpty(searchAfter)) { + throw new IllegalArgumentException("search_after cannot be empty."); + } + return null; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeOptionalString(id); + featureType.writeTo(out); + out.writeMap(attributeFilters, (output, attribute) -> attribute.writeTo(output), StreamOutput::writeStringCollection); + out.writeOptionalString(searchAfter); + } + + /** + * id getter + */ + public String getId() { + return id; + } + + /** + * attributeFilters getter + */ + public Map> getAttributeFilters() { + return attributeFilters; + } + + /** + * searchAfter getter + */ + public String getSearchAfter() { + return searchAfter; + } + + /** + * FeatureType getter + * @return + */ + public FeatureType getFeatureType() { + return featureType; + } +} diff --git a/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/GetRuleResponse.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/GetRuleResponse.java new file mode 100644 index 0000000000000..e3c0bb49043a7 --- /dev/null +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/GetRuleResponse.java @@ -0,0 +1,93 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule; + +import org.opensearch.common.annotation.ExperimentalApi; +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; +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 get API for Rule. + * Example response: + * { + * "rules": [ + * { + * "_id": "z1MJApUB0zgMcDmz-UQq", + * "description": "Rule for tagging query_group_id to index123" + * "index_pattern": ["index123"], + * "query_group": "query_group_id", + * "updated_at": "2025-02-14T01:19:22.589Z" + * }, + * ... + * ], + * "search_after": ["z1MJApUB0zgMcDmz-UQq"] + * } + * @opensearch.experimental + */ +@ExperimentalApi +public class GetRuleResponse extends ActionResponse implements ToXContent, ToXContentObject { + private final Map 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 rules, String searchAfter) { + this.rules = rules; + this.searchAfter = searchAfter; + } + + /** + * Constructs a GetRuleResponse from a StreamInput for deserialization + * @param in - The {@link StreamInput} instance to read from. + */ + public GetRuleResponse(StreamInput in) throws IOException { + this(in.readMap(StreamInput::readString, Rule::new), in.readOptionalString()); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeMap(rules, StreamOutput::writeString, (outStream, rule) -> rule.writeTo(outStream)); + out.writeOptionalString(searchAfter); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.startArray("rules"); + for (Map.Entry entry : rules.entrySet()) { + entry.getValue().toXContent(builder, new MapParams(Map.of(_ID_STRING, entry.getKey()))); + } + builder.endArray(); + if (searchAfter != null && !searchAfter.isEmpty()) { + builder.field("search_after", new Object[] { searchAfter }); + } + builder.endObject(); + return builder; + } + + /** + * rules getter + */ + public Map getRules() { + return rules; + } +} diff --git a/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/RuleAttribute.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/RuleAttribute.java new file mode 100644 index 0000000000000..cd1e76f22d9d0 --- /dev/null +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/RuleAttribute.java @@ -0,0 +1,47 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule; + +import org.opensearch.rule.autotagging.Attribute; + +/** + * Generic Rule attributes that features can use out of the use by using the lib. + * @opensearch.experimental + */ +public enum RuleAttribute implements Attribute { + /** + * Represents the index_pattern attribute in RuleAttribute + */ + INDEX_PATTERN("index_pattern"); + + private final String name; + + RuleAttribute(String name) { + this.name = name; + validateAttribute(); + } + + @Override + public String getName() { + return name; + } + + /** + * Retrieves the RuleAttribute from a name string + * @param name - attribute name + */ + public static RuleAttribute fromName(String name) { + for (RuleAttribute attr : RuleAttribute.values()) { + if (attr.getName().equals(name)) { + return attr; + } + } + throw new IllegalArgumentException("Unknown RuleAttribute: " + name); + } +} diff --git a/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/RuleEntityParser.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/RuleEntityParser.java new file mode 100644 index 0000000000000..dcc1b8ae1f681 --- /dev/null +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/RuleEntityParser.java @@ -0,0 +1,27 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule; + +import org.opensearch.common.annotation.ExperimentalApi; +import org.opensearch.rule.autotagging.Rule; + +/** + * Interface to parse various string representation of Rule entity + * clients can use/implement as per their choice of storage for the Rule + */ +@ExperimentalApi +public interface RuleEntityParser { + /** + * Parses the src string into {@link Rule} object + * @param src String representation of Rule, it could be a XContentObject or something else based on + * where and how it is stored + * @return Rule + */ + Rule parse(String src); +} diff --git a/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/RulePersistenceService.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/RulePersistenceService.java new file mode 100644 index 0000000000000..5d585b8541a51 --- /dev/null +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/RulePersistenceService.java @@ -0,0 +1,25 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule; + +import org.opensearch.core.action.ActionListener; + +/** + * Interface for a service that handles rule persistence CRUD operations. + * @opensearch.experimental + */ +public interface RulePersistenceService { + + /** + * Get rules based on the provided request. + * @param request The request containing the details for retrieving the rule. + * @param listener The listener that will handle the response or failure. + */ + void getRule(GetRuleRequest request, ActionListener listener); +} diff --git a/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/RuleQueryMapper.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/RuleQueryMapper.java new file mode 100644 index 0000000000000..62dce30d04109 --- /dev/null +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/RuleQueryMapper.java @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule; + +import org.opensearch.common.annotation.ExperimentalApi; + +/** + * This interface is responsible for creating query objects which storage layer can use + * to query the backend + * @param + */ +@ExperimentalApi +public interface RuleQueryMapper { + /** + * This method translates the {@link GetRuleRequest} to a storage engine specific query object + * @param request + * @return + */ + T from(GetRuleRequest request); +} diff --git a/libs/autotagging-commons/src/main/java/org/opensearch/rule/attribute_extractor/AttributeExtractor.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/attribute_extractor/AttributeExtractor.java similarity index 93% rename from libs/autotagging-commons/src/main/java/org/opensearch/rule/attribute_extractor/AttributeExtractor.java rename to modules/autotagging-commons/common/src/main/java/org/opensearch/rule/attribute_extractor/AttributeExtractor.java index 3e13ea54fad34..186211c65a76e 100644 --- a/libs/autotagging-commons/src/main/java/org/opensearch/rule/attribute_extractor/AttributeExtractor.java +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/attribute_extractor/AttributeExtractor.java @@ -8,7 +8,7 @@ package org.opensearch.rule.attribute_extractor; -import org.opensearch.autotagging.Attribute; +import org.opensearch.rule.autotagging.Attribute; /** * This interface defines the contract for extracting the attributes for Rule based auto-tagging feature diff --git a/libs/autotagging-commons/src/main/java/org/opensearch/rule/attribute_extractor/package-info.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/attribute_extractor/package-info.java similarity index 100% rename from libs/autotagging-commons/src/main/java/org/opensearch/rule/attribute_extractor/package-info.java rename to modules/autotagging-commons/common/src/main/java/org/opensearch/rule/attribute_extractor/package-info.java diff --git a/server/src/main/java/org/opensearch/autotagging/Attribute.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/Attribute.java similarity index 94% rename from server/src/main/java/org/opensearch/autotagging/Attribute.java rename to modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/Attribute.java index 61dfc7e704c20..76aa31d7d00f0 100644 --- a/server/src/main/java/org/opensearch/autotagging/Attribute.java +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/Attribute.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.autotagging; +package org.opensearch.rule.autotagging; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; @@ -23,6 +23,10 @@ * @opensearch.experimental */ public interface Attribute extends Writeable { + /** + * Returns the attribute string representation + * @return + */ String getName(); /** diff --git a/server/src/main/java/org/opensearch/autotagging/AutoTaggingRegistry.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/AutoTaggingRegistry.java similarity index 90% rename from server/src/main/java/org/opensearch/autotagging/AutoTaggingRegistry.java rename to modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/AutoTaggingRegistry.java index 394b89922dd2b..9cfc5ccb1e342 100644 --- a/server/src/main/java/org/opensearch/autotagging/AutoTaggingRegistry.java +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/AutoTaggingRegistry.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.autotagging; +package org.opensearch.rule.autotagging; import org.opensearch.ResourceNotFoundException; @@ -26,8 +26,20 @@ public class AutoTaggingRegistry { * The registration of FeatureType should only be done during boot-up. */ public static final Map featureTypesRegistryMap = new HashMap<>(); + /** + * Max chars a feature type can assume + */ public static final int MAX_FEATURE_TYPE_NAME_LENGTH = 30; + /** + * Make the class un-initialisable + */ + private AutoTaggingRegistry() {} + + /** + * Registers the new feature type + * @param featureType + */ public static void registerFeatureType(FeatureType featureType) { validateFeatureType(featureType); String name = featureType.getName(); diff --git a/server/src/main/java/org/opensearch/autotagging/FeatureType.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/FeatureType.java similarity index 73% rename from server/src/main/java/org/opensearch/autotagging/FeatureType.java rename to modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/FeatureType.java index b446f62f6d764..c752f917264de 100644 --- a/server/src/main/java/org/opensearch/autotagging/FeatureType.java +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/FeatureType.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.autotagging; +package org.opensearch.rule.autotagging; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; @@ -28,9 +28,19 @@ * @opensearch.experimental */ public interface FeatureType extends Writeable { + /** + * Default value for max attribute values + */ int DEFAULT_MAX_ATTRIBUTE_VALUES = 10; + /** + * Default value for max number of chars in a single value + */ int DEFAULT_MAX_ATTRIBUTE_VALUE_LENGTH = 100; + /** + * Returns name + * @return + */ String getName(); /** @@ -39,16 +49,32 @@ public interface FeatureType extends Writeable { */ Map getAllowedAttributesRegistry(); + /** + * returns max attribute values + * @return + */ default int getMaxNumberOfValuesPerAttribute() { return DEFAULT_MAX_ATTRIBUTE_VALUES; } + /** + * returns value for max number of chars in a single value + * @return + */ default int getMaxCharLengthPerAttributeValue() { return DEFAULT_MAX_ATTRIBUTE_VALUE_LENGTH; } + /** + * makes the feature type usable and available to framework plugin + */ void registerFeatureType(); + /** + * checks the validity of the input attribute + * @param attribute + * @return + */ default boolean isValidAttribute(Attribute attribute) { return getAllowedAttributesRegistry().containsValue(attribute); } @@ -67,7 +93,22 @@ default void writeTo(StreamOutput out) throws IOException { out.writeString(getName()); } + /** + * parses the FeatureType using StreamInput + * @param in + * @return + * @throws IOException + */ static FeatureType from(StreamInput in) throws IOException { return AutoTaggingRegistry.getFeatureType(in.readString()); } + + /** + * Returns the instance for the passed param + * @param name + * @return + */ + static FeatureType from(String name) { + return AutoTaggingRegistry.getFeatureType(name); + } } diff --git a/server/src/main/java/org/opensearch/autotagging/Rule.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/Rule.java similarity index 83% rename from server/src/main/java/org/opensearch/autotagging/Rule.java rename to modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/Rule.java index 10e8a503b8dc6..e1b003f0085c6 100644 --- a/server/src/main/java/org/opensearch/autotagging/Rule.java +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/Rule.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.autotagging; +package org.opensearch.rule.autotagging; import org.opensearch.core.common.io.stream.StreamInput; import org.opensearch.core.common.io.stream.StreamOutput; @@ -44,10 +44,27 @@ public class Rule implements Writeable, ToXContentObject { private final String featureValue; private final String updatedAt; private final RuleValidator ruleValidator; + /** + * id field + */ public static final String _ID_STRING = "_id"; + /** + * description field + */ public static final String DESCRIPTION_STRING = "description"; + /** + * updated_at field + */ public static final String UPDATED_AT_STRING = "updated_at"; + /** + * Main constructor + * @param description + * @param attributeMap + * @param featureType + * @param featureValue + * @param updatedAt + */ public Rule( String description, Map> attributeMap, @@ -64,6 +81,11 @@ public Rule( this.ruleValidator.validate(); } + /** + * constructor to init the object from StreamInput + * @param in + * @throws IOException + */ public Rule(StreamInput in) throws IOException { description = in.readString(); featureType = FeatureType.from(in); @@ -83,26 +105,53 @@ public void writeTo(StreamOutput out) throws IOException { out.writeString(updatedAt); } + /** + * static utility method to parse the Rule object + * @param parser + * @param featureType + * @return + * @throws IOException + */ public static Rule fromXContent(final XContentParser parser, FeatureType featureType) throws IOException { return Builder.fromXContent(parser, featureType).build(); } + /** + * description getter + * @return + */ public String getDescription() { return description; } + /** + * feature value getter + * @return + */ public String getFeatureValue() { return featureValue; } + /** + * updatedAt getter + * @return + */ public String getUpdatedAt() { return updatedAt; } + /** + * FeatureType getter + * @return + */ public FeatureType getFeatureType() { return featureType; } + /** + * attribute map getter + * @return + */ public Map> getAttributeMap() { return attributeMap; } @@ -163,6 +212,13 @@ public static class Builder { private Builder() {} + /** + * Parses the Rule object from XContentParser + * @param parser + * @param featureType + * @return + * @throws IOException + */ public static Builder fromXContent(XContentParser parser, FeatureType featureType) throws IOException { if (parser.currentToken() == null) { parser.nextToken(); @@ -194,7 +250,7 @@ public static Builder fromXContent(XContentParser parser, FeatureType featureTyp return builder.attributeMap(attributeMap1); } - public static void fromXContentParseArray( + private static void fromXContentParseArray( XContentParser parser, String fieldName, FeatureType featureType, @@ -215,39 +271,76 @@ public static void fromXContentParseArray( attributeMap.put(attribute, attributeValueSet); } + /** + * sets the description + * @param description + * @return + */ public Builder description(String description) { this.description = description; return this; } + /** + * sets the feature value + * @param featureValue + * @return + */ public Builder featureValue(String featureValue) { this.featureValue = featureValue; return this; } + /** + * Sets the attribute map + * @param attributeMap + * @return + */ public Builder attributeMap(Map> attributeMap) { this.attributeMap = attributeMap; return this; } + /** + * sets the feature type + * @param featureType + * @return + */ public Builder featureType(FeatureType featureType) { this.featureType = featureType; return this; } + /** + * sets the updatedAt + * @param updatedAt + * @return + */ public Builder updatedAt(String updatedAt) { this.updatedAt = updatedAt; return this; } + /** + * Builds the Rule object + * @return + */ public Rule build() { return new Rule(description, attributeMap, featureType, featureValue, updatedAt); } + /** + * Returns feature type for Rule + * @return + */ public String getFeatureValue() { return featureValue; } + /** + * Returns attribute map + * @return + */ public Map> getAttributeMap() { return attributeMap; } diff --git a/server/src/main/java/org/opensearch/autotagging/RuleValidator.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/RuleValidator.java similarity index 92% rename from server/src/main/java/org/opensearch/autotagging/RuleValidator.java rename to modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/RuleValidator.java index 9614761042081..3314598e8211a 100644 --- a/server/src/main/java/org/opensearch/autotagging/RuleValidator.java +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/RuleValidator.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.autotagging; +package org.opensearch.rule.autotagging; import org.opensearch.common.ValidationException; import org.joda.time.Instant; @@ -31,8 +31,19 @@ public class RuleValidator { private final String featureValue; private final String updatedAt; private final FeatureType featureType; + /** + * Max description length + */ public static final int MAX_DESCRIPTION_LENGTH = 256; + /** + * deafult constructor + * @param description + * @param attributeMap + * @param featureValue + * @param updatedAt + * @param featureType + */ public RuleValidator( String description, Map> attributeMap, @@ -47,6 +58,9 @@ public RuleValidator( this.featureType = featureType; } + /** + * validates the rule object fields + */ public void validate() { List errorMessages = new ArrayList<>(); errorMessages.addAll(validateStringFields()); @@ -80,6 +94,15 @@ private boolean isNullOrEmpty(String str) { return str == null || str.isEmpty(); } + /** + * Utility method which checks the empty string in context of Rule + * @param str + * @return + */ + public static boolean isEmpty(String str) { + return str != null && str.isEmpty(); + } + private List validateFeatureType() { if (featureType == null) { return List.of("Couldn't identify which feature the rule belongs to. Rule feature can't be null."); diff --git a/server/src/main/java/org/opensearch/autotagging/package-info.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/package-info.java similarity index 86% rename from server/src/main/java/org/opensearch/autotagging/package-info.java rename to modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/package-info.java index 1c0794c18241b..d93f3be6493c3 100644 --- a/server/src/main/java/org/opensearch/autotagging/package-info.java +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/autotagging/package-info.java @@ -10,4 +10,4 @@ * This package contains auto tagging constructs */ -package org.opensearch.autotagging; +package org.opensearch.rule.autotagging; diff --git a/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/package-info.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/package-info.java new file mode 100644 index 0000000000000..8dc3a4a68af26 --- /dev/null +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/package-info.java @@ -0,0 +1,13 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** + * Holds the common constructs for Rule Framework + */ + +package org.opensearch.rule; diff --git a/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/service/IndexStoredRulePersistenceService.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/service/IndexStoredRulePersistenceService.java new file mode 100644 index 0000000000000..52fe043182e5b --- /dev/null +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/service/IndexStoredRulePersistenceService.java @@ -0,0 +1,135 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule.service; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.ResourceNotFoundException; +import org.opensearch.action.search.SearchRequestBuilder; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.action.ActionListener; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.rule.GetRuleRequest; +import org.opensearch.rule.GetRuleResponse; +import org.opensearch.rule.RuleEntityParser; +import org.opensearch.rule.RulePersistenceService; +import org.opensearch.rule.RuleQueryMapper; +import org.opensearch.rule.autotagging.Rule; +import org.opensearch.search.SearchHit; +import org.opensearch.search.sort.SortOrder; +import org.opensearch.transport.client.Client; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import static org.opensearch.rule.autotagging.Rule._ID_STRING; + +/** + * This class encapsulates the logic to manage the lifecycle of rules at index level + * @opensearch.experimental + */ +public class IndexStoredRulePersistenceService implements RulePersistenceService { + /** + * The system index name used for storing rules + */ + private final String indexName; + private final Client client; + private final int maxRulesPerPage; + private final RuleEntityParser parser; + private final RuleQueryMapper queryBuilder; + private static final Logger logger = LogManager.getLogger(IndexStoredRulePersistenceService.class); + + /** + * Constructs an instance of {@link IndexStoredRulePersistenceService} with the specified parameters. + * This service handles persistence and retrieval of stored rules within an OpenSearch index. + * @param indexName - The name of the OpenSearch index where the rules are stored. + * @param client - The OpenSearch client used to interact with the OpenSearch cluster. + * @param maxRulesPerPage - The maximum number of rules that can be returned in a single get request. + * @param parser + * @param queryBuilder + */ + public IndexStoredRulePersistenceService( + String indexName, + Client client, + int maxRulesPerPage, + RuleEntityParser parser, + RuleQueryMapper queryBuilder + ) { + this.indexName = indexName; + this.client = client; + this.maxRulesPerPage = maxRulesPerPage; + this.parser = parser; + this.queryBuilder = queryBuilder; + } + + /** + * Entry point for the get rule api logic in persistence service. + * @param getRuleRequest the getRuleRequest to process. + * @param listener the listener for GetRuleResponse. + */ + public void getRule(GetRuleRequest getRuleRequest, ActionListener listener) { + final QueryBuilder getQueryBuilder = queryBuilder.from(getRuleRequest) + .filter(QueryBuilders.existsQuery(getRuleRequest.getFeatureType().getName())); + getRuleFromIndex(getRuleRequest.getId(), getQueryBuilder, getRuleRequest.getSearchAfter(), listener); + } + + /** + * Get rules from index. If id is provided, we only get a single rule. + * Otherwise, we get all rules that satisfy the attributeFilters. + * @param queryBuilder query object + * @param searchAfter - The sort values from the last document of the previous page, used for pagination + * @param listener - ActionListener for GetRuleResponse + */ + private void getRuleFromIndex(String id, QueryBuilder queryBuilder, String searchAfter, ActionListener listener) { + // Stash the current thread context when interacting with system index to perform + // operations as the system itself, bypassing authorization checks. This ensures that + // actions within this block are trusted and executed with system-level privileges. + try (ThreadContext.StoredContext context = getContext()) { + SearchRequestBuilder searchRequest = client.prepareSearch(indexName).setQuery(queryBuilder).setSize(maxRulesPerPage); + if (searchAfter != null) { + searchRequest.addSort(_ID_STRING, SortOrder.ASC).searchAfter(new Object[] { searchAfter }); + } + searchRequest.execute(ActionListener.wrap(searchResponse -> { + List hits = Arrays.asList(searchResponse.getHits().getHits()); + if (hasNoResults(id, listener, hits)) return; + handleGetRuleResponse(hits, listener); + }, e -> { + logger.error("Failed to fetch all rules: {}", e.getMessage()); + listener.onFailure(e); + })); + } + } + + private static boolean hasNoResults(String id, ActionListener listener, List hits) { + if (id != null && hits.isEmpty()) { + logger.error("Rule with ID " + id + " not found."); + listener.onFailure(new ResourceNotFoundException("Rule with ID " + id + " not found.")); + return true; + } + return false; + } + + /** + * Process searchResponse from index and send a GetRuleResponse + * @param hits - Response received from index + * @param listener - ActionListener for GetRuleResponse + */ + void handleGetRuleResponse(List hits, ActionListener listener) { + Map ruleMap = hits.stream().collect(Collectors.toMap(SearchHit::getId, hit -> parser.parse(hit.getSourceAsString()))); + String nextSearchAfter = hits.isEmpty() ? null : hits.get(hits.size() - 1).getId(); + listener.onResponse(new GetRuleResponse(ruleMap, nextSearchAfter)); + } + + private ThreadContext.StoredContext getContext() { + return client.threadPool().getThreadContext().stashContext(); + } +} diff --git a/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/service/package-info.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/service/package-info.java new file mode 100644 index 0000000000000..f51b281884dc6 --- /dev/null +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/service/package-info.java @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** + * This package contains abstract service classes for rules + */ +package org.opensearch.rule.service; diff --git a/libs/autotagging-commons/src/main/java/org/opensearch/rule/storage/AttributeValueStore.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/storage/AttributeValueStore.java similarity index 100% rename from libs/autotagging-commons/src/main/java/org/opensearch/rule/storage/AttributeValueStore.java rename to modules/autotagging-commons/common/src/main/java/org/opensearch/rule/storage/AttributeValueStore.java diff --git a/libs/autotagging-commons/src/main/java/org/opensearch/rule/storage/AttributeValueStoreFactory.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/storage/AttributeValueStoreFactory.java similarity index 94% rename from libs/autotagging-commons/src/main/java/org/opensearch/rule/storage/AttributeValueStoreFactory.java rename to modules/autotagging-commons/common/src/main/java/org/opensearch/rule/storage/AttributeValueStoreFactory.java index 8cda4bd26fdf0..e12e95bfdf3f6 100644 --- a/libs/autotagging-commons/src/main/java/org/opensearch/rule/storage/AttributeValueStoreFactory.java +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/storage/AttributeValueStoreFactory.java @@ -8,8 +8,8 @@ package org.opensearch.rule.storage; -import org.opensearch.autotagging.Attribute; -import org.opensearch.autotagging.FeatureType; +import org.opensearch.rule.autotagging.Attribute; +import org.opensearch.rule.autotagging.FeatureType; import java.util.HashMap; import java.util.Map; diff --git a/libs/autotagging-commons/src/main/java/org/opensearch/rule/storage/DefaultAttributeValueStore.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/storage/DefaultAttributeValueStore.java similarity index 100% rename from libs/autotagging-commons/src/main/java/org/opensearch/rule/storage/DefaultAttributeValueStore.java rename to modules/autotagging-commons/common/src/main/java/org/opensearch/rule/storage/DefaultAttributeValueStore.java diff --git a/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/storage/IndexBasedRuleQueryMapper.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/storage/IndexBasedRuleQueryMapper.java new file mode 100644 index 0000000000000..6f57ef478b577 --- /dev/null +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/storage/IndexBasedRuleQueryMapper.java @@ -0,0 +1,57 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule.storage; + +import org.opensearch.common.annotation.ExperimentalApi; +import org.opensearch.index.query.BoolQueryBuilder; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.index.query.QueryBuilders; +import org.opensearch.rule.GetRuleRequest; +import org.opensearch.rule.RuleQueryMapper; +import org.opensearch.rule.autotagging.Attribute; + +import java.util.Map; +import java.util.Set; + +import static org.opensearch.rule.autotagging.Rule._ID_STRING; + +/** + * This class is used to build opensearch index based query object + */ +@ExperimentalApi +public class IndexBasedRuleQueryMapper implements RuleQueryMapper { + + /** + * Default constructor + */ + public IndexBasedRuleQueryMapper() {} + + @Override + public QueryBuilder from(GetRuleRequest request) { + final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); + final Map> attributeFilters = request.getAttributeFilters(); + final String id = request.getId(); + + if (id != null) { + return boolQuery.must(QueryBuilders.termQuery(_ID_STRING, id)); + } + for (Map.Entry> entry : attributeFilters.entrySet()) { + Attribute attribute = entry.getKey(); + Set values = entry.getValue(); + if (values != null && !values.isEmpty()) { + BoolQueryBuilder attributeQuery = QueryBuilders.boolQuery(); + for (String value : values) { + attributeQuery.should(QueryBuilders.matchQuery(attribute.getName(), value)); + } + boolQuery.must(attributeQuery); + } + } + return boolQuery; + } +} diff --git a/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/storage/XContentRuleParser.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/storage/XContentRuleParser.java new file mode 100644 index 0000000000000..2fca880bff4f8 --- /dev/null +++ b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/storage/XContentRuleParser.java @@ -0,0 +1,52 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule.storage; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.opensearch.common.annotation.ExperimentalApi; +import org.opensearch.core.xcontent.DeprecationHandler; +import org.opensearch.core.xcontent.MediaTypeRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.core.xcontent.XContentParser; +import org.opensearch.rule.RuleEntityParser; +import org.opensearch.rule.autotagging.FeatureType; +import org.opensearch.rule.autotagging.Rule; + +import java.io.IOException; + +/** + * Rule parser for json XContent representation of Rule + */ +@ExperimentalApi +public class XContentRuleParser implements RuleEntityParser { + private final FeatureType featureType; + private static final Logger logger = LogManager.getLogger(XContentRuleParser.class); + + /** + * Constructor + * @param featureType + */ + public XContentRuleParser(FeatureType featureType) { + this.featureType = featureType; + } + + @Override + public Rule parse(String src) { + try ( + XContentParser parser = MediaTypeRegistry.JSON.xContent() + .createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, src) + ) { + return Rule.Builder.fromXContent(parser, featureType).build(); + } catch (IOException e) { + logger.info("Issue met when parsing rule : {}", e.getMessage()); + throw new RuntimeException("Cannot parse rule from index."); + } + } +} diff --git a/libs/autotagging-commons/src/main/java/org/opensearch/rule/storage/package-info.java b/modules/autotagging-commons/common/src/main/java/org/opensearch/rule/storage/package-info.java similarity index 100% rename from libs/autotagging-commons/src/main/java/org/opensearch/rule/storage/package-info.java rename to modules/autotagging-commons/common/src/main/java/org/opensearch/rule/storage/package-info.java diff --git a/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/IndexStoredRuleUtilsTests.java b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/IndexStoredRuleUtilsTests.java new file mode 100644 index 0000000000000..46aaf6770b17c --- /dev/null +++ b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/IndexStoredRuleUtilsTests.java @@ -0,0 +1,48 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule; + +import org.opensearch.index.query.BoolQueryBuilder; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.rule.storage.IndexBasedRuleQueryMapper; +import org.opensearch.rule.utils.RuleTestUtils; +import org.opensearch.test.OpenSearchTestCase; + +import java.util.HashMap; + +public class IndexStoredRuleUtilsTests extends OpenSearchTestCase { + RuleQueryMapper sut; + + public void setUp() throws Exception { + super.setUp(); + sut = new IndexBasedRuleQueryMapper(); + } + + public void testBuildGetRuleQuery_WithId() { + QueryBuilder query = sut.from( + new GetRuleRequest(RuleTestUtils._ID_ONE, new HashMap<>(), null, RuleTestUtils.MockRuleFeatureType.INSTANCE) + ); + assertNotNull(query); + BoolQueryBuilder queryBuilder = (BoolQueryBuilder) query; + assertEquals(1, queryBuilder.must().size()); + QueryBuilder idQuery = queryBuilder.must().get(0); + assertTrue(idQuery.toString().contains(RuleTestUtils._ID_ONE)); + } + + public void testBuildGetRuleQuery_WithAttributes() { + QueryBuilder queryBuilder = sut.from( + new GetRuleRequest(null, RuleTestUtils.ATTRIBUTE_MAP, null, RuleTestUtils.MockRuleFeatureType.INSTANCE) + ); + assertNotNull(queryBuilder); + BoolQueryBuilder query = (BoolQueryBuilder) queryBuilder; + assertTrue(query.must().size() == 1); + assertTrue(query.toString().contains(RuleTestUtils.MockRuleAttributes.MOCK_RULE_ATTRIBUTE_ONE.getName())); + assertTrue(query.toString().contains(RuleTestUtils.ATTRIBUTE_VALUE_ONE)); + } +} diff --git a/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/RuleAttributeTests.java b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/RuleAttributeTests.java new file mode 100644 index 0000000000000..53492d020e205 --- /dev/null +++ b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/RuleAttributeTests.java @@ -0,0 +1,28 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule; + +import org.opensearch.test.OpenSearchTestCase; + +public class RuleAttributeTests extends OpenSearchTestCase { + + public void testGetName() { + RuleAttribute attribute = RuleAttribute.INDEX_PATTERN; + assertEquals("index_pattern", attribute.getName()); + } + + public void testFromName() { + RuleAttribute attribute = RuleAttribute.fromName("index_pattern"); + assertEquals(RuleAttribute.INDEX_PATTERN, attribute); + } + + public void testFromName_throwsException() { + assertThrows(IllegalArgumentException.class, () -> RuleAttribute.fromName("invalid_attribute")); + } +} diff --git a/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/XContentRuleParserTests.java b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/XContentRuleParserTests.java new file mode 100644 index 0000000000000..67b7cb8f54c53 --- /dev/null +++ b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/XContentRuleParserTests.java @@ -0,0 +1,55 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule; + +import org.opensearch.rule.autotagging.Rule; +import org.opensearch.rule.storage.XContentRuleParser; +import org.opensearch.rule.utils.RuleTestUtils; +import org.opensearch.test.OpenSearchTestCase; + +import java.io.IOException; +import java.time.Instant; +import java.util.Locale; + +public class XContentRuleParserTests extends OpenSearchTestCase { + public static final String VALID_JSON = String.format(Locale.ROOT, """ + { + "description": "%s", + "mock_feature_type": "feature value", + "mock_attribute_one": ["attribute_value_one", "attribute_value_two"], + "updated_at": "%s" + } + """, RuleTestUtils.DESCRIPTION_ONE, Instant.now().toString()); + + private static final String INVALID_JSON = """ + { + "name": "TestRule", + "description": "A test rule for unit testing", + "mock_attribute_three": ["attribute_value_one", "attribute_value_two"] + } + """; + RuleEntityParser sut; + + public void setUp() throws Exception { + super.setUp(); + sut = new XContentRuleParser(RuleTestUtils.MockRuleFeatureType.INSTANCE); + } + + public void testParseRule_Success() throws IOException { + Rule parsedRule = sut.parse(VALID_JSON); + assertNotNull(parsedRule); + assertEquals(RuleTestUtils.DESCRIPTION_ONE, parsedRule.getDescription()); + assertEquals(RuleTestUtils.MockRuleFeatureType.INSTANCE, parsedRule.getFeatureType()); + } + + public void testParseRule_InvalidJson() { + Exception exception = assertThrows(RuntimeException.class, () -> sut.parse(INVALID_JSON)); + assertTrue(exception.getMessage().contains("mock_attribute_three is not a valid attribute within the mock_feature_type feature.")); + } +} diff --git a/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/action/GetRuleRequestTests.java b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/action/GetRuleRequestTests.java new file mode 100644 index 0000000000000..a451a58356606 --- /dev/null +++ b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/action/GetRuleRequestTests.java @@ -0,0 +1,126 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule.action; + +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.rule.GetRuleRequest; +import org.opensearch.rule.autotagging.Attribute; +import org.opensearch.rule.autotagging.Rule; +import org.opensearch.rule.utils.RuleTestUtils; +import org.opensearch.test.OpenSearchTestCase; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class GetRuleRequestTests extends OpenSearchTestCase { + /** + * Test case to verify the serialization and deserialization of GetRuleRequest + */ + public void testSerialization() throws IOException { + GetRuleRequest request = new GetRuleRequest(_ID_ONE, ATTRIBUTE_MAP, null, RuleTestUtils.MockRuleFeatureType.INSTANCE); + assertEquals(_ID_ONE, request.getId()); + assertNull(request.validate()); + assertNull(request.getSearchAfter()); + assertEquals(RuleTestUtils.MockRuleFeatureType.INSTANCE, request.getFeatureType()); + BytesStreamOutput out = new BytesStreamOutput(); + request.writeTo(out); + StreamInput streamInput = out.bytes().streamInput(); + GetRuleRequest otherRequest = new GetRuleRequest(streamInput); + assertEquals(request.getId(), otherRequest.getId()); + assertEquals(request.getAttributeFilters(), otherRequest.getAttributeFilters()); + } + + /** + * Test case to verify the serialization and deserialization of GetRuleRequest when name is null + */ + public void testSerializationWithNull() throws IOException { + GetRuleRequest request = new GetRuleRequest( + (String) null, + new HashMap<>(), + SEARCH_AFTER, + RuleTestUtils.MockRuleFeatureType.INSTANCE + ); + assertNull(request.getId()); + BytesStreamOutput out = new BytesStreamOutput(); + request.writeTo(out); + StreamInput streamInput = out.bytes().streamInput(); + GetRuleRequest otherRequest = new GetRuleRequest(streamInput); + assertEquals(request.getId(), otherRequest.getId()); + assertEquals(request.getAttributeFilters(), otherRequest.getAttributeFilters()); + } + + public void testValidate() { + GetRuleRequest request = new GetRuleRequest("", ATTRIBUTE_MAP, null, RuleTestUtils.MockRuleFeatureType.INSTANCE); + assertThrows(IllegalArgumentException.class, request::validate); + request = new GetRuleRequest(_ID_ONE, ATTRIBUTE_MAP, "", RuleTestUtils.MockRuleFeatureType.INSTANCE); + assertThrows(IllegalArgumentException.class, request::validate); + } + + public static final String _ID_ONE = "id_1"; + public static final String SEARCH_AFTER = "search_after"; + public static final String _ID_TWO = "G5iIq84j7eK1qIAAAAIH53=1"; + public static final String FEATURE_VALUE_ONE = "feature_value_one"; + public static final String FEATURE_VALUE_TWO = "feature_value_two"; + public static final String ATTRIBUTE_VALUE_ONE = "mock_attribute_one"; + public static final String ATTRIBUTE_VALUE_TWO = "mock_attribute_two"; + public static final String DESCRIPTION_ONE = "description_1"; + public static final String DESCRIPTION_TWO = "description_2"; + public static final String TIMESTAMP_ONE = "2024-01-26T08:58:57.558Z"; + public static final String TIMESTAMP_TWO = "2023-01-26T08:58:57.558Z"; + public static final Map> ATTRIBUTE_MAP = Map.of( + RuleTestUtils.MockRuleAttributes.MOCK_RULE_ATTRIBUTE_ONE, + Set.of(ATTRIBUTE_VALUE_ONE) + ); + + public static final Rule ruleOne = Rule.builder() + .description(DESCRIPTION_ONE) + .featureType(RuleTestUtils.MockRuleFeatureType.INSTANCE) + .featureValue(FEATURE_VALUE_ONE) + .attributeMap(ATTRIBUTE_MAP) + .updatedAt(TIMESTAMP_ONE) + .build(); + + public static final Rule ruleTwo = Rule.builder() + .description(DESCRIPTION_TWO) + .featureType(RuleTestUtils.MockRuleFeatureType.INSTANCE) + .featureValue(FEATURE_VALUE_TWO) + .attributeMap(Map.of(RuleTestUtils.MockRuleAttributes.MOCK_RULE_ATTRIBUTE_TWO, Set.of(ATTRIBUTE_VALUE_TWO))) + .updatedAt(TIMESTAMP_TWO) + .build(); + + public static Map ruleMap() { + return Map.of(_ID_ONE, ruleOne, _ID_TWO, ruleTwo); + } + + public static void assertEqualRules(Map mapOne, Map mapTwo, boolean ruleUpdated) { + assertEquals(mapOne.size(), mapTwo.size()); + for (Map.Entry entry : mapOne.entrySet()) { + String id = entry.getKey(); + assertTrue(mapTwo.containsKey(id)); + Rule one = mapOne.get(id); + Rule two = mapTwo.get(id); + assertEqualRule(one, two, ruleUpdated); + } + } + + public static void assertEqualRule(Rule one, Rule two, boolean ruleUpdated) { + if (ruleUpdated) { + assertEquals(one.getDescription(), two.getDescription()); + assertEquals(one.getFeatureType(), two.getFeatureType()); + assertEquals(one.getFeatureValue(), two.getFeatureValue()); + assertEquals(one.getAttributeMap(), two.getAttributeMap()); + assertEquals(one.getAttributeMap(), two.getAttributeMap()); + } else { + assertEquals(one, two); + } + } +} diff --git a/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/action/GetRuleResponseTests.java b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/action/GetRuleResponseTests.java new file mode 100644 index 0000000000000..f01bb94fab276 --- /dev/null +++ b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/action/GetRuleResponseTests.java @@ -0,0 +1,138 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule.action; + +import org.opensearch.common.io.stream.BytesStreamOutput; +import org.opensearch.common.xcontent.json.JsonXContent; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.XContentBuilder; +import org.opensearch.rule.GetRuleResponse; +import org.opensearch.rule.autotagging.Attribute; +import org.opensearch.rule.autotagging.Rule; +import org.opensearch.rule.utils.RuleTestUtils; +import org.opensearch.test.OpenSearchTestCase; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import static org.opensearch.rule.action.GetRuleRequestTests.SEARCH_AFTER; +import static org.opensearch.rule.action.GetRuleRequestTests._ID_ONE; +import static org.opensearch.rule.action.GetRuleRequestTests.assertEqualRules; +import static org.opensearch.rule.action.GetRuleRequestTests.ruleMap; +import static org.mockito.Mockito.mock; + +public class GetRuleResponseTests extends OpenSearchTestCase { + public static final String FEATURE_VALUE_ONE = "feature_value_one"; + public static final String ATTRIBUTE_VALUE_ONE = "mock_attribute_one"; + public static final String DESCRIPTION_ONE = "description_1"; + public static final String TIMESTAMP_ONE = "2024-01-26T08:58:57.558Z"; + static final Map> ATTRIBUTE_MAP = Map.of( + RuleTestUtils.MockRuleAttributes.MOCK_RULE_ATTRIBUTE_ONE, + Set.of(ATTRIBUTE_VALUE_ONE) + ); + + public static final Rule ruleOne = Rule.builder() + .description(DESCRIPTION_ONE) + .featureType(RuleTestUtils.MockRuleFeatureType.INSTANCE) + .featureValue(FEATURE_VALUE_ONE) + .attributeMap(ATTRIBUTE_MAP) + .updatedAt(TIMESTAMP_ONE) + .build(); + + /** + * Test case to verify the serialization and deserialization of GetRuleResponse + */ + public void testSerializationSingleRule() throws IOException { + Map map = new HashMap<>(); + map.put(_ID_ONE, ruleOne); + GetRuleResponse response = new GetRuleResponse(Map.of(_ID_ONE, ruleOne), null); + assertEquals(response.getRules(), map); + + BytesStreamOutput out = new BytesStreamOutput(); + response.writeTo(out); + StreamInput streamInput = out.bytes().streamInput(); + + GetRuleResponse otherResponse = new GetRuleResponse(streamInput); + assertEqualRules(response.getRules(), otherResponse.getRules(), false); + } + + /** + * Test case to verify the serialization and deserialization of GetRuleResponse when the result contains multiple rules + */ + public void testSerializationMultipleRule() throws IOException { + GetRuleResponse response = new GetRuleResponse(ruleMap(), SEARCH_AFTER); + assertEquals(response.getRules(), ruleMap()); + + BytesStreamOutput out = new BytesStreamOutput(); + response.writeTo(out); + StreamInput streamInput = out.bytes().streamInput(); + + GetRuleResponse otherResponse = new GetRuleResponse(streamInput); + assertEquals(2, otherResponse.getRules().size()); + assertEqualRules(response.getRules(), otherResponse.getRules(), false); + } + + /** + * Test case to verify the serialization and deserialization of GetRuleResponse when the result is empty + */ + public void testSerializationNull() throws IOException { + Map map = new HashMap<>(); + GetRuleResponse response = new GetRuleResponse(map, SEARCH_AFTER); + assertEquals(response.getRules(), map); + + BytesStreamOutput out = new BytesStreamOutput(); + response.writeTo(out); + StreamInput streamInput = out.bytes().streamInput(); + + GetRuleResponse otherResponse = new GetRuleResponse(streamInput); + assertEquals(0, otherResponse.getRules().size()); + } + + /** + * Test case to verify the toXContent of GetRuleResponse + */ + public void testToXContentGetSingleRule() throws IOException { + Map map = new HashMap<>(); + map.put(_ID_ONE, ruleOne); + GetRuleResponse response = new GetRuleResponse(Map.of(_ID_ONE, ruleOne), SEARCH_AFTER); + XContentBuilder builder = JsonXContent.contentBuilder().prettyPrint(); + String actual = response.toXContent(builder, mock(ToXContent.Params.class)).toString(); + String expected = "{\n" + + " \"rules\" : [\n" + + " {\n" + + " \"_id\" : \"id_1\",\n" + + " \"description\" : \"description_1\",\n" + + " \"mock_attribute_one\" : [\n" + + " \"mock_attribute_one\"\n" + + " ],\n" + + " \"mock_feature_type\" : \"feature_value_one\",\n" + + " \"updated_at\" : \"2024-01-26T08:58:57.558Z\"\n" + + " }\n" + + " ],\n" + + " \"search_after\" : [\n" + + " \"search_after\"\n" + + " ]\n" + + "}"; + assertEquals(expected, actual); + } + + /** + * Test case to verify toXContent of GetRuleResponse when the result contains zero Rule + */ + public void testToXContentGetZeroRule() throws IOException { + XContentBuilder builder = JsonXContent.contentBuilder().prettyPrint(); + GetRuleResponse otherResponse = new GetRuleResponse(new HashMap<>(), null); + String actual = otherResponse.toXContent(builder, mock(ToXContent.Params.class)).toString(); + String expected = "{\n" + " \"rules\" : [ ]\n" + "}"; + assertEquals(expected, actual); + } +} diff --git a/server/src/test/java/org/opensearch/autotagging/AutoTaggingRegistryTests.java b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/autotagging/AutoTaggingRegistryTests.java similarity index 85% rename from server/src/test/java/org/opensearch/autotagging/AutoTaggingRegistryTests.java rename to modules/autotagging-commons/common/src/test/java/org/opensearch/rule/autotagging/AutoTaggingRegistryTests.java index 8bd240dad99e6..eee1d527dc6e9 100644 --- a/server/src/test/java/org/opensearch/autotagging/AutoTaggingRegistryTests.java +++ b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/autotagging/AutoTaggingRegistryTests.java @@ -6,15 +6,15 @@ * compatible open source license. */ -package org.opensearch.autotagging; +package org.opensearch.rule.autotagging; import org.opensearch.ResourceNotFoundException; import org.opensearch.test.OpenSearchTestCase; import org.junit.BeforeClass; -import static org.opensearch.autotagging.AutoTaggingRegistry.MAX_FEATURE_TYPE_NAME_LENGTH; -import static org.opensearch.autotagging.RuleTests.INVALID_FEATURE; -import static org.opensearch.autotagging.RuleTests.TEST_FEATURE_TYPE; +import static org.opensearch.rule.autotagging.AutoTaggingRegistry.MAX_FEATURE_TYPE_NAME_LENGTH; +import static org.opensearch.rule.autotagging.RuleTests.INVALID_FEATURE; +import static org.opensearch.rule.autotagging.RuleTests.TEST_FEATURE_TYPE; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; diff --git a/server/src/test/java/org/opensearch/autotagging/FeatureTypeTests.java b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/autotagging/FeatureTypeTests.java similarity index 76% rename from server/src/test/java/org/opensearch/autotagging/FeatureTypeTests.java rename to modules/autotagging-commons/common/src/test/java/org/opensearch/rule/autotagging/FeatureTypeTests.java index e8cf46818c515..168487f14dc62 100644 --- a/server/src/test/java/org/opensearch/autotagging/FeatureTypeTests.java +++ b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/autotagging/FeatureTypeTests.java @@ -6,17 +6,17 @@ * compatible open source license. */ -package org.opensearch.autotagging; +package org.opensearch.rule.autotagging; import org.opensearch.core.common.io.stream.StreamOutput; import org.opensearch.test.OpenSearchTestCase; import java.io.IOException; -import static org.opensearch.autotagging.RuleTests.FEATURE_TYPE; -import static org.opensearch.autotagging.RuleTests.INVALID_ATTRIBUTE; -import static org.opensearch.autotagging.RuleTests.TEST_ATTR1_NAME; -import static org.opensearch.autotagging.RuleTests.TestAttribute.TEST_ATTRIBUTE_1; +import static org.opensearch.rule.autotagging.RuleTests.FEATURE_TYPE; +import static org.opensearch.rule.autotagging.RuleTests.INVALID_ATTRIBUTE; +import static org.opensearch.rule.autotagging.RuleTests.TEST_ATTR1_NAME; +import static org.opensearch.rule.autotagging.RuleTests.TestAttribute.TEST_ATTRIBUTE_1; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; diff --git a/server/src/test/java/org/opensearch/autotagging/RuleTests.java b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/autotagging/RuleTests.java similarity index 95% rename from server/src/test/java/org/opensearch/autotagging/RuleTests.java rename to modules/autotagging-commons/common/src/test/java/org/opensearch/rule/autotagging/RuleTests.java index 5f27942639126..4ae89b496a1b3 100644 --- a/server/src/test/java/org/opensearch/autotagging/RuleTests.java +++ b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/autotagging/RuleTests.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.autotagging; +package org.opensearch.rule.autotagging; import org.opensearch.common.xcontent.json.JsonXContent; import org.opensearch.core.common.io.stream.Writeable; @@ -20,9 +20,9 @@ import java.util.Map; import java.util.Set; -import static org.opensearch.autotagging.Rule._ID_STRING; -import static org.opensearch.autotagging.RuleTests.TestAttribute.TEST_ATTRIBUTE_1; -import static org.opensearch.autotagging.RuleTests.TestAttribute.TEST_ATTRIBUTE_2; +import static org.opensearch.rule.autotagging.Rule._ID_STRING; +import static org.opensearch.rule.autotagging.RuleTests.TestAttribute.TEST_ATTRIBUTE_1; +import static org.opensearch.rule.autotagging.RuleTests.TestAttribute.TEST_ATTRIBUTE_2; public class RuleTests extends AbstractSerializingTestCase { public static final String TEST_ATTR1_NAME = "test_attr1"; diff --git a/server/src/test/java/org/opensearch/autotagging/RuleValidatorTests.java b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/autotagging/RuleValidatorTests.java similarity index 90% rename from server/src/test/java/org/opensearch/autotagging/RuleValidatorTests.java rename to modules/autotagging-commons/common/src/test/java/org/opensearch/rule/autotagging/RuleValidatorTests.java index 8fbf16cd34c52..b24f9daa034c6 100644 --- a/server/src/test/java/org/opensearch/autotagging/RuleValidatorTests.java +++ b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/autotagging/RuleValidatorTests.java @@ -6,7 +6,7 @@ * compatible open source license. */ -package org.opensearch.autotagging; +package org.opensearch.rule.autotagging; import org.opensearch.test.OpenSearchTestCase; @@ -16,12 +16,12 @@ import java.util.Map; import java.util.Set; -import static org.opensearch.autotagging.RuleTests.ATTRIBUTE_MAP; -import static org.opensearch.autotagging.RuleTests.DESCRIPTION; -import static org.opensearch.autotagging.RuleTests.FEATURE_TYPE; -import static org.opensearch.autotagging.RuleTests.FEATURE_VALUE; -import static org.opensearch.autotagging.RuleTests.TestAttribute.TEST_ATTRIBUTE_1; -import static org.opensearch.autotagging.RuleTests.UPDATED_AT; +import static org.opensearch.rule.autotagging.RuleTests.ATTRIBUTE_MAP; +import static org.opensearch.rule.autotagging.RuleTests.DESCRIPTION; +import static org.opensearch.rule.autotagging.RuleTests.FEATURE_TYPE; +import static org.opensearch.rule.autotagging.RuleTests.FEATURE_VALUE; +import static org.opensearch.rule.autotagging.RuleTests.TestAttribute.TEST_ATTRIBUTE_1; +import static org.opensearch.rule.autotagging.RuleTests.UPDATED_AT; public class RuleValidatorTests extends OpenSearchTestCase { diff --git a/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/service/IndexStoredRulePersistenceServiceTests.java b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/service/IndexStoredRulePersistenceServiceTests.java new file mode 100644 index 0000000000000..71c495d895900 --- /dev/null +++ b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/service/IndexStoredRulePersistenceServiceTests.java @@ -0,0 +1,165 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule.service; + +import org.apache.lucene.search.TotalHits; +import org.opensearch.ResourceNotFoundException; +import org.opensearch.action.search.SearchRequestBuilder; +import org.opensearch.action.search.SearchResponse; +import org.opensearch.cluster.ClusterState; +import org.opensearch.cluster.metadata.Metadata; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.common.bytes.BytesArray; +import org.opensearch.index.query.QueryBuilder; +import org.opensearch.rule.GetRuleRequest; +import org.opensearch.rule.GetRuleResponse; +import org.opensearch.rule.RuleEntityParser; +import org.opensearch.rule.RulePersistenceService; +import org.opensearch.rule.RuleQueryMapper; +import org.opensearch.rule.autotagging.Rule; +import org.opensearch.rule.utils.RuleTestUtils; +import org.opensearch.search.SearchHit; +import org.opensearch.search.SearchHits; +import org.opensearch.test.OpenSearchTestCase; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.client.Client; + +import java.util.HashMap; + +import org.mockito.ArgumentCaptor; + +import static org.opensearch.rule.XContentRuleParserTests.VALID_JSON; +import static org.opensearch.rule.utils.RuleTestUtils.TEST_INDEX_NAME; +import static org.opensearch.rule.utils.RuleTestUtils._ID_ONE; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@SuppressWarnings("unchecked") +public class IndexStoredRulePersistenceServiceTests extends OpenSearchTestCase { + + public static final int MAX_VALUES_PER_PAGE = 50; + + public void testGetRuleByIdSuccess() { + GetRuleRequest getRuleRequest = mock(GetRuleRequest.class); + when(getRuleRequest.getId()).thenReturn(_ID_ONE); + when(getRuleRequest.getAttributeFilters()).thenReturn(new HashMap<>()); + QueryBuilder queryBuilder = mock(QueryBuilder.class); + RuleQueryMapper mockRuleQueryMapper = mock(RuleQueryMapper.class); + RuleEntityParser mockRuleEntityParser = mock(RuleEntityParser.class); + Rule mockRule = mock(Rule.class); + + when(mockRuleEntityParser.parse(anyString())).thenReturn(mockRule); + when(mockRuleQueryMapper.from(getRuleRequest)).thenReturn(queryBuilder); + when(queryBuilder.filter(any())).thenReturn(queryBuilder); + + SearchRequestBuilder searchRequestBuilder = mock(SearchRequestBuilder.class); + Client client = setUpMockClient(searchRequestBuilder); + + RulePersistenceService rulePersistenceService = new IndexStoredRulePersistenceService( + TEST_INDEX_NAME, + client, + MAX_VALUES_PER_PAGE, + mockRuleEntityParser, + mockRuleQueryMapper + ); + + SearchResponse searchResponse = mock(SearchResponse.class); + SearchHits searchHits = new SearchHits(new SearchHit[] { new SearchHit(1) }, new TotalHits(1, TotalHits.Relation.EQUAL_TO), 1.0f); + when(searchResponse.getHits()).thenReturn(searchHits); + SearchHit hit = searchHits.getHits()[0]; + hit.sourceRef(new BytesArray(VALID_JSON)); + + ActionListener listener = mock(ActionListener.class); + + doAnswer((invocation) -> { + ActionListener actionListener = invocation.getArgument(0); + actionListener.onResponse(searchResponse); + return null; + }).when(searchRequestBuilder).execute(any(ActionListener.class)); + + when(getRuleRequest.getFeatureType()).thenReturn(RuleTestUtils.MockRuleFeatureType.INSTANCE); + rulePersistenceService.getRule(getRuleRequest, listener); + + ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(GetRuleResponse.class); + verify(listener).onResponse(responseCaptor.capture()); + GetRuleResponse response = responseCaptor.getValue(); + assertEquals(response.getRules().size(), 1); + } + + public void testGetRuleByIdNotFound() { + GetRuleRequest getRuleRequest = mock(GetRuleRequest.class); + when(getRuleRequest.getId()).thenReturn(_ID_ONE); + QueryBuilder queryBuilder = mock(QueryBuilder.class); + RuleQueryMapper mockRuleQueryMapper = mock(RuleQueryMapper.class); + RuleEntityParser mockRuleEntityParser = mock(RuleEntityParser.class); + Rule mockRule = mock(Rule.class); + + when(mockRuleEntityParser.parse(anyString())).thenReturn(mockRule); + when(mockRuleQueryMapper.from(getRuleRequest)).thenReturn(queryBuilder); + when(queryBuilder.filter(any())).thenReturn(queryBuilder); + + SearchRequestBuilder searchRequestBuilder = mock(SearchRequestBuilder.class); + Client client = setUpMockClient(searchRequestBuilder); + + RulePersistenceService rulePersistenceService = new IndexStoredRulePersistenceService( + TEST_INDEX_NAME, + client, + MAX_VALUES_PER_PAGE, + mockRuleEntityParser, + mockRuleQueryMapper + ); + + SearchResponse searchResponse = mock(SearchResponse.class); + when(searchResponse.getHits()).thenReturn(new SearchHits(new SearchHit[] {}, new TotalHits(0, TotalHits.Relation.EQUAL_TO), 1.0f)); + + ActionListener listener = mock(ActionListener.class); + + doAnswer(invocationOnMock -> { + ActionListener actionListener = invocationOnMock.getArgument(0); + actionListener.onResponse(searchResponse); + return null; + }).when(searchRequestBuilder).execute(any(ActionListener.class)); + + when(getRuleRequest.getFeatureType()).thenReturn(RuleTestUtils.MockRuleFeatureType.INSTANCE); + rulePersistenceService.getRule(getRuleRequest, listener); + + ArgumentCaptor exceptionCaptor = ArgumentCaptor.forClass(Exception.class); + verify(listener).onFailure(exceptionCaptor.capture()); + Exception exception = exceptionCaptor.getValue(); + assertTrue(exception instanceof ResourceNotFoundException); + } + + private Client setUpMockClient(SearchRequestBuilder searchRequestBuilder) { + Client client = mock(Client.class); + ClusterService clusterService = mock(ClusterService.class); + ClusterState clusterState = mock(ClusterState.class); + Metadata metadata = mock(Metadata.class); + ThreadPool threadPool = mock(ThreadPool.class); + + ThreadContext threadContext = new ThreadContext(Settings.EMPTY); + when(client.threadPool()).thenReturn(threadPool); + when(threadPool.getThreadContext()).thenReturn(threadContext); + when(clusterService.state()).thenReturn(clusterState); + when(clusterState.metadata()).thenReturn(metadata); + + when(client.prepareSearch(TEST_INDEX_NAME)).thenReturn(searchRequestBuilder); + when(searchRequestBuilder.setQuery(any(QueryBuilder.class))).thenReturn(searchRequestBuilder); + when(searchRequestBuilder.setSize(anyInt())).thenReturn(searchRequestBuilder); + + return client; + } +} diff --git a/libs/autotagging-commons/src/test/java/org/opensearch/rule/storage/AttributeValueStoreFactoryTests.java b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/storage/AttributeValueStoreFactoryTests.java similarity index 57% rename from libs/autotagging-commons/src/test/java/org/opensearch/rule/storage/AttributeValueStoreFactoryTests.java rename to modules/autotagging-commons/common/src/test/java/org/opensearch/rule/storage/AttributeValueStoreFactoryTests.java index 5cdc128c50620..ef0bb6c9d6aca 100644 --- a/libs/autotagging-commons/src/test/java/org/opensearch/rule/storage/AttributeValueStoreFactoryTests.java +++ b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/storage/AttributeValueStoreFactoryTests.java @@ -8,9 +8,9 @@ package org.opensearch.rule.storage; -import org.opensearch.autotagging.Attribute; -import org.opensearch.rule.InMemoryRuleProcessingServiceTests.TestAttribute; -import org.opensearch.rule.InMemoryRuleProcessingServiceTests.WLMFeatureType; +import org.opensearch.rule.autotagging.Attribute; +import org.opensearch.rule.utils.RuleTestUtils.MockRuleAttributes; +import org.opensearch.rule.utils.RuleTestUtils.MockRuleFeatureType; import org.opensearch.test.OpenSearchTestCase; public class AttributeValueStoreFactoryTests extends OpenSearchTestCase { @@ -19,20 +19,22 @@ public class AttributeValueStoreFactoryTests extends OpenSearchTestCase { @Override public void setUp() throws Exception { super.setUp(); - sut = new AttributeValueStoreFactory(WLMFeatureType.WLM, DefaultAttributeValueStore::new); + sut = new AttributeValueStoreFactory(MockRuleFeatureType.INSTANCE, DefaultAttributeValueStore::new); } public void testFeatureLevelStoreInitialisation() { - for (Attribute attribute : WLMFeatureType.WLM.getAllowedAttributesRegistry().values()) { + for (Attribute attribute : MockRuleFeatureType.INSTANCE.getAllowedAttributesRegistry().values()) { assertTrue(sut.getAttributeValueStore(attribute) instanceof DefaultAttributeValueStore); } } public void testValidGetAttributeValueStore() { - assertTrue(sut.getAttributeValueStore(TestAttribute.TEST_ATTRIBUTE) instanceof DefaultAttributeValueStore); + assertTrue( + sut.getAttributeValueStore(MockRuleAttributes.MOCK_RULE_ATTRIBUTE_ONE) instanceof DefaultAttributeValueStore + ); } public void testInValidGetAttributeValueStore() { - assertThrows(IllegalArgumentException.class, () -> { sut.getAttributeValueStore(TestAttribute.INVALID_ATTRIBUTE); }); + assertThrows(IllegalArgumentException.class, () -> { sut.getAttributeValueStore(MockRuleAttributes.INVALID_ATTRIBUTE); }); } } diff --git a/libs/autotagging-commons/src/test/java/org/opensearch/rule/storage/AttributeValueStoreTests.java b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/storage/AttributeValueStoreTests.java similarity index 100% rename from libs/autotagging-commons/src/test/java/org/opensearch/rule/storage/AttributeValueStoreTests.java rename to modules/autotagging-commons/common/src/test/java/org/opensearch/rule/storage/AttributeValueStoreTests.java diff --git a/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/utils/RuleTestUtils.java b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/utils/RuleTestUtils.java new file mode 100644 index 0000000000000..6ec75e4b942ff --- /dev/null +++ b/modules/autotagging-commons/common/src/test/java/org/opensearch/rule/utils/RuleTestUtils.java @@ -0,0 +1,79 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule.utils; + +import org.opensearch.rule.autotagging.Attribute; +import org.opensearch.rule.autotagging.AutoTaggingRegistry; +import org.opensearch.rule.autotagging.FeatureType; + +import java.util.Map; +import java.util.Set; + +public class RuleTestUtils { + public static final String _ID_ONE = "AgfUO5Ja9yfvhdONlYi3TQ=="; + public static final String ATTRIBUTE_VALUE_ONE = "mock_attribute_one"; + public static final String ATTRIBUTE_VALUE_TWO = "mock_attribute_two"; + public static final String DESCRIPTION_ONE = "description_1"; + public static final String FEATURE_TYPE_NAME = "mock_feature_type"; + public static final String TEST_INDEX_NAME = ".test_index_for_rule"; + public static final Map> ATTRIBUTE_MAP = Map.of( + MockRuleAttributes.MOCK_RULE_ATTRIBUTE_ONE, + Set.of(ATTRIBUTE_VALUE_ONE) + ); + + public static final String INVALID_ATTRIBUTE = "invalid_attribute"; + + public static class MockRuleFeatureType implements FeatureType { + + public static final MockRuleFeatureType INSTANCE = new MockRuleFeatureType(); + + private MockRuleFeatureType() {} + + static { + INSTANCE.registerFeatureType(); + } + + @Override + public String getName() { + return FEATURE_TYPE_NAME; + } + + @Override + public Map getAllowedAttributesRegistry() { + return Map.of( + ATTRIBUTE_VALUE_ONE, + MockRuleAttributes.MOCK_RULE_ATTRIBUTE_ONE, + ATTRIBUTE_VALUE_TWO, + MockRuleAttributes.MOCK_RULE_ATTRIBUTE_TWO + ); + } + + @Override + public void registerFeatureType() { + AutoTaggingRegistry.registerFeatureType(INSTANCE); + } + } + + public enum MockRuleAttributes implements Attribute { + MOCK_RULE_ATTRIBUTE_ONE(ATTRIBUTE_VALUE_ONE), + MOCK_RULE_ATTRIBUTE_TWO(ATTRIBUTE_VALUE_TWO), + INVALID_ATTRIBUTE(RuleTestUtils.INVALID_ATTRIBUTE); + + private final String name; + + MockRuleAttributes(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + } +} diff --git a/modules/autotagging-commons/spi/build.gradle b/modules/autotagging-commons/spi/build.gradle new file mode 100644 index 0000000000000..a91160c612f56 --- /dev/null +++ b/modules/autotagging-commons/spi/build.gradle @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +apply plugin: 'opensearch.build' +apply plugin: 'opensearch.publish' + + +base { + group = 'org.opensearch.plugin' + archivesName = 'autotagging-commons-spi' +} + +dependencies { + api project(':modules:autotagging-commons:common') +} + +disableTasks("forbiddenApisMain") + +testingConventions { + enabled = false +} diff --git a/modules/autotagging-commons/spi/src/main/java/org/opensearch/rule/spi/RuleFrameworkExtension.java b/modules/autotagging-commons/spi/src/main/java/org/opensearch/rule/spi/RuleFrameworkExtension.java new file mode 100644 index 0000000000000..6197fa9e03093 --- /dev/null +++ b/modules/autotagging-commons/spi/src/main/java/org/opensearch/rule/spi/RuleFrameworkExtension.java @@ -0,0 +1,33 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule.spi; + +import org.opensearch.rule.RulePersistenceService; +import org.opensearch.rule.autotagging.FeatureType; + +import java.util.function.Supplier; + +/** + * This interface exposes methods for the RuleFrameworkPlugin to extract framework related + * implementations from the consumer plugins of this extension + */ +public interface RuleFrameworkExtension { + /** + * This method is used to flow implementation from consumer plugins into framework plugin + * @return the plugin specific implementation of RulePersistenceService + */ + Supplier getRulePersistenceServiceSupplier(); + + /** + * It tells the framework its FeatureType which can be used by Transport classes to handle the + * consumer specific persistence + * @return + */ + FeatureType getFeatureType(); +} diff --git a/modules/autotagging-commons/spi/src/main/java/org/opensearch/rule/spi/package-info.java b/modules/autotagging-commons/spi/src/main/java/org/opensearch/rule/spi/package-info.java new file mode 100644 index 0000000000000..c9ea0158ddd22 --- /dev/null +++ b/modules/autotagging-commons/spi/src/main/java/org/opensearch/rule/spi/package-info.java @@ -0,0 +1,11 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ +/** + * Contains extension points for framework plugin + */ +package org.opensearch.rule.spi; diff --git a/libs/autotagging-commons/src/main/java/org/opensearch/rule/InMemoryRuleProcessingService.java b/modules/autotagging-commons/src/main/java/org/opensearch/rule/InMemoryRuleProcessingService.java similarity index 96% rename from libs/autotagging-commons/src/main/java/org/opensearch/rule/InMemoryRuleProcessingService.java rename to modules/autotagging-commons/src/main/java/org/opensearch/rule/InMemoryRuleProcessingService.java index 219f6fa5e1999..8f8b81268e5a7 100644 --- a/libs/autotagging-commons/src/main/java/org/opensearch/rule/InMemoryRuleProcessingService.java +++ b/modules/autotagging-commons/src/main/java/org/opensearch/rule/InMemoryRuleProcessingService.java @@ -8,10 +8,10 @@ package org.opensearch.rule; -import org.opensearch.autotagging.Attribute; -import org.opensearch.autotagging.FeatureType; -import org.opensearch.autotagging.Rule; import org.opensearch.rule.attribute_extractor.AttributeExtractor; +import org.opensearch.rule.autotagging.Attribute; +import org.opensearch.rule.autotagging.FeatureType; +import org.opensearch.rule.autotagging.Rule; import org.opensearch.rule.storage.AttributeValueStore; import org.opensearch.rule.storage.AttributeValueStoreFactory; diff --git a/modules/autotagging-commons/src/main/java/org/opensearch/rule/RuleFrameworkPlugin.java b/modules/autotagging-commons/src/main/java/org/opensearch/rule/RuleFrameworkPlugin.java new file mode 100644 index 0000000000000..d57840fcb6549 --- /dev/null +++ b/modules/autotagging-commons/src/main/java/org/opensearch/rule/RuleFrameworkPlugin.java @@ -0,0 +1,86 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule; + +import org.opensearch.action.ActionRequest; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.node.DiscoveryNodes; +import org.opensearch.common.inject.Module; +import org.opensearch.common.settings.ClusterSettings; +import org.opensearch.common.settings.IndexScopedSettings; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.settings.SettingsFilter; +import org.opensearch.core.action.ActionResponse; +import org.opensearch.plugins.ActionPlugin; +import org.opensearch.plugins.ExtensiblePlugin; +import org.opensearch.plugins.Plugin; +import org.opensearch.rest.RestController; +import org.opensearch.rest.RestHandler; +import org.opensearch.rule.action.GetRuleAction; +import org.opensearch.rule.action.TransportGetRuleAction; +import org.opensearch.rule.autotagging.AutoTaggingRegistry; +import org.opensearch.rule.rest.RestGetRuleAction; +import org.opensearch.rule.spi.RuleFrameworkExtension; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.function.Supplier; + +/** + * This plugin provides the central APIs which can provide CRUD support to all consumers of Rule framework + */ +public class RuleFrameworkPlugin extends Plugin implements ExtensiblePlugin, ActionPlugin { + + /** + * constructor for RuleFrameworkPlugin + */ + public RuleFrameworkPlugin() {} + + private final RulePersistenceServiceRegistry rulePersistenceServiceRegistry = new RulePersistenceServiceRegistry(); + private final List ruleFrameworkExtensions = new ArrayList<>(); + + @Override + public List> getActions() { + // We are consuming the extensions at this place to ensure that the RulePersistenceService is initialised + ruleFrameworkExtensions.forEach(this::consumeFrameworkExtension); + return List.of(new ActionPlugin.ActionHandler<>(GetRuleAction.INSTANCE, TransportGetRuleAction.class)); + } + + @Override + public List getRestHandlers( + Settings settings, + RestController restController, + ClusterSettings clusterSettings, + IndexScopedSettings indexScopedSettings, + SettingsFilter settingsFilter, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier nodesInCluster + ) { + return List.of(new RestGetRuleAction()); + } + + @Override + public Collection createGuiceModules() { + return List.of(b -> { b.bind(RulePersistenceServiceRegistry.class).toInstance(rulePersistenceServiceRegistry); }); + } + + @Override + public void loadExtensions(ExtensionLoader loader) { + ruleFrameworkExtensions.addAll(loader.loadExtensions(RuleFrameworkExtension.class)); + } + + private void consumeFrameworkExtension(RuleFrameworkExtension ruleFrameworkExtension) { + AutoTaggingRegistry.registerFeatureType(ruleFrameworkExtension.getFeatureType()); + rulePersistenceServiceRegistry.register( + ruleFrameworkExtension.getFeatureType(), + ruleFrameworkExtension.getRulePersistenceServiceSupplier().get() + ); + } +} diff --git a/modules/autotagging-commons/src/main/java/org/opensearch/rule/RulePersistenceServiceRegistry.java b/modules/autotagging-commons/src/main/java/org/opensearch/rule/RulePersistenceServiceRegistry.java new file mode 100644 index 0000000000000..57f47f1a6ad0f --- /dev/null +++ b/modules/autotagging-commons/src/main/java/org/opensearch/rule/RulePersistenceServiceRegistry.java @@ -0,0 +1,48 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule; + +import org.opensearch.rule.autotagging.FeatureType; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * This class manages implementations of {@link RulePersistenceService} + */ +public class RulePersistenceServiceRegistry { + private final Map rulePersistenceServices = new ConcurrentHashMap<>(); + + /** + * default constructor + */ + public RulePersistenceServiceRegistry() {} + + /** + * This method is used to register the concrete implementations of RulePersistenceService + * @param featureType + * @param rulePersistenceService + */ + public void register(FeatureType featureType, RulePersistenceService rulePersistenceService) { + if (rulePersistenceServices.put(featureType.getName(), rulePersistenceService) != null) { + throw new IllegalArgumentException("Duplicate rule persistence service: " + featureType.getName()); + } + } + + /** + * It is used to get feature type specific {@link RulePersistenceService} implementation + * @param featureType - the type of feature to retrieve the persistence service for + */ + public RulePersistenceService getRulePersistenceService(FeatureType featureType) { + if (!rulePersistenceServices.containsKey(featureType.getName())) { + throw new IllegalArgumentException("Unknown feature type: " + featureType.getName()); + } + return rulePersistenceServices.get(featureType.getName()); + } +} diff --git a/modules/autotagging-commons/src/main/java/org/opensearch/rule/action/GetRuleAction.java b/modules/autotagging-commons/src/main/java/org/opensearch/rule/action/GetRuleAction.java new file mode 100644 index 0000000000000..e59eabf682510 --- /dev/null +++ b/modules/autotagging-commons/src/main/java/org/opensearch/rule/action/GetRuleAction.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule.action; + +import org.opensearch.action.ActionType; +import org.opensearch.rule.GetRuleResponse; + +/** + * Action type for getting Rules + * @opensearch.experimental + */ +public class GetRuleAction extends ActionType { + + /** + * An instance of GetWlmRuleAction + */ + public static final GetRuleAction INSTANCE = new GetRuleAction(); + + /** + * Name for GetRuleAction + */ + public static final String NAME = "cluster:admin/opensearch/rule/_get"; + + /** + * Default constructor for GetRuleAction + */ + private GetRuleAction() { + super(NAME, GetRuleResponse::new); + } +} diff --git a/modules/autotagging-commons/src/main/java/org/opensearch/rule/action/TransportGetRuleAction.java b/modules/autotagging-commons/src/main/java/org/opensearch/rule/action/TransportGetRuleAction.java new file mode 100644 index 0000000000000..94321e5d40713 --- /dev/null +++ b/modules/autotagging-commons/src/main/java/org/opensearch/rule/action/TransportGetRuleAction.java @@ -0,0 +1,53 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule.action; + +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.common.inject.Inject; +import org.opensearch.core.action.ActionListener; +import org.opensearch.rule.GetRuleRequest; +import org.opensearch.rule.GetRuleResponse; +import org.opensearch.rule.RulePersistenceService; +import org.opensearch.rule.RulePersistenceServiceRegistry; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +/** + * Transport action to get Rules + * @opensearch.experimental + */ +public class TransportGetRuleAction extends HandledTransportAction { + + private final RulePersistenceServiceRegistry rulePersistenceServiceRegistry; + + /** + * Constructor for TransportGetWlmRuleAction + * @param transportService - a {@link TransportService} object + * @param actionFilters - a {@link ActionFilters} object + * @param rulePersistenceServiceRegistry - a {@link RulePersistenceServiceRegistry} object + */ + @Inject + public TransportGetRuleAction( + TransportService transportService, + ActionFilters actionFilters, + RulePersistenceServiceRegistry rulePersistenceServiceRegistry + ) { + super(GetRuleAction.NAME, transportService, actionFilters, GetRuleRequest::new); + this.rulePersistenceServiceRegistry = rulePersistenceServiceRegistry; + } + + @Override + protected void doExecute(Task task, GetRuleRequest request, ActionListener listener) { + final RulePersistenceService rulePersistenceService = rulePersistenceServiceRegistry.getRulePersistenceService( + request.getFeatureType() + ); + rulePersistenceService.getRule(request, listener); + } +} diff --git a/modules/autotagging-commons/src/main/java/org/opensearch/rule/action/package-info.java b/modules/autotagging-commons/src/main/java/org/opensearch/rule/action/package-info.java new file mode 100644 index 0000000000000..91913aff23eac --- /dev/null +++ b/modules/autotagging-commons/src/main/java/org/opensearch/rule/action/package-info.java @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** + * This package contains abstract action classes for rules + */ +package org.opensearch.rule.action; diff --git a/libs/autotagging-commons/src/main/java/org/opensearch/rule/package-info.java b/modules/autotagging-commons/src/main/java/org/opensearch/rule/package-info.java similarity index 100% rename from libs/autotagging-commons/src/main/java/org/opensearch/rule/package-info.java rename to modules/autotagging-commons/src/main/java/org/opensearch/rule/package-info.java diff --git a/modules/autotagging-commons/src/main/java/org/opensearch/rule/rest/RestGetRuleAction.java b/modules/autotagging-commons/src/main/java/org/opensearch/rule/rest/RestGetRuleAction.java new file mode 100644 index 0000000000000..f1bd56927c41f --- /dev/null +++ b/modules/autotagging-commons/src/main/java/org/opensearch/rule/rest/RestGetRuleAction.java @@ -0,0 +1,135 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule.rest; + +import org.opensearch.common.annotation.ExperimentalApi; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.BytesRestResponse; +import org.opensearch.rest.RestChannel; +import org.opensearch.rest.RestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.RestResponse; +import org.opensearch.rest.action.RestResponseListener; +import org.opensearch.rule.GetRuleRequest; +import org.opensearch.rule.GetRuleResponse; +import org.opensearch.rule.action.GetRuleAction; +import org.opensearch.rule.autotagging.Attribute; +import org.opensearch.rule.autotagging.FeatureType; +import org.opensearch.transport.client.node.NodeClient; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.opensearch.rest.RestRequest.Method.GET; +import static org.opensearch.rule.autotagging.Rule._ID_STRING; + +/** + * Rest action to get a Rule + * @opensearch.experimental + */ +@ExperimentalApi +public class RestGetRuleAction extends BaseRestHandler { + /** + * field name used for pagination + */ + public static final String SEARCH_AFTER_STRING = "search_after"; + /** + * field name to specify feature type + */ + public static final String FEATURE_TYPE = "featureType"; + + /** + * constructor for RestGetRuleAction + */ + public RestGetRuleAction() {} + + @Override + public String getName() { + return "get_rule"; + } + + @Override + public List routes() { + return List.of(new RestHandler.Route(GET, "_rules/{featureType}/"), new RestHandler.Route(GET, "_rules/{featureType}/{_id}")); + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + final Map> attributeFilters = new HashMap<>(); + + if (!request.hasParam(FEATURE_TYPE)) { + throw new IllegalArgumentException("Invalid route."); + } + + final FeatureType featureType = FeatureType.from(request.param(FEATURE_TYPE)); + final List requestParams = request.params() + .keySet() + .stream() + .filter(key -> !key.equals(FEATURE_TYPE) && !key.equals(_ID_STRING) && !key.equals(SEARCH_AFTER_STRING)) + .toList(); + + for (String attributeName : requestParams) { + Attribute attribute = featureType.getAttributeFromName(attributeName); + if (attribute == null) { + throw new IllegalArgumentException(attributeName + " is not a valid attribute under feature type " + featureType.getName()); + } + attributeFilters.put(attribute, parseAttributeValues(request.param(attributeName), attributeName, featureType)); + } + final GetRuleRequest getRuleRequest = new GetRuleRequest( + request.param(_ID_STRING), + attributeFilters, + request.param(SEARCH_AFTER_STRING), + featureType + ); + return channel -> client.execute(GetRuleAction.INSTANCE, getRuleRequest, getRuleResponse(channel)); + } + + /** + * Parses a comma-separated string of attribute values and validates each value. + * @param attributeValues - the comma-separated string representing attributeValues + * @param attributeName - attribute name + */ + private HashSet parseAttributeValues(String attributeValues, String attributeName, FeatureType featureType) { + String[] valuesArray = attributeValues.split(","); + int maxNumberOfValuesPerAttribute = featureType.getMaxNumberOfValuesPerAttribute(); + if (valuesArray.length > maxNumberOfValuesPerAttribute) { + throw new IllegalArgumentException( + "The attribute value length for " + attributeName + " exceeds the maximum allowed of " + maxNumberOfValuesPerAttribute + ); + } + for (String value : valuesArray) { + if (value == null || value.trim().isEmpty() || value.length() > featureType.getMaxCharLengthPerAttributeValue()) { + throw new IllegalArgumentException( + "Invalid attribute value for: " + + attributeName + + " : String cannot be empty or over " + + featureType.getMaxCharLengthPerAttributeValue() + + " characters." + ); + } + } + return new HashSet<>(Arrays.asList(valuesArray)); + } + + private RestResponseListener getRuleResponse(final RestChannel channel) { + return new RestResponseListener<>(channel) { + @Override + public RestResponse buildResponse(final GetRuleResponse response) throws Exception { + return new BytesRestResponse(RestStatus.OK, response.toXContent(channel.newBuilder(), ToXContent.EMPTY_PARAMS)); + } + }; + } +} diff --git a/modules/autotagging-commons/src/main/java/org/opensearch/rule/rest/package-info.java b/modules/autotagging-commons/src/main/java/org/opensearch/rule/rest/package-info.java new file mode 100644 index 0000000000000..c1000b90b1856 --- /dev/null +++ b/modules/autotagging-commons/src/main/java/org/opensearch/rule/rest/package-info.java @@ -0,0 +1,12 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +/** + * This package contains abstract rest classes for rules + */ +package org.opensearch.rule.rest; diff --git a/libs/autotagging-commons/src/test/java/org/opensearch/rule/InMemoryRuleProcessingServiceTests.java b/modules/autotagging-commons/src/test/java/org/opensearch/rule/InMemoryRuleProcessingServiceTests.java similarity index 92% rename from libs/autotagging-commons/src/test/java/org/opensearch/rule/InMemoryRuleProcessingServiceTests.java rename to modules/autotagging-commons/src/test/java/org/opensearch/rule/InMemoryRuleProcessingServiceTests.java index d12900a79b121..20a53c345edf7 100644 --- a/libs/autotagging-commons/src/test/java/org/opensearch/rule/InMemoryRuleProcessingServiceTests.java +++ b/modules/autotagging-commons/src/test/java/org/opensearch/rule/InMemoryRuleProcessingServiceTests.java @@ -8,15 +8,14 @@ package org.opensearch.rule; -import org.opensearch.autotagging.Attribute; -import org.opensearch.autotagging.FeatureType; -import org.opensearch.autotagging.Rule; -import org.opensearch.core.common.io.stream.StreamOutput; import org.opensearch.rule.attribute_extractor.AttributeExtractor; +import org.opensearch.rule.autotagging.Attribute; +import org.opensearch.rule.autotagging.AutoTaggingRegistry; +import org.opensearch.rule.autotagging.FeatureType; +import org.opensearch.rule.autotagging.Rule; import org.opensearch.rule.storage.DefaultAttributeValueStore; import org.opensearch.test.OpenSearchTestCase; -import java.io.IOException; import java.util.List; import java.util.Map; import java.util.Optional; @@ -124,9 +123,13 @@ public Iterable extract() { public enum WLMFeatureType implements FeatureType { WLM; + static { + WLM.registerFeatureType(); + } + @Override public String getName() { - return ""; + return "wlm"; } @Override @@ -135,7 +138,9 @@ public Map getAllowedAttributesRegistry() { } @Override - public void registerFeatureType() {} + public void registerFeatureType() { + AutoTaggingRegistry.registerFeatureType(WLM); + } } public enum TestAttribute implements Attribute { @@ -155,8 +160,5 @@ public String getName() { @Override public void validateAttribute() {} - - @Override - public void writeTo(StreamOutput out) throws IOException {} } } diff --git a/modules/autotagging-commons/src/test/java/org/opensearch/rule/RuleFrameworkPluginTests.java b/modules/autotagging-commons/src/test/java/org/opensearch/rule/RuleFrameworkPluginTests.java new file mode 100644 index 0000000000000..fd4c5d18cf2cf --- /dev/null +++ b/modules/autotagging-commons/src/test/java/org/opensearch/rule/RuleFrameworkPluginTests.java @@ -0,0 +1,49 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule; + +import org.opensearch.action.ActionRequest; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.node.DiscoveryNodes; +import org.opensearch.common.settings.Settings; +import org.opensearch.core.action.ActionResponse; +import org.opensearch.plugins.ActionPlugin; +import org.opensearch.rest.RestHandler; +import org.opensearch.rule.action.GetRuleAction; +import org.opensearch.rule.rest.RestGetRuleAction; +import org.opensearch.test.OpenSearchTestCase; + +import java.util.List; + +import static org.mockito.Mockito.mock; + +public class RuleFrameworkPluginTests extends OpenSearchTestCase { + RuleFrameworkPlugin plugin = new RuleFrameworkPlugin();; + + public void testGetActions() { + List> handlers = plugin.getActions(); + assertEquals(1, handlers.size()); + assertEquals(GetRuleAction.INSTANCE.name(), handlers.get(0).getAction().name()); + } + + public void testGetRestHandlers() { + Settings settings = Settings.EMPTY; + RestHandler handler = plugin.getRestHandlers( + settings, + mock(org.opensearch.rest.RestController.class), + null, + null, + null, + mock(IndexNameExpressionResolver.class), + () -> mock(DiscoveryNodes.class) + ).get(0); + + assertTrue(handler instanceof RestGetRuleAction); + } +} diff --git a/modules/autotagging-commons/src/test/java/org/opensearch/rule/RulePersistenceServiceRegistryTests.java b/modules/autotagging-commons/src/test/java/org/opensearch/rule/RulePersistenceServiceRegistryTests.java new file mode 100644 index 0000000000000..116ad7a730c58 --- /dev/null +++ b/modules/autotagging-commons/src/test/java/org/opensearch/rule/RulePersistenceServiceRegistryTests.java @@ -0,0 +1,48 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule; + +import org.opensearch.rule.autotagging.FeatureType; +import org.opensearch.test.OpenSearchTestCase; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class RulePersistenceServiceRegistryTests extends OpenSearchTestCase { + RulePersistenceServiceRegistry registry = new RulePersistenceServiceRegistry();; + FeatureType mockFeatureType = mock(FeatureType.class);; + RulePersistenceService mockService = mock(RulePersistenceService.class); + + public void testRegisterAndGetService() { + when(mockFeatureType.getName()).thenReturn("test_feature"); + registry.register(mockFeatureType, mockService); + RulePersistenceService retrievedService = registry.getRulePersistenceService(mockFeatureType); + assertSame(mockService, retrievedService); + } + + public void testRegisterDuplicateService() { + when(mockFeatureType.getName()).thenReturn("duplicate_feature"); + registry.register(mockFeatureType, mockService); + RulePersistenceService anotherService = mock(RulePersistenceService.class); + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + () -> registry.register(mockFeatureType, anotherService) + ); + assertTrue(ex.getMessage().contains("Duplicate rule persistence service: duplicate_feature")); + } + + public void testGetRulePersistenceService_UnknownFeature() { + when(mockFeatureType.getName()).thenReturn("unknown_feature"); + IllegalArgumentException ex = assertThrows( + IllegalArgumentException.class, + () -> registry.getRulePersistenceService(mockFeatureType) + ); + assertTrue(ex.getMessage().contains("Unknown feature type: unknown_feature")); + } +} diff --git a/modules/autotagging-commons/src/test/java/org/opensearch/rule/action/GetRuleActionTests.java b/modules/autotagging-commons/src/test/java/org/opensearch/rule/action/GetRuleActionTests.java new file mode 100644 index 0000000000000..982e5ca50b0be --- /dev/null +++ b/modules/autotagging-commons/src/test/java/org/opensearch/rule/action/GetRuleActionTests.java @@ -0,0 +1,22 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule.action; + +import org.opensearch.core.common.io.stream.Writeable; +import org.opensearch.test.OpenSearchTestCase; + +public class GetRuleActionTests extends OpenSearchTestCase { + public void testGetName() { + assertEquals("cluster:admin/opensearch/rule/_get", GetRuleAction.NAME); + } + + public void testGetResponseReader() { + assertTrue(GetRuleAction.INSTANCE.getResponseReader() instanceof Writeable.Reader); + } +} diff --git a/modules/autotagging-commons/src/test/java/org/opensearch/rule/action/TransportGetRuleActionTests.java b/modules/autotagging-commons/src/test/java/org/opensearch/rule/action/TransportGetRuleActionTests.java new file mode 100644 index 0000000000000..369af17ed581f --- /dev/null +++ b/modules/autotagging-commons/src/test/java/org/opensearch/rule/action/TransportGetRuleActionTests.java @@ -0,0 +1,42 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule.action; + +import org.opensearch.action.support.ActionFilters; +import org.opensearch.rule.GetRuleRequest; +import org.opensearch.rule.RulePersistenceService; +import org.opensearch.rule.RulePersistenceServiceRegistry; +import org.opensearch.test.OpenSearchTestCase; +import org.opensearch.transport.TransportService; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class TransportGetRuleActionTests extends OpenSearchTestCase { + TransportGetRuleAction sut; + + public void testExecute() { + RulePersistenceServiceRegistry rulePersistenceServiceRegistry = mock(RulePersistenceServiceRegistry.class); + TransportService transportService = mock(TransportService.class); + ActionFilters actionFilters = mock(ActionFilters.class); + RulePersistenceService rulePersistenceService = mock(RulePersistenceService.class); + GetRuleRequest getRuleRequest = mock(GetRuleRequest.class); + when(getRuleRequest.getFeatureType()).thenReturn(null); + + when(rulePersistenceServiceRegistry.getRulePersistenceService(any())).thenReturn(rulePersistenceService); + doNothing().when(rulePersistenceService).getRule(any(), any()); + sut = new TransportGetRuleAction(transportService, actionFilters, rulePersistenceServiceRegistry); + sut.doExecute(null, getRuleRequest, null); + verify(rulePersistenceService, times(1)).getRule(any(), any()); + } +} diff --git a/modules/autotagging-commons/src/test/java/org/opensearch/rule/rest/RestGetRuleActionTests.java b/modules/autotagging-commons/src/test/java/org/opensearch/rule/rest/RestGetRuleActionTests.java new file mode 100644 index 0000000000000..726924593e2b8 --- /dev/null +++ b/modules/autotagging-commons/src/test/java/org/opensearch/rule/rest/RestGetRuleActionTests.java @@ -0,0 +1,26 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.rule.rest; + +import org.opensearch.test.OpenSearchTestCase; + +public class RestGetRuleActionTests extends OpenSearchTestCase { + RestGetRuleAction action = new RestGetRuleAction();; + + public void testGetName() { + assertEquals("get_rule", action.getName()); + } + + public void testRoutes() { + var routes = action.routes(); + assertEquals(2, routes.size()); + assertTrue(routes.stream().anyMatch(r -> r.getPath().equals("_rules/{featureType}/"))); + assertTrue(routes.stream().anyMatch(r -> r.getPath().equals("_rules/{featureType}/{_id}"))); + } +} diff --git a/plugins/workload-management/build.gradle b/plugins/workload-management/build.gradle index 5396a74361b77..7ac16405b2458 100644 --- a/plugins/workload-management/build.gradle +++ b/plugins/workload-management/build.gradle @@ -9,6 +9,7 @@ * GitHub history for details. */ +apply plugin: 'opensearch.opensearchplugin' apply plugin: 'opensearch.yaml-rest-test' apply plugin: 'opensearch.java-rest-test' apply plugin: 'opensearch.internal-cluster-test' @@ -16,8 +17,10 @@ apply plugin: 'opensearch.internal-cluster-test' opensearchplugin { description = 'OpenSearch Workload Management Plugin.' classname = 'org.opensearch.plugin.wlm.WorkloadManagementPlugin' + extendedPlugins = [] // Remove autotagging-commons since it's not a plugin } dependencies { - api project(":libs:opensearch-autotagging-commons") + implementation project(':modules:autotagging-commons:common') + implementation project(':modules:autotagging-commons:spi') } diff --git a/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/WorkloadManagementPlugin.java b/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/WorkloadManagementPlugin.java index 85c8cc8409c0f..3187b7d9fdd49 100644 --- a/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/WorkloadManagementPlugin.java +++ b/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/WorkloadManagementPlugin.java @@ -11,6 +11,7 @@ import org.opensearch.action.ActionRequest; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.node.DiscoveryNodes; +import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.inject.Module; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.IndexScopedSettings; @@ -18,6 +19,11 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.settings.SettingsFilter; import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.NamedWriteableRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.env.Environment; +import org.opensearch.env.NodeEnvironment; +import org.opensearch.indices.SystemIndexDescriptor; import org.opensearch.plugin.wlm.action.CreateWorkloadGroupAction; import org.opensearch.plugin.wlm.action.DeleteWorkloadGroupAction; import org.opensearch.plugin.wlm.action.GetWorkloadGroupAction; @@ -30,26 +36,74 @@ import org.opensearch.plugin.wlm.rest.RestDeleteWorkloadGroupAction; import org.opensearch.plugin.wlm.rest.RestGetWorkloadGroupAction; import org.opensearch.plugin.wlm.rest.RestUpdateWorkloadGroupAction; +import org.opensearch.plugin.wlm.rule.WorkloadGroupFeatureType; import org.opensearch.plugin.wlm.service.WorkloadGroupPersistenceService; import org.opensearch.plugins.ActionPlugin; import org.opensearch.plugins.Plugin; +import org.opensearch.plugins.SystemIndexPlugin; +import org.opensearch.repositories.RepositoriesService; import org.opensearch.rest.RestController; import org.opensearch.rest.RestHandler; +import org.opensearch.rule.RulePersistenceService; +import org.opensearch.rule.autotagging.FeatureType; +import org.opensearch.rule.service.IndexStoredRulePersistenceService; +import org.opensearch.rule.spi.RuleFrameworkExtension; +import org.opensearch.rule.storage.IndexBasedRuleQueryMapper; +import org.opensearch.rule.storage.XContentRuleParser; +import org.opensearch.script.ScriptService; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.client.Client; +import org.opensearch.watcher.ResourceWatcherService; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.function.Supplier; /** * Plugin class for WorkloadManagement */ -public class WorkloadManagementPlugin extends Plugin implements ActionPlugin { +public class WorkloadManagementPlugin extends Plugin implements ActionPlugin, SystemIndexPlugin, RuleFrameworkExtension { + /** + * The name of the index where rules are stored. + */ + public static final String INDEX_NAME = ".wlm_rules"; + /** + * The maximum number of rules allowed per GET request. + */ + public static final int MAX_RULES_PER_PAGE = 50; + + private final RulePersistenceServiceHolder rulePersistenceServiceHolder = new RulePersistenceServiceHolder(); /** * Default constructor */ public WorkloadManagementPlugin() {} + @Override + public Collection createComponents( + Client client, + ClusterService clusterService, + ThreadPool threadPool, + ResourceWatcherService resourceWatcherService, + ScriptService scriptService, + NamedXContentRegistry xContentRegistry, + Environment environment, + NodeEnvironment nodeEnvironment, + NamedWriteableRegistry namedWriteableRegistry, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier repositoriesServiceSupplier + ) { + RulePersistenceServiceHolder.rulePersistenceService = new IndexStoredRulePersistenceService( + INDEX_NAME, + client, + MAX_RULES_PER_PAGE, + new XContentRuleParser(WorkloadGroupFeatureType.INSTANCE), + new IndexBasedRuleQueryMapper() + ); + return Collections.emptyList(); + } + @Override public List> getActions() { return List.of( @@ -60,6 +114,11 @@ public WorkloadManagementPlugin() {} ); } + @Override + public Collection getSystemIndexDescriptors(Settings settings) { + return List.of(new SystemIndexDescriptor(INDEX_NAME, "System index used for storing rules")); + } + @Override public List getRestHandlers( Settings settings, @@ -87,4 +146,18 @@ public List> getSettings() { public Collection createGuiceModules() { return List.of(new WorkloadManagementPluginModule()); } + + @Override + public Supplier getRulePersistenceServiceSupplier() { + return () -> RulePersistenceServiceHolder.rulePersistenceService; + } + + @Override + public FeatureType getFeatureType() { + return WorkloadGroupFeatureType.INSTANCE; + } + + static class RulePersistenceServiceHolder { + private static RulePersistenceService rulePersistenceService; + } } diff --git a/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/action/package-info.java b/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/action/package-info.java index 9921500df8a81..df30f55a99b3c 100644 --- a/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/action/package-info.java +++ b/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/action/package-info.java @@ -7,6 +7,6 @@ */ /** - * Package for the action classes of WorkloadManagementPlugin + * Package for the action classes related to query groups in WorkloadManagementPlugin */ package org.opensearch.plugin.wlm.action; diff --git a/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/rest/package-info.java b/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/rest/package-info.java index 7d7cb9028fdb8..889f3e107db07 100644 --- a/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/rest/package-info.java +++ b/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/rest/package-info.java @@ -7,6 +7,6 @@ */ /** - * Package for the rest classes of WorkloadManagementPlugin + * Package for the rest classes related to query groups in WorkloadManagementPlugin */ package org.opensearch.plugin.wlm.rest; diff --git a/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/rule/WorkloadGroupFeatureType.java b/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/rule/WorkloadGroupFeatureType.java new file mode 100644 index 0000000000000..1dce67ee4e72d --- /dev/null +++ b/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/rule/WorkloadGroupFeatureType.java @@ -0,0 +1,64 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugin.wlm.rule; + +import org.opensearch.rule.RuleAttribute; +import org.opensearch.rule.autotagging.Attribute; +import org.opensearch.rule.autotagging.AutoTaggingRegistry; +import org.opensearch.rule.autotagging.FeatureType; + +import java.util.Map; + +/** + * Represents a feature type specific to the workload group feature + * @opensearch.experimental + */ +public class WorkloadGroupFeatureType implements FeatureType { + /** + * The instance for WorkloadGroupFeatureType + */ + public static final WorkloadGroupFeatureType INSTANCE = new WorkloadGroupFeatureType(); + /** + * Name for WorkloadGroupFeatureType + */ + public static final String NAME = "workload_group"; + private static final int MAX_ATTRIBUTE_VALUES = 10; + private static final int MAX_ATTRIBUTE_VALUE_LENGTH = 100; + private static final Map ALLOWED_ATTRIBUTES = Map.of( + RuleAttribute.INDEX_PATTERN.getName(), + RuleAttribute.INDEX_PATTERN + ); + + private WorkloadGroupFeatureType() {} + + @Override + public String getName() { + return NAME; + } + + @Override + public int getMaxNumberOfValuesPerAttribute() { + return MAX_ATTRIBUTE_VALUES; + } + + @Override + public int getMaxCharLengthPerAttributeValue() { + return MAX_ATTRIBUTE_VALUE_LENGTH; + } + + @Override + public Map getAllowedAttributesRegistry() { + return ALLOWED_ATTRIBUTES; + } + + @Override + public void registerFeatureType() { + AutoTaggingRegistry.registerFeatureType(INSTANCE); + } +} diff --git a/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/rule/attribute_extractor/IndicesExtractor.java b/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/rule/attribute_extractor/IndicesExtractor.java index a3230ac919eb1..e556e2984777e 100644 --- a/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/rule/attribute_extractor/IndicesExtractor.java +++ b/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/rule/attribute_extractor/IndicesExtractor.java @@ -9,8 +9,9 @@ package org.opensearch.plugin.wlm.rule.attribute_extractor; import org.opensearch.action.IndicesRequest; -import org.opensearch.autotagging.Attribute; +import org.opensearch.rule.RuleAttribute; import org.opensearch.rule.attribute_extractor.AttributeExtractor; +import org.opensearch.rule.autotagging.Attribute; import java.util.List; @@ -30,8 +31,7 @@ public IndicesExtractor(IndicesRequest indicesRequest) { @Override public Attribute getAttribute() { - // TODO: this will be replaced by WLM defined index_pattern attribute - return null; + return RuleAttribute.INDEX_PATTERN; } @Override diff --git a/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/service/package-info.java b/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/service/package-info.java index 5848e9c936623..e8c88ee656dc7 100644 --- a/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/service/package-info.java +++ b/plugins/workload-management/src/main/java/org/opensearch/plugin/wlm/service/package-info.java @@ -7,6 +7,6 @@ */ /** - * Package for the service classes of WorkloadManagementPlugin + * Package for the service classes related to query groups in WorkloadManagementPlugin */ package org.opensearch.plugin.wlm.service; diff --git a/plugins/workload-management/src/main/resources/META-INF/services/org.opensearch.rule.spi.RuleFrameworkExtension b/plugins/workload-management/src/main/resources/META-INF/services/org.opensearch.rule.spi.RuleFrameworkExtension new file mode 100644 index 0000000000000..71c636200b0d8 --- /dev/null +++ b/plugins/workload-management/src/main/resources/META-INF/services/org.opensearch.rule.spi.RuleFrameworkExtension @@ -0,0 +1,9 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# + +org.opensearch.plugin.wlm.WorkloadManagementPlugin diff --git a/plugins/workload-management/src/test/java/org/opensearch/plugin/wlm/WorkloadManagementPluginTests.java b/plugins/workload-management/src/test/java/org/opensearch/plugin/wlm/WorkloadManagementPluginTests.java new file mode 100644 index 0000000000000..6838102ac3bf1 --- /dev/null +++ b/plugins/workload-management/src/test/java/org/opensearch/plugin/wlm/WorkloadManagementPluginTests.java @@ -0,0 +1,115 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugin.wlm; + +import org.opensearch.action.ActionRequest; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.node.DiscoveryNodes; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.Settings; +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.NamedWriteableRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.env.Environment; +import org.opensearch.indices.SystemIndexDescriptor; +import org.opensearch.plugin.wlm.action.CreateWorkloadGroupAction; +import org.opensearch.plugin.wlm.rest.RestCreateWorkloadGroupAction; +import org.opensearch.plugin.wlm.rest.RestDeleteWorkloadGroupAction; +import org.opensearch.plugin.wlm.rest.RestGetWorkloadGroupAction; +import org.opensearch.plugin.wlm.rest.RestUpdateWorkloadGroupAction; +import org.opensearch.plugin.wlm.service.WorkloadGroupPersistenceService; +import org.opensearch.plugins.ActionPlugin; +import org.opensearch.repositories.RepositoriesService; +import org.opensearch.rest.RestController; +import org.opensearch.rest.RestHandler; +import org.opensearch.rule.RulePersistenceService; +import org.opensearch.rule.autotagging.FeatureType; +import org.opensearch.rule.service.IndexStoredRulePersistenceService; +import org.opensearch.script.ScriptService; +import org.opensearch.test.OpenSearchTestCase; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.client.Client; +import org.opensearch.watcher.ResourceWatcherService; + +import java.util.Collection; +import java.util.List; + +import static org.mockito.Mockito.mock; + +public class WorkloadManagementPluginTests extends OpenSearchTestCase { + WorkloadManagementPlugin plugin = new WorkloadManagementPlugin(); + + public void testGetActionsReturnsHandlers() { + List> actions = plugin.getActions(); + assertEquals(4, actions.size()); + assertEquals(CreateWorkloadGroupAction.INSTANCE.name(), actions.get(0).getAction().name()); + } + + public void testGetRestHandlersReturnsHandlers() { + List handlers = plugin.getRestHandlers( + Settings.EMPTY, + mock(RestController.class), + null, + null, + null, + null, + () -> mock(DiscoveryNodes.class) + ); + + assertEquals(4, handlers.size()); + assertTrue(handlers.stream().anyMatch(h -> h instanceof RestCreateWorkloadGroupAction)); + assertTrue(handlers.stream().anyMatch(h -> h instanceof RestGetWorkloadGroupAction)); + assertTrue(handlers.stream().anyMatch(h -> h instanceof RestDeleteWorkloadGroupAction)); + assertTrue(handlers.stream().anyMatch(h -> h instanceof RestUpdateWorkloadGroupAction)); + } + + public void testCreateComponentsInitializesRulePersistenceService() { + Client mockClient = mock(Client.class); + plugin.createComponents( + mockClient, + mock(ClusterService.class), + mock(ThreadPool.class), + mock(ResourceWatcherService.class), + mock(ScriptService.class), + mock(NamedXContentRegistry.class), + mock(Environment.class), + null, + mock(NamedWriteableRegistry.class), + mock(IndexNameExpressionResolver.class), + () -> mock(RepositoriesService.class) + ); + + RulePersistenceService service = plugin.getRulePersistenceServiceSupplier().get(); + assertNotNull(service); + assertTrue(service instanceof IndexStoredRulePersistenceService); + } + + public void testGetSystemIndexDescriptorsReturnsCorrectDescriptor() { + Collection descriptors = plugin.getSystemIndexDescriptors(Settings.EMPTY); + assertEquals(1, descriptors.size()); + SystemIndexDescriptor descriptor = descriptors.iterator().next(); + assertEquals(".wlm_rules", descriptor.getIndexPattern()); + } + + public void testGetFeatureTypeReturnsWorkloadGroupFeatureType() { + FeatureType featureType = plugin.getFeatureType(); + assertEquals("workload_group", featureType.getName()); + } + + public void testGetSettingsIncludesMaxQueryGroupCount() { + List settings = plugin.getSettings(); + assertTrue(settings.contains(WorkloadGroupPersistenceService.MAX_QUERY_GROUP_COUNT)); + } + + public void testCreateGuiceModulesReturnsModule() { + Collection modules = plugin.createGuiceModules(); + assertEquals(1, modules.size()); + assertTrue(modules.iterator().next() instanceof WorkloadManagementPluginModule); + } +} diff --git a/plugins/workload-management/src/test/java/org/opensearch/plugin/wlm/rule/WorkloadGroupFeatureTypeTests.java b/plugins/workload-management/src/test/java/org/opensearch/plugin/wlm/rule/WorkloadGroupFeatureTypeTests.java new file mode 100644 index 0000000000000..c2728a36e9196 --- /dev/null +++ b/plugins/workload-management/src/test/java/org/opensearch/plugin/wlm/rule/WorkloadGroupFeatureTypeTests.java @@ -0,0 +1,42 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + */ + +package org.opensearch.plugin.wlm.rule; + +import org.opensearch.rule.RuleAttribute; +import org.opensearch.rule.autotagging.Attribute; +import org.opensearch.rule.autotagging.AutoTaggingRegistry; +import org.opensearch.test.OpenSearchTestCase; + +import java.util.Map; + +public class WorkloadGroupFeatureTypeTests extends OpenSearchTestCase { + WorkloadGroupFeatureType featureType = WorkloadGroupFeatureType.INSTANCE; + + public void testGetName_returnsCorrectName() { + assertEquals("workload_group", featureType.getName()); + } + + public void testMaxNumberOfValuesPerAttribute() { + assertEquals(10, featureType.getMaxNumberOfValuesPerAttribute()); + } + + public void testMaxCharLengthPerAttributeValue() { + assertEquals(100, featureType.getMaxCharLengthPerAttributeValue()); + } + + public void testGetAllowedAttributesRegistry_containsIndexPattern() { + Map allowedAttributes = featureType.getAllowedAttributesRegistry(); + assertTrue(allowedAttributes.containsKey("index_pattern")); + assertEquals(RuleAttribute.INDEX_PATTERN, allowedAttributes.get("index_pattern")); + } + + public void testRegisterFeatureType() { + AutoTaggingRegistry.registerFeatureType(featureType); + } +}