blob: 2fbb9caa2c0ff5294b217232b11f396cafd46a52 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.cloud.api.collections;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.lucene.util.LuceneTestCase.Slow;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.params.CollectionParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.zookeeper.KeeperException;
import org.junit.Test;
@Slow
public class TestReplicaProperties extends ReplicaPropertiesBase {
public static final String COLLECTION_NAME = "testcollection";
public TestReplicaProperties() {
schemaString = "schema15.xml"; // we need a string id
sliceCount = 2;
}
@Test
@ShardsFixed(num = 4)
public void test() throws Exception {
try (CloudSolrClient client = createCloudClient(null)) {
// Mix up a bunch of different combinations of shards and replicas in order to exercise boundary cases.
// shards, replicationfactor, maxreplicaspernode
int shards = random().nextInt(7);
if (shards < 2) shards = 2;
int rFactor = random().nextInt(4);
if (rFactor < 2) rFactor = 2;
createCollection(null, COLLECTION_NAME, shards, rFactor, shards * rFactor + 1, client, null, "conf1");
}
waitForCollection(cloudClient.getZkStateReader(), COLLECTION_NAME, 2);
waitForRecoveriesToFinish(COLLECTION_NAME, false);
listCollection();
clusterAssignPropertyTest();
}
private void listCollection() throws IOException, SolrServerException {
try (CloudSolrClient client = createCloudClient(null)) {
ModifiableSolrParams params = new ModifiableSolrParams();
params.set("action", CollectionParams.CollectionAction.LIST.toString());
@SuppressWarnings({"rawtypes"})
SolrRequest request = new QueryRequest(params);
request.setPath("/admin/collections");
NamedList<Object> rsp = client.request(request);
@SuppressWarnings({"unchecked"})
List<String> collections = (List<String>) rsp.get("collections");
assertTrue("control_collection was not found in list", collections.contains("control_collection"));
assertTrue(DEFAULT_COLLECTION + " was not found in list", collections.contains(DEFAULT_COLLECTION));
assertTrue(COLLECTION_NAME + " was not found in list", collections.contains(COLLECTION_NAME));
}
}
private void clusterAssignPropertyTest() throws Exception {
try (CloudSolrClient client = createCloudClient(null)) {
client.connect();
try {
doPropertyAction(client,
"action", CollectionParams.CollectionAction.BALANCESHARDUNIQUE.toString(),
"property", "preferredLeader");
} catch (SolrException se) {
assertTrue("Should have seen missing required parameter 'collection' error",
se.getMessage().contains("Missing required parameter: collection"));
}
doPropertyAction(client,
"action", CollectionParams.CollectionAction.BALANCESHARDUNIQUE.toString(),
"collection", COLLECTION_NAME,
"property", "preferredLeader");
verifyUniqueAcrossCollection(client, COLLECTION_NAME, "preferredleader");
doPropertyAction(client,
"action", CollectionParams.CollectionAction.BALANCESHARDUNIQUE.toString(),
"collection", COLLECTION_NAME,
"property", "property.newunique",
"shardUnique", "true");
verifyUniqueAcrossCollection(client, COLLECTION_NAME, "property.newunique");
try {
doPropertyAction(client,
"action", CollectionParams.CollectionAction.BALANCESHARDUNIQUE.toString(),
"collection", COLLECTION_NAME,
"property", "whatever",
"shardUnique", "false");
fail("Should have thrown an exception here.");
} catch (SolrException se) {
assertTrue("Should have gotten a specific error message here",
se.getMessage().contains("Balancing properties amongst replicas in a slice requires that the " +
"property be pre-defined as a unique property (e.g. 'preferredLeader') or that 'shardUnique' be set to 'true'"));
}
// Should be able to set non-unique-per-slice values in several places.
Map<String, Slice> slices = client.getZkStateReader().getClusterState().getCollection(COLLECTION_NAME).getSlicesMap();
List<String> sliceList = new ArrayList<>(slices.keySet());
String c1_s1 = sliceList.get(0);
List<String> replicasList = new ArrayList<>(slices.get(c1_s1).getReplicasMap().keySet());
String c1_s1_r1 = replicasList.get(0);
String c1_s1_r2 = replicasList.get(1);
addProperty(client,
"action", CollectionParams.CollectionAction.ADDREPLICAPROP.toString(),
"collection", COLLECTION_NAME,
"shard", c1_s1,
"replica", c1_s1_r1,
"property", "bogus1",
"property.value", "true");
addProperty(client,
"action", CollectionParams.CollectionAction.ADDREPLICAPROP.toString(),
"collection", COLLECTION_NAME,
"shard", c1_s1,
"replica", c1_s1_r2,
"property", "property.bogus1",
"property.value", "whatever");
try {
doPropertyAction(client,
"action", CollectionParams.CollectionAction.BALANCESHARDUNIQUE.toString(),
"collection", COLLECTION_NAME,
"property", "bogus1",
"shardUnique", "false");
fail("Should have thrown parameter error here");
} catch (SolrException se) {
assertTrue("Should have caught specific exception ",
se.getMessage().contains("Balancing properties amongst replicas in a slice requires that the property be " +
"pre-defined as a unique property (e.g. 'preferredLeader') or that 'shardUnique' be set to 'true'"));
}
// Should have no effect despite the "shardUnique" param being set.
doPropertyAction(client,
"action", CollectionParams.CollectionAction.BALANCESHARDUNIQUE.toString(),
"collection", COLLECTION_NAME,
"property", "property.bogus1",
"shardUnique", "true");
verifyPropertyVal(client, COLLECTION_NAME,
c1_s1_r1, "bogus1", "true");
verifyPropertyVal(client, COLLECTION_NAME,
c1_s1_r2, "property.bogus1", "whatever");
// At this point we've assigned a preferred leader. Make it happen and check that all the nodes that are
// leaders _also_ have the preferredLeader property set.
NamedList<Object> res = doPropertyAction(client,
"action", CollectionParams.CollectionAction.REBALANCELEADERS.toString(),
"collection", COLLECTION_NAME);
verifyLeaderAssignment(client, COLLECTION_NAME);
}
}
private void verifyLeaderAssignment(CloudSolrClient client, String collectionName)
throws InterruptedException, KeeperException {
String lastFailMsg = "";
for (int idx = 0; idx < 300; ++idx) { // Keep trying while Overseer writes the ZK state for up to 30 seconds.
lastFailMsg = "";
ClusterState clusterState = client.getZkStateReader().getClusterState();
for (Slice slice : clusterState.getCollection(collectionName).getSlices()) {
boolean foundLeader = false;
boolean foundPreferred = false;
for (Replica replica : slice.getReplicas()) {
boolean isLeader = replica.getBool("leader", false);
boolean isPreferred = replica.getBool("property.preferredleader", false);
if (isLeader != isPreferred) {
lastFailMsg = "Replica should NOT have preferredLeader != leader. Preferred: " + isPreferred +
" leader is " + isLeader;
}
if (foundLeader && isLeader) {
lastFailMsg = "There should only be a single leader in _any_ shard! Replica " + replica.getName() +
" is the second leader in slice " + slice.getName();
}
if (foundPreferred && isPreferred) {
lastFailMsg = "There should only be a single preferredLeader in _any_ shard! Replica " + replica.getName() +
" is the second preferredLeader in slice " + slice.getName();
}
foundLeader = foundLeader ? foundLeader : isLeader;
foundPreferred = foundPreferred ? foundPreferred : isPreferred;
}
}
if (lastFailMsg.length() == 0) return;
Thread.sleep(100);
}
fail(lastFailMsg);
}
private void addProperty(CloudSolrClient client, String... paramsIn) throws IOException, SolrServerException {
assertTrue("paramsIn must be an even multiple of 2, it is: " + paramsIn.length, (paramsIn.length % 2) == 0);
ModifiableSolrParams params = new ModifiableSolrParams();
for (int idx = 0; idx < paramsIn.length; idx += 2) {
params.set(paramsIn[idx], paramsIn[idx + 1]);
}
QueryRequest request = new QueryRequest(params);
request.setPath("/admin/collections");
client.request(request);
}
}