lucene-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From a.@apache.org
Subject [lucene-solr] branch branch_8x updated: SOLR-13583: Impossible to delete a collection with the same name as an existing alias.
Date Wed, 03 Jul 2019 11:36:17 GMT
This is an automated email from the ASF dual-hosted git repository.

ab pushed a commit to branch branch_8x
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git


The following commit(s) were added to refs/heads/branch_8x by this push:
     new 30a5b2c  SOLR-13583: Impossible to delete a collection with the same name as an existing alias.
30a5b2c is described below

commit 30a5b2cd87981beab21d6282c4b6ca02a31665b8
Author: Andrzej Bialecki <ab@apache.org>
AuthorDate: Wed Jul 3 11:05:52 2019 +0200

    SOLR-13583: Impossible to delete a collection with the same name as an existing alias.
---
 solr/CHANGES.txt                                   |   3 +
 .../solr/cloud/api/collections/AddReplicaCmd.java  |   9 +-
 .../solr/cloud/api/collections/BackupCmd.java      |   9 +-
 .../cloud/api/collections/CreateCollectionCmd.java |   2 +-
 .../solr/cloud/api/collections/CreateShardCmd.java |   9 +-
 .../cloud/api/collections/CreateSnapshotCmd.java   |  10 +-
 .../cloud/api/collections/DeleteCollectionCmd.java |  26 +++-
 .../cloud/api/collections/DeleteReplicaCmd.java    |   9 +-
 .../solr/cloud/api/collections/DeleteShardCmd.java |   9 +-
 .../cloud/api/collections/DeleteSnapshotCmd.java   |   9 +-
 .../solr/cloud/api/collections/MigrateCmd.java     |  13 +-
 .../solr/cloud/api/collections/MoveReplicaCmd.java |   9 +-
 .../api/collections/ReindexCollectionCmd.java      |  18 ++-
 .../solr/cloud/api/collections/RenameCmd.java      |  11 +-
 .../solr/cloud/api/collections/SplitShardCmd.java  |   9 +-
 .../solr/handler/admin/CollectionsHandler.java     |  66 +++++---
 .../apache/solr/cloud/CollectionsAPISolrJTest.java | 166 ++++++++++++++++++---
 .../apache/solr/cloud/ReindexCollectionTest.java   |  17 ++-
 solr/solr-ref-guide/src/aliases.adoc               |   8 +-
 .../solrj/request/CollectionAdminRequest.java      |   6 +
 .../solr/common/params/CollectionAdminParams.java  |   3 +
 21 files changed, 355 insertions(+), 66 deletions(-)

diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index d12f854..6ab2dbf 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -216,6 +216,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 49b1e26..8c6f002 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 bd3bd3b..ceec4e2 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 372ae53..708f7da 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 ffe9890..08d27e5 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 d761a88..abc3597 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 6c0b147..98c7ca3 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<String> aliasReferences = checkAliasReference(zkStateReader, extCollection);
+    boolean followAliases = message.getBool(FOLLOW_ALIASES, false);
+    List<String> 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<String> okayExceptions = new HashSet<>(1);
       okayExceptions.add(NonExistentCoreException.class.getName());
+      ZkNodeProps internalMsg = message.plus(NAME, collection);
 
-      List<Replica> failedReplicas = ocmh.collectionCmd(message, params, results, null, asyncId, okayExceptions);
+      List<Replica> 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<String> checkAliasReference(ZkStateReader zkStateReader, String extCollection) throws Exception {
+  private List<String> checkAliasReference(ZkStateReader zkStateReader, String extCollection, boolean followAliases) throws Exception {
     Aliases aliases = zkStateReader.getAliases();
-    List<String> aliasesRefs = referencedByAlias(extCollection, aliases);
+    List<String> aliasesRefs = referencedByAlias(extCollection, aliases, followAliases);
     List<String> 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<String> referencedByAlias(String extCollection, Aliases aliases) throws IllegalArgumentException {
+  public static List<String> 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 d999a6f..a6b45b5 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 c5c8e99..4aca282 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 88881cf..634692d 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 55210f1..2ecf5f8 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 fc39b9d..4e462f6 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 57e7a62..da9bacb 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.
  * <p>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 2a33e49..7296f6c 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 e36c76f..dd86ec4 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 c3bfda2..d292976 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)),
-
-    RELOAD_OP(RELOAD, (req, rsp, h) -> copy(req.getParams().required(), null, NAME)),
+    DELETE_OP(DELETE, (req, rsp, h) -> {
+      Map<String, Object> 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<String, Object> map = copy(req.getParams().required(), null, NAME);
+      return copy(req.getParams(), map);
+    }),
 
-    RENAME_OP(RENAME, (req, rsp, h) -> copy(req.getParams().required(), null, NAME, CollectionAdminParams.TARGET)),
+    RENAME_OP(RENAME, (req, rsp, h) -> {
+      Map<String, Object> map = copy(req.getParams().required(), null, NAME, CollectionAdminParams.TARGET);
+      return copy(req.getParams(), map, FOLLOW_ALIASES);
+    }),
 
     REINDEXCOLLECTION_OP(REINDEXCOLLECTION, (req, rsp, h) -> {
       Map<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> 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<String, Object> params = copy(req.getParams(), null, NAME, COLLECTION_PROP, CoreAdminParams.COMMIT_NAME);
+      Map<String, Object> 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<String, Object> params = copy(req.getParams(), null, COLLECTION_PROP, CoreAdminParams.COMMIT_NAME);
+      Map<String, Object> 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<String, Object> params = copy(req.getParams(), null, COLLECTION_PROP, CoreAdminParams.COMMIT_NAME);
+      Map<String, Object> 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 ab73781..34d1b8d 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,43 +824,157 @@ 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 = zkStateReader.getAliases();
-    assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName1, aliases.resolveSimpleAlias("foo"));
-    assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName1, aliases.resolveSimpleAlias("simpleAlias"));
-    List<String> compoundAliases = aliases.resolveAliases("compoundAlias");
-    assertEquals(compoundAliases.toString(), 2, compoundAliases.size());
-    assertTrue(compoundAliases.toString(), compoundAliases.contains(collectionName1));
-    assertTrue(compoundAliases.toString(), compoundAliases.contains(collectionName2));
+    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 = zkStateReader.getAliases();
+      assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName1, aliases.resolveSimpleAlias("foo"));
+      assertEquals(aliases.getCollectionAliasListMap().toString(), collectionName1, aliases.resolveSimpleAlias("simpleAlias"));
+      List<String> 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<String> 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 {
+      rename = CollectionAdminRequest.renameCollection("col2", "foo");
+      rename.setFollowAliases(followAliases);
+      rename.process(cluster.getSolrClient());
+      fail("should fail because 'foo' already exists");
+    } catch (Exception e) {
+      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 {
-      CollectionAdminRequest.renameCollection("col2", "foo").process(cluster.getSolrClient());
-      fail("shuold fail because 'foo' already exists");
+      delete.process(solrClient);
+      fail("delete of nonexistent collection 1 should have failed when followAliases=false");
     } catch (Exception e) {
-      assertTrue(e.toString().contains("exists"));
+      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
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 eff0dde..f6d8d04 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);
     // 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 37f4b57..34d9b88 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 <<Routed Aliases,Routed Alias>>
 
-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 85c9088..7f3cbd4 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<T extends CollectionAdminResponse>
   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<T extends CollectionAdminResponse>
       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 5291b7d..5af2345 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";
 }


Mime
View raw message