blob: d6cbf9bcd26438f3b3e48da82aa95312dcd50832 [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;
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.SolrTestCaseJ4.SuppressSSL;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.ConcurrentUpdateSolrClient;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.client.solrj.response.CollectionAdminResponse;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.CollectionParams.CollectionAction;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.Utils;
import org.apache.zookeeper.CreateMode;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.apache.solr.common.params.CommonParams.VERSION_FIELD;
/**
* Super basic testing, no shard restarting or anything.
*/
@Slow
@SuppressSSL(bugUrl = "https://issues.apache.org/jira/browse/SOLR-5776")
public class FullSolrCloudDistribCmdsTest extends AbstractFullDistribZkTestBase {
@BeforeClass
public static void beforeSuperClass() {
schemaString = "schema15.xml"; // we need a string id
}
public FullSolrCloudDistribCmdsTest() {
super();
sliceCount = 3;
}
@Test
@ShardsFixed(num = 6)
// commented 15-Sep-2018 @LuceneTestCase.BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 2-Aug-2018
@BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // annotated on: 24-Dec-2018
public void test() throws Exception {
handle.clear();
handle.put("timestamp", SKIPVAL);
waitForRecoveriesToFinish(false);
// add a doc, update it, and delete it
QueryResponse results;
UpdateRequest uReq;
long docId = addUpdateDelete();
// add 2 docs in a request
SolrInputDocument doc1;
SolrInputDocument doc2;
docId = addTwoDocsInOneRequest(docId);
// two deletes
uReq = new UpdateRequest();
uReq.deleteById(Long.toString(docId-1));
uReq.deleteById(Long.toString(docId-2)).process(cloudClient);
controlClient.deleteById(Long.toString(docId-1));
controlClient.deleteById(Long.toString(docId-2));
commit();
results = query(cloudClient);
assertEquals(0, results.getResults().getNumFound());
results = query(controlClient);
assertEquals(0, results.getResults().getNumFound());
// add two docs together, a 3rd doc and a delete
indexr("id", docId++, t1, "originalcontent");
uReq = new UpdateRequest();
doc1 = new SolrInputDocument();
addFields(doc1, "id", docId++);
uReq.add(doc1);
doc2 = new SolrInputDocument();
addFields(doc2, "id", docId++);
uReq.add(doc2);
uReq.process(cloudClient);
uReq.process(controlClient);
uReq = new UpdateRequest();
uReq.deleteById(Long.toString(docId - 2)).process(cloudClient);
controlClient.deleteById(Long.toString(docId - 2));
commit();
assertDocCounts(VERBOSE);
checkShardConsistency();
results = query(controlClient);
assertEquals(2, results.getResults().getNumFound());
results = query(cloudClient);
assertEquals(2, results.getResults().getNumFound());
docId = testIndexQueryDeleteHierarchical(docId);
docId = testIndexingDocPerRequestWithHttpSolrClient(docId);
testConcurrentIndexing(docId);
// TODO: testOptimisticUpdate(results);
testDeleteByQueryDistrib();
// See SOLR-7384
// testDeleteByIdImplicitRouter();
//
// testDeleteByIdCompositeRouterWithRouterField();
docId = testThatCantForwardToLeaderFails(docId);
docId = testIndexingBatchPerRequestWithHttpSolrClient(docId);
}
private void testDeleteByIdImplicitRouter() throws Exception {
SolrClient server = createNewSolrClient("", getBaseUrl((HttpSolrClient) clients.get(0)));
CollectionAdminResponse response;
Map<String, NamedList<Integer>> coresStatus;
CollectionAdminRequest.Create createCollectionRequest
= CollectionAdminRequest.createCollectionWithImplicitRouter("implicit_collection_without_routerfield",
"conf1","shard1,shard2",2);
response = createCollectionRequest.process(server);
assertEquals(0, response.getStatus());
assertTrue(response.isSuccess());
coresStatus = response.getCollectionCoresStatus();
assertEquals(4, coresStatus.size());
for (int i = 0; i < 4; i++) {
NamedList<Integer> status = coresStatus.get("implicit_collection_without_routerfield_shard" + (i / 2 + 1) + "_replica" + (i % 2 + 1));
assertEquals(0, (int) status.get("status"));
assertTrue(status.get("QTime") > 0);
}
waitForRecoveriesToFinish("implicit_collection_without_routerfield", true);
SolrClient shard1 = createNewSolrClient("implicit_collection_without_routerfield_shard1_replica1",
getBaseUrl((HttpSolrClient) clients.get(0)));
SolrClient shard2 = createNewSolrClient("implicit_collection_without_routerfield_shard2_replica1",
getBaseUrl((HttpSolrClient) clients.get(0)));
SolrInputDocument doc = new SolrInputDocument();
int docCounts1, docCounts2;
// Add three documents to shard1
doc.clear();
doc.addField("id", "1");
doc.addField("title", "s1 one");
shard1.add(doc);
shard1.commit();
doc.clear();
doc.addField("id", "2");
doc.addField("title", "s1 two");
shard1.add(doc);
shard1.commit();
doc.clear();
doc.addField("id", "3");
doc.addField("title", "s1 three");
shard1.add(doc);
shard1.commit();
docCounts1 = 3; // Three documents in shard1
// Add two documents to shard2
doc.clear();
doc.addField("id", "4");
doc.addField("title", "s2 four");
shard2.add(doc);
shard2.commit();
doc.clear();
doc.addField("id", "5");
doc.addField("title", "s2 five");
shard2.add(doc);
shard2.commit();
docCounts2 = 2; // Two documents in shard2
// Verify the documents were added to correct shards
ModifiableSolrParams query = new ModifiableSolrParams();
query.set("q", "*:*");
QueryResponse respAll = shard1.query(query);
assertEquals(docCounts1 + docCounts2, respAll.getResults().getNumFound());
query.set("shards", "shard1");
QueryResponse resp1 = shard1.query(query);
assertEquals(docCounts1, resp1.getResults().getNumFound());
query.set("shards", "shard2");
QueryResponse resp2 = shard2.query(query);
assertEquals(docCounts2, resp2.getResults().getNumFound());
// Delete a document in shard2 with update to shard1, with _route_ param
// Should delete.
UpdateRequest deleteRequest = new UpdateRequest();
deleteRequest.deleteById("4", "shard2");
shard1.request(deleteRequest);
shard1.commit();
query.set("shards", "shard2");
resp2 = shard2.query(query);
assertEquals(--docCounts2, resp2.getResults().getNumFound());
// Delete a document in shard2 with update to shard1, without _route_ param
// Shouldn't delete, since deleteById requests are not broadcast to all shard leaders.
deleteRequest = new UpdateRequest();
deleteRequest.deleteById("5");
shard1.request(deleteRequest);
shard1.commit();
query.set("shards", "shard2");
resp2 = shard2.query(query);
assertEquals(docCounts2, resp2.getResults().getNumFound());
// Multiple deleteById commands in a single request
deleteRequest.clear();
deleteRequest.deleteById("2", "shard1");
deleteRequest.deleteById("3", "shard1");
deleteRequest.setCommitWithin(1);
query.set("shards", "shard1");
shard2.request(deleteRequest);
resp1 = shard1.query(query);
--docCounts1;
--docCounts1;
assertEquals(docCounts1, resp1.getResults().getNumFound());
// Test commitWithin, update to shard2, document deleted in shard1
deleteRequest.clear();
deleteRequest.deleteById("1", "shard1");
deleteRequest.setCommitWithin(1);
shard2.request(deleteRequest);
Thread.sleep(1000);
query.set("shards", "shard1");
resp1 = shard1.query(query);
assertEquals(--docCounts1, resp1.getResults().getNumFound());
}
private void testDeleteByIdCompositeRouterWithRouterField() throws Exception {
SolrClient server = createNewSolrClient("", getBaseUrl((HttpSolrClient) clients.get(0)));
CollectionAdminResponse response;
Map<String, NamedList<Integer>> coresStatus;
response = CollectionAdminRequest.createCollection("compositeid_collection_with_routerfield","conf1",2,2)
.setRouterName("compositeId")
.setRouterField("routefield_s")
.setShards("shard1,shard2")
.process(server);
assertEquals(0, response.getStatus());
assertTrue(response.isSuccess());
coresStatus = response.getCollectionCoresStatus();
assertEquals(4, coresStatus.size());
for (int i = 0; i < 4; i++) {
NamedList<Integer> status = coresStatus.get("compositeid_collection_with_routerfield_shard" + (i / 2 + 1) + "_replica" + (i % 2 + 1));
assertEquals(0, (int) status.get("status"));
assertTrue(status.get("QTime") > 0);
}
waitForRecoveriesToFinish("compositeid_collection_with_routerfield", true);
SolrClient shard1 = createNewSolrClient("compositeid_collection_with_routerfield_shard1_replica1",
getBaseUrl((HttpSolrClient) clients.get(0)));
SolrClient shard2 = createNewSolrClient("compositeid_collection_with_routerfield_shard2_replica1",
getBaseUrl((HttpSolrClient) clients.get(0)));
SolrInputDocument doc = new SolrInputDocument();
int docCounts1 = 0, docCounts2 = 0;
// Add three documents to shard1
doc.clear();
doc.addField("id", "1");
doc.addField("title", "s1 one");
doc.addField("routefield_s", "europe");
shard1.add(doc);
shard1.commit();
doc.clear();
doc.addField("id", "2");
doc.addField("title", "s1 two");
doc.addField("routefield_s", "europe");
shard1.add(doc);
shard1.commit();
doc.clear();
doc.addField("id", "3");
doc.addField("title", "s1 three");
doc.addField("routefield_s", "europe");
shard1.add(doc);
shard1.commit();
docCounts1 = 3; // Three documents in shard1
// Add two documents to shard2
doc.clear();
doc.addField("id", "4");
doc.addField("title", "s2 four");
doc.addField("routefield_s", "africa");
shard2.add(doc);
//shard2.commit();
doc.clear();
doc.addField("id", "5");
doc.addField("title", "s2 five");
doc.addField("routefield_s", "africa");
shard2.add(doc);
shard2.commit();
docCounts2 = 2; // Two documents in shard2
// Verify the documents were added to correct shards
ModifiableSolrParams query = new ModifiableSolrParams();
query.set("q", "*:*");
QueryResponse respAll = shard1.query(query);
assertEquals(docCounts1 + docCounts2, respAll.getResults().getNumFound());
query.set("shards", "shard1");
QueryResponse resp1 = shard1.query(query);
assertEquals(docCounts1, resp1.getResults().getNumFound());
query.set("shards", "shard2");
QueryResponse resp2 = shard2.query(query);
assertEquals(docCounts2, resp2.getResults().getNumFound());
// Delete a document in shard2 with update to shard1, with _route_ param
// Should delete.
UpdateRequest deleteRequest = new UpdateRequest();
deleteRequest.deleteById("4", "africa");
deleteRequest.setCommitWithin(1);
shard1.request(deleteRequest);
shard1.commit();
query.set("shards", "shard2");
resp2 = shard2.query(query);
--docCounts2;
assertEquals(docCounts2, resp2.getResults().getNumFound());
// Multiple deleteById commands in a single request
deleteRequest.clear();
deleteRequest.deleteById("2", "europe");
deleteRequest.deleteById("3", "europe");
deleteRequest.setCommitWithin(1);
query.set("shards", "shard1");
shard1.request(deleteRequest);
shard1.commit();
Thread.sleep(1000);
resp1 = shard1.query(query);
--docCounts1;
--docCounts1;
assertEquals(docCounts1, resp1.getResults().getNumFound());
// Test commitWithin, update to shard2, document deleted in shard1
deleteRequest.clear();
deleteRequest.deleteById("1", "europe");
deleteRequest.setCommitWithin(1);
shard2.request(deleteRequest);
query.set("shards", "shard1");
resp1 = shard1.query(query);
--docCounts1;
assertEquals(docCounts1, resp1.getResults().getNumFound());
}
private long testThatCantForwardToLeaderFails(long docId) throws Exception {
ZkStateReader zkStateReader = cloudClient.getZkStateReader();
ZkNodeProps props = zkStateReader.getLeaderRetry(DEFAULT_COLLECTION, "shard1");
chaosMonkey.stopShard("shard1");
Thread.sleep(1000);
// fake that the leader is still advertised
String leaderPath = ZkStateReader.getShardLeadersPath(DEFAULT_COLLECTION, "shard1");
SolrZkClient zkClient = new SolrZkClient(zkServer.getZkAddress(), 10000);
int fails = 0;
try {
zkClient.makePath(leaderPath, Utils.toJSON(props),
CreateMode.EPHEMERAL, true);
for (int i = 0; i < 200; i++) {
try {
index_specific(shardToJetty.get("shard2").get(0).client.solrClient, id, docId++);
} catch (SolrException e) {
// expected
fails++;
break;
} catch (SolrServerException e) {
// expected
fails++;
break;
}
}
} finally {
zkClient.close();
}
assertTrue("A whole shard is down - some of these should fail", fails > 0);
return docId;
}
private long addTwoDocsInOneRequest(long docId) throws
Exception {
QueryResponse results;
UpdateRequest uReq;
uReq = new UpdateRequest();
docId = addDoc(docId, uReq);
docId = addDoc(docId, uReq);
uReq.process(cloudClient);
uReq.process(controlClient);
commit();
checkShardConsistency();
assertDocCounts(VERBOSE);
results = query(cloudClient);
assertEquals(2, results.getResults().getNumFound());
return docId;
}
private long addUpdateDelete() throws Exception,
IOException {
long docId = 99999999L;
indexr("id", docId, t1, "originalcontent");
commit();
ModifiableSolrParams params = new ModifiableSolrParams();
params.add("q", t1 + ":originalcontent");
QueryResponse results = clients.get(0).query(params);
assertEquals(1, results.getResults().getNumFound());
// update doc
indexr("id", docId, t1, "updatedcontent");
commit();
assertDocCounts(VERBOSE);
results = clients.get(0).query(params);
assertEquals(0, results.getResults().getNumFound());
params.set("q", t1 + ":updatedcontent");
results = clients.get(0).query(params);
assertEquals(1, results.getResults().getNumFound());
UpdateRequest uReq = new UpdateRequest();
//uReq.setParam(UpdateParams.UPDATE_CHAIN, DISTRIB_UPDATE_CHAIN);
uReq.deleteById(Long.toString(docId)).process(clients.get(0));
commit();
results = clients.get(0).query(params);
assertEquals(0, results.getResults().getNumFound());
return docId;
}
private void testDeleteByQueryDistrib() throws Exception {
del("*:*");
commit();
assertEquals(0, query(cloudClient).getResults().getNumFound());
}
private long testIndexQueryDeleteHierarchical(long docId) throws Exception {
//index
int topDocsNum = atLeast(10);
int childsNum = 5+random().nextInt(5);
for (int i = 0; i < topDocsNum; ++i) {
UpdateRequest uReq = new UpdateRequest();
SolrInputDocument topDocument = new SolrInputDocument();
topDocument.addField("id", docId++);
topDocument.addField("type_s", "parent");
topDocument.addField(i + "parent_f1_s", "v1");
topDocument.addField(i + "parent_f2_s", "v2");
for (int index = 0; index < childsNum; ++index) {
docId = addChildren("child", topDocument, index, false, docId);
}
uReq.add(topDocument);
uReq.process(cloudClient);
uReq.process(controlClient);
}
commit();
checkShardConsistency();
assertDocCounts(VERBOSE);
//query
// parents
SolrQuery query = new SolrQuery("type_s:parent");
QueryResponse results = cloudClient.query(query);
assertEquals(topDocsNum, results.getResults().getNumFound());
//childs
query = new SolrQuery("type_s:child");
results = cloudClient.query(query);
assertEquals(topDocsNum * childsNum, results.getResults().getNumFound());
//grandchilds
query = new SolrQuery("type_s:grand");
results = cloudClient.query(query);
//each topDoc has t childs where each child has x = 0 + 2 + 4 + ..(t-1)*2 grands
//x = 2 * (1 + 2 + 3 +.. (t-1)) => arithmetic summ of t-1
//x = 2 * ((t-1) * t / 2) = t * (t - 1)
assertEquals(topDocsNum * childsNum * (childsNum - 1), results.getResults().getNumFound());
//delete
del("*:*");
commit();
return docId;
}
private long addChildren(String prefix, SolrInputDocument topDocument, int childIndex, boolean lastLevel, long docId) {
SolrInputDocument childDocument = new SolrInputDocument();
childDocument.addField("id", docId++);
childDocument.addField("type_s", prefix);
for (int index = 0; index < childIndex; ++index) {
childDocument.addField(childIndex + prefix + index + "_s", childIndex + "value"+ index);
}
if (!lastLevel) {
for (int i = 0; i < childIndex * 2; ++i) {
docId = addChildren("grand", childDocument, i, true, docId);
}
}
topDocument.addChildDocument(childDocument);
return docId;
}
private long testIndexingDocPerRequestWithHttpSolrClient(long docId) throws Exception {
int docs = random().nextInt(TEST_NIGHTLY ? 4013 : 97) + 1;
for (int i = 0; i < docs; i++) {
UpdateRequest uReq;
uReq = new UpdateRequest();
docId = addDoc(docId, uReq);
uReq.process(cloudClient);
uReq.process(controlClient);
}
commit();
checkShardConsistency();
assertDocCounts(VERBOSE);
return docId++;
}
private long testIndexingBatchPerRequestWithHttpSolrClient(long docId) throws Exception {
// remove collection
ModifiableSolrParams params = new ModifiableSolrParams();
params.set("action", CollectionAction.DELETE.toString());
params.set("name", "collection1");
QueryRequest request = new QueryRequest(params);
request.setPath("/admin/collections");
cloudClient.request(request);
controlClient.deleteByQuery("*:*");
controlClient.commit();
// somtimes we use an oversharded collection
createCollection(null, "collection2", 7, 3, 100000, cloudClient, null, "conf1");
cloudClient.setDefaultCollection("collection2");
waitForRecoveriesToFinish("collection2", false);
class IndexThread extends Thread {
Integer name;
public IndexThread(Integer name) {
this.name = name;
}
@Override
public void run() {
int rnds = random().nextInt(TEST_NIGHTLY ? 10 : 3) + 1;
for (int i = 0; i < rnds; i++) {
UpdateRequest uReq;
uReq = new UpdateRequest();
int cnt = random().nextInt(TEST_NIGHTLY ? 2000 : 200) + 1;
for (int j = 0; j <cnt; j++) {
addDoc("thread" + name + "_" + i + "_" + j, uReq);
}
try {
uReq.process(cloudClient);
uReq.process(controlClient);
} catch (SolrServerException | IOException e) {
throw new RuntimeException(e);
}
}
}
};
List<Thread> threads = new ArrayList<>();
int nthreads = random().nextInt(TEST_NIGHTLY ? 4 : 2) + 1;
for (int i = 0; i < nthreads; i++) {
IndexThread thread = new IndexThread(i);
threads.add(thread);
thread.start();
}
for (Thread thread : threads) {
thread.join();
}
commit();
waitForRecoveriesToFinish("collection2", false);
printLayout();
SolrQuery query = new SolrQuery("*:*");
long controlCount = controlClient.query(query).getResults()
.getNumFound();
long cloudCount = cloudClient.query(query).getResults().getNumFound();
CloudInspectUtil.compareResults(controlClient, cloudClient);
assertEquals("Control does not match cloud", controlCount, cloudCount);
System.out.println("DOCS:" + controlCount);
return docId;
}
private long addDoc(long docId, UpdateRequest uReq) {
addDoc(Long.toString(docId++), uReq);
return docId;
}
private long addDoc(String docId, UpdateRequest uReq) {
SolrInputDocument doc1 = new SolrInputDocument();
uReq.add(doc1);
addFields(doc1, "id", docId, "text_t", "some text so that it not's negligent work to parse this doc, even though it's still a pretty short doc");
return -1;
}
private long testConcurrentIndexing(long docId) throws Exception {
QueryResponse results = query(cloudClient);
long beforeCount = results.getResults().getNumFound();
int cnt = TEST_NIGHTLY ? 2933 : 313;
try (ConcurrentUpdateSolrClient concurrentClient = getConcurrentUpdateSolrClient(
((HttpSolrClient) clients.get(0)).getBaseURL(), 10, 2, 120000)) {
for (int i = 0; i < cnt; i++) {
index_specific(concurrentClient, id, docId++, "text_t", "some text so that it not's negligent work to parse this doc, even though it's still a pretty short doc");
}
concurrentClient.blockUntilFinished();
commit();
checkShardConsistency();
assertDocCounts(VERBOSE);
}
results = query(cloudClient);
assertEquals(beforeCount + cnt, results.getResults().getNumFound());
return docId;
}
private void testOptimisticUpdate(QueryResponse results) throws Exception {
SolrDocument doc = results.getResults().get(0);
Long version = (Long) doc.getFieldValue(VERSION_FIELD);
Integer theDoc = (Integer) doc.getFieldValue("id");
UpdateRequest uReq = new UpdateRequest();
SolrInputDocument doc1 = new SolrInputDocument();
uReq.setParams(new ModifiableSolrParams());
uReq.getParams().set(VERSION_FIELD, Long.toString(version));
addFields(doc1, "id", theDoc, t1, "theupdatestuff");
uReq.add(doc1);
uReq.process(cloudClient);
uReq.process(controlClient);
commit();
// updating the old version should fail...
SolrInputDocument doc2 = new SolrInputDocument();
uReq = new UpdateRequest();
uReq.setParams(new ModifiableSolrParams());
uReq.getParams().set(VERSION_FIELD, Long.toString(version));
addFields(doc2, "id", theDoc, t1, "thenewupdatestuff");
uReq.add(doc2);
uReq.process(cloudClient);
uReq.process(controlClient);
commit();
ModifiableSolrParams params = new ModifiableSolrParams();
params.add("q", t1 + ":thenewupdatestuff");
QueryResponse res = clients.get(0).query(params);
assertEquals(0, res.getResults().getNumFound());
params = new ModifiableSolrParams();
params.add("q", t1 + ":theupdatestuff");
res = clients.get(0).query(params);
assertEquals(1, res.getResults().getNumFound());
}
private QueryResponse query(SolrClient client) throws SolrServerException, IOException {
SolrQuery query = new SolrQuery("*:*");
return client.query(query);
}
protected SolrInputDocument addRandFields(SolrInputDocument sdoc) {
return sdoc;
}
}