You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ab...@apache.org on 2019/04/10 16:56:47 UTC
[lucene-solr] branch master updated: SOLR-13262: Add collection
RENAME command and support using aliases in most collection admin commands.
This is an automated email from the ASF dual-hosted git repository.
ab pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git
The following commit(s) were added to refs/heads/master by this push:
new 02c4503 SOLR-13262: Add collection RENAME command and support using aliases in most collection admin commands.
02c4503 is described below
commit 02c4503f8c122361c4c99e3776cfdcef15b859bd
Author: Andrzej Bialecki <ab...@apache.org>
AuthorDate: Wed Apr 10 18:44:05 2019 +0200
SOLR-13262: Add collection RENAME command and support using aliases in most collection admin commands.
---
solr/CHANGES.txt | 2 +
.../solr/cloud/api/collections/AddReplicaCmd.java | 4 +-
.../solr/cloud/api/collections/BackupCmd.java | 9 +-
.../solr/cloud/api/collections/CreateAliasCmd.java | 7 +-
.../cloud/api/collections/CreateCollectionCmd.java | 26 +++-
.../solr/cloud/api/collections/CreateShardCmd.java | 5 +-
.../cloud/api/collections/CreateSnapshotCmd.java | 4 +-
.../cloud/api/collections/DeleteCollectionCmd.java | 51 +++++---
.../cloud/api/collections/DeleteReplicaCmd.java | 4 +-
.../solr/cloud/api/collections/DeleteShardCmd.java | 4 +-
.../cloud/api/collections/DeleteSnapshotCmd.java | 3 +-
.../solr/cloud/api/collections/MigrateCmd.java | 7 +-
.../solr/cloud/api/collections/MoveReplicaCmd.java | 4 +-
.../OverseerCollectionMessageHandler.java | 17 +++
.../api/collections/ReindexCollectionCmd.java | 73 ++++-------
.../solr/cloud/api/collections/RenameCmd.java | 70 +++++++++++
.../solr/cloud/api/collections/RestoreCmd.java | 7 ++
.../solr/cloud/api/collections/RoutedAlias.java | 2 +-
.../solr/cloud/api/collections/SplitShardCmd.java | 4 +-
.../cloud/api/collections/TimeRoutedAlias.java | 1 +
.../org/apache/solr/core/backup/BackupManager.java | 1 +
.../solr/handler/admin/CollectionsHandler.java | 35 ++++--
.../org/apache/solr/handler/sql/SolrSchema.java | 9 +-
.../solr/search/join/ScoreJoinQParserPlugin.java | 10 +-
.../apache/solr/cloud/CollectionsAPISolrJTest.java | 57 +++++++++
solr/solr-ref-guide/src/aliases.adoc | 11 ++
solr/solr-ref-guide/src/collections-api.adoc | 64 ++++++++++
.../DelegatingClusterStateProvider.java | 9 ++
.../client/solrj/impl/BaseCloudSolrClient.java | 6 +-
.../solrj/impl/BaseHttpClusterStateProvider.java | 5 +
.../client/solrj/impl/ClusterStateProvider.java | 13 ++
.../solrj/impl/ZkClientClusterStateProvider.java | 5 +
.../solrj/request/CollectionAdminRequest.java | 28 +++++
.../java/org/apache/solr/common/cloud/Aliases.java | 138 +++++++++++++++++++--
.../solr/common/params/CollectionAdminParams.java | 15 +++
.../solr/common/params/CollectionParams.java | 3 +-
36 files changed, 598 insertions(+), 115 deletions(-)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index a3e5af8..51839ba 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -112,6 +112,8 @@ New Features
hierarchy and indexing the new one with the atomic update merged into it. Also, [child] Doc Transformer now works
with RealTimeGet. (Moshe Bla, David Smiley)
+* SOLR-13262: Add collection RENAME command and support using aliases in most collection admin commands. (ab)
+
Bug Fixes
----------------------
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 e532107..5b7f813 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
@@ -95,9 +95,11 @@ public class AddReplicaCmd implements OverseerCollectionMessageHandler.Cmd {
throws IOException, InterruptedException {
log.debug("addReplica() : {}", Utils.toJSONString(message));
- String collectionName = message.getStr(COLLECTION_PROP);
+ String extCollectionName = message.getStr(COLLECTION_PROP);
String shard = message.getStr(SHARD_ID_PROP);
+ final String collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName);
+
DocCollection coll = clusterState.getCollection(collectionName);
if (coll == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Collection: " + collectionName + " does not exist");
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 fd9faad..b9900cc 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
@@ -67,7 +67,8 @@ public class BackupCmd implements OverseerCollectionMessageHandler.Cmd {
@Override
public void call(ClusterState state, ZkNodeProps message, NamedList results) throws Exception {
- String collectionName = message.getStr(COLLECTION_PROP);
+ String extCollectionName = message.getStr(COLLECTION_PROP);
+ String collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName);
String backupName = message.getStr(NAME);
String repo = message.getStr(CoreAdminParams.BACKUP_REPOSITORY);
@@ -92,7 +93,7 @@ public class BackupCmd implements OverseerCollectionMessageHandler.Cmd {
String strategy = message.getStr(CollectionAdminParams.INDEX_BACKUP_STRATEGY, CollectionAdminParams.COPY_FILES_STRATEGY);
switch (strategy) {
case CollectionAdminParams.COPY_FILES_STRATEGY: {
- copyIndexFiles(backupPath, message, results);
+ copyIndexFiles(backupPath, collectionName, message, results);
break;
}
case CollectionAdminParams.NO_INDEX_BACKUP_STRATEGY: {
@@ -115,6 +116,7 @@ public class BackupCmd implements OverseerCollectionMessageHandler.Cmd {
properties.put(BackupManager.BACKUP_NAME_PROP, backupName);
properties.put(BackupManager.COLLECTION_NAME_PROP, collectionName);
+ properties.put(BackupManager.COLLECTION_ALIAS_PROP, extCollectionName);
properties.put(CollectionAdminParams.COLL_CONF, configName);
properties.put(BackupManager.START_TIME_PROP, startTime.toString());
properties.put(BackupManager.INDEX_VERSION_PROP, Version.LATEST.toString());
@@ -155,8 +157,7 @@ public class BackupCmd implements OverseerCollectionMessageHandler.Cmd {
return r.get();
}
- private void copyIndexFiles(URI backupPath, ZkNodeProps request, NamedList results) throws Exception {
- String collectionName = request.getStr(COLLECTION_PROP);
+ private void copyIndexFiles(URI backupPath, String collectionName, ZkNodeProps request, NamedList results) throws Exception {
String backupName = request.getStr(NAME);
String asyncId = request.getStr(ASYNC);
String repoName = request.getStr(CoreAdminParams.BACKUP_REPOSITORY);
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateAliasCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateAliasCmd.java
index 641c4ad..57be84f 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateAliasCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateAliasCmd.java
@@ -31,6 +31,7 @@ import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.params.CollectionAdminParams;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.StrUtils;
@@ -43,7 +44,7 @@ public class CreateAliasCmd extends AliasCmd {
private final OverseerCollectionMessageHandler ocmh;
private static boolean anyRoutingParams(ZkNodeProps message) {
- return message.keySet().stream().anyMatch(k -> k.startsWith(RoutedAlias.ROUTER_PREFIX));
+ return message.keySet().stream().anyMatch(k -> k.startsWith(CollectionAdminParams.ROUTER_PREFIX));
}
@SuppressWarnings("WeakerAccess")
@@ -56,6 +57,10 @@ public class CreateAliasCmd extends AliasCmd {
throws Exception {
final String aliasName = message.getStr(CommonParams.NAME);
ZkStateReader zkStateReader = ocmh.zkStateReader;
+ // make sure we have the latest version of existing aliases
+ if (zkStateReader.aliasesManager != null) { // not a mock ZkStateReader
+ zkStateReader.aliasesManager.update();
+ }
if (!anyRoutingParams(message)) {
callCreatePlainAlias(message, aliasName, zkStateReader);
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 69a8cae..500aae0 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
@@ -45,6 +45,7 @@ import org.apache.solr.cloud.ZkController;
import org.apache.solr.cloud.overseer.ClusterStateMutator;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.common.cloud.Aliases;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.DocRouter;
@@ -80,6 +81,7 @@ import static org.apache.solr.common.cloud.ZkStateReader.REPLICATION_FACTOR;
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.COLOCATED_WITH;
+import static org.apache.solr.common.params.CollectionAdminParams.ALIAS;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.ADDREPLICA;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.MODIFYCOLLECTION;
import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
@@ -101,20 +103,29 @@ public class CreateCollectionCmd implements OverseerCollectionMessageHandler.Cmd
@Override
public void call(ClusterState clusterState, ZkNodeProps message, NamedList results) throws Exception {
+ if (ocmh.zkStateReader.aliasesManager != null) { // not a mock ZkStateReader
+ ocmh.zkStateReader.aliasesManager.update();
+ }
+ final Aliases aliases = ocmh.zkStateReader.getAliases();
final String collectionName = message.getStr(NAME);
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)) {
+ if (clusterState.hasCollection(collectionName) || aliases.hasAlias(collectionName)) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "collection already exists: " + collectionName);
}
+ if (aliases.hasAlias(collectionName)) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "collection alias already exists: " + collectionName);
+ }
String withCollection = message.getStr(CollectionAdminParams.WITH_COLLECTION);
String withCollectionShard = null;
if (withCollection != null) {
- if (!clusterState.hasCollection(withCollection)) {
- throw new SolrException(ErrorCode.BAD_REQUEST, "The 'withCollection' does not exist: " + withCollection);
+ String realWithCollection = aliases.resolveSimpleAlias(withCollection);
+ if (!clusterState.hasCollection(realWithCollection)) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "The 'withCollection' does not exist: " + realWithCollection);
} else {
- DocCollection collection = clusterState.getCollection(withCollection);
+ DocCollection collection = clusterState.getCollection(realWithCollection);
if (collection.getActiveSlices().size() > 1) {
throw new SolrException(ErrorCode.BAD_REQUEST, "The `withCollection` must have only one shard, found: " + collection.getActiveSlices().size());
}
@@ -283,12 +294,14 @@ public class CreateCollectionCmd implements OverseerCollectionMessageHandler.Cmd
}
ocmh.processResponses(results, shardHandler, false, null, async, requestMap, Collections.emptySet());
- if(results.get("failure") != null && ((SimpleOrderedMap)results.get("failure")).size() > 0) {
+ boolean failure = results.get("failure") != null && ((SimpleOrderedMap)results.get("failure")).size() > 0;
+ if (failure) {
// Let's cleanup as we hit an exception
// We shouldn't be passing 'results' here for the cleanup as the response would then contain 'success'
// element, which may be interpreted by the user as a positive ack
ocmh.cleanupCollection(collectionName, new NamedList<Object>());
log.info("Cleaned up artifacts for failed create collection for [{}]", collectionName);
+ return;
} else {
log.debug("Finished create command on all shards for collection: {}", collectionName);
@@ -318,6 +331,9 @@ public class CreateCollectionCmd implements OverseerCollectionMessageHandler.Cmd
}
}
+ // create an alias pointing to the new collection
+ ocmh.zkStateReader.aliasesManager.applyModificationAndExportToZk(a -> a.cloneWithCollectionAlias(alias, collectionName));
+
} catch (SolrException ex) {
throw ex;
} catch (Exception ex) {
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 229b799..ffe9890 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
@@ -51,14 +51,15 @@ public class CreateShardCmd implements OverseerCollectionMessageHandler.Cmd {
@Override
public void call(ClusterState clusterState, ZkNodeProps message, NamedList results) throws Exception {
- String collectionName = message.getStr(COLLECTION_PROP);
+ String extCollectionName = message.getStr(COLLECTION_PROP);
String sliceName = message.getStr(SHARD_ID_PROP);
boolean waitForFinalState = message.getBool(CommonAdminParams.WAIT_FOR_FINAL_STATE, false);
log.info("Create shard invoked: {}", message);
- if (collectionName == null || sliceName == null)
+ 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);
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 8a091ef..4203b92 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
@@ -64,7 +64,9 @@ public class CreateSnapshotCmd implements OverseerCollectionMessageHandler.Cmd {
@Override
public void call(ClusterState state, ZkNodeProps message, NamedList results) throws Exception {
- String collectionName = message.getStr(COLLECTION_PROP);
+ String extCollectionName = message.getStr(COLLECTION_PROP);
+ String collectionName = ocmh.zkStateReader.getAliases().resolveSimpleAlias(extCollectionName);
+
String commitName = message.getStr(CoreAdminParams.COMMIT_NAME);
String asyncId = message.getStr(ASYNC);
SolrZkClient zkClient = ocmh.zkStateReader.getZkClient();
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 7177f03..0f0dfbd 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
@@ -26,6 +26,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
import org.apache.solr.cloud.Overseer;
import org.apache.solr.common.NonExistentCoreException;
@@ -68,10 +69,18 @@ public class DeleteCollectionCmd implements OverseerCollectionMessageHandler.Cmd
@Override
public void call(ClusterState state, ZkNodeProps message, NamedList results) throws Exception {
- final String collection = message.getStr(NAME);
+ final String extCollection = message.getStr(NAME);
ZkStateReader zkStateReader = ocmh.zkStateReader;
- checkNotReferencedByAlias(zkStateReader, collection);
+ if (zkStateReader.aliasesManager != null) { // not a mock ZkStateReader
+ zkStateReader.aliasesManager.update(); // aliases may have been stale; get latest from ZK
+ }
+
+ String aliasReference = checkAliasReference(zkStateReader, extCollection);
+
+ Aliases aliases = zkStateReader.getAliases();
+ String collection = aliases.resolveSimpleAlias(extCollection);
+
checkNotColocatedWith(zkStateReader, collection);
final boolean deleteHistory = message.getBool(CoreAdminParams.DELETE_METRICS_HISTORY, true);
@@ -115,8 +124,8 @@ public class DeleteCollectionCmd implements OverseerCollectionMessageHandler.Cmd
okayExceptions.add(NonExistentCoreException.class.getName());
List<Replica> failedReplicas = ocmh.collectionCmd(message, params, results, null, asyncId, requestMap, okayExceptions);
- for (Replica failedRepilca : failedReplicas) {
- boolean isSharedFS = failedRepilca.getBool(ZkStateReader.SHARED_STORAGE_PROP, false) && failedRepilca.get("dataDir") != null;
+ for (Replica failedReplica : failedReplicas) {
+ boolean isSharedFS = failedReplica.getBool(ZkStateReader.SHARED_STORAGE_PROP, false) && failedReplica.get("dataDir") != null;
if (isSharedFS) {
// if the replica use a shared FS and it did not receive the unload message, then counter node should not be removed
// because when a new collection with same name is created, new replicas may reuse the old dataDir
@@ -130,7 +139,12 @@ public class DeleteCollectionCmd implements OverseerCollectionMessageHandler.Cmd
// wait for a while until we don't see the collection
zkStateReader.waitForState(collection, 60, TimeUnit.SECONDS, (liveNodes, collectionState) -> collectionState == null);
-
+
+ // we can delete any remaining unique alias
+ if (aliasReference != null) {
+ ocmh.zkStateReader.aliasesManager.applyModificationAndExportToZk(a -> a.cloneWithCollectionAlias(aliasReference, null));
+ }
+
// TimeOut timeout = new TimeOut(60, TimeUnit.SECONDS, timeSource);
// boolean removed = false;
// while (! timeout.hasTimedOut()) {
@@ -169,24 +183,33 @@ public class DeleteCollectionCmd implements OverseerCollectionMessageHandler.Cmd
}
}
- private void checkNotReferencedByAlias(ZkStateReader zkStateReader, String collection) throws Exception {
- String alias = referencedByAlias(collection, zkStateReader.getAliases());
- if (alias != null) {
+ // it's ok if a collection is referenced either by none or exactly by a single alias.
+ // This method returns the single alias to delete, if present, or null
+ private String checkAliasReference(ZkStateReader zkStateReader, String extCollection) throws Exception {
+ List<String> aliases = referencedByAlias(extCollection, zkStateReader.getAliases());
+ if (aliases.size() > 1) {
zkStateReader.aliasesManager.update(); // aliases may have been stale; get latest from ZK
- alias = referencedByAlias(collection, zkStateReader.getAliases());
- if (alias != null) {
+ aliases = referencedByAlias(extCollection, zkStateReader.getAliases());
+ if (aliases.size() > 1) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
- "Collection : " + collection + " is part of alias " + alias + " remove or modify the alias before removing this collection.");
+ "Collection : " + extCollection + " is part of aliases: " + aliases + ", remove or modify the aliases before removing this collection.");
}
}
+ if (!aliases.isEmpty()) {
+ return aliases.get(0);
+ } else {
+ return null;
+ }
}
- public static String referencedByAlias(String collection, Aliases aliases) {
+ public static List<String> referencedByAlias(String extCollection, Aliases aliases) throws IllegalArgumentException {
Objects.requireNonNull(aliases);
+ // this quickly produces error if the name is a complex alias
+ String collection = aliases.resolveSimpleAlias(extCollection);
return aliases.getCollectionAliasListMap().entrySet().stream()
- .filter(e -> e.getValue().contains(collection))
+ .filter(e -> e.getValue().contains(collection) || e.getValue().contains(extCollection))
.map(Map.Entry::getKey) // alias name
- .findFirst().orElse(null);
+ .collect(Collectors.toList());
}
private void checkNotColocatedWith(ZkStateReader zkStateReader, String collection) throws Exception {
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 ec158bb..2ea163f 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
@@ -81,10 +81,12 @@ public class DeleteReplicaCmd implements Cmd {
ocmh.checkRequired(message, COLLECTION_PROP, SHARD_ID_PROP, REPLICA_PROP);
- String collectionName = message.getStr(COLLECTION_PROP);
+ String extCollectionName = message.getStr(COLLECTION_PROP);
String shard = message.getStr(SHARD_ID_PROP);
String replicaName = message.getStr(REPLICA_PROP);
+ String collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName);
+
DocCollection coll = clusterState.getCollection(collectionName);
Slice slice = coll.getSlice(shard);
if (slice == null) {
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 fa50c4a..e38aa4a 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
@@ -62,9 +62,11 @@ public class DeleteShardCmd implements OverseerCollectionMessageHandler.Cmd {
@Override
public void call(ClusterState clusterState, ZkNodeProps message, NamedList results) throws Exception {
- String collectionName = message.getStr(ZkStateReader.COLLECTION_PROP);
+ String extCollectionName = message.getStr(ZkStateReader.COLLECTION_PROP);
String sliceId = message.getStr(ZkStateReader.SHARD_ID_PROP);
+ String collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName);
+
log.info("Delete shard invoked");
Slice slice = clusterState.getCollection(collectionName).getSlice(sliceId);
if (slice == null) throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
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 21d9cb0..8e8c577 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
@@ -64,7 +64,8 @@ public class DeleteSnapshotCmd implements OverseerCollectionMessageHandler.Cmd {
@Override
public void call(ClusterState state, ZkNodeProps message, NamedList results) throws Exception {
- String collectionName = message.getStr(COLLECTION_PROP);
+ String extCollectionName = message.getStr(COLLECTION_PROP);
+ String collectionName = ocmh.zkStateReader.getAliases().resolveSimpleAlias(extCollectionName);
String commitName = message.getStr(CoreAdminParams.COMMIT_NAME);
String asyncId = message.getStr(ASYNC);
Map<String, String> requestMap = new HashMap<>();
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 f22544a..236d46f 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
@@ -73,11 +73,14 @@ public class MigrateCmd implements OverseerCollectionMessageHandler.Cmd {
@Override
public void call(ClusterState clusterState, ZkNodeProps message, NamedList results) throws Exception {
- String sourceCollectionName = message.getStr("collection");
+ String extSourceCollectionName = message.getStr("collection");
String splitKey = message.getStr("split.key");
- String targetCollectionName = message.getStr("target.collection");
+ 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);
+
DocCollection sourceCollection = clusterState.getCollection(sourceCollectionName);
if (sourceCollection == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown source collection: " + sourceCollectionName);
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 6071b1b..fc39b9d 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
@@ -72,7 +72,7 @@ public class MoveReplicaCmd implements OverseerCollectionMessageHandler.Cmd {
private void moveReplica(ClusterState clusterState, ZkNodeProps message, NamedList results) throws Exception {
log.debug("moveReplica() : {}", Utils.toJSONString(message));
ocmh.checkRequired(message, COLLECTION_PROP, CollectionParams.TARGET_NODE);
- String collection = message.getStr(COLLECTION_PROP);
+ String extCollection = message.getStr(COLLECTION_PROP);
String targetNode = message.getStr(CollectionParams.TARGET_NODE);
boolean waitForFinalState = message.getBool(WAIT_FOR_FINAL_STATE, false);
boolean inPlaceMove = message.getBool(IN_PLACE_MOVE, true);
@@ -80,6 +80,8 @@ public class MoveReplicaCmd implements OverseerCollectionMessageHandler.Cmd {
String async = message.getStr(ASYNC);
+ String collection = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollection);
+
DocCollection coll = clusterState.getCollection(collection);
if (coll == null) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Collection: " + collection + " does not exist");
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerCollectionMessageHandler.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerCollectionMessageHandler.java
index 8f5dbd6..19cbee7 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerCollectionMessageHandler.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/OverseerCollectionMessageHandler.java
@@ -243,6 +243,7 @@ public class OverseerCollectionMessageHandler implements OverseerMessageHandler,
.put(MOVEREPLICA, new MoveReplicaCmd(this))
.put(REINDEXCOLLECTION, new ReindexCollectionCmd(this))
.put(UTILIZENODE, new UtilizeNodeCmd(this))
+ .put(RENAME, new RenameCmd(this))
.build()
;
}
@@ -455,6 +456,22 @@ public class OverseerCollectionMessageHandler implements OverseerMessageHandler,
}
+ void checkResults(String label, NamedList<Object> results, boolean failureIsFatal) throws SolrException {
+ Object failure = results.get("failure");
+ if (failure == null) {
+ failure = results.get("error");
+ }
+ if (failure != null) {
+ String msg = "Error: " + label + ": " + Utils.toJSONString(results);
+ if (failureIsFatal) {
+ throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, msg);
+ } else {
+ log.error(msg);
+ }
+ }
+ }
+
+
//TODO should we not remove in the next release ?
private void migrateStateFormat(ClusterState state, ZkNodeProps message, NamedList results) throws Exception {
final String collectionName = message.getStr(COLLECTION_PROP);
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 553c4bf..57e7a62 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
@@ -44,7 +44,6 @@ import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.cloud.Overseer;
import org.apache.solr.common.SolrException;
-import org.apache.solr.common.cloud.Aliases;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.DocRouter;
@@ -174,32 +173,23 @@ public class ReindexCollectionCmd implements OverseerCollectionMessageHandler.Cm
log.debug("*** called: {}", message);
- String collection = message.getStr(CommonParams.NAME);
- // before resolving aliases
- String originalCollection = collection;
- Aliases aliases = ocmh.zkStateReader.getAliases();
- if (collection != null) {
- // resolve aliases - the source may be an alias
- List<String> aliasList = aliases.resolveAliases(collection);
- if (aliasList != null && !aliasList.isEmpty()) {
- collection = aliasList.get(0);
- }
- }
+ String extCollection = message.getStr(CommonParams.NAME);
- if (collection == null || !clusterState.hasCollection(collection)) {
- throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Source collection name must be specified and must exist");
+ if (extCollection == null) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Source collection name must be specified");
+ }
+ String collection = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollection);
+ if (!clusterState.hasCollection(collection)) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Source collection name must exist");
}
String target = message.getStr(TARGET);
if (target == null) {
target = collection;
} else {
// resolve aliases
- List<String> aliasList = aliases.resolveAliases(target);
- if (aliasList != null && !aliasList.isEmpty()) {
- target = aliasList.get(0);
- }
+ target = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(target);
}
- boolean sameTarget = target.equals(collection) || target.equals(originalCollection);
+ boolean sameTarget = target.equals(collection) || target.equals(extCollection);
boolean removeSource = message.getBool(REMOVE_SOURCE, false);
Cmd command = Cmd.get(message.getStr(COMMAND, Cmd.START.toLower()));
if (command == null) {
@@ -255,7 +245,7 @@ public class ReindexCollectionCmd implements OverseerCollectionMessageHandler.Cm
int seq = tmpCollectionSeq.getAndIncrement();
if (sameTarget) {
do {
- targetCollection = TARGET_COL_PREFIX + originalCollection + "_" + seq;
+ targetCollection = TARGET_COL_PREFIX + extCollection + "_" + seq;
if (!clusterState.hasCollection(targetCollection)) {
break;
}
@@ -264,7 +254,7 @@ public class ReindexCollectionCmd implements OverseerCollectionMessageHandler.Cm
} else {
targetCollection = target;
}
- String chkCollection = CHK_COL_PREFIX + originalCollection;
+ String chkCollection = CHK_COL_PREFIX + extCollection;
String daemonUrl = null;
Exception exc = null;
boolean createdTarget = false;
@@ -294,7 +284,7 @@ public class ReindexCollectionCmd implements OverseerCollectionMessageHandler.Cm
CoreAdminParams.DELETE_METRICS_HISTORY, "true"
);
ocmh.commandMap.get(CollectionParams.CollectionAction.DELETE).call(clusterState, cmd, cmdResults);
- checkResults("deleting old checkpoint collection " + chkCollection, cmdResults, true);
+ ocmh.checkResults("deleting old checkpoint collection " + chkCollection, cmdResults, true);
}
if (maybeAbort(collection)) {
@@ -343,7 +333,7 @@ public class ReindexCollectionCmd implements OverseerCollectionMessageHandler.Cm
cmdResults = new NamedList<>();
ocmh.commandMap.get(CollectionParams.CollectionAction.CREATE).call(clusterState, cmd, cmdResults);
createdTarget = true;
- checkResults("creating target collection " + targetCollection, cmdResults, true);
+ ocmh.checkResults("creating target collection " + targetCollection, cmdResults, true);
// create the checkpoint collection - use RF=1 and 1 shard
cmd = new ZkNodeProps(
@@ -357,7 +347,7 @@ public class ReindexCollectionCmd implements OverseerCollectionMessageHandler.Cm
);
cmdResults = new NamedList<>();
ocmh.commandMap.get(CollectionParams.CollectionAction.CREATE).call(clusterState, cmd, cmdResults);
- checkResults("creating checkpoint collection " + chkCollection, cmdResults, true);
+ ocmh.checkResults("creating checkpoint collection " + chkCollection, cmdResults, true);
// wait for a while until we see both collections
TimeOut waitUntil = new TimeOut(30, TimeUnit.SECONDS, ocmh.timeSource);
boolean created = false;
@@ -439,14 +429,14 @@ public class ReindexCollectionCmd implements OverseerCollectionMessageHandler.Cm
// 5. if (sameTarget) set up an alias to use targetCollection as the source name
if (sameTarget) {
- log.debug("- setting up alias from " + originalCollection + " to " + targetCollection);
+ log.debug("- setting up alias from " + extCollection + " to " + targetCollection);
cmd = new ZkNodeProps(
- CommonParams.NAME, originalCollection,
+ CommonParams.NAME, extCollection,
"collections", targetCollection);
cmdResults = new NamedList<>();
- ocmh.commandMap.get(CollectionParams.CollectionAction.CREATEALIAS).call(clusterState, cmd, results);
- checkResults("setting up alias " + originalCollection + " -> " + targetCollection, cmdResults, true);
- reindexingState.put("alias", originalCollection + " -> " + targetCollection);
+ ocmh.commandMap.get(CollectionParams.CollectionAction.CREATEALIAS).call(clusterState, cmd, cmdResults);
+ ocmh.checkResults("setting up alias " + extCollection + " -> " + targetCollection, cmdResults, true);
+ reindexingState.put("alias", extCollection + " -> " + targetCollection);
}
reindexingState.remove("daemonUrl");
@@ -468,7 +458,7 @@ public class ReindexCollectionCmd implements OverseerCollectionMessageHandler.Cm
);
cmdResults = new NamedList<>();
ocmh.commandMap.get(CollectionParams.CollectionAction.DELETE).call(clusterState, cmd, cmdResults);
- checkResults("deleting checkpoint collection " + chkCollection, cmdResults, true);
+ ocmh.checkResults("deleting checkpoint collection " + chkCollection, cmdResults, true);
// 7. optionally delete the source collection
if (removeSource) {
@@ -480,7 +470,7 @@ public class ReindexCollectionCmd implements OverseerCollectionMessageHandler.Cm
);
cmdResults = new NamedList<>();
ocmh.commandMap.get(CollectionParams.CollectionAction.DELETE).call(clusterState, cmd, cmdResults);
- checkResults("deleting source collection " + collection, cmdResults, true);
+ ocmh.checkResults("deleting source collection " + collection, cmdResults, true);
} else {
// 8. clear readOnly on source
ZkNodeProps props = new ZkNodeProps(
@@ -500,7 +490,7 @@ public class ReindexCollectionCmd implements OverseerCollectionMessageHandler.Cm
reindexingState.put(PHASE, "done");
removeReindexingState(collection);
} catch (Exception e) {
- log.warn("Error during reindexing of " + originalCollection, e);
+ log.warn("Error during reindexing of " + extCollection, e);
exc = e;
aborted = true;
} finally {
@@ -563,21 +553,6 @@ public class ReindexCollectionCmd implements OverseerCollectionMessageHandler.Cm
}
}
- private void checkResults(String label, NamedList<Object> results, boolean failureIsFatal) throws Exception {
- Object failure = results.get("failure");
- if (failure == null) {
- failure = results.get("error");
- }
- if (failure != null) {
- String msg = "Error: " + label + ": " + Utils.toJSONString(results);
- if (failureIsFatal) {
- throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, msg);
- } else {
- log.error(msg);
- }
- }
- }
-
private boolean maybeAbort(String collection) throws Exception {
DocCollection coll = ocmh.cloudManager.getClusterStateProvider().getClusterState().getCollectionOrNull(collection);
if (coll == null) {
@@ -798,7 +773,7 @@ public class ReindexCollectionCmd implements OverseerCollectionMessageHandler.Cm
CoreAdminParams.DELETE_METRICS_HISTORY, "true"
);
ocmh.commandMap.get(CollectionParams.CollectionAction.DELETE).call(clusterState, cmd, cmdResults);
- checkResults("CLEANUP: deleting target collection " + targetCollection, cmdResults, false);
+ ocmh.checkResults("CLEANUP: deleting target collection " + targetCollection, cmdResults, false);
}
// remove chk collection
@@ -811,7 +786,7 @@ public class ReindexCollectionCmd implements OverseerCollectionMessageHandler.Cm
);
cmdResults = new NamedList<>();
ocmh.commandMap.get(CollectionParams.CollectionAction.DELETE).call(clusterState, cmd, cmdResults);
- checkResults("CLEANUP: deleting checkpoint collection " + chkCollection, cmdResults, false);
+ ocmh.checkResults("CLEANUP: deleting checkpoint collection " + chkCollection, cmdResults, false);
}
log.debug(" -- turning readOnly mode off for " + collection);
ZkNodeProps props = new ZkNodeProps(
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
new file mode 100644
index 0000000..2a33e49
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/RenameCmd.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.solr.cloud.api.collections;
+
+import java.lang.invoke.MethodHandles;
+
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.cloud.Aliases;
+import org.apache.solr.common.cloud.ClusterState;
+import org.apache.solr.common.cloud.ZkNodeProps;
+import org.apache.solr.common.params.CollectionAdminParams;
+import org.apache.solr.common.params.CoreAdminParams;
+import org.apache.solr.common.util.NamedList;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ */
+public class RenameCmd implements OverseerCollectionMessageHandler.Cmd {
+ private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+ private final OverseerCollectionMessageHandler ocmh;
+
+ public RenameCmd(OverseerCollectionMessageHandler ocmh) {
+ this.ocmh = ocmh;
+ }
+
+ @Override
+ public void call(ClusterState state, ZkNodeProps message, NamedList results) throws Exception {
+ String extCollectionName = message.getStr(CoreAdminParams.NAME);
+ String target = message.getStr(CollectionAdminParams.TARGET);
+
+ if (ocmh.zkStateReader.aliasesManager != null) { // not a mock ZkStateReader
+ ocmh.zkStateReader.aliasesManager.update();
+ }
+
+ if (extCollectionName == null || target == null) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "both collection 'name' and 'target' name must be specified");
+ }
+ Aliases aliases = ocmh.zkStateReader.getAliases();
+
+ String collectionName = aliases.resolveSimpleAlias(extCollectionName);
+ if (!state.hasCollection(collectionName)) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "source collection '" + collectionName + "' not found.");
+ }
+ if (ocmh.zkStateReader.getAliases().hasAlias(target)) {
+ throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "target alias '" + target + "' exists: "
+ + ocmh.zkStateReader.getAliases().getCollectionAliasListMap().get(target));
+ }
+
+ ocmh.zkStateReader.aliasesManager.applyModificationAndExportToZk(a -> a.cloneWithRename(extCollectionName, target));
+
+ }
+}
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/RestoreCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/RestoreCmd.java
index 3a70f11..1983562 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/RestoreCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/RestoreCmd.java
@@ -107,6 +107,7 @@ public class RestoreCmd implements OverseerCollectionMessageHandler.Cmd {
Properties properties = backupMgr.readBackupProperties(location, backupName);
String backupCollection = properties.getProperty(BackupManager.COLLECTION_NAME_PROP);
+ String backupCollectionAlias = properties.getProperty(BackupManager.COLLECTION_ALIAS_PROP);
DocCollection backupCollectionState = backupMgr.readCollectionState(location, backupName, backupCollection);
// Get the Solr nodes to restore a collection.
@@ -416,6 +417,12 @@ public class RestoreCmd implements OverseerCollectionMessageHandler.Cmd {
}
}
+ if (backupCollectionAlias != null && !backupCollectionAlias.equals(backupCollection)) {
+ log.debug("Restoring alias {} -> {}", backupCollectionAlias, backupCollection);
+ ocmh.zkStateReader.aliasesManager
+ .applyModificationAndExportToZk(a -> a.cloneWithCollectionAlias(backupCollectionAlias, backupCollection));
+ }
+
log.info("Completed restoring collection={} backupName={}", restoreCollection, backupName);
} finally {
if (sessionWrapper != null) sessionWrapper.release();
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/RoutedAlias.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/RoutedAlias.java
index 8bb95cc..027100f 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/RoutedAlias.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/RoutedAlias.java
@@ -29,6 +29,7 @@ import org.apache.solr.update.AddUpdateCommand;
import static org.apache.solr.common.SolrException.ErrorCode.BAD_REQUEST;
import static org.apache.solr.common.SolrException.ErrorCode.SERVER_ERROR;
+import static org.apache.solr.common.params.CollectionAdminParams.ROUTER_PREFIX;
public interface RoutedAlias {
@@ -40,7 +41,6 @@ public interface RoutedAlias {
CATEGORY
}
- String ROUTER_PREFIX = "router.";
String ROUTER_TYPE_NAME = ROUTER_PREFIX + "name";
String ROUTER_FIELD = ROUTER_PREFIX + "field";
String CREATE_COLLECTION_PREFIX = "create-collection.";
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 1040d79..4658733 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
@@ -107,7 +107,9 @@ public class SplitShardCmd implements OverseerCollectionMessageHandler.Cmd {
}
boolean withTiming = message.getBool(CommonParams.TIMING, false);
- String collectionName = message.getStr(CoreAdminParams.COLLECTION);
+ String extCollectionName = message.getStr(CoreAdminParams.COLLECTION);
+
+ String collectionName = ocmh.cloudManager.getClusterStateProvider().resolveSimpleAlias(extCollectionName);
log.debug("Split shard invoked: {}", message);
ZkStateReader zkStateReader = ocmh.zkStateReader;
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/TimeRoutedAlias.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/TimeRoutedAlias.java
index 94a4a84..8685961 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/TimeRoutedAlias.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/TimeRoutedAlias.java
@@ -61,6 +61,7 @@ import static org.apache.solr.cloud.api.collections.TimeRoutedAlias.CreationType
import static org.apache.solr.cloud.api.collections.TimeRoutedAlias.CreationType.NONE;
import static org.apache.solr.cloud.api.collections.TimeRoutedAlias.CreationType.SYNCHRONOUS;
import static org.apache.solr.common.SolrException.ErrorCode.BAD_REQUEST;
+import static org.apache.solr.common.params.CollectionAdminParams.ROUTER_PREFIX;
import static org.apache.solr.common.params.CommonParams.TZ;
/**
diff --git a/solr/core/src/java/org/apache/solr/core/backup/BackupManager.java b/solr/core/src/java/org/apache/solr/core/backup/BackupManager.java
index afba4b1..b15bbfe 100644
--- a/solr/core/src/java/org/apache/solr/core/backup/BackupManager.java
+++ b/solr/core/src/java/org/apache/solr/core/backup/BackupManager.java
@@ -62,6 +62,7 @@ public class BackupManager {
// Backup properties
public static final String COLLECTION_NAME_PROP = "collection";
+ public static final String COLLECTION_ALIAS_PROP = "collectionAlias";
public static final String BACKUP_NAME_PROP = "backupName";
public static final String INDEX_VERSION_PROP = "index.version";
public static final String START_TIME_PROP = "startTime";
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 921e7c8..fc7b59a 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
@@ -137,6 +137,7 @@ import static org.apache.solr.common.cloud.ZkStateReader.REPLICA_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.cloud.ZkStateReader.TLOG_REPLICAS;
+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;
@@ -481,7 +482,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
NRT_REPLICAS,
POLICY,
WAIT_FOR_FINAL_STATE,
- WITH_COLLECTION);
+ WITH_COLLECTION,
+ ALIAS);
props.putIfAbsent(STATE_FORMAT, "2");
@@ -542,6 +544,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
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)),
+
REINDEXCOLLECTION_OP(REINDEXCOLLECTION, (req, rsp, h) -> {
Map<String, Object> m = copy(req.getParams().required(), null, NAME);
copy(req.getParams(), m,
@@ -572,7 +576,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
}),
SYNCSHARD_OP(SYNCSHARD, (req, rsp, h) -> {
- String collection = req.getParams().required().get("collection");
+ String extCollection = req.getParams().required().get("collection");
+ String collection = h.coreContainer.getZkController().getZkStateReader().getAliases().resolveSimpleAlias(extCollection);
String shard = req.getParams().required().get("shard");
ClusterState clusterState = h.coreContainer.getZkController().getClusterState();
@@ -811,7 +816,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
return null;
}),
COLLECTIONPROP_OP(COLLECTIONPROP, (req, rsp, h) -> {
- String collection = req.getParams().required().get(NAME);
+ String extCollection = req.getParams().required().get(NAME);
+ String collection = h.coreContainer.getZkController().getZkStateReader().getAliases().resolveSimpleAlias(extCollection);
String name = req.getParams().required().get(PROPERTY_NAME);
String val = req.getParams().get(PROPERTY_VALUE);
CollectionProperties cp = new CollectionProperties(h.coreContainer.getZkController().getZkClient());
@@ -921,6 +927,7 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
NamedList<Object> results = new NamedList<>();
Map<String, DocCollection> collections = h.coreContainer.getZkController().getZkStateReader().getClusterState().getCollectionsMap();
List<String> collectionList = new ArrayList<>(collections.keySet());
+ // XXX should we add aliases here?
results.add("collections", collectionList);
SolrResponse response = new OverseerSolrResponse(results);
rsp.getValues().addAll(response.getResponse());
@@ -1027,7 +1034,9 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
BACKUP_OP(BACKUP, (req, rsp, h) -> {
req.getParams().required().check(NAME, COLLECTION_PROP);
- String collectionName = req.getParams().get(COLLECTION_PROP);
+ String extCollectionName = req.getParams().get(COLLECTION_PROP);
+ String collectionName = h.coreContainer.getZkController().getZkStateReader()
+ .getAliases().resolveSimpleAlias(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.");
@@ -1077,6 +1086,9 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
if (clusterState.hasCollection(collectionName)) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Collection '" + collectionName + "' exists, no action taken.");
}
+ if (h.coreContainer.getZkController().getZkStateReader().getAliases().hasAlias(collectionName)) {
+ throw new SolrException(ErrorCode.BAD_REQUEST, "Collection '" + collectionName + "' is an existing alias, no action taken.");
+ }
CoreContainer cc = h.coreContainer;
String repo = req.getParams().get(CoreAdminParams.BACKUP_REPOSITORY);
@@ -1126,7 +1138,9 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
CREATESNAPSHOT_OP(CREATESNAPSHOT, (req, rsp, h) -> {
req.getParams().required().check(COLLECTION_PROP, CoreAdminParams.COMMIT_NAME);
- String collectionName = req.getParams().get(COLLECTION_PROP);
+ String extCollectionName = req.getParams().get(COLLECTION_PROP);
+ String collectionName = h.coreContainer.getZkController().getZkStateReader()
+ .getAliases().resolveSimpleAlias(extCollectionName);
String commitName = req.getParams().get(CoreAdminParams.COMMIT_NAME);
ClusterState clusterState = h.coreContainer.getZkController().getClusterState();
if (!clusterState.hasCollection(collectionName)) {
@@ -1146,7 +1160,9 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
DELETESNAPSHOT_OP(DELETESNAPSHOT, (req, rsp, h) -> {
req.getParams().required().check(COLLECTION_PROP, CoreAdminParams.COMMIT_NAME);
- String collectionName = req.getParams().get(COLLECTION_PROP);
+ String extCollectionName = req.getParams().get(COLLECTION_PROP);
+ String collectionName = h.coreContainer.getZkController().getZkStateReader()
+ .getAliases().resolveSimpleAlias(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.");
@@ -1158,7 +1174,9 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
LISTSNAPSHOTS_OP(LISTSNAPSHOTS, (req, rsp, h) -> {
req.getParams().required().check(COLLECTION_PROP);
- String collectionName = req.getParams().get(COLLECTION_PROP);
+ String extCollectionName = req.getParams().get(COLLECTION_PROP);
+ String collectionName = h.coreContainer.getZkController().getZkStateReader()
+ .getAliases().resolveSimpleAlias(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.");
@@ -1256,7 +1274,8 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
private static void forceLeaderElection(SolrQueryRequest req, CollectionsHandler handler) {
ZkController zkController = handler.coreContainer.getZkController();
ClusterState clusterState = zkController.getClusterState();
- String collectionName = req.getParams().required().get(COLLECTION_PROP);
+ String extCollectionName = req.getParams().required().get(COLLECTION_PROP);
+ String collectionName = zkController.zkStateReader.getAliases().resolveSimpleAlias(extCollectionName);
String sliceId = req.getParams().required().get(SHARD_ID_PROP);
log.info("Force leader invoked, state: {}", clusterState);
diff --git a/solr/core/src/java/org/apache/solr/handler/sql/SolrSchema.java b/solr/core/src/java/org/apache/solr/handler/sql/SolrSchema.java
index e4d7a2d..5b0a74f 100644
--- a/solr/core/src/java/org/apache/solr/handler/sql/SolrSchema.java
+++ b/solr/core/src/java/org/apache/solr/handler/sql/SolrSchema.java
@@ -22,6 +22,7 @@ import java.util.EnumSet;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
+import java.util.Set;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
@@ -60,13 +61,17 @@ class SolrSchema extends AbstractSchema {
final ImmutableMap.Builder<String, Table> builder = ImmutableMap.builder();
- for (String collection : clusterState.getCollectionsMap().keySet()) {
+ Set<String> collections = clusterState.getCollectionsMap().keySet();
+ for (String collection : collections) {
builder.put(collection, new SolrTable(this, collection));
}
Aliases aliases = zkStateReader.getAliases();
for (String alias : aliases.getCollectionAliasListMap().keySet()) {
- builder.put(alias, new SolrTable(this, alias));
+ // don't create duplicate entries
+ if (!collections.contains(alias)) {
+ builder.put(alias, new SolrTable(this, alias));
+ }
}
return builder.build();
diff --git a/solr/core/src/java/org/apache/solr/search/join/ScoreJoinQParserPlugin.java b/solr/core/src/java/org/apache/solr/search/join/ScoreJoinQParserPlugin.java
index c2f8662..7bd78c0 100644
--- a/solr/core/src/java/org/apache/solr/search/join/ScoreJoinQParserPlugin.java
+++ b/solr/core/src/java/org/apache/solr/search/join/ScoreJoinQParserPlugin.java
@@ -17,7 +17,6 @@
package org.apache.solr.search.join;
import java.io.IOException;
-import java.util.List;
import java.util.Objects;
import org.apache.lucene.index.DocValuesType;
@@ -294,14 +293,13 @@ public class ScoreJoinQParserPlugin extends QParserPlugin {
private static String resolveAlias(String fromIndex, ZkController zkController) {
final Aliases aliases = zkController.getZkStateReader().getAliases();
- List<String> collections = aliases.resolveAliases(fromIndex); // if not an alias, returns input
- if (collections.size() != 1) {
+ try {
+ return aliases.resolveSimpleAlias(fromIndex); // if not an alias, returns input
+ } catch (IllegalArgumentException e) {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"SolrCloud join: Collection alias '" + fromIndex +
- "' maps to multiple collections (" + collections +
- "), which is not currently supported for joins.");
+ "' maps to multiple collectiions, which is not currently supported for joins.", e);
}
- return collections.get(0);
}
private static String findLocalReplicaForFromIndex(ZkController zkController, String fromIndex) {
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 ce02567..ab73781 100644
--- a/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/CollectionsAPISolrJTest.java
@@ -54,6 +54,7 @@ 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.SolrInputDocument;
+import org.apache.solr.common.cloud.Aliases;
import org.apache.solr.common.cloud.ClusterProperties;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.Replica;
@@ -791,6 +792,62 @@ public class CollectionsAPISolrJTest extends SolrCloudTestCase {
assertEquals("num docs after turning off read-only", NUM_DOCS * 3, rsp.getResults().getNumFound());
}
+ @Test
+ public void testRenameCollection() throws Exception {
+ String collectionName1 = "testRename_collection1";
+ String collectionName2 = "testRename_collection2";
+ CollectionAdminRequest.createCollection(collectionName1, "conf", 1, 1).setAlias("col1").process(cluster.getSolrClient());
+ CollectionAdminRequest.createCollection(collectionName2, "conf", 1, 1).setAlias("col2").process(cluster.getSolrClient());
+
+ 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));
+
+ CollectionAdminRequest.createAlias("compoundAlias", "col1,col2").process(cluster.getSolrClient());
+ CollectionAdminRequest.createAlias("simpleAlias", "col1").process(cluster.getSolrClient());
+ CollectionAdminRequest.createCategoryRoutedAlias("catAlias", "field1", 100,
+ CollectionAdminRequest.createCollection("_unused_", "conf", 1, 1)).process(cluster.getSolrClient());
+
+ CollectionAdminRequest.renameCollection("col1", "foo").process(cluster.getSolrClient());
+ 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));
+
+ CollectionAdminRequest.renameCollection(collectionName1, collectionName2).process(cluster.getSolrClient());
+ zkStateReader.aliasesManager.update();
+
+ aliases = zkStateReader.getAliases();
+ 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");
+ assertEquals(compoundAliases.toString(), 1, compoundAliases.size());
+ assertTrue(compoundAliases.toString(), compoundAliases.contains(collectionName2));
+
+ try {
+ CollectionAdminRequest.renameCollection("catAlias", "bar").process(cluster.getSolrClient());
+ fail("category-based alias renaming should fail");
+ } catch (Exception e) {
+ assertTrue(e.toString().contains("is a routed alias"));
+ }
+
+ try {
+ CollectionAdminRequest.renameCollection("col2", "foo").process(cluster.getSolrClient());
+ fail("shuold fail because 'foo' already exists");
+ } catch (Exception e) {
+ assertTrue(e.toString().contains("exists"));
+ }
+ }
@Test
public void testOverseerStatus() throws IOException, SolrServerException {
diff --git a/solr/solr-ref-guide/src/aliases.adoc b/solr/solr-ref-guide/src/aliases.adoc
index 610530e..8654031 100644
--- a/solr/solr-ref-guide/src/aliases.adoc
+++ b/solr/solr-ref-guide/src/aliases.adoc
@@ -48,6 +48,17 @@ With multiple collections in an alias this is always a problem, so if you have a
However, for analytical use cases where results are sorted on numeric, date or alphanumeric field values rather
than relevancy calculations this is not a problem.
+== Collection admin commands and aliases
+Starting with version 8.1 SolrCloud supports using alias names in collection admin commands where normally a
+collection name is expected. This works only when the following criteria are satisfied:
+
+* an alias must not refer to more than one collection
+* an alias must not refer to a Routed Alias (see below)
+
+If all criteria are satisfied then the command will resolve 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.
+
== Routed Aliases
To address the update limitations associated with standard aliases and provide additional useful features, the concept of
diff --git a/solr/solr-ref-guide/src/collections-api.adoc b/solr/solr-ref-guide/src/collections-api.adoc
index 07e112f..1c39a1b 100644
--- a/solr/solr-ref-guide/src/collections-api.adoc
+++ b/solr/solr-ref-guide/src/collections-api.adoc
@@ -120,6 +120,11 @@ If `true`, the request will complete only when all affected replicas become acti
The name of the collection with which all replicas of this collection must be co-located. The collection must already exist and must have a single shard named `shard1`.
See <<colocating-collections.adoc#colocating-collections, Colocating collections>> for more details.
+`alias`::
+Starting with version 8.1 when a collection is created additionally an alias (by default with the same name) is created
+that points to this collection. This parameter allows changing the name of this alias, effectively combining
+this operation with <<createalias, CREATEALIAS>>
+
Collections are first created in read-write mode but can be put in `readOnly`
mode using the <<modifycollection, MODIFYCOLLECTION>> action.
@@ -392,6 +397,65 @@ http://localhost:8983/solr/admin/collections?action=RELOAD&name=newCollection&wt
</response>
----
+[[rename]]
+== RENAME: Rename a Collection
+
+`/admin/collections?action=RENAME&name=_existingName_&target=_targetName_`
+
+Renaming a collection sets up a standard alias that points to the underlying collection, so
+that the same (unmodified) collection can now be referred to in query, index and admin operations
+using the new name.
+
+This command does NOT actually rename the underlying Solr collection - it sets up a new one-to-one alias
+using the new name, or renames the existing alias so that it uses the new name, while still referring to
+the same underlying Solr collection. However, from the user's point of view the collection can now be
+accessed using the new name, and the new name can be also referred to in other aliases.
+
+The following limitations apply:
+
+* the existing name must be either a SolrCloud collection or a standard alias referring to a single collection.
+Aliases that refer to more than 1 collection are not supported.
+* the existing name must not be a Routed Alias.
+* the target name must not be an existing alias.
+
+=== RENAME Command Parameters
+
+`name`::
+Name of the existing SolrCloud collection or an alias that refers to exactly one collection and is not
+a Routed Alias.
+
+`target`::
+Target name of the collection. This will be the new alias that refers to the underlying SolrCloud collection.
+The original name (or alias) of the collection will be replaced also in the existing aliases so that they
+also refer to the new name. Target name must not be an existing alias.
+
+=== Examples using RENAME
+Assuming there are two actual SolrCloud collections named `collection1` and `collection2`,
+and the following aliases already exist:
+
+* `col1 -> collection1`: this resolves to `collection1`.
+* `col2 -> collection2`: this resolves to `collection2`.
+* `simpleAlias -> col1`: this resolves to `collection1`.
+* `compoundAlias -> col1,col2`: this resolves to `collection1,collection2`
+
+The RENAME of `col1` to `foo` will change the aliases to the following:
+
+* `foo -> collection1`: this resolves to `collection1`.
+* `col2 -> collection2`: this resolves to `collection2`.
+* `simpleAlias -> foo`: this resolves to `collection1`.
+* `compoundAlias -> foo,col2`: this resolves to `collection1,collection2`.
+
+If we then rename `collection1` (which is an actual collection name) to `collection2` (which is also
+an actual collection name) the following aliases will exist now:
+
+* `foo -> collection2`: this resolves to `collection2`.
+* `col2 -> collection2`: this resolves to `collection2`.
+* `simpleAlias -> foo`: this resolves to `collection2`.
+* `compoundAlias -> foo,col2`: this would resolve now to `collection2,collection2` so it's reduced to simply `collection2`.
+* `collection1` -> `collection2`: this newly created alias effectively hides `collection1` from regular query and
+update commands, which are directed now to `collection2`.
+
+
[[splitshard]]
== SPLITSHARD: Split a Shard
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/cloud/autoscaling/DelegatingClusterStateProvider.java b/solr/solrj/src/java/org/apache/solr/client/solrj/cloud/autoscaling/DelegatingClusterStateProvider.java
index e0b9bac..437e1c5 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/cloud/autoscaling/DelegatingClusterStateProvider.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/cloud/autoscaling/DelegatingClusterStateProvider.java
@@ -64,6 +64,15 @@ public class DelegatingClusterStateProvider implements ClusterStateProvider {
}
@Override
+ public String resolveSimpleAlias(String alias) throws IllegalArgumentException {
+ if (delegate != null) {
+ return delegate.resolveSimpleAlias(alias);
+ } else {
+ return alias;
+ }
+ }
+
+ @Override
public ClusterState getClusterState() throws IOException {
if (delegate != null) {
return delegate.getClusterState();
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/BaseCloudSolrClient.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/BaseCloudSolrClient.java
index 7ae1e02..e320624 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/BaseCloudSolrClient.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/BaseCloudSolrClient.java
@@ -1062,11 +1062,7 @@ public abstract class BaseCloudSolrClient extends SolrClient {
for (String collectionName : inputCollections) {
if (getClusterStateProvider().getState(collectionName) == null) {
// perhaps it's an alias
- List<String> aliasedCollections = getClusterStateProvider().resolveAlias(collectionName);
- // one more level of alias indirection... (dubious that we should support this)
- for (String aliasedCollection : aliasedCollections) {
- collectionNames.addAll(getClusterStateProvider().resolveAlias(aliasedCollection));
- }
+ collectionNames.addAll(getClusterStateProvider().resolveAlias(collectionName));
} else {
collectionNames.add(collectionName); // it's a collection
}
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/BaseHttpClusterStateProvider.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/BaseHttpClusterStateProvider.java
index 042b6e4..461e993 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/BaseHttpClusterStateProvider.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/BaseHttpClusterStateProvider.java
@@ -192,6 +192,11 @@ public abstract class BaseHttpClusterStateProvider implements ClusterStateProvid
return Aliases.resolveAliasesGivenAliasMap(getAliases(false), aliasName);
}
+ @Override
+ public String resolveSimpleAlias(String aliasName) throws IllegalArgumentException {
+ return Aliases.resolveSimpleAliasGivenAliasMap(getAliases(false), aliasName);
+ }
+
private Map<String, List<String>> getAliases(boolean forceFetch) {
if (this.liveNodes == null) {
throw new RuntimeException("We don't know of any live_nodes to fetch the"
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/ClusterStateProvider.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/ClusterStateProvider.java
index c04b80d..26c95c1 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/ClusterStateProvider.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/ClusterStateProvider.java
@@ -45,6 +45,19 @@ public interface ClusterStateProvider extends SolrCloseable {
List<String> resolveAlias(String alias);
/**
+ * Given a collection alias, return a single collection it points to, or the original name if it's not an
+ * alias.
+ * @throws IllegalArgumentException if an alias points to more than 1 collection, either directly or indirectly.
+ */
+ default String resolveSimpleAlias(String alias) throws IllegalArgumentException {
+ List<String> aliases = resolveAlias(alias);
+ if (aliases.size() > 1) {
+ throw new IllegalArgumentException("Simple alias '" + alias + "' points to more than 1 collection: " + aliases);
+ }
+ return aliases.get(0);
+ }
+
+ /**
* Obtain the current cluster state.
*/
ClusterState getClusterState() throws IOException;
diff --git a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/ZkClientClusterStateProvider.java b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/ZkClientClusterStateProvider.java
index 53ff466..e1f33a8 100644
--- a/solr/solrj/src/java/org/apache/solr/client/solrj/impl/ZkClientClusterStateProvider.java
+++ b/solr/solrj/src/java/org/apache/solr/client/solrj/impl/ZkClientClusterStateProvider.java
@@ -93,6 +93,11 @@ public class ZkClientClusterStateProvider implements ClusterStateProvider {
}
@Override
+ public String resolveSimpleAlias(String alias) throws IllegalArgumentException {
+ return zkStateReader.getAliases().resolveSimpleAlias(alias);
+ }
+
+ @Override
public Object getClusterProperty(String propertyName) {
Map<String, Object> props = zkStateReader.getClusterProperties();
return props.get(propertyName);
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 ad1c6b7..b747f7e 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
@@ -67,6 +67,7 @@ import static org.apache.solr.common.params.CollectionAdminParams.COLOCATED_WITH
import static org.apache.solr.common.params.CollectionAdminParams.COUNT_PROP;
import static org.apache.solr.common.params.CollectionAdminParams.CREATE_NODE_SET_PARAM;
import static org.apache.solr.common.params.CollectionAdminParams.CREATE_NODE_SET_SHUFFLE_PARAM;
+import static org.apache.solr.common.params.CollectionAdminParams.ALIAS;
import static org.apache.solr.common.params.CollectionAdminParams.WITH_COLLECTION;
/**
@@ -433,6 +434,7 @@ public abstract class CollectionAdminRequest<T extends CollectionAdminResponse>
protected Properties properties;
protected Boolean autoAddReplicas;
+ protected String alias;
protected Integer stateFormat;
protected String[] rule , snitch;
protected String withCollection;
@@ -476,6 +478,11 @@ public abstract class CollectionAdminRequest<T extends CollectionAdminResponse>
public Create setRule(String... s){ this.rule = s; return this; }
public Create setSnitch(String... s){ this.snitch = s; return this; }
+ public Create setAlias(String alias) {
+ this.alias = alias;
+ return this;
+ }
+
public String getConfigName() { return configName; }
public String getCreateNodeSet() { return createNodeSet; }
public String getRouterName() { return routerName; }
@@ -573,6 +580,7 @@ public abstract class CollectionAdminRequest<T extends CollectionAdminResponse>
if (snitch != null) params.set(DocCollection.SNITCH, snitch);
params.setNonNull(POLICY, policy);
params.setNonNull(WITH_COLLECTION, withCollection);
+ params.setNonNull(ALIAS, alias);
return params;
}
@@ -606,6 +614,26 @@ public abstract class CollectionAdminRequest<T extends CollectionAdminResponse>
}
}
+ public static Rename renameCollection(String collection, String target) {
+ return new Rename(collection, target);
+ }
+
+ public static class Rename extends AsyncCollectionSpecificAdminRequest {
+ String target;
+
+ public Rename(String collection, String target) {
+ super(CollectionAction.RENAME, collection);
+ this.target = target;
+ }
+
+ @Override
+ public SolrParams getParams() {
+ ModifiableSolrParams params = (ModifiableSolrParams) super.getParams();
+ params.set(CollectionAdminParams.TARGET, target);
+ return params;
+ }
+ }
+
/**
* Returns a SolrRequest to delete a node.
*/
diff --git a/solr/solrj/src/java/org/apache/solr/common/cloud/Aliases.java b/solr/solrj/src/java/org/apache/solr/common/cloud/Aliases.java
index 439b936..c603391 100644
--- a/solr/solrj/src/java/org/apache/solr/common/cloud/Aliases.java
+++ b/solr/solrj/src/java/org/apache/solr/common/cloud/Aliases.java
@@ -19,6 +19,7 @@ package org.apache.solr.common.cloud;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
@@ -26,6 +27,7 @@ import java.util.Objects;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
+import org.apache.solr.common.params.CollectionAdminParams;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.Utils;
@@ -180,26 +182,74 @@ public class Aliases {
return resolveAliasesGivenAliasMap(collectionAliases, aliasName);
}
+ /**
+ * Returns true if an alias is defined, false otherwise.
+ */
+ public boolean hasAlias(String aliasName) {
+ return collectionAliases.containsKey(aliasName);
+ }
+
+ /**
+ * Resolve an alias that points to a single collection. One level of alias indirection is supported.
+ * @param aliasName alias name
+ * @return original name if there's no such alias, or a resolved name. If an alias points to more than 1
+ * collection (directly or indirectly) an exception is thrown
+ * @throws IllegalArgumentException if either direct or indirect alias points to more than 1 name.
+ */
+ public String resolveSimpleAlias(String aliasName) throws IllegalArgumentException {
+ return resolveSimpleAliasGivenAliasMap(collectionAliases, aliasName);
+ }
+
+ /** @lucene.internal */
+ @SuppressWarnings("JavaDoc")
+ public static String resolveSimpleAliasGivenAliasMap(Map<String, List<String>> collectionAliasListMap,
+ String aliasName) throws IllegalArgumentException {
+ List<String> level1 = collectionAliasListMap.get(aliasName);
+ if (level1 == null || level1.isEmpty()) {
+ return aliasName; // simple collection name
+ }
+ if (level1.size() > 1) {
+ throw new IllegalArgumentException("Simple alias '" + aliasName + "' points to more than 1 collection: " + level1);
+ }
+ List<String> level2 = collectionAliasListMap.get(level1.get(0));
+ if (level2 == null || level2.isEmpty()) {
+ return level1.get(0); // simple alias
+ }
+ if (level2.size() > 1) {
+ throw new IllegalArgumentException("Simple alias '" + aliasName + "' resolves to '"
+ + level1.get(0) + "' which points to more than 1 collection: " + level2);
+ }
+ return level2.get(0);
+ }
+
/** @lucene.internal */
@SuppressWarnings("JavaDoc")
public static List<String> resolveAliasesGivenAliasMap(Map<String, List<String>> collectionAliasListMap, String aliasName) {
- //return collectionAliasListMap.getOrDefault(aliasName, Collections.singletonList(aliasName));
- // TODO deprecate and remove this dubious feature?
// Due to another level of indirection, this is more complicated...
List<String> level1 = collectionAliasListMap.get(aliasName);
if (level1 == null) {
return Collections.singletonList(aliasName);// is a collection
}
- List<String> result = new ArrayList<>(level1.size());
- for (String level1Alias : level1) {
+ // avoid allocating objects if possible
+ LinkedHashSet<String> uniqueResult = null;
+ for (int i = 0; i < level1.size(); i++) {
+ String level1Alias = level1.get(i);
List<String> level2 = collectionAliasListMap.get(level1Alias);
- if (level2 == null) {
- result.add(level1Alias);
+ if (level2 == null && uniqueResult != null) {
+ uniqueResult.add(level1Alias);
} else {
- result.addAll(level2);
+ if (uniqueResult == null) { // lazy init
+ uniqueResult = new LinkedHashSet<>(level1.size());
+ uniqueResult.addAll(level1.subList(0, i));
+ }
+ uniqueResult.addAll(level2);
}
}
- return Collections.unmodifiableList(result);
+ if (uniqueResult == null) {
+ return level1;
+ } else {
+ return Collections.unmodifiableList(new ArrayList<>(uniqueResult));
+ }
}
/**
@@ -222,6 +272,15 @@ public class Aliases {
newColProperties = new LinkedHashMap<>(this.collectionAliasProperties);//clone to modify
newColProperties.remove(alias);
newColAliases.remove(alias);
+ // remove second-level alias from compound aliases
+ for (Map.Entry<String, List<String>> entry : newColAliases.entrySet()) {
+ List<String> list = entry.getValue();
+ if (list.contains(alias)) {
+ list = new ArrayList<>(list);
+ list.remove(alias);
+ entry.setValue(Collections.unmodifiableList(list));
+ }
+ }
} else {
newColProperties = this.collectionAliasProperties;// no changes
// java representation is a list, so split before adding to maintain consistency
@@ -231,6 +290,69 @@ public class Aliases {
}
/**
+ * Rename an alias. This performs a "deep rename", which changes also the second-level alias lists.
+ * Renaming routed aliases is not supported.
+ * <p>
+ * Note that the state in zookeeper is unaffected by this method and the change must still be persisted via
+ * {@link ZkStateReader.AliasesManager#applyModificationAndExportToZk(UnaryOperator)}
+ *
+ * @param before previous alias name, must not be null
+ * @param after new alias name. If this is null then it's equivalent to calling {@link #cloneWithCollectionAlias(String, String)}
+ * with the second argument set to null, ie. removing an alias.
+ * @return new instance with the renamed alias
+ * @throws IllegalArgumentException when either <code>before</code> or <code>after</code> is empty, or
+ * the <code>before</code> name is a routed alias
+ */
+ public Aliases cloneWithRename(String before, String after) {
+ if (before == null) {
+ throw new NullPointerException("'before' and 'after' cannot be null");
+ }
+ if (after == null) {
+ return cloneWithCollectionAlias(before, after);
+ }
+ if (before.isEmpty() || after.isEmpty()) {
+ throw new IllegalArgumentException("'before' and 'after' cannot be empty");
+ }
+ if (before.equals(after)) {
+ return this;
+ }
+ Map<String, String> props = collectionAliasProperties.get(before);
+ if (props != null) {
+ if (props.keySet().stream().anyMatch(k -> k.startsWith(CollectionAdminParams.ROUTER_PREFIX))) {
+ throw new IllegalArgumentException("source name '" + before + "' is a routed alias.");
+ }
+ }
+ Map<String, Map<String, String>> newColProperties = new LinkedHashMap<>(this.collectionAliasProperties);
+ Map<String, List<String>> newColAliases = new LinkedHashMap<>(this.collectionAliases);//clone to modify
+ List<String> level1 = newColAliases.remove(before);
+ props = newColProperties.remove(before);
+ if (level1 != null) {
+ newColAliases.put(after, level1);
+ }
+ if (props != null) {
+ newColProperties.put(after, props);
+ }
+ for (Map.Entry<String, List<String>> entry : newColAliases.entrySet()) {
+ List<String> collections = entry.getValue();
+ if (collections.contains(before)) {
+ LinkedHashSet<String> newCollections = new LinkedHashSet<>(collections.size());
+ for (String coll : collections) {
+ if (coll.equals(before)) {
+ newCollections.add(after);
+ } else {
+ newCollections.add(coll);
+ }
+ }
+ entry.setValue(Collections.unmodifiableList(new ArrayList<>(newCollections)));
+ }
+ }
+ if (level1 == null) { // create an alias that points to the collection
+ newColAliases.put(before, Collections.singletonList(after));
+ }
+ return new Aliases(newColAliases, newColProperties, zNodeVersion);
+ }
+
+ /**
* Set the value for some properties on a collection alias. This is done by creating a new Aliases instance
* with the same data as the current one but with a modification based on the parameters.
* <p>
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 cf0faa8..5291b7d 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
@@ -109,4 +109,19 @@ public interface CollectionAdminParams {
* or the autoscaling policy based strategy to assign replicas to nodes. The default is false.
*/
String USE_LEGACY_REPLICA_ASSIGNMENT = "useLegacyReplicaAssignment";
+
+ /**
+ * When creating a collection create also a specified alias.
+ */
+ String ALIAS = "alias";
+
+ /**
+ * Specifies the target of RENAME operation.
+ */
+ String TARGET = "target";
+
+ /**
+ * Prefix for {@link org.apache.solr.common.cloud.DocRouter} properties
+ */
+ String ROUTER_PREFIX = "router.";
}
diff --git a/solr/solrj/src/java/org/apache/solr/common/params/CollectionParams.java b/solr/solrj/src/java/org/apache/solr/common/params/CollectionParams.java
index cfef82c..88b2aaa 100644
--- a/solr/solrj/src/java/org/apache/solr/common/params/CollectionParams.java
+++ b/solr/solrj/src/java/org/apache/solr/common/params/CollectionParams.java
@@ -125,7 +125,8 @@ public interface CollectionParams {
MERGESHARDS(true, LockLevel.SHARD),
COLSTATUS(true, LockLevel.NONE),
// this command implements its own locking
- REINDEXCOLLECTION(true, LockLevel.NONE)
+ REINDEXCOLLECTION(true, LockLevel.NONE),
+ RENAME(true, LockLevel.COLLECTION)
;
public final boolean isWrite;