From commits-return-107538-archive-asf-public=cust-asf.ponee.io@lucene.apache.org Wed Apr 10 18:12:51 2019 Return-Path: X-Original-To: archive-asf-public@cust-asf.ponee.io Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [207.244.88.153]) by mx-eu-01.ponee.io (Postfix) with SMTP id 139A8180626 for ; Wed, 10 Apr 2019 20:12:50 +0200 (CEST) Received: (qmail 51104 invoked by uid 500); 10 Apr 2019 18:12:50 -0000 Mailing-List: contact commits-help@lucene.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@lucene.apache.org Delivered-To: mailing list commits@lucene.apache.org Received: (qmail 51095 invoked by uid 99); 10 Apr 2019 18:12:50 -0000 Received: from ec2-52-202-80-70.compute-1.amazonaws.com (HELO gitbox.apache.org) (52.202.80.70) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 10 Apr 2019 18:12:50 +0000 Received: by gitbox.apache.org (ASF Mail Server at gitbox.apache.org, from userid 33) id 47E5C80D89; Wed, 10 Apr 2019 18:12:50 +0000 (UTC) Date: Wed, 10 Apr 2019 18:12:50 +0000 To: "commits@lucene.apache.org" Subject: [lucene-solr] branch branch_8x updated: SOLR-13262: Add collection RENAME command and support using aliases in most collection admin commands. MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Message-ID: <155491997006.16751.10364286833603061461@gitbox.apache.org> From: ab@apache.org X-Git-Host: gitbox.apache.org X-Git-Repo: lucene-solr X-Git-Refname: refs/heads/branch_8x X-Git-Reftype: branch X-Git-Oldrev: 4fe1f410f4c8ce83538e555a69abf97929946f31 X-Git-Newrev: f83098752c5340f352d146460fd8fc1caa03301c X-Git-Rev: f83098752c5340f352d146460fd8fc1caa03301c X-Git-NotificationType: ref_changed_plus_diff X-Git-Multimail-Version: 1.5.dev Auto-Submitted: auto-generated 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 f830987 SOLR-13262: Add collection RENAME command and support using aliases in most collection admin commands. f830987 is described below commit f83098752c5340f352d146460fd8fc1caa03301c Author: Andrzej Bialecki 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 34a71b2..a0a5634 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -82,6 +82,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()); 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 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 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 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 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 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 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 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 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 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 results = new NamedList<>(); Map collections = h.coreContainer.getZkController().getZkStateReader().getClusterState().getCollectionsMap(); List 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 builder = ImmutableMap.builder(); - for (String collection : clusterState.getCollectionsMap().keySet()) { + Set 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 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 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 <> 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 <> + Collections are first created in read-write mode but can be put in `readOnly` mode using the <> action. @@ -392,6 +397,65 @@ http://localhost:8983/solr/admin/collections?action=RELOAD&name=newCollection&wt ---- +[[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 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> 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 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 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 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 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 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 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 } } + 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> collectionAliasListMap, + String aliasName) throws IllegalArgumentException { + List 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 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 resolveAliasesGivenAliasMap(Map> 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 level1 = collectionAliasListMap.get(aliasName); if (level1 == null) { return Collections.singletonList(aliasName);// is a collection } - List result = new ArrayList<>(level1.size()); - for (String level1Alias : level1) { + // avoid allocating objects if possible + LinkedHashSet uniqueResult = null; + for (int i = 0; i < level1.size(); i++) { + String level1Alias = level1.get(i); List 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> entry : newColAliases.entrySet()) { + List 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. + *

+ * 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 before or after is empty, or + * the before 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 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> newColProperties = new LinkedHashMap<>(this.collectionAliasProperties); + Map> newColAliases = new LinkedHashMap<>(this.collectionAliases);//clone to modify + List 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> entry : newColAliases.entrySet()) { + List collections = entry.getValue(); + if (collections.contains(before)) { + LinkedHashSet 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. *

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;