Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Add temporal routing processors for time-based document routing ([#18920](https://github.com/opensearch-project/OpenSearch/issues/18920))
- Implement Query Rewriting Infrastructure ([#19060](https://github.com/opensearch-project/OpenSearch/pull/19060))
- The dynamic mapping parameter supports false_allow_templates ([#19065](https://github.com/opensearch-project/OpenSearch/pull/19065))
- [Rule-based Autotagging] add security attributes to rules ([#19232](https://github.com/opensearch-project/OpenSearch/pull/19232))
- Add a toBuilder method in EngineConfig to support easy modification of configs([#19054](https://github.com/opensearch-project/OpenSearch/pull/19054))
- Add StoreFactory plugin interface for custom Store implementations([#19091](https://github.com/opensearch-project/OpenSearch/pull/19091))
- Use S3CrtClient for higher throughput while uploading files to S3 ([#18800](https://github.com/opensearch-project/OpenSearch/pull/18800))
Expand Down
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ commonscodec = "1.18.0"
commonslang = "3.18.0"
commonscompress = "1.28.0"
commonsio = "2.16.0"
commonscollections4 = "4.5.0"
# plugin dependencies
aws = "2.30.31"
awscrt = "0.35.0"
Expand Down
2 changes: 1 addition & 1 deletion modules/autotagging-commons/common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ 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 "org.apache.commons:commons-collections4:${versions.commonscollections4}"
implementation project(":libs:opensearch-common")
compileOnly project(":server")

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
e5cf89f0c6e132fc970bd9a465fdcb8dbe94f75a
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

import org.opensearch.rule.autotagging.Attribute;

import java.util.TreeMap;

/**
* Generic Rule attributes that features can use out of the use by using the lib.
* @opensearch.experimental
Expand All @@ -32,6 +34,11 @@ public String getName() {
return name;
}

@Override
public TreeMap<Integer, String> getPrioritizedSubfields() {
return new TreeMap<>();
}

/**
* Retrieves the RuleAttribute from a name string
* @param name - attribute name
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*
* 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.xcontent.XContentBuilder;
import org.opensearch.core.xcontent.XContentParseException;
import org.opensearch.core.xcontent.XContentParser;
import org.opensearch.rule.autotagging.Attribute;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;

/**
* Security attribute for the rules. Example:
* principal: {
* "username": ["alice", "bob"],
* "role": ["admin"]
* }
* @opensearch.experimental
*/
public enum SecurityAttribute implements Attribute {
/**
* Represents the principal attribute
*/
PRINCIPAL("principal");

/**
* Represents the username subfield
*/
public static final String USERNAME = "username";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make this more generic so that there's no reference to username or role?

I'm not sure how WLM works, but I know that a request will match to the best workload group. How does the scoring work for matching? Can we assign weights to different attributes to help with the matching problem to determine the best group?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believed we need to reference to username or role because they are auto tagging's attributes. I just made them Attribute instead of a String to make it clearer

/**
* Represents the role subfield
*/
public static final String ROLE = "role";
private static final TreeMap<Integer, String> PRIORITIZED_SUBFIELDS = new TreeMap<>(Map.of(1, USERNAME, 2, ROLE));
private final String name;

SecurityAttribute(String name) {
this.name = name;
validateAttribute();
}

@Override
public String getName() {
return name;
}

@Override
public TreeMap<Integer, String> getPrioritizedSubfields() {
return PRIORITIZED_SUBFIELDS;
}

/**
* Parses the attribute values for security attribute
* Example:
* {
* "username": ["alice"],
* "role": ["all_access"]
* }
* will be parsed into a set with values "username|alice" and "role|all_access"
* @param parser the XContent parser
*/
@Override
public Set<String> fromXContentParseAttributeValues(XContentParser parser) throws IOException {
Set<String> resultSet = new HashSet<>();

if (parser.currentToken() != XContentParser.Token.START_OBJECT) {
throw new XContentParseException(
parser.getTokenLocation(),
"Expected START_OBJECT token for " + getName() + " attribute but got " + parser.currentToken()
);
}
List<String> allowedSubfieldsName = PRIORITIZED_SUBFIELDS.values().stream().toList();
while (parser.nextToken() != XContentParser.Token.END_OBJECT) {
String subFieldName = parser.currentName();
parser.nextToken();
if (!allowedSubfieldsName.contains(subFieldName)) {
throw new XContentParseException(
parser.getTokenLocation(),
"Invalid field: " + subFieldName + ". Allowed fields are: " + String.join(", ", allowedSubfieldsName)
);
}
resultSet.addAll(
Attribute.super.fromXContentParseAttributeValues(parser).stream()
.map(s -> subFieldName + '|' + s)
.collect(Collectors.toSet())
);
}

return resultSet;
}

@Override
public void toXContentWriteAttributeValues(XContentBuilder builder, Set<String> values) throws IOException {
builder.startObject(getName());
Map<String, Set<String>> grouped = new HashMap<>();
// For each string in the values set, split it into two parts using the first '|' as delimiter:
// parts[0] is the prefix (e.g., "username" or "role")
// parts[1] is the actual value (e.g., "name1", "role1")
for (String value : values) {
String[] parts = value.split("\\|", 2);
if (parts.length == 2) {
grouped.computeIfAbsent(parts[0], k -> new HashSet<>()).add(parts[1]);
}
}
for (Map.Entry<String, Set<String>> entry : grouped.entrySet()) {
builder.array(entry.getKey(), entry.getValue().toArray(new String[0]));
}
builder.endObject();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
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;
Expand All @@ -34,7 +33,7 @@
@ExperimentalApi
public class GetRuleRequest extends ActionRequest {
private final String id;
private final Map<Attribute, Set<String>> attributeFilters;
private final Map<String, Set<String>> attributeFilters;
private final String searchAfter;
private final FeatureType featureType;

Expand All @@ -45,7 +44,7 @@ public class GetRuleRequest extends ActionRequest {
* @param searchAfter - The sort value used for pagination.
* @param featureType - The feature type related to rule.
*/
public GetRuleRequest(String id, Map<Attribute, Set<String>> attributeFilters, String searchAfter, FeatureType featureType) {
public GetRuleRequest(String id, Map<String, Set<String>> attributeFilters, String searchAfter, FeatureType featureType) {
this.id = id;
this.attributeFilters = attributeFilters;
this.searchAfter = searchAfter;
Expand All @@ -60,7 +59,7 @@ 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()));
attributeFilters = in.readMap(StreamInput::readString, i -> new HashSet<>(i.readStringList()));
searchAfter = in.readOptionalString();
}

Expand All @@ -80,7 +79,7 @@ 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.writeMap(attributeFilters, StreamOutput::writeString, StreamOutput::writeStringCollection);
out.writeOptionalString(searchAfter);
}

Expand All @@ -94,7 +93,7 @@ public String getId() {
/**
* attributeFilters getter
*/
public Map<Attribute, Set<String>> getAttributeFilters() {
public Map<String, Set<String>> getAttributeFilters() {
return attributeFilters;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,22 @@
* @param <V>
*/
public interface AttributeExtractor<V> {

/**
* Defines the combination style used when a request contains multiple values
* for an attribute.
*/
enum LogicalOperator {
/**
* Logical AND
*/
AND,
/**
* Logical OR
*/
OR
}

/**
* This method returns the Attribute which it is responsible for extracting
* @return attribute
Expand All @@ -26,4 +42,13 @@ public interface AttributeExtractor<V> {
* @return attribute value
*/
Iterable<V> extract();

/**
* Returns the logical operator used when a request contains multiple values
* for an attribute.
* For example, if the request targets both index A and B, then a rule must
* have both index A and B as attributes, requiring an AND operator.
* @return the logical operator (e.g., AND, OR)
*/
LogicalOperator getLogicalOperator();
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@
import org.opensearch.core.common.io.stream.StreamInput;
import org.opensearch.core.common.io.stream.StreamOutput;
import org.opensearch.core.common.io.stream.Writeable;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.core.xcontent.XContentParseException;
import org.opensearch.core.xcontent.XContentParser;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.TreeMap;

/**
* Represents an attribute within the auto-tagging feature. Attributes define characteristics that can
Expand All @@ -29,6 +35,11 @@ public interface Attribute extends Writeable {
*/
String getName();

/**
* Returns a map of subfields ordered by priority, where 1 represents the highest priority.
*/
TreeMap<Integer, String> getPrioritizedSubfields();

/**
* Ensure that `validateAttribute` is called in the constructor of attribute implementations
* to prevent potential serialization issues.
Expand All @@ -45,6 +56,39 @@ default void writeTo(StreamOutput out) throws IOException {
out.writeString(getName());
}

/**
* Parses attribute values for specific attributes. This default function takes in parser
* and returns a set of string.
* For example, ["index1", "index2"] will be parsed to a set with values "index1" and "index2"
* @param parser the XContent parser
*/
default Set<String> fromXContentParseAttributeValues(XContentParser parser) throws IOException {
if (parser.currentToken() != XContentParser.Token.START_ARRAY) {
throw new XContentParseException(
parser.getTokenLocation(),
"Expected START_ARRAY token for " + getName() + " attribute but got " + parser.currentToken()
);
}
Set<String> attributeValueSet = new HashSet<>();
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
if (parser.currentToken() == XContentParser.Token.VALUE_STRING) {
attributeValueSet.add(parser.text());
} else {
throw new XContentParseException("Unexpected token in array: " + parser.currentToken());
}
}
return attributeValueSet;
}

/**
* Writes a set of attribute values for a specific attribute
* @param builder the XContent builder
* @param values the set of string values to write
*/
default void toXContentWriteAttributeValues(XContentBuilder builder, Set<String> values) throws IOException {
builder.array(getName(), values.toArray(new String[0]));
}

/**
* Retrieves an attribute from the given feature type based on its name.
* Implementations of `FeatureType.getAttributeFromName` must be thread-safe as this method
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ private static void validateFeatureType(FeatureType featureType) {
"Feature type name " + name + " should not be null, empty or have more than " + MAX_FEATURE_TYPE_NAME_LENGTH + "characters"
);
}
if (featureType.getOrderedAttributes() == null) {
throw new IllegalStateException(
"Function getOrderedAttributes() should not return null for feature type: " + featureType.getName()
);
}
if (featureType.getFeatureValueValidator() == null) {
throw new IllegalStateException("FeatureValueValidator is not defined for feature type " + name);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

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

/**
* Represents a feature type within the auto-tagging feature. Feature types define different categories of
Expand Down Expand Up @@ -41,11 +42,19 @@ public interface FeatureType extends Writeable {
*/
String getName();

/**
* Returns a map of top-level attributes sorted by priority, with 1 representing the highest priority.
* Subfields within each attribute are managed separately here {@link org.opensearch.rule.autotagging.Attribute#getPrioritizedSubfields()}.
*/
Map<Attribute, Integer> getOrderedAttributes();

/**
* Returns the registry of allowed attributes for this feature type.
* Implementations must ensure that access to this registry is thread-safe.
*/
Map<String, Attribute> getAllowedAttributesRegistry();
default Map<String, Attribute> getAllowedAttributesRegistry() {
return getOrderedAttributes().keySet().stream().collect(Collectors.toUnmodifiableMap(Attribute::getName, attribute -> attribute));
}

/**
* returns the validator for feature value
Expand Down
Loading
Loading