Skip to content

Commit d52d404

Browse files
karenyrxandrross
andauthored
[GRPC] Make transport-grpc extensible to other plugins (opensearch-project#18516)
* [GRPC] Make transport-grpc extensible for other plugins Signed-off-by: Karen Xu <karenxyr@gmail.com> * more UTs for code cov Signed-off-by: Karen Xu <karenx@uber.com> * upgrade to protobufs v0.4.0 and address comments Signed-off-by: Karen Xu <karenxyr@gmail.com> * convert to instance-level and fix changelog Signed-off-by: Karen Xu <karenxyr@gmail.com> * remove converter list and increase codecov Signed-off-by: Karen Xu <karenxyr@gmail.com> --------- Signed-off-by: Karen Xu <karenxyr@gmail.com> Signed-off-by: Karen X <karenxyr@gmail.com> Signed-off-by: Karen Xu <karenx@uber.com> Signed-off-by: Andrew Ross <andrross@amazon.com> Co-authored-by: Karen Xu <karenx@uber.com> Co-authored-by: Andrew Ross <andrross@amazon.com>
1 parent 5fd0a36 commit d52d404

28 files changed

Lines changed: 1366 additions & 112 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
3636
- [Star-Tree] Add star-tree search related stats ([#18707](https://github.com/opensearch-project/OpenSearch/pull/18707))
3737
- Add support for plugins to profile information ([#18656](https://github.com/opensearch-project/OpenSearch/pull/18656))
3838
- Add support for Combined Fields query ([#18724](https://github.com/opensearch-project/OpenSearch/pull/18724))
39+
- Make GRPC transport extensible to allow plugins to register and expose their own GRPC services ([#18516](https://github.com/opensearch-project/OpenSearch/pull/18516))
3940
- Added approximation support for range queries with now in date field ([#18511](https://github.com/opensearch-project/OpenSearch/pull/18511))
4041

4142
### Changed

plugins/transport-grpc/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ dependencies {
3535
implementation "io.grpc:grpc-stub:${versions.grpc}"
3636
implementation "io.grpc:grpc-util:${versions.grpc}"
3737
implementation "io.perfmark:perfmark-api:0.27.0"
38-
implementation "org.opensearch:protobufs:0.3.0"
38+
implementation "org.opensearch:protobufs:0.4.0"
3939
testImplementation project(':test:framework')
4040
}
4141

plugins/transport-grpc/licenses/protobufs-0.3.0.jar.sha1

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
af2d6818dab60d54689122e57f3d3b8fb86cf67b

plugins/transport-grpc/src/main/java/org/opensearch/plugin/transport/grpc/GrpcPlugin.java

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,13 @@
1818
import org.opensearch.core.xcontent.NamedXContentRegistry;
1919
import org.opensearch.env.Environment;
2020
import org.opensearch.env.NodeEnvironment;
21+
import org.opensearch.plugin.transport.grpc.proto.request.search.query.AbstractQueryBuilderProtoUtils;
22+
import org.opensearch.plugin.transport.grpc.proto.request.search.query.QueryBuilderProtoConverter;
23+
import org.opensearch.plugin.transport.grpc.proto.request.search.query.QueryBuilderProtoConverterRegistry;
2124
import org.opensearch.plugin.transport.grpc.services.DocumentServiceImpl;
2225
import org.opensearch.plugin.transport.grpc.services.SearchServiceImpl;
2326
import org.opensearch.plugin.transport.grpc.ssl.SecureNetty4GrpcServerTransport;
27+
import org.opensearch.plugins.ExtensiblePlugin;
2428
import org.opensearch.plugins.NetworkPlugin;
2529
import org.opensearch.plugins.Plugin;
2630
import org.opensearch.plugins.SecureAuxTransportSettingsProvider;
@@ -32,6 +36,7 @@
3236
import org.opensearch.transport.client.Client;
3337
import org.opensearch.watcher.ResourceWatcherService;
3438

39+
import java.util.ArrayList;
3540
import java.util.Collection;
3641
import java.util.Collections;
3742
import java.util.List;
@@ -55,15 +60,55 @@
5560
/**
5661
* Main class for the gRPC plugin.
5762
*/
58-
public final class GrpcPlugin extends Plugin implements NetworkPlugin {
63+
public final class GrpcPlugin extends Plugin implements NetworkPlugin, ExtensiblePlugin {
5964

6065
private Client client;
66+
private final List<QueryBuilderProtoConverter> queryConverters = new ArrayList<>();
67+
private QueryBuilderProtoConverterRegistry queryRegistry;
68+
private AbstractQueryBuilderProtoUtils queryUtils;
6169

6270
/**
6371
* Creates a new GrpcPlugin instance.
6472
*/
6573
public GrpcPlugin() {}
6674

75+
/**
76+
* Loads extensions from other plugins.
77+
* This method is called by the OpenSearch plugin system to load extensions from other plugins.
78+
*
79+
* @param loader The extension loader to use for loading extensions
80+
*/
81+
@Override
82+
public void loadExtensions(ExtensiblePlugin.ExtensionLoader loader) {
83+
// Load query converters from other plugins
84+
List<QueryBuilderProtoConverter> extensions = loader.loadExtensions(QueryBuilderProtoConverter.class);
85+
if (extensions != null) {
86+
queryConverters.addAll(extensions);
87+
}
88+
}
89+
90+
/**
91+
* Get the list of query converters, including those loaded from extensions.
92+
*
93+
* @return The list of query converters
94+
*/
95+
public List<QueryBuilderProtoConverter> getQueryConverters() {
96+
return Collections.unmodifiableList(queryConverters);
97+
}
98+
99+
/**
100+
* Get the query utils instance.
101+
*
102+
* @return The query utils instance
103+
* @throws IllegalStateException if queryUtils is not initialized
104+
*/
105+
public AbstractQueryBuilderProtoUtils getQueryUtils() {
106+
if (queryUtils == null) {
107+
throw new IllegalStateException("Query utils not initialized. Make sure createComponents has been called.");
108+
}
109+
return queryUtils;
110+
}
111+
67112
/**
68113
* Provides auxiliary transports for the plugin.
69114
* Creates and returns a map of transport names to transport suppliers.
@@ -75,6 +120,7 @@ public GrpcPlugin() {}
75120
* @param clusterSettings The cluster settings
76121
* @param tracer The tracer
77122
* @return A map of transport names to transport suppliers
123+
* @throws IllegalStateException if queryRegistry is not initialized
78124
*/
79125
@Override
80126
public Map<String, Supplier<AuxTransport>> getAuxTransports(
@@ -88,7 +134,15 @@ public Map<String, Supplier<AuxTransport>> getAuxTransports(
88134
if (client == null) {
89135
throw new RuntimeException("client cannot be null");
90136
}
91-
List<BindableService> grpcServices = registerGRPCServices(new DocumentServiceImpl(client), new SearchServiceImpl(client));
137+
138+
if (queryRegistry == null) {
139+
throw new IllegalStateException("createComponents must be called before getAuxTransports to initialize the registry");
140+
}
141+
142+
List<BindableService> grpcServices = registerGRPCServices(
143+
new DocumentServiceImpl(client),
144+
new SearchServiceImpl(client, queryUtils)
145+
);
92146
AuxTransport transport = new Netty4GrpcServerTransport(settings, grpcServices, networkService);
93147
return Collections.singletonMap(transport.settingKey(), () -> transport);
94148
}
@@ -106,6 +160,7 @@ public Map<String, Supplier<AuxTransport>> getAuxTransports(
106160
* @param tracer The tracer
107161
* @param secureAuxTransportSettingsProvider provides ssl context params
108162
* @return A map of transport names to transport suppliers
163+
* @throws IllegalStateException if queryRegistry is not initialized
109164
*/
110165
@Override
111166
public Map<String, Supplier<AuxTransport>> getSecureAuxTransports(
@@ -120,7 +175,15 @@ public Map<String, Supplier<AuxTransport>> getSecureAuxTransports(
120175
if (client == null) {
121176
throw new RuntimeException("client cannot be null");
122177
}
123-
List<BindableService> grpcServices = registerGRPCServices(new DocumentServiceImpl(client), new SearchServiceImpl(client));
178+
179+
if (queryRegistry == null) {
180+
throw new IllegalStateException("createComponents must be called before getSecureAuxTransports to initialize the registry");
181+
}
182+
183+
List<BindableService> grpcServices = registerGRPCServices(
184+
new DocumentServiceImpl(client),
185+
new SearchServiceImpl(client, queryUtils)
186+
);
124187
AuxTransport transport = new SecureNetty4GrpcServerTransport(
125188
settings,
126189
grpcServices,
@@ -164,7 +227,7 @@ public List<Setting<?>> getSettings() {
164227

165228
/**
166229
* Creates components used by the plugin.
167-
* Stores the client for later use in creating gRPC services.
230+
* Stores the client for later use in creating gRPC services, and the query registry which registers the types of supported GRPC Search queries.
168231
*
169232
* @param client The client
170233
* @param clusterService The cluster service
@@ -195,6 +258,17 @@ public Collection<Object> createComponents(
195258
) {
196259
this.client = client;
197260

261+
// Create the registry
262+
this.queryRegistry = new QueryBuilderProtoConverterRegistry();
263+
264+
// Create the query utils instance
265+
this.queryUtils = new AbstractQueryBuilderProtoUtils(queryRegistry);
266+
267+
// Register external converters
268+
for (QueryBuilderProtoConverter converter : queryConverters) {
269+
queryRegistry.registerConverter(converter);
270+
}
271+
198272
return super.createComponents(
199273
client,
200274
clusterService,

plugins/transport-grpc/src/main/java/org/opensearch/plugin/transport/grpc/proto/request/search/SearchRequestProtoUtils.java

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import org.opensearch.core.xcontent.XContentParser;
1717
import org.opensearch.index.query.QueryBuilder;
1818
import org.opensearch.plugin.transport.grpc.proto.request.common.FetchSourceContextProtoUtils;
19+
import org.opensearch.plugin.transport.grpc.proto.request.search.query.AbstractQueryBuilderProtoUtils;
1920
import org.opensearch.plugin.transport.grpc.proto.request.search.suggest.TermSuggestionBuilderProtoUtils;
2021
import org.opensearch.protobufs.SearchRequest;
2122
import org.opensearch.protobufs.SearchRequestBody;
@@ -40,9 +41,7 @@
4041
import static org.opensearch.search.suggest.SuggestBuilders.termSuggestion;
4142

4243
/**
43-
* Utility class for converting SearchRequest objects between OpenSearch and Protocol Buffers formats.
44-
* This class provides methods to prepare, parse, and transform search requests to ensure proper
45-
* communication between gRPC clients and the OpenSearch server.
44+
* Utility class for converting SearchRequest Protocol Buffers to objects
4645
*/
4746
public class SearchRequestProtoUtils {
4847

@@ -58,11 +57,16 @@ private SearchRequestProtoUtils() {
5857
*
5958
* @param request the Protocol Buffer SearchRequest to execute
6059
* @param client the client to use for execution
60+
* @param queryUtils the query utils instance for parsing queries
6161
* @return the SearchRequest to execute
6262
* @throws IOException if an I/O exception occurred parsing the request and preparing for
6363
* execution
6464
*/
65-
public static org.opensearch.action.search.SearchRequest prepareRequest(SearchRequest request, Client client) throws IOException {
65+
public static org.opensearch.action.search.SearchRequest prepareRequest(
66+
org.opensearch.protobufs.SearchRequest request,
67+
Client client,
68+
AbstractQueryBuilderProtoUtils queryUtils
69+
) throws IOException {
6670
org.opensearch.action.search.SearchRequest searchRequest = new org.opensearch.action.search.SearchRequest();
6771

6872
/*
@@ -79,26 +83,28 @@ public static org.opensearch.action.search.SearchRequest prepareRequest(SearchRe
7983
*/
8084
IntConsumer setSize = size -> searchRequest.source().size(size);
8185
// TODO avoid hidden cast to NodeClient here
82-
parseSearchRequest(searchRequest, request, ((NodeClient) client).getNamedWriteableRegistry(), setSize);
86+
parseSearchRequest(searchRequest, request, ((NodeClient) client).getNamedWriteableRegistry(), setSize, queryUtils);
8387
return searchRequest;
8488
}
8589

8690
/**
8791
* Parses a protobuf {@link org.opensearch.protobufs.SearchRequest} to a {@link org.opensearch.action.search.SearchRequest}.
8892
* This method is similar to the logic in {@link RestSearchAction#parseSearchRequest(org.opensearch.action.search.SearchRequest, RestRequest, XContentParser, NamedWriteableRegistry, IntConsumer)}
89-
* Specifically, this method handles the URL parameters, and internally calls {@link SearchSourceBuilderProtoUtils#parseProto(SearchSourceBuilder, SearchRequestBody)}
93+
* Specifically, this method handles the URL parameters, and internally calls {@link SearchSourceBuilderProtoUtils#parseProto(SearchSourceBuilder, SearchRequestBody, AbstractQueryBuilderProtoUtils)}
9094
*
9195
* @param searchRequest the SearchRequest to populate
9296
* @param request the Protocol Buffer SearchRequest to parse
9397
* @param namedWriteableRegistry the registry for named writeables
9498
* @param setSize consumer for setting the size parameter
99+
* @param queryUtils the query utils instance for parsing queries
95100
* @throws IOException if an I/O exception occurred during parsing
96101
*/
97102
protected static void parseSearchRequest(
98103
org.opensearch.action.search.SearchRequest searchRequest,
99104
org.opensearch.protobufs.SearchRequest request,
100105
NamedWriteableRegistry namedWriteableRegistry,
101-
IntConsumer setSize
106+
IntConsumer setSize,
107+
AbstractQueryBuilderProtoUtils queryUtils
102108
) throws IOException {
103109
if (searchRequest.source() == null) {
104110
searchRequest.source(new SearchSourceBuilder());
@@ -110,7 +116,7 @@ protected static void parseSearchRequest(
110116
}
111117
searchRequest.indices(indexArr);
112118

113-
SearchSourceBuilderProtoUtils.parseProto(searchRequest.source(), request.getRequestBody());
119+
SearchSourceBuilderProtoUtils.parseProto(searchRequest.source(), request.getRequestBody(), queryUtils);
114120

115121
final int batchedReduceSize = request.hasBatchedReduceSize()
116122
? request.getBatchedReduceSize()

plugins/transport-grpc/src/main/java/org/opensearch/plugin/transport/grpc/proto/request/search/SearchSourceBuilderProtoUtils.java

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,35 @@ private SearchSourceBuilderProtoUtils() {
4242
}
4343

4444
/**
45-
* Parses a protobuf SearchRequestBody into a SearchSourceBuilder.
45+
* Parses a protobuf SearchRequestBody into a SearchSourceBuilder using an instance-based query utils.
4646
* This method is equivalent to {@link SearchSourceBuilder#parseXContent(XContentParser, boolean)}
4747
*
4848
* @param searchSourceBuilder The SearchSourceBuilder to populate
4949
* @param protoRequest The Protocol Buffer SearchRequest to parse
50+
* @param queryUtils The query utils instance to use for parsing queries
5051
* @throws IOException if there's an error during parsing
5152
*/
52-
protected static void parseProto(SearchSourceBuilder searchSourceBuilder, SearchRequestBody protoRequest) throws IOException {
53+
public static void parseProto(
54+
SearchSourceBuilder searchSourceBuilder,
55+
SearchRequestBody protoRequest,
56+
AbstractQueryBuilderProtoUtils queryUtils
57+
) throws IOException {
58+
// Parse all non-query fields
59+
parseNonQueryFields(searchSourceBuilder, protoRequest);
60+
61+
// Handle queries using the instance-based approach
62+
if (protoRequest.hasQuery()) {
63+
searchSourceBuilder.query(queryUtils.parseInnerQueryBuilderProto(protoRequest.getQuery()));
64+
}
65+
if (protoRequest.hasPostFilter()) {
66+
searchSourceBuilder.postFilter(queryUtils.parseInnerQueryBuilderProto(protoRequest.getPostFilter()));
67+
}
68+
}
69+
70+
/**
71+
* Parses all fields except queries from the protobuf SearchRequestBody.
72+
*/
73+
private static void parseNonQueryFields(SearchSourceBuilder searchSourceBuilder, SearchRequestBody protoRequest) throws IOException {
5374
// TODO what to do about parser.getDeprecationHandler() for protos?
5475

5576
if (protoRequest.hasFrom()) {
@@ -111,12 +132,6 @@ protected static void parseProto(SearchSourceBuilder searchSourceBuilder, Search
111132
if (protoRequest.hasVerbosePipeline()) {
112133
searchSourceBuilder.verbosePipeline(protoRequest.getVerbosePipeline());
113134
}
114-
if (protoRequest.hasQuery()) {
115-
searchSourceBuilder.query(AbstractQueryBuilderProtoUtils.parseInnerQueryBuilderProto(protoRequest.getQuery()));
116-
}
117-
if (protoRequest.hasPostFilter()) {
118-
searchSourceBuilder.postFilter(AbstractQueryBuilderProtoUtils.parseInnerQueryBuilderProto(protoRequest.getPostFilter()));
119-
}
120135
if (protoRequest.hasSource()) {
121136
searchSourceBuilder.fetchSource(FetchSourceContextProtoUtils.fromProto(protoRequest.getSource()));
122137
}

plugins/transport-grpc/src/main/java/org/opensearch/plugin/transport/grpc/proto/request/search/query/AbstractQueryBuilderProtoUtils.java

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,19 @@
1919
*/
2020
public class AbstractQueryBuilderProtoUtils {
2121

22-
private AbstractQueryBuilderProtoUtils() {
23-
// Utility class, no instances
22+
private final QueryBuilderProtoConverterRegistry registry;
23+
24+
/**
25+
* Creates a new instance with the specified registry.
26+
*
27+
* @param registry The registry to use for query conversion
28+
* @throws IllegalArgumentException if registry is null
29+
*/
30+
public AbstractQueryBuilderProtoUtils(QueryBuilderProtoConverterRegistry registry) {
31+
if (registry == null) {
32+
throw new IllegalArgumentException("Registry cannot be null");
33+
}
34+
this.registry = registry;
2435
}
2536

2637
/**
@@ -33,23 +44,12 @@ private AbstractQueryBuilderProtoUtils() {
3344
* @return A QueryBuilder instance configured according to the input query parameters
3445
* @throws UnsupportedOperationException if the query type is not supported
3546
*/
36-
public static QueryBuilder parseInnerQueryBuilderProto(QueryContainer queryContainer) throws UnsupportedOperationException {
37-
QueryBuilder result;
38-
39-
if (queryContainer.hasMatchAll()) {
40-
result = MatchAllQueryBuilderProtoUtils.fromProto(queryContainer.getMatchAll());
41-
} else if (queryContainer.hasMatchNone()) {
42-
result = MatchNoneQueryBuilderProtoUtils.fromProto(queryContainer.getMatchNone());
43-
} else if (queryContainer.getTermCount() > 0) {
44-
result = TermQueryBuilderProtoUtils.fromProto(queryContainer.getTermMap());
45-
} else if (queryContainer.hasTerms()) {
46-
result = TermsQueryBuilderProtoUtils.fromProto(queryContainer.getTerms());
47-
}
48-
// TODO add more query types
49-
else {
50-
throw new UnsupportedOperationException("Search query type not supported yet.");
47+
public QueryBuilder parseInnerQueryBuilderProto(QueryContainer queryContainer) throws UnsupportedOperationException {
48+
// Validate input
49+
if (queryContainer == null) {
50+
throw new IllegalArgumentException("Query container cannot be null");
5151
}
5252

53-
return result;
53+
return registry.fromProto(queryContainer);
5454
}
5555
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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+
package org.opensearch.plugin.transport.grpc.proto.request.search.query;
9+
10+
import org.opensearch.index.query.QueryBuilder;
11+
import org.opensearch.protobufs.QueryContainer;
12+
13+
/**
14+
* Converter for MatchAll queries.
15+
* This class implements the QueryBuilderProtoConverter interface to provide MatchAll query support
16+
* for the gRPC transport plugin.
17+
*/
18+
public class MatchAllQueryBuilderProtoConverter implements QueryBuilderProtoConverter {
19+
20+
/**
21+
* Constructs a new MatchAllQueryBuilderProtoConverter.
22+
*/
23+
public MatchAllQueryBuilderProtoConverter() {
24+
// Default constructor
25+
}
26+
27+
@Override
28+
public QueryContainer.QueryContainerCase getHandledQueryCase() {
29+
return QueryContainer.QueryContainerCase.MATCH_ALL;
30+
}
31+
32+
@Override
33+
public QueryBuilder fromProto(QueryContainer queryContainer) {
34+
if (queryContainer == null || !queryContainer.hasMatchAll()) {
35+
throw new IllegalArgumentException("QueryContainer does not contain a MatchAll query");
36+
}
37+
38+
return MatchAllQueryBuilderProtoUtils.fromProto(queryContainer.getMatchAll());
39+
}
40+
}

0 commit comments

Comments
 (0)