diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index 3245266c361..78ebfda038c 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -260,6 +260,9 @@ Bug Fixes * SOLR-13159: Fix atomic update encoding issue for UUID, enum, bool, and binary fields (Thomas Wockinger via Jason Gerlowski) +* SOLR-13583: Impossible to delete a collection with the same name as an existing alias. This fixes also a bug in + REINDEXCOLLECTION when used with removeSource=true which could lead to a data loss. (ab) + Improvements ---------------------- diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/AddReplicaCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/AddReplicaCmd.java index 49b1e264314..8c6f002b966 100644 --- a/solr/core/src/java/org/apache/solr/cloud/api/collections/AddReplicaCmd.java +++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/AddReplicaCmd.java @@ -27,6 +27,7 @@ import static org.apache.solr.common.cloud.ZkStateReader.PULL_REPLICAS; import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP; import static org.apache.solr.common.cloud.ZkStateReader.TLOG_REPLICAS; import static org.apache.solr.common.params.CollectionAdminParams.COLL_CONF; +import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; import static org.apache.solr.common.params.CollectionAdminParams.WITH_COLLECTION; import static org.apache.solr.common.params.CollectionParams.CollectionAction.ADDREPLICA; import static org.apache.solr.common.params.CommonAdminParams.ASYNC; @@ -97,9 +98,15 @@ public class AddReplicaCmd implements OverseerCollectionMessageHandler.Cmd { log.debug("addReplica() : {}", Utils.toJSONString(message)); String extCollectionName = message.getStr(COLLECTION_PROP); + boolean followAliases = message.getBool(FOLLOW_ALIASES, false); String shard = message.getStr(SHARD_ID_PROP); - final String collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName); + final String collectionName; + if (followAliases) { + collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName); + } else { + collectionName = extCollectionName; + } DocCollection coll = clusterState.getCollection(collectionName); if (coll == null) { diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/BackupCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/BackupCmd.java index bd3bd3be885..ceec4e2345b 100644 --- a/solr/core/src/java/org/apache/solr/cloud/api/collections/BackupCmd.java +++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/BackupCmd.java @@ -18,6 +18,7 @@ package org.apache.solr.cloud.api.collections; import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP; import static org.apache.solr.common.cloud.ZkStateReader.CORE_NAME_PROP; +import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; import static org.apache.solr.common.params.CommonAdminParams.ASYNC; import static org.apache.solr.common.params.CommonParams.NAME; @@ -67,7 +68,13 @@ public class BackupCmd implements OverseerCollectionMessageHandler.Cmd { @Override public void call(ClusterState state, ZkNodeProps message, NamedList results) throws Exception { String extCollectionName = message.getStr(COLLECTION_PROP); - String collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName); + boolean followAliases = message.getBool(FOLLOW_ALIASES, false); + String collectionName; + if (followAliases) { + collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName); + } else { + collectionName = extCollectionName; + } String backupName = message.getStr(NAME); String repo = message.getStr(CoreAdminParams.BACKUP_REPOSITORY); diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateCollectionCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateCollectionCmd.java index 372ae533090..708f7da13ea 100644 --- a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateCollectionCmd.java +++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateCollectionCmd.java @@ -112,7 +112,7 @@ public class CreateCollectionCmd implements OverseerCollectionMessageHandler.Cmd final boolean waitForFinalState = message.getBool(WAIT_FOR_FINAL_STATE, false); final String alias = message.getStr(ALIAS, collectionName); log.info("Create collection {}", collectionName); - if (clusterState.hasCollection(collectionName) || aliases.hasAlias(collectionName)) { + if (clusterState.hasCollection(collectionName)) { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "collection already exists: " + collectionName); } if (aliases.hasAlias(collectionName)) { diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateShardCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateShardCmd.java index ffe98901329..08d27e580de 100644 --- a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateShardCmd.java +++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateShardCmd.java @@ -39,6 +39,7 @@ import static org.apache.solr.common.cloud.ZkStateReader.PULL_REPLICAS; import static org.apache.solr.common.cloud.ZkStateReader.REPLICATION_FACTOR; import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP; import static org.apache.solr.common.cloud.ZkStateReader.TLOG_REPLICAS; +import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; import static org.apache.solr.common.params.CommonAdminParams.ASYNC; public class CreateShardCmd implements OverseerCollectionMessageHandler.Cmd { @@ -59,7 +60,13 @@ public class CreateShardCmd implements OverseerCollectionMessageHandler.Cmd { if (extCollectionName == null || sliceName == null) throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "'collection' and 'shard' are required parameters"); - String collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName); + boolean followAliases = message.getBool(FOLLOW_ALIASES, false); + String collectionName; + if (followAliases) { + collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName); + } else { + collectionName = extCollectionName; + } DocCollection collection = clusterState.getCollection(collectionName); int numNrtReplicas = message.getInt(NRT_REPLICAS, message.getInt(REPLICATION_FACTOR, collection.getInt(NRT_REPLICAS, collection.getInt(REPLICATION_FACTOR, 1)))); diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateSnapshotCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateSnapshotCmd.java index d761a88c3db..abc3597e545 100644 --- a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateSnapshotCmd.java +++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateSnapshotCmd.java @@ -18,6 +18,7 @@ package org.apache.solr.cloud.api.collections; import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP; import static org.apache.solr.common.cloud.ZkStateReader.CORE_NAME_PROP; +import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; import static org.apache.solr.common.params.CommonAdminParams.ASYNC; import static org.apache.solr.common.params.CommonParams.NAME; @@ -66,7 +67,14 @@ public class CreateSnapshotCmd implements OverseerCollectionMessageHandler.Cmd { @Override public void call(ClusterState state, ZkNodeProps message, NamedList results) throws Exception { String extCollectionName = message.getStr(COLLECTION_PROP); - String collectionName = ocmh.zkStateReader.getAliases().resolveSimpleAlias(extCollectionName); + boolean followAliases = message.getBool(FOLLOW_ALIASES, false); + + String collectionName; + if (followAliases) { + collectionName = ocmh.zkStateReader.getAliases().resolveSimpleAlias(extCollectionName); + } else { + collectionName = extCollectionName; + } String commitName = message.getStr(CoreAdminParams.COMMIT_NAME); String asyncId = message.getStr(ASYNC); diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteCollectionCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteCollectionCmd.java index 6c0b147b61d..98c7ca31443 100644 --- a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteCollectionCmd.java +++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteCollectionCmd.java @@ -19,6 +19,7 @@ package org.apache.solr.cloud.api.collections; import static org.apache.solr.common.params.CollectionAdminParams.COLOCATED_WITH; +import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; import static org.apache.solr.common.params.CollectionAdminParams.WITH_COLLECTION; import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETE; import static org.apache.solr.common.params.CommonAdminParams.ASYNC; @@ -76,10 +77,17 @@ public class DeleteCollectionCmd implements OverseerCollectionMessageHandler.Cmd zkStateReader.aliasesManager.update(); // aliases may have been stale; get latest from ZK } - List aliasReferences = checkAliasReference(zkStateReader, extCollection); + boolean followAliases = message.getBool(FOLLOW_ALIASES, false); + List aliasReferences = checkAliasReference(zkStateReader, extCollection, followAliases); Aliases aliases = zkStateReader.getAliases(); - String collection = aliases.resolveSimpleAlias(extCollection); + + String collection; + if (followAliases) { + collection = aliases.resolveSimpleAlias(extCollection); + } else { + collection = extCollection; + } checkNotColocatedWith(zkStateReader, collection); @@ -118,8 +126,9 @@ public class DeleteCollectionCmd implements OverseerCollectionMessageHandler.Cmd Set okayExceptions = new HashSet<>(1); okayExceptions.add(NonExistentCoreException.class.getName()); + ZkNodeProps internalMsg = message.plus(NAME, collection); - List failedReplicas = ocmh.collectionCmd(message, params, results, null, asyncId, okayExceptions); + List failedReplicas = ocmh.collectionCmd(internalMsg, params, results, null, asyncId, okayExceptions); for (Replica failedReplica : failedReplicas) { boolean isSharedFS = failedReplica.getBool(ZkStateReader.SHARED_STORAGE_PROP, false) && failedReplica.get("dataDir") != null; if (isSharedFS) { @@ -185,14 +194,14 @@ public class DeleteCollectionCmd implements OverseerCollectionMessageHandler.Cmd } // This method returns the single collection aliases to delete, if present, or null - private List checkAliasReference(ZkStateReader zkStateReader, String extCollection) throws Exception { + private List checkAliasReference(ZkStateReader zkStateReader, String extCollection, boolean followAliases) throws Exception { Aliases aliases = zkStateReader.getAliases(); - List aliasesRefs = referencedByAlias(extCollection, aliases); + List aliasesRefs = referencedByAlias(extCollection, aliases, followAliases); List aliasesToDelete = new ArrayList<>(); if (aliasesRefs.size() > 0) { zkStateReader.aliasesManager.update(); // aliases may have been stale; get latest from ZK aliases = zkStateReader.getAliases(); - aliasesRefs = referencedByAlias(extCollection, aliases); + aliasesRefs = referencedByAlias(extCollection, aliases, followAliases); if (aliasesRefs.size() > 0) { for (String alias : aliasesRefs) { // for back-compat in 8.x we don't automatically remove other @@ -209,11 +218,12 @@ public class DeleteCollectionCmd implements OverseerCollectionMessageHandler.Cmd return aliasesToDelete; } - public static List referencedByAlias(String extCollection, Aliases aliases) throws IllegalArgumentException { + public static List referencedByAlias(String extCollection, Aliases aliases, boolean followAliases) throws IllegalArgumentException { Objects.requireNonNull(aliases); // this quickly produces error if the name is a complex alias - String collection = aliases.resolveSimpleAlias(extCollection); + String collection = followAliases ? aliases.resolveSimpleAlias(extCollection) : extCollection; return aliases.getCollectionAliasListMap().entrySet().stream() + .filter(e -> !e.getKey().equals(collection)) .filter(e -> e.getValue().contains(collection) || e.getValue().contains(extCollection)) .map(Map.Entry::getKey) // alias name .collect(Collectors.toList()); diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteReplicaCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteReplicaCmd.java index d999a6ff3c4..a6b45b5ab42 100644 --- a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteReplicaCmd.java +++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteReplicaCmd.java @@ -20,6 +20,7 @@ import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP; import static org.apache.solr.common.cloud.ZkStateReader.REPLICA_PROP; import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP; import static org.apache.solr.common.params.CollectionAdminParams.COUNT_PROP; +import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; import static org.apache.solr.common.params.CommonAdminParams.ASYNC; import java.lang.invoke.MethodHandles; @@ -85,7 +86,13 @@ public class DeleteReplicaCmd implements Cmd { String shard = message.getStr(SHARD_ID_PROP); String replicaName = message.getStr(REPLICA_PROP); - String collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName); + boolean followAliases = message.getBool(FOLLOW_ALIASES, false); + String collectionName; + if (followAliases) { + collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName); + } else { + collectionName = extCollectionName; + } DocCollection coll = clusterState.getCollection(collectionName); Slice slice = coll.getSlice(shard); diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteShardCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteShardCmd.java index c5c8e99c099..4aca28291c2 100644 --- a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteShardCmd.java +++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteShardCmd.java @@ -20,6 +20,7 @@ package org.apache.solr.cloud.api.collections; import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP; import static org.apache.solr.common.cloud.ZkStateReader.NODE_NAME_PROP; import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP; +import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETEREPLICA; import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETESHARD; import static org.apache.solr.common.params.CommonAdminParams.ASYNC; @@ -65,7 +66,13 @@ public class DeleteShardCmd implements OverseerCollectionMessageHandler.Cmd { String extCollectionName = message.getStr(ZkStateReader.COLLECTION_PROP); String sliceId = message.getStr(ZkStateReader.SHARD_ID_PROP); - String collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName); + boolean followAliases = message.getBool(FOLLOW_ALIASES, false); + String collectionName; + if (followAliases) { + collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName); + } else { + collectionName = extCollectionName; + } log.info("Delete shard invoked"); Slice slice = clusterState.getCollection(collectionName).getSlice(sliceId); diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteSnapshotCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteSnapshotCmd.java index 88881cf21d6..634692d1ee9 100644 --- a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteSnapshotCmd.java +++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteSnapshotCmd.java @@ -18,6 +18,7 @@ package org.apache.solr.cloud.api.collections; import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP; import static org.apache.solr.common.cloud.ZkStateReader.CORE_NAME_PROP; +import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; import static org.apache.solr.common.params.CommonAdminParams.ASYNC; import static org.apache.solr.common.params.CommonParams.NAME; @@ -64,7 +65,13 @@ public class DeleteSnapshotCmd implements OverseerCollectionMessageHandler.Cmd { @Override public void call(ClusterState state, ZkNodeProps message, NamedList results) throws Exception { String extCollectionName = message.getStr(COLLECTION_PROP); - String collectionName = ocmh.zkStateReader.getAliases().resolveSimpleAlias(extCollectionName); + boolean followAliases = message.getBool(FOLLOW_ALIASES, false); + String collectionName; + if (followAliases) { + collectionName = ocmh.zkStateReader.getAliases().resolveSimpleAlias(extCollectionName); + } else { + collectionName = extCollectionName; + } String commitName = message.getStr(CoreAdminParams.COMMIT_NAME); String asyncId = message.getStr(ASYNC); NamedList shardRequestResults = new NamedList(); diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/MigrateCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/MigrateCmd.java index 55210f1fecd..2ecf5f8ac1a 100644 --- a/solr/core/src/java/org/apache/solr/cloud/api/collections/MigrateCmd.java +++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/MigrateCmd.java @@ -54,6 +54,7 @@ import org.slf4j.LoggerFactory; import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP; import static org.apache.solr.common.cloud.ZkStateReader.NRT_REPLICAS; import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP; +import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; import static org.apache.solr.common.params.CollectionParams.CollectionAction.ADDREPLICA; import static org.apache.solr.common.params.CollectionParams.CollectionAction.CREATE; import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETE; @@ -79,8 +80,16 @@ public class MigrateCmd implements OverseerCollectionMessageHandler.Cmd { String extTargetCollectionName = message.getStr("target.collection"); int timeout = message.getInt("forward.timeout", 10 * 60) * 1000; - String sourceCollectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extSourceCollectionName); - String targetCollectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extTargetCollectionName); + boolean followAliases = message.getBool(FOLLOW_ALIASES, false); + String sourceCollectionName; + String targetCollectionName; + if (followAliases) { + sourceCollectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extSourceCollectionName); + targetCollectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extTargetCollectionName); + } else { + sourceCollectionName = extSourceCollectionName; + targetCollectionName = extTargetCollectionName; + } DocCollection sourceCollection = clusterState.getCollection(sourceCollectionName); if (sourceCollection == null) { diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/MoveReplicaCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/MoveReplicaCmd.java index fc39b9dda23..4e462f63829 100644 --- a/solr/core/src/java/org/apache/solr/cloud/api/collections/MoveReplicaCmd.java +++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/MoveReplicaCmd.java @@ -48,6 +48,7 @@ import static org.apache.solr.cloud.api.collections.OverseerCollectionMessageHan import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP; import static org.apache.solr.common.cloud.ZkStateReader.REPLICA_PROP; import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP; +import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; import static org.apache.solr.common.params.CommonAdminParams.ASYNC; import static org.apache.solr.common.params.CommonAdminParams.IN_PLACE_MOVE; import static org.apache.solr.common.params.CommonAdminParams.TIMEOUT; @@ -80,7 +81,13 @@ public class MoveReplicaCmd implements OverseerCollectionMessageHandler.Cmd { String async = message.getStr(ASYNC); - String collection = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollection); + boolean followAliases = message.getBool(FOLLOW_ALIASES, false); + String collection; + if (followAliases) { + collection = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollection); + } else { + collection = extCollection; + } DocCollection coll = clusterState.getCollection(collection); if (coll == null) { diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/ReindexCollectionCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/ReindexCollectionCmd.java index 57e7a629f4d..da9bacb0c7c 100644 --- a/solr/core/src/java/org/apache/solr/cloud/api/collections/ReindexCollectionCmd.java +++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/ReindexCollectionCmd.java @@ -64,6 +64,8 @@ import org.apache.zookeeper.CreateMode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; + /** * Reindex a collection, usually in order to change the index schema. *

WARNING: Reindexing is potentially a lossy operation - some indexed data that is not available as @@ -178,7 +180,13 @@ public class ReindexCollectionCmd implements OverseerCollectionMessageHandler.Cm if (extCollection == null) { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Source collection name must be specified"); } - String collection = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollection); + boolean followAliases = message.getBool(FOLLOW_ALIASES, false); + String collection; + if (followAliases) { + collection = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollection); + } else { + collection = extCollection; + } if (!clusterState.hasCollection(collection)) { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Source collection name must exist"); } @@ -186,8 +194,9 @@ public class ReindexCollectionCmd implements OverseerCollectionMessageHandler.Cm if (target == null) { target = collection; } else { - // resolve aliases - target = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(target); + if (followAliases) { + target = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(target); + } } boolean sameTarget = target.equals(collection) || target.equals(extCollection); boolean removeSource = message.getBool(REMOVE_SOURCE, false); @@ -466,6 +475,7 @@ public class ReindexCollectionCmd implements OverseerCollectionMessageHandler.Cm cmd = new ZkNodeProps( Overseer.QUEUE_OPERATION, CollectionParams.CollectionAction.DELETE.toLower(), CommonParams.NAME, collection, + FOLLOW_ALIASES, "false", CoreAdminParams.DELETE_METRICS_HISTORY, "true" ); cmdResults = new NamedList<>(); @@ -770,6 +780,7 @@ public class ReindexCollectionCmd implements OverseerCollectionMessageHandler.Cm ZkNodeProps cmd = new ZkNodeProps( Overseer.QUEUE_OPERATION, CollectionParams.CollectionAction.DELETE.toLower(), CommonParams.NAME, targetCollection, + FOLLOW_ALIASES, "false", CoreAdminParams.DELETE_METRICS_HISTORY, "true" ); ocmh.commandMap.get(CollectionParams.CollectionAction.DELETE).call(clusterState, cmd, cmdResults); @@ -782,6 +793,7 @@ public class ReindexCollectionCmd implements OverseerCollectionMessageHandler.Cm ZkNodeProps cmd = new ZkNodeProps( Overseer.QUEUE_OPERATION, CollectionParams.CollectionAction.DELETE.toLower(), CommonParams.NAME, chkCollection, + FOLLOW_ALIASES, "false", CoreAdminParams.DELETE_METRICS_HISTORY, "true" ); cmdResults = new NamedList<>(); diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/RenameCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/RenameCmd.java index 2a33e497eea..7296f6c96dd 100644 --- a/solr/core/src/java/org/apache/solr/cloud/api/collections/RenameCmd.java +++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/RenameCmd.java @@ -29,6 +29,8 @@ import org.apache.solr.common.util.NamedList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; + /** * */ @@ -55,7 +57,13 @@ public class RenameCmd implements OverseerCollectionMessageHandler.Cmd { } Aliases aliases = ocmh.zkStateReader.getAliases(); - String collectionName = aliases.resolveSimpleAlias(extCollectionName); + boolean followAliases = message.getBool(FOLLOW_ALIASES, false); + String collectionName; + if (followAliases) { + collectionName = aliases.resolveSimpleAlias(extCollectionName); + } else { + collectionName = extCollectionName; + } if (!state.hasCollection(collectionName)) { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "source collection '" + collectionName + "' not found."); } @@ -65,6 +73,5 @@ public class RenameCmd implements OverseerCollectionMessageHandler.Cmd { } ocmh.zkStateReader.aliasesManager.applyModificationAndExportToZk(a -> a.cloneWithRename(extCollectionName, target)); - } } diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/SplitShardCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/SplitShardCmd.java index e36c76fe05a..dd86ec4478d 100644 --- a/solr/core/src/java/org/apache/solr/cloud/api/collections/SplitShardCmd.java +++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/SplitShardCmd.java @@ -73,6 +73,7 @@ import org.slf4j.LoggerFactory; import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP; import static org.apache.solr.common.cloud.ZkStateReader.REPLICA_TYPE; import static org.apache.solr.common.cloud.ZkStateReader.SHARD_ID_PROP; +import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; import static org.apache.solr.common.params.CollectionParams.CollectionAction.ADDREPLICA; import static org.apache.solr.common.params.CollectionParams.CollectionAction.CREATESHARD; import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETESHARD; @@ -111,7 +112,13 @@ public class SplitShardCmd implements OverseerCollectionMessageHandler.Cmd { String extCollectionName = message.getStr(CoreAdminParams.COLLECTION); - String collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName); + boolean followAliases = message.getBool(FOLLOW_ALIASES, false); + String collectionName; + if (followAliases) { + collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName); + } else { + collectionName = extCollectionName; + } log.debug("Split shard invoked: {}", message); ZkStateReader zkStateReader = ocmh.zkStateReader; diff --git a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java index c3bfda25130..d2929768b61 100644 --- a/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/admin/CollectionsHandler.java @@ -141,6 +141,7 @@ import static org.apache.solr.common.params.CollectionAdminParams.ALIAS; import static org.apache.solr.common.params.CollectionAdminParams.COLLECTION; import static org.apache.solr.common.params.CollectionAdminParams.COLL_CONF; import static org.apache.solr.common.params.CollectionAdminParams.COUNT_PROP; +import static org.apache.solr.common.params.CollectionAdminParams.FOLLOW_ALIASES; import static org.apache.solr.common.params.CollectionAdminParams.PROPERTY_NAME; import static org.apache.solr.common.params.CollectionAdminParams.PROPERTY_VALUE; import static org.apache.solr.common.params.CollectionAdminParams.WITH_COLLECTION; @@ -544,11 +545,20 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission .getColStatus(rsp.getValues()); return null; }), - DELETE_OP(DELETE, (req, rsp, h) -> copy(req.getParams().required(), null, NAME)), + DELETE_OP(DELETE, (req, rsp, h) -> { + Map map = copy(req.getParams().required(), null, NAME); + return copy(req.getParams(), map, FOLLOW_ALIASES); + }), + // XXX should this command support followAliases? + RELOAD_OP(RELOAD, (req, rsp, h) -> { + Map map = copy(req.getParams().required(), null, NAME); + return copy(req.getParams(), map); + }), - RELOAD_OP(RELOAD, (req, rsp, h) -> copy(req.getParams().required(), null, NAME)), - - RENAME_OP(RENAME, (req, rsp, h) -> copy(req.getParams().required(), null, NAME, CollectionAdminParams.TARGET)), + RENAME_OP(RENAME, (req, rsp, h) -> { + Map map = copy(req.getParams().required(), null, NAME, CollectionAdminParams.TARGET); + return copy(req.getParams(), map, FOLLOW_ALIASES); + }), REINDEXCOLLECTION_OP(REINDEXCOLLECTION, (req, rsp, h) -> { Map m = copy(req.getParams().required(), null, NAME); @@ -571,7 +581,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission STATE_FORMAT, CommonParams.ROWS, CommonParams.Q, - CommonParams.FL); + CommonParams.FL, + FOLLOW_ALIASES); if (req.getParams().get("collection." + ZkStateReader.CONFIGNAME_PROP) != null) { m.put(ZkStateReader.CONFIGNAME_PROP, req.getParams().get("collection." + ZkStateReader.CONFIGNAME_PROP)); } @@ -748,7 +759,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission TIMING, SPLIT_METHOD, NUM_SUB_SHARDS, - SPLIT_FUZZ); + SPLIT_FUZZ, + FOLLOW_ALIASES); return copyPropertiesWithPrefix(req.getParams(), map, COLL_PROP_PREFIX); }), DELETESHARD_OP(DELETESHARD, (req, rsp, h) -> { @@ -759,7 +771,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission DELETE_INDEX, DELETE_DATA_DIR, DELETE_INSTANCE_DIR, - DELETE_METRICS_HISTORY); + DELETE_METRICS_HISTORY, + FOLLOW_ALIASES); return map; }), FORCELEADER_OP(FORCELEADER, (req, rsp, h) -> { @@ -772,7 +785,11 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission SHARD_ID_PROP); ClusterState clusterState = h.coreContainer.getZkController().getClusterState(); final String newShardName = SolrIdentifierValidator.validateShardName(req.getParams().get(SHARD_ID_PROP)); - if (!ImplicitDocRouter.NAME.equals(((Map) clusterState.getCollection(req.getParams().get(COLLECTION_PROP)).get(DOC_ROUTER)).get(NAME))) + boolean followAliases = req.getParams().getBool(FOLLOW_ALIASES, false); + String extCollectionName = req.getParams().get(COLLECTION_PROP); + String collectionName = followAliases ? h.coreContainer.getZkController().getZkStateReader() + .getAliases().resolveSimpleAlias(extCollectionName) : extCollectionName; + if (!ImplicitDocRouter.NAME.equals(((Map) clusterState.getCollection(collectionName).get(DOC_ROUTER)).get(NAME))) throw new SolrException(ErrorCode.BAD_REQUEST, "shards can be added only to 'implicit' collections"); copy(req.getParams(), map, REPLICATION_FACTOR, @@ -780,7 +797,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission TLOG_REPLICAS, PULL_REPLICAS, CREATE_NODE_SET, - WAIT_FOR_FINAL_STATE); + WAIT_FOR_FINAL_STATE, + FOLLOW_ALIASES); return copyPropertiesWithPrefix(req.getParams(), map, COLL_PROP_PREFIX); }), DELETEREPLICA_OP(DELETEREPLICA, (req, rsp, h) -> { @@ -794,11 +812,12 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission DELETE_METRICS_HISTORY, COUNT_PROP, REPLICA_PROP, SHARD_ID_PROP, - ONLY_IF_DOWN); + ONLY_IF_DOWN, + FOLLOW_ALIASES); }), MIGRATE_OP(MIGRATE, (req, rsp, h) -> { Map map = copy(req.getParams().required(), null, COLLECTION_PROP, "split.key", "target.collection"); - return copy(req.getParams(), map, "forward.timeout"); + return copy(req.getParams(), map, "forward.timeout", FOLLOW_ALIASES); }), ADDROLE_OP(ADDROLE, (req, rsp, h) -> { Map map = copy(req.getParams().required(), null, "role", "node"); @@ -918,7 +937,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission NRT_REPLICAS, TLOG_REPLICAS, PULL_REPLICAS, - CREATE_NODE_SET); + CREATE_NODE_SET, + FOLLOW_ALIASES); return copyPropertiesWithPrefix(req.getParams(), props, COLL_PROP_PREFIX); }), OVERSEERSTATUS_OP(OVERSEERSTATUS, (req, rsp, h) -> (Map) new LinkedHashMap<>()), @@ -980,6 +1000,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission } return map; }), + // XXX should this command support followAliases? DELETEREPLICAPROP_OP(DELETEREPLICAPROP, (req, rsp, h) -> { Map map = copy(req.getParams().required(), null, COLLECTION_PROP, @@ -988,6 +1009,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission REPLICA_PROP); return copy(req.getParams(), map, PROPERTY_PROP); }), + // XXX should this command support followAliases? BALANCESHARDUNIQUE_OP(BALANCESHARDUNIQUE, (req, rsp, h) -> { Map map = copy(req.getParams().required(), null, COLLECTION_PROP, @@ -1010,6 +1032,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission new RebalanceLeaders(req, rsp, h).execute(); return null; }), + // XXX should this command support followAliases? MODIFYCOLLECTION_OP(MODIFYCOLLECTION, (req, rsp, h) -> { Map m = copy(req.getParams(), null, CollectionAdminRequest.MODIFIABLE_COLLECTION_PROPERTIES); copyPropertiesWithPrefix(req.getParams(), m, COLL_PROP_PREFIX); @@ -1039,8 +1062,9 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission req.getParams().required().check(NAME, COLLECTION_PROP); String extCollectionName = req.getParams().get(COLLECTION_PROP); - String collectionName = h.coreContainer.getZkController().getZkStateReader() - .getAliases().resolveSimpleAlias(extCollectionName); + boolean followAliases = req.getParams().getBool(FOLLOW_ALIASES, false); + String collectionName = followAliases ? h.coreContainer.getZkController().getZkStateReader() + .getAliases().resolveSimpleAlias(extCollectionName) : extCollectionName; ClusterState clusterState = h.coreContainer.getZkController().getClusterState(); if (!clusterState.hasCollection(collectionName)) { throw new SolrException(ErrorCode.BAD_REQUEST, "Collection '" + collectionName + "' does not exist, no action taken."); @@ -1076,7 +1100,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission throw new SolrException(ErrorCode.BAD_REQUEST, "Unknown index backup strategy " + strategy); } - Map params = copy(req.getParams(), null, NAME, COLLECTION_PROP, CoreAdminParams.COMMIT_NAME); + Map params = copy(req.getParams(), null, NAME, COLLECTION_PROP, FOLLOW_ALIASES, CoreAdminParams.COMMIT_NAME); params.put(CoreAdminParams.BACKUP_LOCATION, location); params.put(CollectionAdminParams.INDEX_BACKUP_STRATEGY, strategy); return params; @@ -1143,8 +1167,9 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission req.getParams().required().check(COLLECTION_PROP, CoreAdminParams.COMMIT_NAME); String extCollectionName = req.getParams().get(COLLECTION_PROP); - String collectionName = h.coreContainer.getZkController().getZkStateReader() - .getAliases().resolveSimpleAlias(extCollectionName); + boolean followAliases = req.getParams().getBool(FOLLOW_ALIASES, false); + String collectionName = followAliases ? h.coreContainer.getZkController().getZkStateReader() + .getAliases().resolveSimpleAlias(extCollectionName) : extCollectionName; String commitName = req.getParams().get(CoreAdminParams.COMMIT_NAME); ClusterState clusterState = h.coreContainer.getZkController().getClusterState(); if (!clusterState.hasCollection(collectionName)) { @@ -1158,7 +1183,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission + collectionName + "', no action taken."); } - Map params = copy(req.getParams(), null, COLLECTION_PROP, CoreAdminParams.COMMIT_NAME); + Map params = copy(req.getParams(), null, COLLECTION_PROP, FOLLOW_ALIASES, CoreAdminParams.COMMIT_NAME); return params; }), DELETESNAPSHOT_OP(DELETESNAPSHOT, (req, rsp, h) -> { @@ -1172,7 +1197,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission throw new SolrException(ErrorCode.BAD_REQUEST, "Collection '" + collectionName + "' does not exist, no action taken."); } - Map params = copy(req.getParams(), null, COLLECTION_PROP, CoreAdminParams.COMMIT_NAME); + Map params = copy(req.getParams(), null, COLLECTION_PROP, FOLLOW_ALIASES, CoreAdminParams.COMMIT_NAME); return params; }), LISTSNAPSHOTS_OP(LISTSNAPSHOTS, (req, rsp, h) -> { @@ -1215,7 +1240,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission WAIT_FOR_FINAL_STATE, IN_PLACE_MOVE, "replica", - "shard"); + "shard", + FOLLOW_ALIASES); }), DELETENODE_OP(DELETENODE, (req, rsp, h) -> copy(req.getParams().required(), null, "node")); diff --git a/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java b/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java index ab73781589d..34d1b8d6c86 100644 --- a/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java +++ b/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java @@ -53,9 +53,11 @@ import org.apache.solr.client.solrj.response.CollectionAdminResponse; import org.apache.solr.client.solrj.response.CoreAdminResponse; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.client.solrj.response.V2Response; +import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.cloud.Aliases; import org.apache.solr.common.cloud.ClusterProperties; +import org.apache.solr.common.cloud.ClusterState; import org.apache.solr.common.cloud.DocCollection; import org.apache.solr.common.cloud.Replica; import org.apache.solr.common.cloud.Slice; @@ -794,8 +796,20 @@ public class CollectionsAPISolrJTest extends SolrCloudTestCase { @Test public void testRenameCollection() throws Exception { - String collectionName1 = "testRename_collection1"; - String collectionName2 = "testRename_collection2"; + doTestRenameCollection(true); + CollectionAdminRequest.deleteAlias("col1").process(cluster.getSolrClient()); + CollectionAdminRequest.deleteAlias("col2").process(cluster.getSolrClient()); + CollectionAdminRequest.deleteAlias("foo").process(cluster.getSolrClient()); + CollectionAdminRequest.deleteAlias("simpleAlias").process(cluster.getSolrClient()); + CollectionAdminRequest.deleteAlias("catAlias").process(cluster.getSolrClient()); + CollectionAdminRequest.deleteAlias("compoundAlias").process(cluster.getSolrClient()); + cluster.getSolrClient().getZkStateReader().aliasesManager.update(); + doTestRenameCollection(false); + } + + private void doTestRenameCollection(boolean followAliases) throws Exception { + String collectionName1 = "testRename1_" + followAliases; + String collectionName2 = "testRename2_" + followAliases; CollectionAdminRequest.createCollection(collectionName1, "conf", 1, 1).setAlias("col1").process(cluster.getSolrClient()); CollectionAdminRequest.createCollection(collectionName2, "conf", 1, 1).setAlias("col2").process(cluster.getSolrClient()); @@ -810,45 +824,159 @@ public class CollectionsAPISolrJTest extends SolrCloudTestCase { CollectionAdminRequest.createCategoryRoutedAlias("catAlias", "field1", 100, CollectionAdminRequest.createCollection("_unused_", "conf", 1, 1)).process(cluster.getSolrClient()); - CollectionAdminRequest.renameCollection("col1", "foo").process(cluster.getSolrClient()); + CollectionAdminRequest.Rename rename = CollectionAdminRequest.renameCollection("col1", "foo"); + rename.setFollowAliases(followAliases); ZkStateReader zkStateReader = cluster.getSolrClient().getZkStateReader(); - zkStateReader.aliasesManager.update(); + Aliases aliases; + if (!followAliases) { + try { + rename.process(cluster.getSolrClient()); + } catch (Exception e) { + assertTrue(e.toString(), e.toString().contains("source collection 'col1' not found")); + } + } else { + rename.process(cluster.getSolrClient()); + zkStateReader.aliasesManager.update(); - Aliases aliases = zkStateReader.getAliases(); - assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName1, aliases.resolveSimpleAlias("foo")); - assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName1, aliases.resolveSimpleAlias("simpleAlias")); - List compoundAliases = aliases.resolveAliases("compoundAlias"); - assertEquals(compoundAliases.toString(), 2, compoundAliases.size()); - assertTrue(compoundAliases.toString(), compoundAliases.contains(collectionName1)); - assertTrue(compoundAliases.toString(), compoundAliases.contains(collectionName2)); + aliases = zkStateReader.getAliases(); + assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName1, aliases.resolveSimpleAlias("foo")); + assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName1, aliases.resolveSimpleAlias("simpleAlias")); + List compoundAliases = aliases.resolveAliases("compoundAlias"); + assertEquals(compoundAliases.toString(), 2, compoundAliases.size()); + assertTrue(compoundAliases.toString(), compoundAliases.contains(collectionName1)); + assertTrue(compoundAliases.toString(), compoundAliases.contains(collectionName2)); + } CollectionAdminRequest.renameCollection(collectionName1, collectionName2).process(cluster.getSolrClient()); zkStateReader.aliasesManager.update(); aliases = zkStateReader.getAliases(); - assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName2, aliases.resolveSimpleAlias("foo")); + if (followAliases) { + assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName2, aliases.resolveSimpleAlias("foo")); + } assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName2, aliases.resolveSimpleAlias("simpleAlias")); assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName2, aliases.resolveSimpleAlias(collectionName1)); // we renamed col1 -> col2 so the compound alias contains only "col2,col2" which is reduced to col2 - compoundAliases = aliases.resolveAliases("compoundAlias"); + List compoundAliases = aliases.resolveAliases("compoundAlias"); assertEquals(compoundAliases.toString(), 1, compoundAliases.size()); assertTrue(compoundAliases.toString(), compoundAliases.contains(collectionName2)); try { - CollectionAdminRequest.renameCollection("catAlias", "bar").process(cluster.getSolrClient()); + rename = CollectionAdminRequest.renameCollection("catAlias", "bar"); + rename.setFollowAliases(followAliases); + rename.process(cluster.getSolrClient()); fail("category-based alias renaming should fail"); } catch (Exception e) { - assertTrue(e.toString().contains("is a routed alias")); + if (followAliases) { + assertTrue(e.toString(), e.toString().contains("is a routed alias")); + } else { + assertTrue(e.toString(), e.toString().contains("source collection 'catAlias' not found")); + } } try { - CollectionAdminRequest.renameCollection("col2", "foo").process(cluster.getSolrClient()); - fail("shuold fail because 'foo' already exists"); + rename = CollectionAdminRequest.renameCollection("col2", "foo"); + rename.setFollowAliases(followAliases); + rename.process(cluster.getSolrClient()); + fail("should fail because 'foo' already exists"); } catch (Exception e) { - assertTrue(e.toString().contains("exists")); + if (followAliases) { + assertTrue(e.toString(), e.toString().contains("exists")); + } else { + assertTrue(e.toString(), e.toString().contains("source collection 'col2' not found")); + } } } + @Test + public void testDeleteAliasedCollection() throws Exception { + CloudSolrClient solrClient = cluster.getSolrClient(); + String collectionName1 = "aliasedCollection1"; + String collectionName2 = "aliasedCollection2"; + CollectionAdminRequest.createCollection(collectionName1, "conf", 1, 1).process(solrClient); + CollectionAdminRequest.createCollection(collectionName2, "conf", 1, 1).process(solrClient); + + cluster.waitForActiveCollection(collectionName1, 1, 1); + cluster.waitForActiveCollection(collectionName2, 1, 1); + + waitForState("Expected collection1 to be created with 1 shard and 1 replica", collectionName1, clusterShape(1, 1)); + waitForState("Expected collection2 to be created with 1 shard and 1 replica", collectionName2, clusterShape(1, 1)); + + SolrInputDocument doc = new SolrInputDocument("id", "1"); + solrClient.add(collectionName1, doc); + doc = new SolrInputDocument("id", "2"); + solrClient.add(collectionName2, doc); + solrClient.commit(collectionName1); + solrClient.commit(collectionName2); + + assertDoc(solrClient, collectionName1, "1"); + assertDoc(solrClient, collectionName2, "2"); + + CollectionAdminRequest.createAlias(collectionName1, collectionName2).process(solrClient); + + RetryUtil.retryUntil("didn't get the new aliases", 10, 1000, TimeUnit.MILLISECONDS, () -> { + try { + solrClient.getZkStateReader().aliasesManager.update(); + return solrClient.getZkStateReader().getAliases() + .resolveSimpleAlias(collectionName1).equals(collectionName2); + } catch (Exception e) { + fail("exception caught refreshing aliases: " + e); + return false; + } + }); + + // both results should come from collection 2 + assertDoc(solrClient, collectionName1, "2"); // aliased + assertDoc(solrClient, collectionName2, "2"); // direct + + // should be able to remove collection 1 when followAliases = false + CollectionAdminRequest.Delete delete = CollectionAdminRequest.deleteCollection(collectionName1); + delete.setFollowAliases(false); + delete.process(solrClient); + ClusterState state = solrClient.getClusterStateProvider().getClusterState(); + assertFalse(state.getCollectionsMap().toString(), state.hasCollection(collectionName1)); + // search should still work, returning results from collection 2 + assertDoc(solrClient, collectionName1, "2"); // aliased + assertDoc(solrClient, collectionName2, "2"); // direct + + // without aliases this collection doesn't exist anymore + delete = CollectionAdminRequest.deleteCollection(collectionName1); + delete.setFollowAliases(false); + try { + delete.process(solrClient); + fail("delete of nonexistent collection 1 should have failed when followAliases=false"); + } catch (Exception e) { + assertTrue(e.toString(), e.toString().contains(collectionName1)); + } + + // with followAliases=true collection 2 (and the alias) should both be removed + delete.setFollowAliases(true); + delete.process(solrClient); + + state = solrClient.getClusterStateProvider().getClusterState(); + // the collection is gone + assertFalse(state.getCollectionsMap().toString(), state.hasCollection(collectionName2)); + + // and the alias is gone + RetryUtil.retryUntil("didn't get the new aliases", 10, 1000, TimeUnit.MILLISECONDS, () -> { + try { + solrClient.getZkStateReader().aliasesManager.update(); + return !solrClient.getZkStateReader().getAliases().hasAlias(collectionName1); + } catch (Exception e) { + fail("exception caught refreshing aliases: " + e); + return false; + } + }); + } + + private void assertDoc(CloudSolrClient solrClient, String collection, String id) throws Exception { + QueryResponse rsp = solrClient.query(collection, params(CommonParams.Q, "*:*")); + assertEquals(rsp.toString(), 1, rsp.getResults().getNumFound()); + SolrDocument sdoc = rsp.getResults().get(0); + assertEquals(sdoc.toString(), id, sdoc.getFieldValue("id")); + + } + @Test public void testOverseerStatus() throws IOException, SolrServerException { CollectionAdminResponse response = new CollectionAdminRequest.OverseerStatus().process(cluster.getSolrClient()); diff --git a/solr/core/src/test/org/apache/solr/cloud/ReindexCollectionTest.java b/solr/core/src/test/org/apache/solr/cloud/ReindexCollectionTest.java index eff0dde0707..f03e4f8693b 100644 --- a/solr/core/src/test/org/apache/solr/cloud/ReindexCollectionTest.java +++ b/solr/core/src/test/org/apache/solr/cloud/ReindexCollectionTest.java @@ -150,8 +150,16 @@ public class ReindexCollectionTest extends SolrCloudTestCase { assertEquals("copied num docs", NUM_DOCS, queryResponse.getResults().getNumFound()); } + @Test public void testSameTargetReindexing() throws Exception { - final String sourceCollection = "sameTargetReindexing"; + doTestSameTargetReindexing(false, false); + doTestSameTargetReindexing(false, true); + doTestSameTargetReindexing(true, false); + doTestSameTargetReindexing(true, true); + } + + private void doTestSameTargetReindexing(boolean sourceRemove, boolean followAliases) throws Exception { + final String sourceCollection = "sameTargetReindexing_" + sourceRemove + "_" + followAliases; final String targetCollection = sourceCollection; createCollection(sourceCollection, "conf1", 2, 2); @@ -160,6 +168,8 @@ public class ReindexCollectionTest extends SolrCloudTestCase { CollectionAdminRequest.ReindexCollection req = CollectionAdminRequest.reindexCollection(sourceCollection) .setTarget(targetCollection); + req.setRemoveSource(sourceRemove); + req.setFollowAliases(followAliases); req.process(solrClient); String realTargetCollection = null; @@ -183,11 +193,16 @@ public class ReindexCollectionTest extends SolrCloudTestCase { ReindexCollectionCmd.State state = ReindexCollectionCmd.State.get(coll.getStr(ReindexCollectionCmd.REINDEXING_STATE)); return ReindexCollectionCmd.State.FINISHED == state; }); + solrClient.getZkStateReader().aliasesManager.update(); SolrTestCaseJ4.Solr11035BandAid(solrClient, targetCollection, "id", NUM_DOCS, "*:*", - "ReindexCollectionTest.testSameTargetReindex", false); + "ReindexCollectionTest.testSameTargetReindex_" + sourceRemove, false); // verify the target docs exist QueryResponse rsp = solrClient.query(targetCollection, params(CommonParams.Q, "*:*")); assertEquals("copied num docs", NUM_DOCS, rsp.getResults().getNumFound()); + ClusterState state = solrClient.getClusterStateProvider().getClusterState(); + if (sourceRemove) { + assertFalse("source collection still present", state.hasCollection(sourceCollection)); + } } @Test diff --git a/solr/solr-ref-guide/src/aliases.adoc b/solr/solr-ref-guide/src/aliases.adoc index 37f4b577cef..34d9b883abf 100644 --- a/solr/solr-ref-guide/src/aliases.adoc +++ b/solr/solr-ref-guide/src/aliases.adoc @@ -282,9 +282,15 @@ As always, patches and pull requests are welcome! Starting with version 8.1 SolrCloud supports using alias names in collection commands where normally a collection name is expected. This works only when the following criteria are satisfied: +* a request parameter `followAliases=true` is used * an alias must not refer to more than one collection * an alias must not refer to a <> -If all criteria are satisfied then the command will resolve alias names and operate on the collections the aliases +If all criteria are satisfied then the command will resolve all alias names and operate on the collections the aliases refer to as if it was invoked with the collection names instead. Otherwise the command will not be executed and an exception will be thrown. + +The `followAliases=true` parameter should be used with care so that the resolved targets are indeed the intended ones. +In case of multi-level aliases or shadow aliases (an alias with the same name as an existing collection but pointing +to other collections) the use of this option is strongly discouraged because effects may be difficult to +predict correctly. diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionAdminRequest.java b/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionAdminRequest.java index 85c9088d1c8..7f3cbd41bde 100644 --- a/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionAdminRequest.java +++ b/solr/solrj/src/java/org/apache/solr/client/solrj/request/CollectionAdminRequest.java @@ -246,6 +246,7 @@ public abstract class CollectionAdminRequest protected abstract static class AsyncCollectionSpecificAdminRequest extends AsyncCollectionAdminRequest { protected String collection; + protected Boolean followAliases; public AsyncCollectionSpecificAdminRequest(CollectionAction action, String collection) { super(action); @@ -256,10 +257,15 @@ public abstract class CollectionAdminRequest return collection; } + public void setFollowAliases(Boolean followAliases) { + this.followAliases = followAliases; + } + @Override public SolrParams getParams() { ModifiableSolrParams params = new ModifiableSolrParams(super.getParams()); params.set(CoreAdminParams.NAME, collection); + params.setNonNull(CollectionAdminParams.FOLLOW_ALIASES, followAliases); return params; } } diff --git a/solr/solrj/src/java/org/apache/solr/common/params/CollectionAdminParams.java b/solr/solrj/src/java/org/apache/solr/common/params/CollectionAdminParams.java index 5291b7d6958..5af2345356f 100644 --- a/solr/solrj/src/java/org/apache/solr/common/params/CollectionAdminParams.java +++ b/solr/solrj/src/java/org/apache/solr/common/params/CollectionAdminParams.java @@ -124,4 +124,7 @@ public interface CollectionAdminParams { * Prefix for {@link org.apache.solr.common.cloud.DocRouter} properties */ String ROUTER_PREFIX = "router."; + + /** Option to follow aliases when deciding the target of a collection admin command. */ + String FOLLOW_ALIASES = "followAliases"; }