Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Projected Views #11957

Merged
merged 63 commits into from
Feb 20, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
6bf2cf6
Basic view stubs
peternied Dec 8, 2023
3522cc7
Cluster metadat
peternied Dec 14, 2023
4e12632
Basic request handling
peternied Dec 15, 2023
3a15863
Accepting requests
peternied Dec 15, 2023
23632b6
Search rewriting and design dco
peternied Dec 18, 2023
6031347
Functional views
peternied Dec 19, 2023
c66425a
Minor changes
peternied Dec 19, 2023
c191cc8
Resource Requset
peternied Dec 19, 2023
4abefd0
Add FAQ sectino
peternied Dec 19, 2023
dace87a
Tidying up
peternied Dec 22, 2023
c88febe
Read prepare for merge into OpenSearch
peternied Jan 17, 2024
61e8c98
Fix build issue
peternied Jan 17, 2024
ac25c96
Fix build issues
peternied Jan 29, 2024
8e41fbe
Unit tests for view
peternied Jan 29, 2024
cf22a8d
Basic pattern for decoupled views in metadata vs transport requests
peternied Feb 1, 2024
2f615cf
Merge branch 'views-core' into views
peternied Feb 1, 2024
d45fcb9
Integration tests and some unit tests
peternied Feb 3, 2024
2832b24
Spotless and compile issue
peternied Feb 5, 2024
db00415
Clean up license headers
peternied Feb 5, 2024
6a947e4
Fix logger usage miss
peternied Feb 5, 2024
6156eb9
Before adding all action types
peternied Feb 5, 2024
a7dbd93
Expand action types
peternied Feb 5, 2024
f12efaa
wire up all actions
peternied Feb 6, 2024
135acba
Functional integration test
peternied Feb 6, 2024
ac456da
refactor how view names are listed in tests
peternied Feb 6, 2024
327d158
More tests
peternied Feb 6, 2024
b95056a
Following up on PR feedback
peternied Feb 6, 2024
ea78012
Add changelog entry
peternied Feb 6, 2024
242ab85
Merge branch 'main' into views-core
peternied Feb 6, 2024
7810ddf
Merge remote-tracking branch 'origin/main' into views-core
peternied Feb 7, 2024
01655d0
Fix build warning about test annotations
peternied Feb 8, 2024
292a322
Round out integration tests
peternied Feb 9, 2024
07bcfe0
Add test cases
peternied Feb 9, 2024
e7f36b0
Fix compile issue
peternied Feb 9, 2024
65fa8e7
Rename test class
peternied Feb 12, 2024
4327237
PR feedback pass
peternied Feb 12, 2024
5beefbb
Spotless
peternied Feb 12, 2024
16b2b52
Merge remote-tracking branch 'origin/main' into views-core
peternied Feb 13, 2024
edd8902
More logging information
peternied Feb 13, 2024
ba385e4
Fix spotless
peternied Feb 13, 2024
0663818
More detailed logging from checkStaticState
peternied Feb 13, 2024
7f28330
Detact metadataFilesEligibleToDelete from its source collection
peternied Feb 13, 2024
8cb9f55
Fix spotless issue
peternied Feb 13, 2024
9d0e1b0
Breakout exceptions into their own clases
peternied Feb 13, 2024
25e3ca9
Merge remote-tracking branch 'peternied/views-core' into views-core
peternied Feb 13, 2024
e58acdb
Clean up logger output message
peternied Feb 13, 2024
6e9dffb
Revert changes to NamedRoute
peternied Feb 13, 2024
cba189e
Revert changes to SearchRequest and follow pipeline model
peternied Feb 13, 2024
eae9511
Remove unused codepath
peternied Feb 13, 2024
fcacb01
Update exception serialization tests
peternied Feb 14, 2024
db8f049
PR feedback
peternied Feb 15, 2024
4936736
Fix error in RemoteSegmentStoreDirectory when debug logging is enabled
peternied Feb 15, 2024
8c39071
Add changelog entry
peternied Feb 15, 2024
7259789
Use sorted set to store targets for views
peternied Feb 15, 2024
fa913f7
Test compile checks
peternied Feb 15, 2024
a9777be
Fix compile issues related to switch to set
peternied Feb 15, 2024
8796381
Add test to validate debug log statement doesn't have errors
peternied Feb 15, 2024
0dd44f7
Add test to validate debug log statement doesn't have errors
peternied Feb 15, 2024
d77f812
Dial back the level of detail validated for the segments that are del…
peternied Feb 15, 2024
946a691
Merge branch 'RemoteSegmentStoreDirectory' into views-core
peternied Feb 15, 2024
fc600ac
Dial back the level of detail validated for the segments that are del…
peternied Feb 15, 2024
bbf4945
Merge branch 'RemoteSegmentStoreDirectory' into views-core
peternied Feb 15, 2024
2ac6e68
Merge remote-tracking branch 'origin/main' into views-core
peternied Feb 16, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* 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.action.admin.indices.view;

import org.opensearch.action.search.SearchRequest;
import org.opensearch.action.search.SearchResponse;
import org.opensearch.index.IndexNotFoundException;
import org.opensearch.test.BackgroundIndexer;
import org.opensearch.test.OpenSearchIntegTestCase;
import org.opensearch.test.OpenSearchIntegTestCase.ClusterScope;
import org.opensearch.test.OpenSearchIntegTestCase.Scope;

import java.util.List;

import static org.opensearch.test.hamcrest.OpenSearchAssertions.assertHitCount;
import static org.hamcrest.Matchers.is;

@ClusterScope(scope = Scope.TEST, numDataNodes = 2)
public class ViewIT extends OpenSearchIntegTestCase {

private int createIndexWithDocs(final String indexName) throws Exception {
createIndex(indexName);
ensureGreen(indexName);

final int numOfDocs = scaledRandomIntBetween(0, 200);
try (final BackgroundIndexer indexer = new BackgroundIndexer(indexName, "_doc", client(), numOfDocs)) {
waitForDocs(numOfDocs, indexer);
}

refresh(indexName);
assertHitCount(client().prepareSearch(indexName).setSize(0).get(), numOfDocs);
return numOfDocs;
}

private CreateViewAction.Response createView(final String name, final String indexPattern) throws Exception {
final CreateViewAction.Request request = new CreateViewAction.Request(
name,
null,
List.of(new CreateViewAction.Request.Target(indexPattern))
);
final CreateViewAction.Response response = client().admin().indices().createView(request).actionGet();
performRemoteStoreTestAction();
return response;
}

private SearchResponse searchView(final String viewName) throws Exception {
final SearchViewAction.Request request = SearchViewAction.createRequestWith(viewName, new SearchRequest());
final SearchResponse response = client().admin().indices().searchView(request).actionGet();
return response;
}

public void testBasicOperations() throws Exception {
final String indexInView1 = "index-1";
final String indexInView2 = "index-2";
final String indexNotInView = "another-index-1";

final int indexInView1DocCount = createIndexWithDocs(indexInView1);
final int indexInView2DocCount = createIndexWithDocs(indexInView2);
createIndexWithDocs(indexNotInView);

logger.info("Testing view with no matches");
createView("no-matches", "this-pattern-will-match-nothing");
final IndexNotFoundException ex = assertThrows(IndexNotFoundException.class, () -> searchView("no-matches"));
assertThat(ex.getMessage(), is("no such index [this-pattern-will-match-nothing]"));

logger.info("Testing view with exact index match");
createView("only-index-1", "index-1");
assertHitCount(searchView("only-index-1"), indexInView1DocCount);

logger.info("Testing view with wildcard matches");
createView("both-indices", "index-*");
assertHitCount(searchView("both-indices"), indexInView1DocCount + indexInView2DocCount);
}
}
peternied marked this conversation as resolved.
Show resolved Hide resolved
11 changes: 11 additions & 0 deletions server/src/main/java/org/opensearch/action/ActionModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@
import org.opensearch.action.admin.indices.upgrade.post.UpgradeSettingsAction;
import org.opensearch.action.admin.indices.validate.query.TransportValidateQueryAction;
import org.opensearch.action.admin.indices.validate.query.ValidateQueryAction;
import org.opensearch.action.admin.indices.view.CreateViewAction;
import org.opensearch.action.admin.indices.view.SearchViewAction;
import org.opensearch.action.bulk.BulkAction;
import org.opensearch.action.bulk.TransportBulkAction;
import org.opensearch.action.bulk.TransportShardBulkAction;
Expand Down Expand Up @@ -409,6 +411,7 @@
import org.opensearch.rest.action.admin.indices.RestUpgradeAction;
import org.opensearch.rest.action.admin.indices.RestUpgradeStatusAction;
import org.opensearch.rest.action.admin.indices.RestValidateQueryAction;
import org.opensearch.rest.action.admin.indices.RestViewAction;
import org.opensearch.rest.action.cat.AbstractCatAction;
import org.opensearch.rest.action.cat.RestAliasAction;
import org.opensearch.rest.action.cat.RestAllocationAction;
Expand Down Expand Up @@ -721,6 +724,10 @@ public <Request extends ActionRequest, Response extends ActionResponse> void reg
actions.register(ResolveIndexAction.INSTANCE, ResolveIndexAction.TransportAction.class);
actions.register(DataStreamsStatsAction.INSTANCE, DataStreamsStatsAction.TransportAction.class);

// Views:
actions.register(CreateViewAction.INSTANCE, CreateViewAction.TransportAction.class);
actions.register(SearchViewAction.INSTANCE, SearchViewAction.TransportAction.class);

// Persistent tasks:
actions.register(StartPersistentTaskAction.INSTANCE, StartPersistentTaskAction.TransportAction.class);
actions.register(UpdatePersistentTaskStatusAction.INSTANCE, UpdatePersistentTaskStatusAction.TransportAction.class);
Expand Down Expand Up @@ -915,6 +922,10 @@ public void initRestHandlers(Supplier<DiscoveryNodes> nodesInCluster) {
registerHandler.accept(new RestResolveIndexAction());
registerHandler.accept(new RestDataStreamsStatsAction());

// View API
registerHandler.accept(new RestViewAction.CreateViewHandler());
registerHandler.accept(new RestViewAction.SearchViewHandler());

// CAT API
registerHandler.accept(new RestAllocationAction());
registerHandler.accept(new RestCatSegmentReplicationAction());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
package org.opensearch.action.admin.indices.view;

import org.opensearch.action.ActionRequestValidationException;
import org.opensearch.action.ActionType;
import org.opensearch.action.ValidateActions;
import org.opensearch.action.support.ActionFilters;
import org.opensearch.action.support.clustermanager.ClusterManagerNodeRequest;
import org.opensearch.action.support.clustermanager.TransportClusterManagerNodeAction;
import org.opensearch.cluster.ClusterState;
import org.opensearch.cluster.block.ClusterBlockException;
import org.opensearch.cluster.block.ClusterBlockLevel;
import org.opensearch.cluster.metadata.IndexNameExpressionResolver;
import org.opensearch.cluster.metadata.View;
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.common.annotation.ExperimentalApi;
import org.opensearch.common.inject.Inject;
import org.opensearch.core.ParseField;
import org.opensearch.core.action.ActionListener;
import org.opensearch.core.action.ActionResponse;
import org.opensearch.core.common.Strings;
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.common.util.CollectionUtils;
import org.opensearch.core.xcontent.ConstructingObjectParser;
import org.opensearch.core.xcontent.ToXContentObject;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.core.xcontent.XContentParser;
import org.opensearch.threadpool.ThreadPool;
import org.opensearch.transport.TransportService;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

/** Action to create a view */
public class CreateViewAction extends ActionType<CreateViewAction.Response> {

public static final CreateViewAction INSTANCE = new CreateViewAction();
public static final String NAME = "cluster:admin:views:create";
peternied marked this conversation as resolved.
Show resolved Hide resolved

private CreateViewAction() {
super(NAME, CreateViewAction.Response::new);
}

/**
* Request for Creating View
*/
@ExperimentalApi
public static class Request extends ClusterManagerNodeRequest<Request> {
private final String name;
private final String description;
private final List<Target> targets;

public Request(final String name, final String description, final List<Target> targets) {
this.name = name;
this.description = Objects.requireNonNullElse(description, "");
this.targets = targets;
}

public Request(final StreamInput in) throws IOException {
super(in);
this.name = in.readString();
this.description = in.readString();
this.targets = in.readList(Target::new);
}

public String getName() {
return name;
}

public String getDescription() {
return description;
}

public List<Target> getTargets() {
return new ArrayList<>(targets);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Request that = (Request) o;
return name.equals(that.name) && description.equals(that.description) && targets.equals(that.targets);
}

@Override
public int hashCode() {
return Objects.hash(name, description, targets);
}

@Override
public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;
if (Strings.isNullOrEmpty(name)) {
validationException = ValidateActions.addValidationError("Name is cannot be empty or null", validationException);
peternied marked this conversation as resolved.
Show resolved Hide resolved
}
if (CollectionUtils.isEmpty(targets)) {
validationException = ValidateActions.addValidationError("targets cannot be empty", validationException);
} else {
for (final Target target : targets) {
validationException = target.validate();
}
}

return validationException;
}

@Override
public void writeTo(final StreamOutput out) throws IOException {
super.writeTo(out);
out.writeString(name);
out.writeString(description);
out.writeList(targets);
}

/** View target representation for create requests */
@ExperimentalApi
public static class Target implements Writeable {
public final String indexPattern;

public Target(final String indexPattern) {
this.indexPattern = indexPattern;
}

public Target(final StreamInput in) throws IOException {
this.indexPattern = in.readString();
}

public String getIndexPattern() {
return indexPattern;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Target that = (Target) o;
return indexPattern.equals(that.indexPattern);
}

@Override
public int hashCode() {
return Objects.hash(indexPattern);
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(indexPattern);
}

public ActionRequestValidationException validate() {
ActionRequestValidationException validationException = null;

if (Strings.isNullOrEmpty(indexPattern)) {
validationException = ValidateActions.addValidationError("index pattern cannot be empty or null", validationException);
}

return validationException;
}

private static final ConstructingObjectParser<Target, Void> PARSER = new ConstructingObjectParser<>(
"target",
args -> new Target((String) args[0])
);
static {
PARSER.declareString(ConstructingObjectParser.constructorArg(), View.Target.INDEX_PATTERN_FIELD);
}

public static Target fromXContent(final XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
}

@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<Request, Void> PARSER = new ConstructingObjectParser<>(
"create_view_request",
args -> new Request((String) args[0], (String) args[1], (List<Target>) args[2])
);

static {
PARSER.declareString(ConstructingObjectParser.constructorArg(), View.NAME_FIELD);
PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), View.DESCRIPTION_FIELD);
PARSER.declareObjectArray(ConstructingObjectParser.constructorArg(), (p, c) -> Target.fromXContent(p), View.TARGETS_FIELD);
}

public static Request fromXContent(final XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
}

/** Response for view creation */
@ExperimentalApi
public static class Response extends ActionResponse implements ToXContentObject {

private final View createdView;

public Response(final View createdView) {
this.createdView = createdView;
}

public Response(final StreamInput in) throws IOException {
super(in);
this.createdView = new View(in);
}

@Override
public void writeTo(final StreamOutput out) throws IOException {
this.createdView.writeTo(out);
}

@Override
public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException {
builder.startObject();
builder.field("view", createdView);
builder.endObject();
return builder;
}

private static final ConstructingObjectParser<Response, Void> PARSER = new ConstructingObjectParser<>(
"create_view_response",
args -> new Response((View) args[0])
);
static {
PARSER.declareObject(ConstructingObjectParser.constructorArg(), View.PARSER, new ParseField("view"));
}

public static Response fromXContent(final XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
}

/**
* Transport Action for creating a View
*/
public static class TransportAction extends TransportClusterManagerNodeAction<Request, Response> {

private final ViewService viewService;

@Inject
public TransportAction(
final TransportService transportService,
final ClusterService clusterService,
final ThreadPool threadPool,
final ActionFilters actionFilters,
final IndexNameExpressionResolver indexNameExpressionResolver,
final ViewService viewService
) {
super(NAME, transportService, clusterService, threadPool, actionFilters, Request::new, indexNameExpressionResolver);
this.viewService = viewService;
}

@Override
protected String executor() {
return ThreadPool.Names.MANAGEMENT;
peternied marked this conversation as resolved.
Show resolved Hide resolved
}

@Override
protected Response read(final StreamInput in) throws IOException {
return new Response(in);
}

@Override
protected void clusterManagerOperation(final Request request, final ClusterState state, final ActionListener<Response> listener)
throws Exception {
viewService.createView(request, listener);
}

@Override
protected ClusterBlockException checkBlock(final Request request, final ClusterState state) {
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE);
}
}
}
Loading
Loading