blob: bc3b1086e86b9c66bb99c0696a1d4e063a15d26f [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 org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.params.SolrParams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class CloudInspectUtil {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
/**
* When a and b are known to be different, this method tells if the difference
* is legal given the adds and deletes that failed from b.
*
* @param a first list of docs
* @param b second list of docs
* @param aName label for first list of docs
* @param bName label for second list of docs
* @param bAddFails null or list of the ids of adds that failed for b
* @param bDeleteFails null or list of the ids of deletes that failed for b
* @return true if the difference in a and b is legal
*/
@SuppressWarnings({"unchecked"})
public static boolean checkIfDiffIsLegal(SolrDocumentList a,
SolrDocumentList b, String aName, String bName, Set<String> bAddFails,
Set<String> bDeleteFails) {
boolean legal = true;
@SuppressWarnings({"rawtypes"})
Set<Map> setA = new HashSet<>();
for (SolrDocument sdoc : a) {
setA.add(new HashMap<>(sdoc));
}
@SuppressWarnings({"rawtypes"})
Set<Map> setB = new HashSet<>();
for (SolrDocument sdoc : b) {
setB.add(new HashMap<>(sdoc));
}
@SuppressWarnings({"rawtypes"})
Set<Map> onlyInA = new HashSet<>(setA);
onlyInA.removeAll(setB);
@SuppressWarnings({"rawtypes"})
Set<Map> onlyInB = new HashSet<>(setB);
onlyInB.removeAll(setA);
if (onlyInA.size() == 0 && onlyInB.size() == 0) {
throw new IllegalArgumentException("No difference between list a and b");
}
System.err.println("###### Only in " + aName + ": " + onlyInA);
System.err.println("###### Only in " + bName + ": " + onlyInB);
for (@SuppressWarnings({"rawtypes"})Map doc : onlyInA) {
if (bAddFails == null || !bAddFails.contains(doc.get("id"))) {
legal = false;
// System.err.println("###### Only in " + aName + ": " + doc.get("id"));
} else {
System.err.println("###### Only in " + aName + ": " + doc.get("id")
+ ", but this is expected because we found an add fail for "
+ doc.get("id"));
}
}
for (@SuppressWarnings({"rawtypes"})Map doc : onlyInB) {
if (bDeleteFails == null || !bDeleteFails.contains(doc.get("id"))) {
legal = false;
// System.err.println("###### Only in " + bName + ": " + doc.get("id"));
} else {
System.err.println("###### Only in " + bName + ": " + doc.get("id")
+ ", but this is expected because we found a delete fail for "
+ doc.get("id"));
}
}
return legal;
}
/**
* Shows the difference between two lists of documents.
*
* @param a the first list
* @param b the second list
* @param aName label for the first list
* @param bName label for the second list
* @return the documents only in list a
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static Set<Map> showDiff(SolrDocumentList a, SolrDocumentList b,
String aName, String bName) {
System.err.println("######" + aName + ": " + toStr(a, 10));
System.err.println("######" + bName + ": " + toStr(b, 10));
System.err.println("###### sizes=" + a.size() + "," + b.size());
Set<Map> setA = new HashSet<>();
for (SolrDocument sdoc : a) {
setA.add(new HashMap(sdoc));
}
Set<Map> setB = new HashSet<>();
for (SolrDocument sdoc : b) {
setB.add(new HashMap(sdoc));
}
Set<Map> onlyInA = new HashSet<>(setA);
onlyInA.removeAll(setB);
Set<Map> onlyInB = new HashSet<>(setB);
onlyInB.removeAll(setA);
if (onlyInA.size() > 0) {
System.err.println("###### Only in " + aName + ": " + onlyInA);
}
if (onlyInB.size() > 0) {
System.err.println("###### Only in " + bName + ": " + onlyInB);
}
onlyInA.addAll(onlyInB);
return onlyInA;
}
private static String toStr(SolrDocumentList lst, int maxSz) {
if (lst.size() <= maxSz) return lst.toString();
StringBuilder sb = new StringBuilder("SolrDocumentList[sz=" + lst.size());
if (lst.size() != lst.getNumFound()) {
sb.append(" numFound=").append(lst.getNumFound());
}
sb.append("]=");
sb.append(lst.subList(0, maxSz / 2).toString());
sb.append(" , [...] , ");
sb.append(lst.subList(lst.size() - maxSz / 2, lst.size()).toString());
return sb.toString();
}
/**
* Compares the results of the control and cloud clients.
*
* @return true if the compared results are illegal.
*/
public static boolean compareResults(SolrClient controlClient, SolrClient cloudClient)
throws SolrServerException, IOException {
return compareResults(controlClient, cloudClient, null, null);
}
/**
* Compares the results of the control and cloud clients.
*
* @return true if the compared results are illegal.
*/
public static boolean compareResults(SolrClient controlClient, SolrClient cloudClient, Set<String> addFails, Set<String> deleteFails)
throws SolrServerException, IOException {
SolrParams q = SolrTestCaseJ4.params("q","*:*","rows","0", "tests","checkShardConsistency(vsControl)"); // add a tag to aid in debugging via logs
SolrDocumentList controlDocList = controlClient.query(q).getResults();
long controlDocs = controlDocList.getNumFound();
SolrDocumentList cloudDocList = cloudClient.query(q).getResults();
long cloudClientDocs = cloudDocList.getNumFound();
// re-execute the query getting ids
q = SolrTestCaseJ4.params("q", "*:*", "rows", "100000", "fl", "id", "tests", "checkShardConsistency(vsControl)/getIds"); // add a tag to aid in debugging via logs
controlDocList = controlClient.query(q).getResults();
if (controlDocs != controlDocList.getNumFound()) {
log.error("Something changed! control now {}", controlDocList.getNumFound());
}
cloudDocList = cloudClient.query(q).getResults();
if (cloudClientDocs != cloudDocList.getNumFound()) {
log.error("Something changed! cloudClient now {}", cloudDocList.getNumFound());
}
if (controlDocs != cloudClientDocs && (addFails != null || deleteFails != null)) {
boolean legal = CloudInspectUtil.checkIfDiffIsLegal(controlDocList, cloudDocList,
"controlDocList", "cloudDocList", addFails, deleteFails);
if (legal) {
return false;
}
}
@SuppressWarnings({"rawtypes"})
Set<Map> differences = CloudInspectUtil.showDiff(controlDocList, cloudDocList,
"controlDocList", "cloudDocList");
try {
// get versions for the mismatched ids
boolean foundId = false;
// use filter() to allow being parsed as 'terms in set' query instead of a (weighted/scored)
// BooleanQuery so we don't trip too many boolean clauses
StringBuilder ids = new StringBuilder("filter(id:(");
for (@SuppressWarnings({"rawtypes"})Map doc : differences) {
ids.append(" ").append(doc.get("id"));
foundId = true;
}
ids.append("))");
if (foundId) {
// get versions for those ids that don't match
q = SolrTestCaseJ4.params("q", ids.toString(), "rows", "100000", "fl", "id,_version_",
"sort", "id asc", "tests",
"checkShardConsistency(vsControl)/getVers"); // add a tag to aid in
// debugging via logs
// use POST, the ids in the query above is constructed and could be huge
SolrDocumentList a = controlClient.query(q, SolrRequest.METHOD.POST).getResults();
SolrDocumentList b = cloudClient.query(q, SolrRequest.METHOD.POST).getResults();
log.error("controlClient :{}\n\tcloudClient :{}", a, b);
}
} catch (Exception e) {
// swallow any exceptions, this is just useful for producing debug output,
// and shouldn't usurp the original issue with mismatches.
log.error("Unable to find versions for mismatched ids", e);
}
return true;
}
}