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

Cross cluster search integration tests #2178

Merged
Merged
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

Large diffs are not rendered by default.

55 changes: 48 additions & 7 deletions src/integrationTest/java/org/opensearch/security/Song.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@
*/
package org.opensearch.security;

import java.util.Map;
import java.util.Objects;

class Song {
public class Song {

static final String FIELD_TITLE = "title";
static final String FIELD_ARTIST = "artist";
static final String FIELD_LYRICS = "lyrics";

static final String FIELD_STARS = "stars";

static final String FIELD_GENRE = "genre";
static final String ARTIST_FIRST = "First artist";
static final String ARTIST_STRING = "String";
static final String ARTIST_TWINS = "Twins";
Expand All @@ -26,19 +29,57 @@ class Song {
static final String ARTIST_NO = "No!";
static final String TITLE_POISON = "Poison";

public static final String ARTIST_YES = "yes";

public static final String TITLE_AFFIRMATIVE = "Affirmative";

public static final String ARTIST_UNKNOWN = "unknown";
public static final String TITLE_CONFIDENTIAL = "confidential";

public static final String LYRICS_1 = "Very deep subject";
public static final String LYRICS_2 = "Once upon a time";
public static final String LYRICS_3 = "giant nonsense";
public static final String LYRICS_4 = "Much too much";
public static final String LYRICS_5 = "Little to little";
public static final String LYRICS_6 = "confidential secret classified";

static final String GENRE_ROCK = "rock";
static final String GENRE_JAZZ = "jazz";
static final String GENRE_BLUES = "blues";

static final String QUERY_TITLE_NEXT_SONG = FIELD_TITLE + ":" + "\"" + TITLE_NEXT_SONG + "\"";
static final String QUERY_TITLE_POISON = FIELD_TITLE + ":" + TITLE_POISON;
static final String QUERY_TITLE_MAGNUM_OPUS = FIELD_TITLE + ":" + TITLE_MAGNUM_OPUS;

static final Object[][] SONGS = {
{FIELD_ARTIST, ARTIST_FIRST, FIELD_TITLE, TITLE_MAGNUM_OPUS ,FIELD_LYRICS, LYRICS_1, FIELD_STARS, 1},
{FIELD_ARTIST, ARTIST_STRING, FIELD_TITLE, TITLE_SONG_1_PLUS_1, FIELD_LYRICS, LYRICS_2, FIELD_STARS, 2},
{FIELD_ARTIST, ARTIST_TWINS, FIELD_TITLE, TITLE_NEXT_SONG, FIELD_LYRICS, LYRICS_3, FIELD_STARS, 3},
{FIELD_ARTIST, ARTIST_NO, FIELD_TITLE, TITLE_POISON, FIELD_LYRICS, LYRICS_4, FIELD_STARS, 4}
static final Map[] SONGS = {
new Song(ARTIST_FIRST, TITLE_MAGNUM_OPUS ,LYRICS_1, 1, GENRE_ROCK).asMap(),
new Song(ARTIST_STRING, TITLE_SONG_1_PLUS_1, LYRICS_2, 2, GENRE_BLUES).asMap(),
new Song(ARTIST_TWINS, TITLE_NEXT_SONG, LYRICS_3, 3, GENRE_JAZZ).asMap(),
new Song(ARTIST_NO, TITLE_POISON, LYRICS_4, 4, GENRE_ROCK).asMap(),
new Song(ARTIST_YES, TITLE_AFFIRMATIVE,LYRICS_5, 5, GENRE_BLUES).asMap(),
new Song(ARTIST_UNKNOWN, TITLE_CONFIDENTIAL, LYRICS_6, 6, GENRE_JAZZ).asMap()
};

private final String artist;
private final String title;
private final String lyrics;
private final Integer stars;

private final String genre;

public Song(String artist, String title, String lyrics, Integer stars, String genre) {
this.artist = Objects.requireNonNull(artist, "Artist is required");
this.title = Objects.requireNonNull(title, "Title is required");
this.lyrics = Objects.requireNonNull(lyrics, "Lyrics is required");
this.stars = Objects.requireNonNull(stars, "Stars field is required");
this.genre = Objects.requireNonNull(genre, "Genre field is required");
}

public Map<String, Object> asMap() {
return Map.of(FIELD_ARTIST, artist,
FIELD_TITLE, title,
FIELD_LYRICS, lyrics,
FIELD_STARS, stars,
FIELD_GENRE, genre);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,10 @@ public Set<String> getRoleNames() {
return roles.stream().map(Role::getName).collect(Collectors.toSet());
}

public Object getAttribute(String attributeName) {
return attributes.get(attributeName);
}

@Override
public XContentBuilder toXContent(XContentBuilder xContentBuilder, Params params) throws IOException {
xContentBuilder.startObject();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import static org.opensearch.test.framework.certificate.PublicKeyUsage.CLIENT_AUTH;
import static org.opensearch.test.framework.certificate.PublicKeyUsage.CRL_SIGN;
import static org.opensearch.test.framework.certificate.PublicKeyUsage.DIGITAL_SIGNATURE;
Expand All @@ -50,6 +53,8 @@
*/
public class TestCertificates {

private static final Logger log = LogManager.getLogger(TestCertificates.class);

public static final Integer MAX_NUMBER_OF_NODE_CERTIFICATES = 3;

private static final String CA_SUBJECT = "DC=com,DC=example,O=Example Com Inc.,OU=Example Com Inc. Root CA,CN=Example Com Inc. Root CA";
Expand All @@ -68,6 +73,7 @@ public TestCertificates() {
.mapToObj(this::createNodeCertificate)
.collect(Collectors.toList());
this.adminCertificate = createAdminCertificate();
log.info("Test certificates successfully generated");
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

import org.opensearch.index.reindex.ReindexModulePlugin;
Expand All @@ -50,17 +52,18 @@
import static org.opensearch.test.framework.cluster.NodeType.DATA;

public enum ClusterManager {

//3 nodes (1m, 2d)
DEFAULT(new NodeSettings(true, false), new NodeSettings(false, true), new NodeSettings(false, true)),
DEFAULT(new NodeSettings(NodeRole.CLUSTER_MANAGER), new NodeSettings(NodeRole.DATA), new NodeSettings(NodeRole.DATA)),

//1 node (1md)
SINGLENODE(new NodeSettings(true, true)),
SINGLENODE(new NodeSettings(NodeRole.CLUSTER_MANAGER, NodeRole.DATA)),

SINGLE_REMOTE_CLIENT(new NodeSettings(NodeRole.CLUSTER_MANAGER, NodeRole.DATA, NodeRole.REMOTE_CLUSTER_CLIENT)),

//4 node (1m, 2d, 1c)
CLIENTNODE(new NodeSettings(true, false), new NodeSettings(false, true), new NodeSettings(false, true), new NodeSettings(false, false)),
CLIENTNODE(new NodeSettings(NodeRole.CLUSTER_MANAGER), new NodeSettings(NodeRole.DATA), new NodeSettings(NodeRole.DATA), new NodeSettings()),

THREE_CLUSTER_MANAGERS(new NodeSettings(true, false), new NodeSettings(true, false), new NodeSettings(true, false), new NodeSettings(false, true), new NodeSettings(false, true));
THREE_CLUSTER_MANAGERS(new NodeSettings(NodeRole.CLUSTER_MANAGER), new NodeSettings(NodeRole.CLUSTER_MANAGER), new NodeSettings(NodeRole.CLUSTER_MANAGER), new NodeSettings(NodeRole.DATA), new NodeSettings(NodeRole.DATA));

private List<NodeSettings> nodeSettings = new LinkedList<>();

Expand All @@ -73,51 +76,60 @@ public List<NodeSettings> getNodeSettings() {
}

public List<NodeSettings> getClusterManagerNodeSettings() {
return unmodifiableList(nodeSettings.stream().filter(a -> a.clusterManagerNode).collect(Collectors.toList()));
return unmodifiableList(nodeSettings.stream().filter(a -> a.containRole(NodeRole.CLUSTER_MANAGER)).collect(Collectors.toList()));
}

public List<NodeSettings> getNonClusterManagerNodeSettings() {
return unmodifiableList(nodeSettings.stream().filter(a -> !a.clusterManagerNode).collect(Collectors.toList()));
return unmodifiableList(nodeSettings.stream().filter(a -> !a.containRole(NodeRole.CLUSTER_MANAGER)).collect(Collectors.toList()));
}

public int getNodes() {
return nodeSettings.size();
}

public int getClusterManagerNodes() {
return (int) nodeSettings.stream().filter(a -> a.clusterManagerNode).count();
return (int) nodeSettings.stream().filter(a -> a.containRole(NodeRole.CLUSTER_MANAGER)).count();
}

public int getDataNodes() {
return (int) nodeSettings.stream().filter(a -> a.dataNode).count();
return (int) nodeSettings.stream().filter(a -> a.containRole(NodeRole.DATA)).count();
}

public int getClientNodes() {
return (int) nodeSettings.stream().filter(a -> !a.clusterManagerNode && !a.dataNode).count();
return (int) nodeSettings.stream().filter(a -> a.isClientNode()).count();
}


public static class NodeSettings {

private final static List<Class<? extends Plugin>> DEFAULT_PLUGINS = List.of(Netty4ModulePlugin.class, OpenSearchSecurityPlugin.class,
MatrixAggregationModulePlugin.class, ParentJoinModulePlugin.class, PercolatorModulePlugin.class, ReindexModulePlugin.class);
public final boolean clusterManagerNode;
public final boolean dataNode;

private final Set<NodeRole> roles;
public final List<Class<? extends Plugin>> plugins;

public NodeSettings(boolean clusterManagerNode, boolean dataNode) {
this(clusterManagerNode, dataNode, Collections.emptyList());
public NodeSettings(NodeRole...roles) {
this(roles.length == 0 ? Collections.emptySet() : EnumSet.copyOf(Arrays.asList(roles)), Collections.emptyList());
}

public NodeSettings(boolean clusterManagerNode, boolean dataNode, List<Class<? extends Plugin>> additionalPlugins) {
public NodeSettings(Set<NodeRole> roles, List<Class<? extends Plugin>> additionalPlugins) {
super();
this.clusterManagerNode = clusterManagerNode;
this.dataNode = dataNode;
this.roles = Objects.requireNonNull(roles, "Node roles set must not be null");
this.plugins = mergePlugins(additionalPlugins, DEFAULT_PLUGINS);
}

public boolean containRole(NodeRole nodeRole) {
return roles.contains(nodeRole);
}

public boolean isClientNode() {
return (roles.contains(NodeRole.DATA) == false) && (roles.contains(NodeRole.CLUSTER_MANAGER));
}

NodeType recognizeNodeType() {
if (clusterManagerNode) {
if (roles.contains(NodeRole.CLUSTER_MANAGER)) {
return CLUSTER_MANAGER;
} else if (dataNode) {
} else if (roles.contains(NodeRole.DATA)) {
return DATA;
} else {
return CLIENT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,11 @@ public void before() throws Throwable {
for (Map.Entry<String, LocalCluster> entry : remotes.entrySet()) {
@SuppressWarnings("resource")
InetSocketAddress transportAddress = entry.getValue().localOpenSearchCluster.clusterManagerNode().getTransportAddress();
String key = "cluster.remote." + entry.getKey() + ".seeds";
String value = transportAddress.getHostString() + ":" + transportAddress.getPort();
log.info("Remote cluster '{}' added to configuration with the following seed '{}'", key, value);
nodeOverride = Settings.builder().put(nodeOverride)
.putList("cluster.remote." + entry.getKey() + ".seeds", transportAddress.getHostString() + ":" + transportAddress.getPort())
.putList(key, value)
.build();
}

Expand Down Expand Up @@ -250,7 +253,6 @@ public static class Builder {
private boolean loadConfigurationIntoIndex = true;

public Builder() {
this.testCertificates = new TestCertificates();
}

public Builder dependsOn(Object object) {
Expand Down Expand Up @@ -367,9 +369,16 @@ public Builder loadConfigurationIntoIndex(boolean loadConfigurationIntoIndex) {
this.loadConfigurationIntoIndex = loadConfigurationIntoIndex;
return this;
}
public Builder certificates(TestCertificates certificates) {
this.testCertificates = certificates;
return this;
}

public LocalCluster build() {
try {
if(testCertificates == null){
testCertificates = new TestCertificates();
}

clusterName += "_" + num.incrementAndGet();
Settings settings = nodeOverrideSettingsBuilder.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ boolean hasAssignedType(NodeType type) {
CompletableFuture<StartStage> start() {
CompletableFuture<StartStage> completableFuture = new CompletableFuture<>();
Class<? extends Plugin>[] mergedPlugins = nodeSettings.pluginsWithAddition(additionalPlugins);
this.node = new PluginAwareNode(nodeSettings.clusterManagerNode, getOpenSearchSettings(), mergedPlugins);
this.node = new PluginAwareNode(nodeSettings.containRole(NodeRole.CLUSTER_MANAGER), getOpenSearchSettings(), mergedPlugins);

new Thread(new Runnable() {

Expand Down Expand Up @@ -495,12 +495,15 @@ private Settings getMinimalOpenSearchSettings() {

private List<String> createNodeRolesSettings() {
final ImmutableList.Builder<String> nodeRolesBuilder = ImmutableList.<String>builder();
if (nodeSettings.dataNode) {
if (nodeSettings.containRole(NodeRole.DATA)) {
nodeRolesBuilder.add("data");
}
if (nodeSettings.clusterManagerNode) {
if (nodeSettings.containRole(NodeRole.CLUSTER_MANAGER)) {
nodeRolesBuilder.add("cluster_manager");
}
if(nodeSettings.containRole(NodeRole.REMOTE_CLUSTER_CLIENT)) {
nodeRolesBuilder.add("remote_cluster_client");
}
return nodeRolesBuilder.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright OpenSearch Contributors
* 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.test.framework.cluster;

enum NodeRole {
DATA, CLUSTER_MANAGER, REMOTE_CLUSTER_CLIENT
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ public static SearchRequest searchRequestWithScroll(String indexName, int pageSi
return searchRequest;
}

public static SearchRequest searchAll(String...indexNames) {
SearchRequest searchRequest = new SearchRequest(indexNames);
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
searchRequest.source(searchSourceBuilder);
return searchRequest;
}

public static SearchScrollRequest getSearchScrollRequest(SearchResponse searchResponse) {
SearchScrollRequest scrollRequest = new SearchScrollRequest(searchResponse.getScrollId());
scrollRequest.scroll(new TimeValue(1, MINUTES));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

import javax.net.ssl.SSLContext;
Expand Down Expand Up @@ -167,6 +168,13 @@ public HttpResponse patch(String path, String body) {
return executeRequest(uriRequest, CONTENT_TYPE_JSON);
}

public HttpResponse assignRoleToUser(String username, String roleName) {
Objects.requireNonNull(roleName, "Role name is required");
Objects.requireNonNull(username, "User name is required");
String body = String.format("[{\"op\":\"add\",\"path\":\"/opendistro_security_roles\",\"value\":[\"%s\"]}]", roleName);
DarshitChanpura marked this conversation as resolved.
Show resolved Hide resolved
return patch("_plugins/_security/api/internalusers/" + username, body);
}

public HttpResponse executeRequest(HttpUriRequest uriRequest, Header... requestSpecificHeaders) {

try(CloseableHttpClient httpClient = getHTTPClient()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ public GetResponseDocumentFieldValueMatcher(String fieldName, Object fieldValue)
@Override
protected boolean matchesSafely(GetResponse response, Description mismatchDescription) {
Map<String, Object> source = response.getSource();
if(source == null) {
mismatchDescription.appendText("Source is not available in search results");
return false;
}
if(source.containsKey(fieldName) == false) {
mismatchDescription.appendText("Document does not contain field ").appendValue(fieldName);
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ protected boolean matchesSafely(GetResponse response, Description mismatchDescri
mismatchDescription.appendText("Document contain incorrect id which is ").appendValue(response.getId());
return false;
}
if(response.isExists() == false) {
mismatchDescription.appendText("Document does not exist or is inaccessible");
return false;
}
return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class SearchHitContainsFieldWithValueMatcher<T> extends TypeSafeDiagnosingMatche
mismatchDescription.appendText("Source document is null, is fetch source option set to true?");
return false;
}
if(!source.containsKey(fieldName)) {
if(source.containsKey(fieldName) == false) {
Copy link
Member

Choose a reason for hiding this comment

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

nit: This change should not be needed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Please correct me if I am wrong, but I thought that some documents define the convention to use expressions like source.containsKey(fieldName) == false instead of !source.containsKey(fieldName)

Copy link
Member

Choose a reason for hiding this comment

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

interesting...I'm not aware about such documents.. If you have one handy could you please link it here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is an implicit rule that negative boolean expressions should use the form foo == false instead of !foo for better readability of the code. While this isn't strictly enforced, if might get called out in PR reviews as something to change.

Please see https://github.com/opensearch-project/OpenSearch/blob/main/DEVELOPER_GUIDE.md .

But this file is placed in the main OpenSearch repository and not in the security plugin repository. Therefore I am not sure if the rule is applicable.

mismatchDescription.appendText("Document does not contain field ").appendValue(fieldName);
return false;
}
Expand Down
Loading