SOLR-13475: Null Pointer Exception when querying collection through collection alias.

This commit is contained in:
Andrzej Bialecki 2019-05-20 15:19:35 +02:00
parent 62f969403a
commit 93e57e63cd
7 changed files with 63 additions and 25 deletions

View File

@ -93,6 +93,11 @@ New Features
* SOLR-12304: The MoreLikeThisComponent now supports the mlt.interestingTerms parameter. Previously this option was * SOLR-12304: The MoreLikeThisComponent now supports the mlt.interestingTerms parameter. Previously this option was
unique to the MLT handler. (Alessandro Benedetti via David Smiley) unique to the MLT handler. (Alessandro Benedetti via David Smiley)
Bug Fixes
----------------------
* SOLR-13475: Null Pointer Exception when querying collection through collection alias. (Jörn Franke, ab)
Other Changes Other Changes
---------------------- ----------------------

View File

@ -85,6 +85,9 @@ public class CreateAliasCmd extends AliasCmd {
private void callCreatePlainAlias(ZkNodeProps message, String aliasName, ZkStateReader zkStateReader) { private void callCreatePlainAlias(ZkNodeProps message, String aliasName, ZkStateReader zkStateReader) {
final List<String> canonicalCollectionList = parseCollectionsParameter(message.get("collections")); final List<String> canonicalCollectionList = parseCollectionsParameter(message.get("collections"));
if (canonicalCollectionList.isEmpty()) {
throw new SolrException(BAD_REQUEST, "'collections' parameter doesn't contain any collection names.");
}
final String canonicalCollectionsString = StrUtils.join(canonicalCollectionList, ','); final String canonicalCollectionsString = StrUtils.join(canonicalCollectionList, ',');
validateAllCollectionsExistAndNoDuplicates(canonicalCollectionList, zkStateReader); validateAllCollectionsExistAndNoDuplicates(canonicalCollectionList, zkStateReader);
zkStateReader.aliasesManager zkStateReader.aliasesManager
@ -101,6 +104,7 @@ public class CreateAliasCmd extends AliasCmd {
if (colls instanceof List) return (List<String>) colls; if (colls instanceof List) return (List<String>) colls;
return StrUtils.splitSmart(colls.toString(), ",", true).stream() return StrUtils.splitSmart(colls.toString(), ",", true).stream()
.map(String::trim) .map(String::trim)
.filter(s -> !s.isEmpty())
.collect(Collectors.toList()); .collect(Collectors.toList());
} }

View File

@ -329,8 +329,10 @@ public class CreateCollectionCmd implements OverseerCollectionMessageHandler.Cmd
} }
} }
// create an alias pointing to the new collection // create an alias pointing to the new collection, if different from the collectionName
ocmh.zkStateReader.aliasesManager.applyModificationAndExportToZk(a -> a.cloneWithCollectionAlias(alias, collectionName)); if (!alias.equals(collectionName)) {
ocmh.zkStateReader.aliasesManager.applyModificationAndExportToZk(a -> a.cloneWithCollectionAlias(alias, collectionName));
}
} catch (SolrException ex) { } catch (SolrException ex) {
throw ex; throw ex;

View File

@ -25,6 +25,7 @@ import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
import static org.apache.solr.common.params.CommonParams.NAME; import static org.apache.solr.common.params.CommonParams.NAME;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -75,7 +76,7 @@ public class DeleteCollectionCmd implements OverseerCollectionMessageHandler.Cmd
zkStateReader.aliasesManager.update(); // aliases may have been stale; get latest from ZK zkStateReader.aliasesManager.update(); // aliases may have been stale; get latest from ZK
} }
String aliasReference = checkAliasReference(zkStateReader, extCollection); List<String> aliasReferences = checkAliasReference(zkStateReader, extCollection);
Aliases aliases = zkStateReader.getAliases(); Aliases aliases = zkStateReader.getAliases();
String collection = aliases.resolveSimpleAlias(extCollection); String collection = aliases.resolveSimpleAlias(extCollection);
@ -135,9 +136,14 @@ public class DeleteCollectionCmd implements OverseerCollectionMessageHandler.Cmd
// wait for a while until we don't see the collection // wait for a while until we don't see the collection
zkStateReader.waitForState(collection, 60, TimeUnit.SECONDS, (liveNodes, collectionState) -> collectionState == null); zkStateReader.waitForState(collection, 60, TimeUnit.SECONDS, (liveNodes, collectionState) -> collectionState == null);
// we can delete any remaining unique alias // we can delete any remaining unique aliases
if (aliasReference != null) { if (!aliasReferences.isEmpty()) {
ocmh.zkStateReader.aliasesManager.applyModificationAndExportToZk(a -> a.cloneWithCollectionAlias(aliasReference, null)); ocmh.zkStateReader.aliasesManager.applyModificationAndExportToZk(a -> {
for (String alias : aliasReferences) {
a = a.cloneWithCollectionAlias(alias, null);
}
return a;
});
} }
// TimeOut timeout = new TimeOut(60, TimeUnit.SECONDS, timeSource); // TimeOut timeout = new TimeOut(60, TimeUnit.SECONDS, timeSource);
@ -178,23 +184,29 @@ public class DeleteCollectionCmd implements OverseerCollectionMessageHandler.Cmd
} }
} }
// it's ok if a collection is referenced either by none or exactly by a single alias. // This method returns the single collection aliases to delete, if present, or null
// This method returns the single alias to delete, if present, or null private List<String> checkAliasReference(ZkStateReader zkStateReader, String extCollection) throws Exception {
private String checkAliasReference(ZkStateReader zkStateReader, String extCollection) throws Exception { Aliases aliases = zkStateReader.getAliases();
List<String> aliases = referencedByAlias(extCollection, zkStateReader.getAliases()); List<String> aliasesRefs = referencedByAlias(extCollection, aliases);
if (aliases.size() > 1) { List<String> aliasesToDelete = new ArrayList<>();
if (aliasesRefs.size() > 0) {
zkStateReader.aliasesManager.update(); // aliases may have been stale; get latest from ZK zkStateReader.aliasesManager.update(); // aliases may have been stale; get latest from ZK
aliases = referencedByAlias(extCollection, zkStateReader.getAliases()); aliases = zkStateReader.getAliases();
if (aliases.size() > 1) { aliasesRefs = referencedByAlias(extCollection, aliases);
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, if (aliasesRefs.size() > 0) {
"Collection : " + extCollection + " is part of aliases: " + aliases + ", remove or modify the aliases before removing this collection."); for (String alias : aliasesRefs) {
// for back-compat in 8.x we don't automatically remove other
// aliases that point only to this collection
if (!extCollection.equals(alias)) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Collection : " + extCollection + " is part of aliases: " + aliasesRefs + ", remove or modify the aliases before removing this collection.");
} else {
aliasesToDelete.add(alias);
}
}
} }
} }
if (!aliases.isEmpty()) { return aliasesToDelete;
return aliases.get(0);
} else {
return null;
}
} }
public static List<String> referencedByAlias(String extCollection, Aliases aliases) throws IllegalArgumentException { public static List<String> referencedByAlias(String extCollection, Aliases aliases) throws IllegalArgumentException {

View File

@ -543,9 +543,19 @@ public class AliasIntegrationTest extends SolrCloudTestCase {
.commit(cluster.getSolrClient(), "collection2"); .commit(cluster.getSolrClient(), "collection2");
/////////////// ///////////////
// make sure there's only one level of alias
CollectionAdminRequest.deleteAlias("collection1").process(cluster.getSolrClient());
CollectionAdminRequest.createAlias("testalias1", "collection1").process(cluster.getSolrClient()); CollectionAdminRequest.createAlias("testalias1", "collection1").process(cluster.getSolrClient());
// ensure that the alias has been registered // verify proper resolution on the server-side
ZkStateReader zkStateReader = cluster.getSolrClient().getZkStateReader();
zkStateReader.aliasesManager.update();
Aliases aliases = zkStateReader.getAliases();
List<String> collections = aliases.resolveAliases("testalias1");
assertEquals(collections.toString(), 1, collections.size());
assertTrue(collections.contains("collection1"));
// ensure that the alias is visible in the API
assertEquals("collection1", assertEquals("collection1",
new CollectionAdminRequest.ListAliases().process(cluster.getSolrClient()).getAliases().get("testalias1")); new CollectionAdminRequest.ListAliases().process(cluster.getSolrClient()).getAliases().get("testalias1"));

View File

@ -121,8 +121,8 @@ The name of the collection with which all replicas of this collection must be co
See <<colocating-collections.adoc#colocating-collections, Colocating collections>> for more details. See <<colocating-collections.adoc#colocating-collections, Colocating collections>> for more details.
`alias`:: `alias`::
Starting with version 8.1 when a collection is created additionally an alias (by default with the same name) is created Starting with version 8.1 when a collection is created additionally an alias can be created
that points to this collection. This parameter allows changing the name of this alias, effectively combining that points to this collection. This parameter allows specifying the name of this alias, effectively combining
this operation with <<createalias, CREATEALIAS>> this operation with <<createalias, CREATEALIAS>>
Collections are first created in read-write mode but can be put in `readOnly` Collections are first created in read-write mode but can be put in `readOnly`

View File

@ -248,11 +248,15 @@ public class Aliases {
for (int i = 0; i < level1.size(); i++) { for (int i = 0; i < level1.size(); i++) {
String level1Alias = level1.get(i); String level1Alias = level1.get(i);
List<String> level2 = collectionAliasListMap.get(level1Alias); List<String> level2 = collectionAliasListMap.get(level1Alias);
if (level2 == null && uniqueResult != null) { if (level2 == null) {
uniqueResult.add(level1Alias); // will copy all level1alias-es so far on lazy init
if (uniqueResult != null) {
uniqueResult.add(level1Alias);
}
} else { } else {
if (uniqueResult == null) { // lazy init if (uniqueResult == null) { // lazy init
uniqueResult = new LinkedHashSet<>(level1.size()); uniqueResult = new LinkedHashSet<>(level1.size());
// add all level1Alias-es so far
uniqueResult.addAll(level1.subList(0, i)); uniqueResult.addAll(level1.subList(0, i));
} }
uniqueResult.addAll(level2); uniqueResult.addAll(level2);
@ -294,6 +298,7 @@ public class Aliases {
entry.setValue(Collections.unmodifiableList(list)); entry.setValue(Collections.unmodifiableList(list));
} }
} }
newColAliases.entrySet().removeIf(entry -> entry.getValue().isEmpty());
} else { } else {
newColProperties = this.collectionAliasProperties;// no changes newColProperties = this.collectionAliasProperties;// no changes
// java representation is a list, so split before adding to maintain consistency // java representation is a list, so split before adding to maintain consistency