Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
@@ -0,0 +1,125 @@
/*
* 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;

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

/**
* Key representing 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

/**
* Key representing the role subfield.
*/
public static final String ROLE = "role";
private final String name;
private static final List<String> ALLOWED_SUBFIELDS = List.of(USERNAME, ROLE);

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

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

@Override
public List<String> getPrioritizedSubfields() {
return ALLOWED_SUBFIELDS;
}

@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()
);
}
while (parser.nextToken() != XContentParser.Token.END_OBJECT) {
String subFieldName = parser.currentName();
parser.nextToken();
if (!ALLOWED_SUBFIELDS.contains(subFieldName)) {
throw new XContentParseException(
parser.getTokenLocation(),
"Invalid field: " + subFieldName + ". Allowed fields are: " + String.join(", ", ALLOWED_SUBFIELDS)
);
}
if (parser.currentToken() != XContentParser.Token.START_ARRAY) {
throw new XContentParseException(
parser.getTokenLocation(),
"Expected array for field: " + subFieldName + " but got " + parser.currentToken()
);
}
while (parser.nextToken() != XContentParser.Token.END_ARRAY) {
if (parser.currentToken() == XContentParser.Token.VALUE_STRING) {
// prefix each value with the subFieldName (e.g., "username_name1")
resultSet.add(String.join("_", subFieldName, parser.text()));
} else {
throw new XContentParseException(
parser.getTokenLocation(),
"Expected string value in array under '" + subFieldName + "', but got " + parser.currentToken()
);
}
}
}

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 underscore 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 @@ -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 CombinationStyle {
/**
* 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 combination style 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 combination.
* @return the combination style (e.g., AND, OR)
*/
CombinationStyle getCombinationStyle();
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,15 @@
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.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

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

/**
* Returns the allowed subfields ordered from highest to lowest priority
*/
default List<String> getPrioritizedSubfields() {
return new ArrayList<>();
}

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

/**
* Parses attribute values for specific attributes
* @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 @@ -13,6 +13,7 @@
import org.opensearch.core.common.io.stream.Writeable;

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

/**
Expand Down Expand Up @@ -47,6 +48,12 @@ public interface FeatureType extends Writeable {
*/
Map<String, Attribute> getAllowedAttributesRegistry();

/**
* Returns the list of attributes ordered by their processing priority.
* @return List of prioritized attributes.
*/
List<Attribute> getPrioritizedAttributesList();

/**
* returns the validator for feature value
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ public XContentBuilder toXContent(final XContentBuilder builder, final Params pa
builder.field(ID_STRING, id);
builder.field(DESCRIPTION_STRING, description);
for (Map.Entry<Attribute, Set<String>> entry : attributeMap.entrySet()) {
builder.array(entry.getKey().getName(), entry.getValue().toArray(new String[0]));
entry.getKey().toXContentWriteAttributeValues(builder, entry.getValue());
}
builder.field(featureType.getName(), featureValue);
builder.field(UPDATED_AT_STRING, updatedAt);
Expand Down Expand Up @@ -258,14 +258,14 @@ public static Builder fromXContent(XContentParser parser, FeatureType featureTyp
builder.featureType(featureType);
builder.featureValue(parser.text());
}
} else if (token == XContentParser.Token.START_ARRAY) {
fromXContentParseArray(parser, fieldName, featureType, attributeMap1);
} else if (token == XContentParser.Token.START_ARRAY || token == XContentParser.Token.START_OBJECT) {
fromXContentParseAttribute(parser, fieldName, featureType, attributeMap1);
}
}
return builder.attributeMap(attributeMap1);
}

private static void fromXContentParseArray(
private static void fromXContentParseAttribute(
XContentParser parser,
String fieldName,
FeatureType featureType,
Expand All @@ -275,15 +275,7 @@ private static void fromXContentParseArray(
if (attribute == null) {
throw new XContentParseException(fieldName + " is not a valid attribute within the " + featureType.getName() + " feature.");
}
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());
}
}
attributeMap.put(attribute, attributeValueSet);
attributeMap.put(attribute, attribute.fromXContentParseAttributeValues(parser));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@

package org.opensearch.rule.storage;

import java.util.Optional;
import java.util.List;
import java.util.Set;

/**
* This interface provides apis to store Rule attribute values
Expand All @@ -23,16 +24,17 @@ public interface AttributeValueStore<K, V> {

/**
* removes the key and associated value from attribute value store
* @param key to be removed
* @param key key of the value to be removed
* @param value to be removed
*/
void remove(K key);
void remove(K key, V value);

/**
* Returns the value associated with the key
* @param key in the data structure
* @return
*/
Optional<V> get(K key);
List<Set<V>> get(K key);

/**
* Clears all the keys and their associated values from the attribute value store
Expand Down
Loading
Loading