Skip to content

Commit 0684342

Browse files
Adding QueryGroup schema (#13669)
* rebase with opensearch/main Signed-off-by: Kaushal Kumar <[email protected]> * add resourceLimitGroupId propagation logic from coordinator to data nodes Signed-off-by: Kaushal Kumar <[email protected]> * add sandbox schema Signed-off-by: Kaushal Kumar <[email protected]> * add resourceLimitGroupTests Signed-off-by: Kaushal Kumar <[email protected]> * add resourceLimitGroupMetadata tests Signed-off-by: Kaushal Kumar <[email protected]> * run spotlessApply Signed-off-by: Kaushal Kumar <[email protected]> * add mode field in ResourceLimitGroup schema Signed-off-by: Kaushal Kumar <[email protected]> * fix breaking testcases Signed-off-by: Kaushal Kumar <[email protected]> * add task cancellation skeleton Signed-off-by: Kaushal Kumar <[email protected]> * add multitenant labels in searchSource builder Signed-off-by: Kaushal Kumar <[email protected]> * write custom xcontent parser for ResourceLimitGroup Signed-off-by: Kaushal Kumar <[email protected]> * remove unrelated changes Signed-off-by: Kaushal Kumar <[email protected]> * remove non-existing import fro cluster settings Signed-off-by: Kaushal Kumar <[email protected]> * remove non releated changes Signed-off-by: Kaushal Kumar <[email protected]> * add _id as the resourceLimitGroup key Signed-off-by: Kaushal Kumar <[email protected]> * add change to register resource limit group metadata Signed-off-by: Kaushal Kumar <[email protected]> * add updatedAt in resource limit group Signed-off-by: Kaushal Kumar <[email protected]> * rename resourceLimitGroup to queryGroup Signed-off-by: Kaushal Kumar <[email protected]> * address the comments on PR Signed-off-by: Kaushal Kumar <[email protected]> * rename the mode member var to resiliency mode Signed-off-by: Kaushal Kumar <[email protected]> * address comments Signed-off-by: Kaushal Kumar <[email protected]> * add change in CHANGELOG Signed-off-by: Kaushal Kumar <[email protected]> * add tests for custom namedWritable QueryGroupMetadata Signed-off-by: Kaushal Kumar <[email protected]> * structure resourceLimits into an object Signed-off-by: Kaushal Kumar <[email protected]> * add QueryGroup.toXContent test case Signed-off-by: Kaushal Kumar <[email protected]> * fix precommit errors Signed-off-by: Kaushal Kumar <[email protected]> * fix precommit errors Signed-off-by: Kaushal Kumar <[email protected]> * fix assemble errors Signed-off-by: Kaushal Kumar <[email protected]> * fix checkstyle errors Signed-off-by: Kaushal Kumar <[email protected]> * address comments Signed-off-by: Kaushal Kumar <[email protected]> --------- Signed-off-by: Kaushal Kumar <[email protected]>
1 parent 41fa085 commit 0684342

File tree

12 files changed

+810
-14
lines changed

12 files changed

+810
-14
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
99
- [Remote Store] Rate limiter for remote store low priority uploads ([#14374](https://github.com/opensearch-project/OpenSearch/pull/14374/))
1010
- Apply the date histogram rewrite optimization to range aggregation ([#13865](https://github.com/opensearch-project/OpenSearch/pull/13865))
1111
- [Writable Warm] Add composite directory implementation and integrate it with FileCache ([12782](https://github.com/opensearch-project/OpenSearch/pull/12782))
12+
- [Workload Management] Add QueryGroup schema ([13669](https://github.com/opensearch-project/OpenSearch/pull/13669))
1213
- Add batching supported processor base type AbstractBatchingProcessor ([#14554](https://github.com/opensearch-project/OpenSearch/pull/14554))
1314
- Fix race condition while parsing derived fields from search definition ([14445](https://github.com/opensearch-project/OpenSearch/pull/14445))
1415
- Add allowlist setting for ingest-common and search-pipeline-common processors ([#14439](https://github.com/opensearch-project/OpenSearch/issues/14439))

server/src/main/java/org/opensearch/cluster/ClusterModule.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import org.opensearch.cluster.metadata.MetadataIndexTemplateService;
4949
import org.opensearch.cluster.metadata.MetadataMappingService;
5050
import org.opensearch.cluster.metadata.MetadataUpdateSettingsService;
51+
import org.opensearch.cluster.metadata.QueryGroupMetadata;
5152
import org.opensearch.cluster.metadata.RepositoriesMetadata;
5253
import org.opensearch.cluster.metadata.ViewMetadata;
5354
import org.opensearch.cluster.metadata.WeightedRoutingMetadata;
@@ -214,6 +215,8 @@ public static List<Entry> getNamedWriteables() {
214215
DecommissionAttributeMetadata::new,
215216
DecommissionAttributeMetadata::readDiffFrom
216217
);
218+
219+
registerMetadataCustom(entries, QueryGroupMetadata.TYPE, QueryGroupMetadata::new, QueryGroupMetadata::readDiffFrom);
217220
// Task Status (not Diffable)
218221
entries.add(new Entry(Task.Status.class, PersistentTasksNodeService.Status.NAME, PersistentTasksNodeService.Status::new));
219222
return entries;

server/src/main/java/org/opensearch/cluster/metadata/Metadata.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,6 +1368,25 @@ public Builder removeDataStream(String name) {
13681368
return this;
13691369
}
13701370

1371+
public Builder queryGroups(final Map<String, QueryGroup> queryGroups) {
1372+
this.customs.put(QueryGroupMetadata.TYPE, new QueryGroupMetadata(queryGroups));
1373+
return this;
1374+
}
1375+
1376+
public Builder put(final QueryGroup queryGroup) {
1377+
Objects.requireNonNull(queryGroup, "queryGroup should not be null");
1378+
Map<String, QueryGroup> existing = new HashMap<>(getQueryGroups());
1379+
existing.put(queryGroup.get_id(), queryGroup);
1380+
return queryGroups(existing);
1381+
}
1382+
1383+
private Map<String, QueryGroup> getQueryGroups() {
1384+
return Optional.ofNullable(this.customs.get(QueryGroupMetadata.TYPE))
1385+
.map(o -> (QueryGroupMetadata) o)
1386+
.map(QueryGroupMetadata::queryGroups)
1387+
.orElse(Collections.emptyMap());
1388+
}
1389+
13711390
private Map<String, View> getViews() {
13721391
return Optional.ofNullable(customs.get(ViewMetadata.TYPE))
13731392
.map(o -> (ViewMetadata) o)
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*/
8+
9+
package org.opensearch.cluster.metadata;
10+
11+
import org.opensearch.cluster.AbstractDiffable;
12+
import org.opensearch.cluster.Diff;
13+
import org.opensearch.common.UUIDs;
14+
import org.opensearch.common.annotation.ExperimentalApi;
15+
import org.opensearch.core.common.io.stream.StreamInput;
16+
import org.opensearch.core.common.io.stream.StreamOutput;
17+
import org.opensearch.core.xcontent.ToXContentObject;
18+
import org.opensearch.core.xcontent.XContentBuilder;
19+
import org.opensearch.core.xcontent.XContentParser;
20+
import org.opensearch.search.ResourceType;
21+
import org.joda.time.Instant;
22+
23+
import java.io.IOException;
24+
import java.util.HashMap;
25+
import java.util.Map;
26+
import java.util.Objects;
27+
28+
/**
29+
* Class to define the QueryGroup schema
30+
* {
31+
* "_id": "fafjafjkaf9ag8a9ga9g7ag0aagaga",
32+
* "resourceLimits": {
33+
* "jvm": 0.4
34+
* },
35+
* "resiliency_mode": "enforced",
36+
* "name": "analytics",
37+
* "updatedAt": 4513232415
38+
* }
39+
*/
40+
@ExperimentalApi
41+
public class QueryGroup extends AbstractDiffable<QueryGroup> implements ToXContentObject {
42+
43+
private static final int MAX_CHARS_ALLOWED_IN_NAME = 50;
44+
private final String name;
45+
private final String _id;
46+
private final ResiliencyMode resiliencyMode;
47+
// It is an epoch in millis
48+
private final long updatedAtInMillis;
49+
private final Map<ResourceType, Object> resourceLimits;
50+
51+
public QueryGroup(String name, ResiliencyMode resiliencyMode, Map<ResourceType, Object> resourceLimits) {
52+
this(name, UUIDs.randomBase64UUID(), resiliencyMode, resourceLimits, Instant.now().getMillis());
53+
}
54+
55+
public QueryGroup(String name, String _id, ResiliencyMode resiliencyMode, Map<ResourceType, Object> resourceLimits, long updatedAt) {
56+
Objects.requireNonNull(name, "QueryGroup.name can't be null");
57+
Objects.requireNonNull(resourceLimits, "QueryGroup.resourceLimits can't be null");
58+
Objects.requireNonNull(resiliencyMode, "QueryGroup.resiliencyMode can't be null");
59+
Objects.requireNonNull(_id, "QueryGroup._id can't be null");
60+
61+
if (name.length() > MAX_CHARS_ALLOWED_IN_NAME) {
62+
throw new IllegalArgumentException("QueryGroup.name shouldn't be more than 50 chars long");
63+
}
64+
65+
if (resourceLimits.isEmpty()) {
66+
throw new IllegalArgumentException("QueryGroup.resourceLimits should at least have 1 resource limit");
67+
}
68+
validateResourceLimits(resourceLimits);
69+
if (!isValid(updatedAt)) {
70+
throw new IllegalArgumentException("QueryGroup.updatedAtInMillis is not a valid epoch");
71+
}
72+
73+
this.name = name;
74+
this._id = _id;
75+
this.resiliencyMode = resiliencyMode;
76+
this.resourceLimits = resourceLimits;
77+
this.updatedAtInMillis = updatedAt;
78+
}
79+
80+
private static boolean isValid(long updatedAt) {
81+
long minValidTimestamp = Instant.ofEpochMilli(0L).getMillis();
82+
83+
// Use Instant.now() to get the current time in seconds since epoch
84+
long currentSeconds = Instant.now().getMillis();
85+
86+
// Check if the timestamp is within a reasonable range
87+
return minValidTimestamp <= updatedAt && updatedAt <= currentSeconds;
88+
}
89+
90+
public QueryGroup(StreamInput in) throws IOException {
91+
this(
92+
in.readString(),
93+
in.readString(),
94+
ResiliencyMode.fromName(in.readString()),
95+
in.readMap((i) -> ResourceType.fromName(i.readString()), StreamInput::readGenericValue),
96+
in.readLong()
97+
);
98+
}
99+
100+
@Override
101+
public void writeTo(StreamOutput out) throws IOException {
102+
out.writeString(name);
103+
out.writeString(_id);
104+
out.writeString(resiliencyMode.getName());
105+
out.writeMap(resourceLimits, ResourceType::writeTo, StreamOutput::writeGenericValue);
106+
out.writeLong(updatedAtInMillis);
107+
}
108+
109+
private void validateResourceLimits(Map<ResourceType, Object> resourceLimits) {
110+
for (Map.Entry<ResourceType, Object> resource : resourceLimits.entrySet()) {
111+
Double threshold = (Double) resource.getValue();
112+
Objects.requireNonNull(resource.getKey(), "resourceName can't be null");
113+
Objects.requireNonNull(threshold, "resource limit threshold for" + resource.getKey().getName() + " : can't be null");
114+
115+
if (Double.compare(threshold, 1.0) > 0) {
116+
throw new IllegalArgumentException("resource value should be less than 1.0");
117+
}
118+
}
119+
}
120+
121+
@Override
122+
public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException {
123+
builder.startObject();
124+
builder.field("_id", _id);
125+
builder.field("name", name);
126+
builder.field("resiliency_mode", resiliencyMode.getName());
127+
builder.field("updatedAt", updatedAtInMillis);
128+
// write resource limits
129+
builder.startObject("resourceLimits");
130+
for (ResourceType resourceType : ResourceType.values()) {
131+
if (resourceLimits.containsKey(resourceType)) {
132+
builder.field(resourceType.getName(), resourceLimits.get(resourceType));
133+
}
134+
}
135+
builder.endObject();
136+
137+
builder.endObject();
138+
return builder;
139+
}
140+
141+
public static QueryGroup fromXContent(final XContentParser parser) throws IOException {
142+
if (parser.currentToken() == null) { // fresh parser? move to the first token
143+
parser.nextToken();
144+
}
145+
146+
Builder builder = builder();
147+
148+
XContentParser.Token token = parser.currentToken();
149+
150+
if (token != XContentParser.Token.START_OBJECT) {
151+
throw new IllegalArgumentException("Expected START_OBJECT token but found [" + parser.currentName() + "]");
152+
}
153+
154+
String fieldName = "";
155+
// Map to hold resources
156+
final Map<ResourceType, Object> resourceLimits = new HashMap<>();
157+
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
158+
if (token == XContentParser.Token.FIELD_NAME) {
159+
fieldName = parser.currentName();
160+
} else if (token.isValue()) {
161+
if (fieldName.equals("_id")) {
162+
builder._id(parser.text());
163+
} else if (fieldName.equals("name")) {
164+
builder.name(parser.text());
165+
} else if (fieldName.equals("resiliency_mode")) {
166+
builder.mode(parser.text());
167+
} else if (fieldName.equals("updatedAt")) {
168+
builder.updatedAt(parser.longValue());
169+
} else {
170+
throw new IllegalArgumentException(fieldName + " is not a valid field in QueryGroup");
171+
}
172+
} else if (token == XContentParser.Token.START_OBJECT) {
173+
174+
if (!fieldName.equals("resourceLimits")) {
175+
throw new IllegalArgumentException(
176+
"QueryGroup.resourceLimits is an object and expected token was { " + " but found " + token
177+
);
178+
}
179+
180+
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
181+
if (token == XContentParser.Token.FIELD_NAME) {
182+
fieldName = parser.currentName();
183+
} else {
184+
resourceLimits.put(ResourceType.fromName(fieldName), parser.doubleValue());
185+
}
186+
}
187+
188+
}
189+
}
190+
builder.resourceLimits(resourceLimits);
191+
return builder.build();
192+
}
193+
194+
public static Diff<QueryGroup> readDiff(final StreamInput in) throws IOException {
195+
return readDiffFrom(QueryGroup::new, in);
196+
}
197+
198+
@Override
199+
public boolean equals(Object o) {
200+
if (this == o) return true;
201+
if (o == null || getClass() != o.getClass()) return false;
202+
QueryGroup that = (QueryGroup) o;
203+
return Objects.equals(name, that.name)
204+
&& Objects.equals(resourceLimits, that.resourceLimits)
205+
&& Objects.equals(_id, that._id)
206+
&& updatedAtInMillis == that.updatedAtInMillis;
207+
}
208+
209+
@Override
210+
public int hashCode() {
211+
return Objects.hash(name, resourceLimits, updatedAtInMillis, _id);
212+
}
213+
214+
public String getName() {
215+
return name;
216+
}
217+
218+
public ResiliencyMode getResiliencyMode() {
219+
return resiliencyMode;
220+
}
221+
222+
public Map<ResourceType, Object> getResourceLimits() {
223+
return resourceLimits;
224+
}
225+
226+
public String get_id() {
227+
return _id;
228+
}
229+
230+
public long getUpdatedAtInMillis() {
231+
return updatedAtInMillis;
232+
}
233+
234+
/**
235+
* builder method for the {@link QueryGroup}
236+
* @return Builder object
237+
*/
238+
public static Builder builder() {
239+
return new Builder();
240+
}
241+
242+
/**
243+
* This enum models the different QueryGroup resiliency modes
244+
* SOFT - means that this query group can consume more than query group resource limits if node is not in duress
245+
* ENFORCED - means that it will never breach the assigned limits and will cancel as soon as the limits are breached
246+
* MONITOR - it will not cause any cancellation but just log the eligible task cancellations
247+
*/
248+
@ExperimentalApi
249+
public enum ResiliencyMode {
250+
SOFT("soft"),
251+
ENFORCED("enforced"),
252+
MONITOR("monitor");
253+
254+
private final String name;
255+
256+
ResiliencyMode(String mode) {
257+
this.name = mode;
258+
}
259+
260+
public String getName() {
261+
return name;
262+
}
263+
264+
public static ResiliencyMode fromName(String s) {
265+
for (ResiliencyMode mode : values()) {
266+
if (mode.getName().equalsIgnoreCase(s)) return mode;
267+
268+
}
269+
throw new IllegalArgumentException("Invalid value for QueryGroupMode: " + s);
270+
}
271+
272+
}
273+
274+
/**
275+
* Builder class for {@link QueryGroup}
276+
*/
277+
@ExperimentalApi
278+
public static class Builder {
279+
private String name;
280+
private String _id;
281+
private ResiliencyMode resiliencyMode;
282+
private long updatedAt;
283+
private Map<ResourceType, Object> resourceLimits;
284+
285+
private Builder() {}
286+
287+
public Builder name(String name) {
288+
this.name = name;
289+
return this;
290+
}
291+
292+
public Builder _id(String _id) {
293+
this._id = _id;
294+
return this;
295+
}
296+
297+
public Builder mode(String mode) {
298+
this.resiliencyMode = ResiliencyMode.fromName(mode);
299+
return this;
300+
}
301+
302+
public Builder updatedAt(long updatedAt) {
303+
this.updatedAt = updatedAt;
304+
return this;
305+
}
306+
307+
public Builder resourceLimits(Map<ResourceType, Object> resourceLimits) {
308+
this.resourceLimits = resourceLimits;
309+
return this;
310+
}
311+
312+
public QueryGroup build() {
313+
return new QueryGroup(name, _id, resiliencyMode, resourceLimits, updatedAt);
314+
}
315+
316+
}
317+
}

0 commit comments

Comments
 (0)