blob: cde6f0497d2fe1f7f41012a3d4daca873b99114d [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.handler.component;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.io.FileUtils;
import org.apache.solr.SolrJettyTestBase;
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.HttpSolrClient;
import org.apache.solr.client.solrj.request.CoreAdminRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.ShardParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.response.SolrQueryResponse;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
public class DistributedDebugComponentTest extends SolrJettyTestBase {
private static SolrClient collection1;
private static SolrClient collection2;
private static String shard1;
private static String shard2;
private static File solrHome;
private static File createSolrHome() throws Exception {
File workDir = createTempDir().toFile();
setupJettyTestHome(workDir, "collection1");
FileUtils.copyDirectory(new File(workDir, "collection1"), new File(workDir, "collection2"));
return workDir;
}
@BeforeClass
public static void createThings() throws Exception {
systemSetPropertySolrDisableShardsWhitelist("true");
solrHome = createSolrHome();
createAndStartJetty(solrHome.getAbsolutePath());
String url = jetty.getBaseUrl().toString();
collection1 = getHttpSolrClient(url + "/collection1");
collection2 = getHttpSolrClient(url + "/collection2");
String urlCollection1 = jetty.getBaseUrl().toString() + "/" + "collection1";
String urlCollection2 = jetty.getBaseUrl().toString() + "/" + "collection2";
shard1 = urlCollection1.replaceAll("https?://", "");
shard2 = urlCollection2.replaceAll("https?://", "");
//create second core
try (HttpSolrClient nodeClient = getHttpSolrClient(url)) {
CoreAdminRequest.Create req = new CoreAdminRequest.Create();
req.setCoreName("collection2");
req.setConfigSet("collection1");
nodeClient.request(req);
}
SolrInputDocument doc = new SolrInputDocument();
doc.setField("id", "1");
doc.setField("text", "batman");
collection1.add(doc);
collection1.commit();
doc.setField("id", "2");
doc.setField("text", "superman");
collection2.add(doc);
collection2.commit();
}
@AfterClass
public static void destroyThings() throws Exception {
if (null != collection1) {
collection1.close();
collection1 = null;
}
if (null != collection2) {
collection2.close();
collection2 = null;
}
if (null != jetty) {
jetty.stop();
jetty=null;
}
resetExceptionIgnores();
systemClearPropertySolrDisableShardsWhitelist();
}
@Test
@SuppressWarnings("unchecked")
public void testSimpleSearch() throws Exception {
SolrQuery query = new SolrQuery();
query.setQuery("*:*");
query.set("debug", "track");
query.set("distrib", "true");
query.setFields("id", "text");
query.set("shards", shard1 + "," + shard2);
if (random().nextBoolean()) {
query.add("omitHeader", Boolean.toString(random().nextBoolean()));
}
QueryResponse response = collection1.query(query);
NamedList<Object> track = (NamedList<Object>) response.getDebugMap().get("track");
assertNotNull(track);
assertNotNull(track.get("rid"));
assertNotNull(track.get("EXECUTE_QUERY"));
assertNotNull(((NamedList<Object>)track.get("EXECUTE_QUERY")).get(shard1));
assertNotNull(((NamedList<Object>)track.get("EXECUTE_QUERY")).get(shard2));
assertNotNull(((NamedList<Object>)track.get("GET_FIELDS")).get(shard1));
assertNotNull(((NamedList<Object>)track.get("GET_FIELDS")).get(shard2));
assertElementsPresent((NamedList<String>)((NamedList<Object>)track.get("EXECUTE_QUERY")).get(shard1),
"QTime", "ElapsedTime", "RequestPurpose", "NumFound", "Response");
assertElementsPresent((NamedList<String>)((NamedList<Object>)track.get("EXECUTE_QUERY")).get(shard2),
"QTime", "ElapsedTime", "RequestPurpose", "NumFound", "Response");
assertElementsPresent((NamedList<String>)((NamedList<Object>)track.get("GET_FIELDS")).get(shard1),
"QTime", "ElapsedTime", "RequestPurpose", "NumFound", "Response");
assertElementsPresent((NamedList<String>)((NamedList<Object>)track.get("GET_FIELDS")).get(shard2),
"QTime", "ElapsedTime", "RequestPurpose", "NumFound", "Response");
query.setQuery("id:1");
response = collection1.query(query);
track = (NamedList<Object>) response.getDebugMap().get("track");
assertNotNull(((NamedList<Object>)track.get("EXECUTE_QUERY")).get(shard1));
assertNotNull(((NamedList<Object>)track.get("EXECUTE_QUERY")).get(shard2));
assertNotNull(((NamedList<Object>)track.get("GET_FIELDS")).get(shard1));
// This test is invalid, as GET_FIELDS should not be executed in shard 2
assertNull(((NamedList<Object>)track.get("GET_FIELDS")).get(shard2));
}
@Test
@SuppressWarnings("resource") // Cannot close client in this loop!
public void testRandom() throws Exception {
final int NUM_ITERS = atLeast(50);
for (int i = 0; i < NUM_ITERS; i++) {
final SolrClient client = random().nextBoolean() ? collection1 : collection2;
SolrQuery q = new SolrQuery();
q.set("distrib", "true");
q.setFields("id", "text");
boolean shard1Results = random().nextBoolean();
boolean shard2Results = random().nextBoolean();
String qs = "_query_with_no_results_";
if (shard1Results) {
qs += " OR batman";
}
if (shard2Results) {
qs += " OR superman";
}
q.setQuery(qs);
Set<String> shards = new HashSet<String>(Arrays.asList(shard1, shard2));
if (random().nextBoolean()) {
shards.remove(shard1);
} else if (random().nextBoolean()) {
shards.remove(shard2);
}
q.set("shards", String.join(",", shards));
List<String> debug = new ArrayList<String>(10);
boolean all = false;
final boolean timing = random().nextBoolean();
final boolean query = random().nextBoolean();
final boolean results = random().nextBoolean();
final boolean track = random().nextBoolean();
if (timing) { debug.add("timing"); }
if (query) { debug.add("query"); }
if (results) { debug.add("results"); }
if (track) { debug.add("track"); }
if (debug.isEmpty()) {
debug.add("true");
all = true;
}
q.set("debug", debug.toArray(new String[debug.size()]));
QueryResponse r = client.query(q);
try {
assertDebug(r, all || track, "track");
assertDebug(r, all || query, "rawquerystring");
assertDebug(r, all || query, "querystring");
assertDebug(r, all || query, "parsedquery");
assertDebug(r, all || query, "parsedquery_toString");
assertDebug(r, all || query, "QParser");
assertDebug(r, all || results, "explain");
assertDebug(r, all || timing, "timing");
} catch (AssertionError e) {
throw new AssertionError(q.toString() + ": " + e.getMessage(), e);
}
}
}
/**
* Asserts that the specified debug result key does or does not exist in the
* response based on the expected boolean.
*/
private void assertDebug(QueryResponse response, boolean expected, String key) {
if (expected) {
assertInDebug(response, key);
} else {
assertNotInDebug(response, key);
}
}
/**
* Asserts that the specified debug result key does exist in the response and is non-null
*/
private void assertInDebug(QueryResponse response, String key) {
assertNotNull("debug map is null", response.getDebugMap());
assertNotNull("debug map has null for : " + key, response.getDebugMap().get(key));
}
/**
* Asserts that the specified debug result key does NOT exist in the response
*/
private void assertNotInDebug(QueryResponse response, String key) {
assertNotNull("debug map is null", response.getDebugMap());
assertFalse("debug map contains: " + key, response.getDebugMap().containsKey(key));
}
@Test
public void testDebugSections() throws Exception {
SolrQuery query = new SolrQuery();
query.setQuery("text:_query_with_no_results_");
query.set("distrib", "true");
query.setFields("id", "text");
query.set("shards", shard1 + "," + shard2);
verifyDebugSections(query, collection1);
query.setQuery("id:1 OR text:_query_with_no_results_ OR id:[0 TO 300]");
verifyDebugSections(query, collection1);
}
private void verifyDebugSections(SolrQuery query, SolrClient client) throws SolrServerException, IOException {
query.set("debugQuery", "true");
query.remove("debug");
QueryResponse response = client.query(query);
assertFalse(response.getDebugMap().isEmpty());
assertInDebug(response, "track");
assertInDebug(response, "rawquerystring");
assertInDebug(response, "querystring");
assertInDebug(response, "parsedquery");
assertInDebug(response, "parsedquery_toString");
assertInDebug(response, "QParser");
assertInDebug(response, "explain");
assertInDebug(response, "timing");
query.set("debug", "true");
query.remove("debugQuery");
response = client.query(query);
assertFalse(response.getDebugMap().isEmpty());
assertInDebug(response, "track");
assertInDebug(response, "rawquerystring");
assertInDebug(response, "querystring");
assertInDebug(response, "parsedquery");
assertInDebug(response, "parsedquery_toString");
assertInDebug(response, "QParser");
assertInDebug(response, "explain");
assertInDebug(response, "timing");
query.set("debug", "track");
response = client.query(query);
assertFalse(response.getDebugMap().isEmpty());
assertInDebug(response, "track");
assertNotInDebug(response, "rawquerystring");
assertNotInDebug(response, "querystring");
assertNotInDebug(response, "parsedquery");
assertNotInDebug(response, "parsedquery_toString");
assertNotInDebug(response, "QParser");
assertNotInDebug(response, "explain");
assertNotInDebug(response, "timing");
query.set("debug", "query");
response = client.query(query);
assertFalse(response.getDebugMap().isEmpty());
assertNotInDebug(response, "track");
assertInDebug(response, "rawquerystring");
assertInDebug(response, "querystring");
assertInDebug(response, "parsedquery");
assertInDebug(response, "parsedquery_toString");
assertInDebug(response, "QParser");
assertNotInDebug(response, "explain");
assertNotInDebug(response, "timing");
query.set("debug", "results");
response = client.query(query);
assertFalse(response.getDebugMap().isEmpty());
assertNotInDebug(response, "track");
assertNotInDebug(response, "rawquerystring");
assertNotInDebug(response, "querystring");
assertNotInDebug(response, "parsedquery");
assertNotInDebug(response, "parsedquery_toString");
assertNotInDebug(response, "QParser");
assertInDebug(response, "explain");
assertNotInDebug(response, "timing");
query.set("debug", "timing");
response = client.query(query);
assertFalse(response.getDebugMap().isEmpty());
assertNotInDebug(response, "track");
assertNotInDebug(response, "rawquerystring");
assertNotInDebug(response, "querystring");
assertNotInDebug(response, "parsedquery");
assertNotInDebug(response, "parsedquery_toString");
assertNotInDebug(response, "QParser");
assertNotInDebug(response, "explain");
assertInDebug(response, "timing");
query.set("debug", "false");
response = client.query(query);
assertNull(response.getDebugMap());
}
@Test
// commented out on: 24-Dec-2018 @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // added 20-Sep-2018
public void testCompareWithNonDistributedRequest() throws SolrServerException, IOException {
SolrQuery query = new SolrQuery();
query.setQuery("id:1 OR id:2");
query.setFilterQueries("id:[0 TO 10]", "id:[0 TO 5]");
query.setRows(1);
query.setSort("id", SolrQuery.ORDER.asc); // thus only return id:1 since rows 1
query.set("debug", "true");
query.set("distrib", "true");
query.setFields("id");
if (random().nextBoolean()) { // can affect rb.onePassDistributedQuery
query.addField("text");
}
query.set(ShardParams.DISTRIB_SINGLE_PASS, random().nextBoolean());
query.set("shards", shard1 + "," + shard2);
QueryResponse distribResponse = collection1.query(query);
// same query but not distributed
query.set("distrib", "false");
query.remove("shards");
QueryResponse nonDistribResponse = collection1.query(query);
assertNotNull(distribResponse.getDebugMap().get("track"));
assertNull(nonDistribResponse.getDebugMap().get("track"));
assertEquals(distribResponse.getDebugMap().size() - 1, nonDistribResponse.getDebugMap().size());
assertSectionEquals(distribResponse, nonDistribResponse, "explain");
assertSectionEquals(distribResponse, nonDistribResponse, "rawquerystring");
assertSectionEquals(distribResponse, nonDistribResponse, "querystring");
assertSectionEquals(distribResponse, nonDistribResponse, "parsedquery");
assertSectionEquals(distribResponse, nonDistribResponse, "parsedquery_toString");
assertSectionEquals(distribResponse, nonDistribResponse, "QParser");
assertSectionEquals(distribResponse, nonDistribResponse, "filter_queries");
assertSectionEquals(distribResponse, nonDistribResponse, "parsed_filter_queries");
// timing should have the same sections:
assertSameKeys((NamedList<?>)nonDistribResponse.getDebugMap().get("timing"), (NamedList<?>)distribResponse.getDebugMap().get("timing"));
}
public void testTolerantSearch() throws SolrServerException, IOException {
String badShard = DEAD_HOST_1;
SolrQuery query = new SolrQuery();
query.setQuery("*:*");
query.set("debug", "true");
query.set("distrib", "true");
query.setFields("id", "text");
query.set("shards", shard1 + "," + shard2 + "," + badShard);
// verify that the request would fail if shards.tolerant=false
ignoreException("Server refused connection");
expectThrows(SolrException.class, () -> collection1.query(query));
query.set(ShardParams.SHARDS_TOLERANT, "true");
QueryResponse response = collection1.query(query);
assertTrue((Boolean)response.getResponseHeader().get(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY));
@SuppressWarnings("unchecked")
NamedList<String> badShardTrack =
(((NamedList<NamedList<NamedList<String>>>)response.getDebugMap().get("track")).get("EXECUTE_QUERY")).get(badShard);
assertEquals("Unexpected response size for shard", 1, badShardTrack.size());
Entry<String, String> exception = badShardTrack.iterator().next();
assertEquals("Expected key 'Exception' not found", "Exception", exception.getKey());
assertNotNull("Exception message should not be null", exception.getValue());
unIgnoreException("Server refused connection");
}
/**
* Compares the same section on the two query responses
*/
private void assertSectionEquals(QueryResponse distrib, QueryResponse nonDistrib, String section) {
assertEquals(section + " debug should be equal", distrib.getDebugMap().get(section), nonDistrib.getDebugMap().get(section));
}
@SuppressWarnings({"unchecked", "rawtypes"})
private void assertSameKeys(NamedList object, NamedList object2) {
Iterator<Map.Entry<String,Object>> iteratorObj2 = (object2).iterator();
for (Map.Entry<String,Object> entry:(NamedList<Object>)object) {
assertTrue(iteratorObj2.hasNext());
Map.Entry<String,Object> entry2 = iteratorObj2.next();
assertEquals(entry.getKey(), entry2.getKey());
if (entry.getValue() instanceof NamedList) {
assertTrue(entry2.getValue() instanceof NamedList);
assertSameKeys((NamedList)entry.getValue(), (NamedList)entry2.getValue());
}
}
assertFalse(iteratorObj2.hasNext());
}
private void assertElementsPresent(NamedList<String> namedList, String...elements) {
for(String element:elements) {
String value = namedList.get(element);
assertNotNull("Expected element '" + element + "' but was not found", value);
assertTrue("Expected element '" + element + "' but was empty", !value.isEmpty());
}
}
}