This is an automated email from the ASF dual-hosted git repository.
gerlowskija pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr.git
The following commit(s) were added to refs/heads/main by this push:
new 1a2ab763ec8 SOLR-16393: Tweak v2 "create alias" API to be more
REST-ful (#1590)
1a2ab763ec8 is described below
commit 1a2ab763ec8a564f352344fdf6eeb9871f414b7f
Author: Jason Gerlowski <[email protected]>
AuthorDate: Wed Apr 26 11:02:17 2023 -0400
SOLR-16393: Tweak v2 "create alias" API to be more REST-ful (#1590)
This commit makes various cosmetic improvements to Solr's v2
create alias API, to bring it more into line with the more REST-ful
direction we're targeting for our v2 API. The v2 API is now available
at: `POST /api/aliases`. The request body has also been restructured
slightly: the top-level "create-alias" command property has been
removed, the router "name" field has been renamed to "type", and
routers now are always defined in a list.
This commit also migrates the API to JAX-RS.
---
solr/CHANGES.txt | 5 +
.../solr/cloud/api/collections/RoutedAlias.java | 6 +-
.../cloud/api/collections/TimeRoutedAlias.java | 4 +-
.../solr/handler/admin/CollectionsHandler.java | 168 +--------
.../solr/handler/admin/api/CreateAliasAPI.java | 416 +++++++++++++++++++--
.../handler/admin/api/CreateCollectionAPI.java | 13 +-
.../apache/solr/cloud/AliasIntegrationTest.java | 2 +-
.../apache/solr/cloud/CreateRoutedAliasTest.java | 74 ++--
.../solr/handler/admin/TestCollectionAPIs.java | 10 -
.../handler/admin/V2CollectionsAPIMappingTest.java | 68 ----
.../solr/handler/admin/api/CreateAliasAPITest.java | 348 +++++++++++++++++
.../deployment-guide/pages/alias-management.adoc | 104 +++---
.../solrj/request/beans/CreateAliasPayload.java | 63 ----
13 files changed, 849 insertions(+), 432 deletions(-)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 79456936332..dcdd1a7fd08 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -100,6 +100,11 @@ Improvements
* SOLR-15493: Throw an error message if the feature store doesn't exist.
(Ilaria Petreti via Alessandro Benedetti)
+* SOLR-16393: The v2 "create alias" API has been tweaked to be more intuitive.
The format of the request body has changed
+ slightly: the "create-alias" command specifier has been removed, the "name"
field for individual routers has been renamed
+ to "type", and the routers themselves are now always grouped into a list.
Additionally the v2 API has moved to the new path
+ `POST /api/aliases`. (Jason Gerlowski)
+
Optimizations
---------------------
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 93abb90faf4..d41eed3611d 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
@@ -62,7 +62,11 @@ public abstract class RoutedAlias {
public static final Set<String> MINIMAL_REQUIRED_PARAMS =
Set.of(ROUTER_TYPE_NAME, ROUTER_FIELD);
public static final String ROUTED_ALIAS_NAME_CORE_PROP = "routedAliasName";
// core prop
- private static final String DIMENSIONAL = "Dimensional[";
+ public static final String DIMENSIONAL = "Dimensional[";
+
+ public static final String TIME = "time";
+
+ public static final String CATEGORY = "category";
// This class is created once per request and the overseer methods prevent
duplicate create
// requests from creating extra copies via locking on the alias name. All we
need to track here is
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 ec7395bb81f..c833217d6b1 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
@@ -248,13 +248,13 @@ public class TimeRoutedAlias extends RoutedAlias {
return aliasName + TYPE.getSeparatorPrefix() + nextCollName;
}
- private Instant parseStringAsInstant(String str, TimeZone zone) {
+ public static Instant parseStringAsInstant(String str, TimeZone zone) {
Instant start = DateMathParser.parseMath(new Date(), str,
zone).toInstant();
checkMillis(start);
return start;
}
- private void checkMillis(Instant date) {
+ private static void checkMillis(Instant date) {
if (!date.truncatedTo(ChronoUnit.SECONDS).equals(date)) {
throw new SolrException(
BAD_REQUEST,
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 4268a95420f..aa9dd746529 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
@@ -30,7 +30,6 @@ import static
org.apache.solr.cloud.api.collections.CollectionHandlingUtils.ONLY
import static
org.apache.solr.cloud.api.collections.CollectionHandlingUtils.ONLY_IF_DOWN;
import static
org.apache.solr.cloud.api.collections.CollectionHandlingUtils.REQUESTID;
import static
org.apache.solr.cloud.api.collections.CollectionHandlingUtils.SHARD_UNIQUE;
-import static
org.apache.solr.cloud.api.collections.RoutedAlias.CREATE_COLLECTION_PREFIX;
import static org.apache.solr.common.SolrException.ErrorCode.BAD_REQUEST;
import static org.apache.solr.common.cloud.ZkStateReader.COLLECTION_PROP;
import static org.apache.solr.common.cloud.ZkStateReader.NRT_REPLICAS;
@@ -124,14 +123,12 @@ import static
org.apache.solr.common.params.ShardParams._ROUTE_;
import static org.apache.solr.common.util.StrUtils.formatString;
import java.io.IOException;
-import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
@@ -161,11 +158,9 @@ import
org.apache.solr.cloud.ZkController.NotInClusterStateException;
import org.apache.solr.cloud.ZkShardTerms;
import
org.apache.solr.cloud.api.collections.DistributedCollectionConfigSetCommandRunner;
import org.apache.solr.cloud.api.collections.ReindexCollectionCmd;
-import org.apache.solr.cloud.api.collections.RoutedAlias;
import org.apache.solr.cloud.overseer.SliceMutator;
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.ClusterProperties;
import org.apache.solr.common.cloud.ClusterState;
import org.apache.solr.common.cloud.DocCollection;
@@ -175,7 +170,6 @@ import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.Replica.State;
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.cloud.SolrZkClient;
-import org.apache.solr.common.cloud.ZkCmdExecutor;
import org.apache.solr.common.cloud.ZkCoreNodeProps;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.cloud.ZkStateReader;
@@ -243,7 +237,6 @@ import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.security.AuthorizationContext;
import org.apache.solr.security.PermissionNameProvider;
import org.apache.solr.util.tracing.TraceUtils;
-import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -299,16 +292,6 @@ public class CollectionsHandler extends RequestHandlerBase
implements Permission
return this.coreContainer;
}
- protected void copyFromClusterProp(Map<String, Object> props, String prop)
throws IOException {
- if (props.get(prop) != null) return; // if it's already specified , return
- Object defVal =
- new
ClusterProperties(coreContainer.getZkController().getZkStateReader().getZkClient())
- .getClusterProperty(
- List.of(CollectionAdminParams.DEFAULTS,
CollectionAdminParams.COLLECTION, prop),
- null);
- if (defVal != null) props.put(prop, String.valueOf(defVal));
- }
-
@Override
public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp)
throws Exception {
// Make sure the cores is enabled
@@ -539,42 +522,6 @@ public class CollectionsHandler extends RequestHandlerBase
implements Permission
return Category.ADMIN;
}
- private static void createSysConfigSet(CoreContainer coreContainer)
- throws KeeperException, InterruptedException {
- SolrZkClient zk =
coreContainer.getZkController().getZkStateReader().getZkClient();
- ZkCmdExecutor cmdExecutor = new ZkCmdExecutor(zk.getZkClientTimeout());
- cmdExecutor.ensureExists(ZkStateReader.CONFIGS_ZKNODE, zk);
- cmdExecutor.ensureExists(
- ZkStateReader.CONFIGS_ZKNODE + "/" +
CollectionAdminParams.SYSTEM_COLL, zk);
-
- try {
- String path =
- ZkStateReader.CONFIGS_ZKNODE + "/" +
CollectionAdminParams.SYSTEM_COLL + "/schema.xml";
- byte[] data;
- try (InputStream inputStream =
-
CollectionsHandler.class.getResourceAsStream("/SystemCollectionSchema.xml")) {
- assert inputStream != null;
- data = inputStream.readAllBytes();
- }
- assert data != null && data.length > 0;
- cmdExecutor.ensureExists(path, data, CreateMode.PERSISTENT, zk);
- path =
- ZkStateReader.CONFIGS_ZKNODE
- + "/"
- + CollectionAdminParams.SYSTEM_COLL
- + "/solrconfig.xml";
- try (InputStream inputStream =
-
CollectionsHandler.class.getResourceAsStream("/SystemCollectionSolrConfig.xml"))
{
- assert inputStream != null;
- data = inputStream.readAllBytes();
- }
- assert data != null && data.length > 0;
- cmdExecutor.ensureExists(path, data, CreateMode.PERSISTENT, zk);
- } catch (IOException e) {
- throw new SolrException(ErrorCode.SERVER_ERROR, e);
- }
- }
-
private static void addStatusToResponse(
NamedList<Object> results, RequestStatusState state, String msg) {
SimpleOrderedMap<String> status = new SimpleOrderedMap<>();
@@ -716,107 +663,12 @@ public class CollectionsHandler extends
RequestHandlerBase implements Permission
CREATEALIAS_OP(
CREATEALIAS,
(req, rsp, h) -> {
- String alias = req.getParams().get(NAME);
- SolrIdentifierValidator.validateAliasName(alias);
- String collections = req.getParams().get("collections");
- RoutedAlias routedAlias = null;
- Exception ex = null;
- HashMap<String, Object> possiblyModifiedParams = new HashMap<>();
- try {
- // note that RA specific validation occurs here.
- req.getParams().toMap(possiblyModifiedParams);
- @SuppressWarnings({"unchecked", "rawtypes"})
- // This is awful because RoutedAlias lies about what types it wants
- Map<String, String> temp = (Map<String, String>) (Map)
possiblyModifiedParams;
- routedAlias = RoutedAlias.fromProps(alias, temp);
- } catch (SolrException e) {
- // we'll throw this later if we are in fact creating a routed
alias.
- ex = e;
- }
- ModifiableSolrParams finalParams = new ModifiableSolrParams();
- for (Map.Entry<String, Object> entry :
possiblyModifiedParams.entrySet()) {
- if (entry.getValue().getClass().isArray()) {
- // v2 api hits this case
- for (Object o : (Object[]) entry.getValue()) {
- finalParams.add(entry.getKey(), o.toString());
- }
- } else {
- finalParams.add(entry.getKey(), entry.getValue().toString());
- }
- }
-
- if (collections != null) {
- if (routedAlias != null) {
- throw new SolrException(
- BAD_REQUEST, "Collections cannot be specified when creating
a routed alias.");
- } else {
- //////////////////////////////////////
- // Regular alias creation indicated //
- //////////////////////////////////////
- return copy(finalParams.required(), null, NAME, "collections");
- }
- } else {
- if (routedAlias != null) {
- CoreContainer coreContainer1 = h.getCoreContainer();
- Aliases aliases = coreContainer1.getAliases();
- String aliasName = routedAlias.getAliasName();
- if (aliases.hasAlias(aliasName) &&
!aliases.isRoutedAlias(aliasName)) {
- throw new SolrException(
- BAD_REQUEST,
- "Cannot add routing parameters to existing non-routed
Alias: " + aliasName);
- }
- }
- }
-
- /////////////////////////////////////////////////
- // We are creating a routed alias from here on //
- /////////////////////////////////////////////////
-
- // If our prior creation attempt had issues expose them now.
- if (ex != null) {
- throw ex;
- }
-
- // Now filter out just the parameters we care about from the request
- assert routedAlias != null;
- Map<String, Object> result = copy(finalParams, null,
routedAlias.getRequiredParams());
- copy(finalParams, result, routedAlias.getOptionalParams());
-
- ModifiableSolrParams createCollParams = new ModifiableSolrParams();
// without prefix
-
- // add to result params that start with "create-collection.".
- // Additionally, save these without the prefix to createCollParams
- for (Map.Entry<String, String[]> entry : finalParams) {
- final String p = entry.getKey();
- if (p.startsWith(CREATE_COLLECTION_PREFIX)) {
- // This is what SolrParams#getAll(Map, Collection)} does
- final String[] v = entry.getValue();
- if (v.length == 1) {
- result.put(p, v[0]);
- } else {
- result.put(p, v);
- }
-
createCollParams.set(p.substring(CREATE_COLLECTION_PREFIX.length()), v);
- }
- }
-
- // Verify that the create-collection prefix'ed params appear to be
valid.
- if (createCollParams.get(NAME) != null) {
- throw new SolrException(
- BAD_REQUEST,
- "routed aliases calculate names for their "
- + "dependent collections, you cannot specify the name.");
- }
- if (createCollParams.get(COLL_CONF) == null) {
- throw new SolrException(
- SolrException.ErrorCode.BAD_REQUEST, "We require an explicit "
+ COLL_CONF);
- }
- final var createRequestBody =
-
CreateCollectionAPI.CreateCollectionRequestBody.fromV1Params(createCollParams,
false);
- createRequestBody.name = "TMP_name_TMP_name_TMP"; // just to pass
validation
- createRequestBody.validate();
-
- return result;
+ final CreateAliasAPI.CreateAliasRequestBody reqBody =
+ CreateAliasAPI.createFromSolrParams(req.getParams());
+ final SolrJerseyResponse response =
+ new CreateAliasAPI(h.coreContainer, req,
rsp).createAlias(reqBody);
+ V2ApiUtils.squashIntoSolrResponseWithoutHeader(rsp, response);
+ return null;
}),
DELETEALIAS_OP(
@@ -1929,12 +1781,6 @@ public class CollectionsHandler extends
RequestHandlerBase implements Permission
}
}
- private static void verifyShardsParam(String shardsParam) {
- for (String shard : shardsParam.split(",")) {
- SolrIdentifierValidator.validateShardName(shard);
- }
- }
-
interface CollectionOp {
Map<String, Object> execute(SolrQueryRequest req, SolrQueryResponse rsp,
CollectionsHandler h)
throws Exception;
@@ -1949,6 +1795,7 @@ public class CollectionsHandler extends
RequestHandlerBase implements Permission
public Collection<Class<? extends JerseyResource>> getJerseyResources() {
return List.of(
AddReplicaPropertyAPI.class,
+ CreateAliasAPI.class,
CreateCollectionAPI.class,
CreateCollectionBackupAPI.class,
DeleteAliasAPI.class,
@@ -1969,7 +1816,6 @@ public class CollectionsHandler extends
RequestHandlerBase implements Permission
@Override
public Collection<Api> getApis() {
final List<Api> apis = new ArrayList<>();
- apis.addAll(AnnotatedApi.getApis(new CreateAliasAPI(this)));
apis.addAll(AnnotatedApi.getApis(new RestoreCollectionAPI(this)));
apis.addAll(AnnotatedApi.getApis(new SplitShardAPI(this)));
apis.addAll(AnnotatedApi.getApis(new CreateShardAPI(this)));
diff --git
a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateAliasAPI.java
b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateAliasAPI.java
index 41ebdbcefda..95f191c485a 100644
--- a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateAliasAPI.java
+++ b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateAliasAPI.java
@@ -17,63 +17,401 @@
package org.apache.solr.handler.admin.api;
-import static org.apache.solr.client.solrj.SolrRequest.METHOD.POST;
+import static
org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
+import static
org.apache.solr.client.solrj.request.beans.V2ApiConstants.COLLECTIONS;
+import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
+import static org.apache.solr.cloud.api.collections.RoutedAlias.CATEGORY;
import static
org.apache.solr.cloud.api.collections.RoutedAlias.CREATE_COLLECTION_PREFIX;
+import static
org.apache.solr.cloud.api.collections.RoutedAlias.ROUTER_TYPE_NAME;
+import static org.apache.solr.cloud.api.collections.RoutedAlias.TIME;
+import static
org.apache.solr.cloud.api.collections.TimeRoutedAlias.ROUTER_MAX_FUTURE;
+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.ACTION;
-import static org.apache.solr.handler.ClusterAPI.wrapParams;
-import static
org.apache.solr.handler.admin.api.CreateCollectionAPI.CreateCollectionRequestBody.convertV2CreateCollectionMapToV1ParamMap;
-import static org.apache.solr.handler.api.V2ApiUtils.flattenMapWithPrefix;
+import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
+import static org.apache.solr.common.params.CommonParams.NAME;
+import static org.apache.solr.common.params.CommonParams.START;
+import static
org.apache.solr.handler.admin.CollectionsHandler.DEFAULT_COLLECTION_OP_TIMEOUT;
import static
org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
import java.util.Map;
-import org.apache.solr.api.Command;
-import org.apache.solr.api.EndPoint;
-import org.apache.solr.api.PayloadObj;
-import org.apache.solr.client.solrj.request.beans.CreateAliasPayload;
-import org.apache.solr.client.solrj.request.beans.V2ApiConstants;
+import javax.inject.Inject;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import org.apache.solr.client.solrj.RoutedAliasTypes;
+import org.apache.solr.client.solrj.SolrResponse;
+import org.apache.solr.client.solrj.util.SolrIdentifierValidator;
+import org.apache.solr.cloud.api.collections.RoutedAlias;
+import org.apache.solr.cloud.api.collections.TimeRoutedAlias;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.cloud.Aliases;
+import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.params.CollectionParams;
+import org.apache.solr.common.params.CoreAdminParams;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.util.CollectionUtil;
+import org.apache.solr.common.util.StrUtils;
+import org.apache.solr.core.CoreContainer;
import org.apache.solr.handler.admin.CollectionsHandler;
+import org.apache.solr.jersey.JacksonReflectMapWriter;
+import org.apache.solr.jersey.PermissionName;
+import org.apache.solr.jersey.SolrJerseyResponse;
+import org.apache.solr.jersey.SubResponseAccumulatingJerseyResponse;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.util.TimeZoneUtils;
-@EndPoint(
- path = {"/aliases"},
- method = POST,
- permission = COLL_EDIT_PERM)
-public class CreateAliasAPI {
+/**
+ * V2 API for creating an alias
+ *
+ * <p>This API is analogous to the v1 /admin/collections?action=CREATEALIAS
command.
+ */
+@Path("/aliases")
+public class CreateAliasAPI extends AdminAPIBase {
+ @Inject
+ public CreateAliasAPI(
+ CoreContainer coreContainer,
+ SolrQueryRequest solrQueryRequest,
+ SolrQueryResponse solrQueryResponse) {
+ super(coreContainer, solrQueryRequest, solrQueryResponse);
+ }
+
+ @POST
+ @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML,
BINARY_CONTENT_TYPE_V2})
+ @PermissionName(COLL_EDIT_PERM)
+ public SolrJerseyResponse createAlias(CreateAliasRequestBody requestBody)
throws Exception {
+ final SubResponseAccumulatingJerseyResponse response =
+ instantiateJerseyResponse(SubResponseAccumulatingJerseyResponse.class);
+ recordCollectionForLogAndTracing(null, solrQueryRequest);
+
+ if (requestBody == null) {
+ throw new SolrException(BAD_REQUEST, "Request body is required but
missing");
+ }
+ requestBody.validate();
+
+ ZkNodeProps remoteMessage;
+ // Validation ensures that the request has either collections or a router
but not both.
+ if (CollectionUtil.isNotEmpty(requestBody.collections)) {
+ remoteMessage = createRemoteMessageForTraditionalAlias(requestBody);
+ } else { // Creating a routed alias
+ assert CollectionUtil.isNotEmpty(requestBody.routers);
+ final Aliases aliases = coreContainer.getAliases();
+ if (aliases.hasAlias(requestBody.name) &&
!aliases.isRoutedAlias(requestBody.name)) {
+ throw new SolrException(
+ BAD_REQUEST,
+ "Cannot add routing parameters to existing non-routed Alias: " +
requestBody.name);
+ }
+
+ remoteMessage = createRemoteMessageForRoutedAlias(requestBody);
+ }
+
+ final SolrResponse remoteResponse =
+ CollectionsHandler.submitCollectionApiCommand(
+ coreContainer,
+ coreContainer.getDistributedCollectionCommandRunner(),
+ remoteMessage,
+ CollectionParams.CollectionAction.CREATEALIAS,
+ DEFAULT_COLLECTION_OP_TIMEOUT);
+ if (remoteResponse.getException() != null) {
+ throw remoteResponse.getException();
+ }
+
+ if (requestBody.async != null) {
+ response.requestId = requestBody.async;
+ }
+ return response;
+ }
+
+ public static ZkNodeProps createRemoteMessageForTraditionalAlias(
+ CreateAliasRequestBody requestBody) {
+ final Map<String, Object> remoteMessage = new HashMap<>();
+
+ remoteMessage.put(QUEUE_OPERATION,
CollectionParams.CollectionAction.CREATEALIAS.toLower());
+ remoteMessage.put(NAME, requestBody.name);
+ remoteMessage.put("collections", String.join(",",
requestBody.collections));
+ remoteMessage.put(ASYNC, requestBody.async);
+
+ return new ZkNodeProps(remoteMessage);
+ }
+
+ public static ZkNodeProps
createRemoteMessageForRoutedAlias(CreateAliasRequestBody requestBody) {
+ final Map<String, Object> remoteMessage = new HashMap<>();
+ remoteMessage.put(QUEUE_OPERATION,
CollectionParams.CollectionAction.CREATEALIAS.toLower());
+ remoteMessage.put(NAME, requestBody.name);
+ if (StrUtils.isNotBlank(requestBody.async)) remoteMessage.put(ASYNC,
requestBody.async);
+
+ if (requestBody.routers.size() > 1) { // Multi-dimensional alias
+ for (int i = 0; i < requestBody.routers.size(); i++) {
+ requestBody.routers.get(i).addRemoteMessageProperties(remoteMessage,
"router." + i + ".");
+ }
+ } else if (requestBody.routers.size() == 1) { // Single dimensional alias
+ requestBody.routers.get(0).addRemoteMessageProperties(remoteMessage,
"router.");
+ }
+
+ if (requestBody.collCreationParameters != null) {
+ requestBody.collCreationParameters.addToRemoteMessageWithPrefix(
+ remoteMessage, "create-collection.");
+ }
+ return new ZkNodeProps(remoteMessage);
+ }
+
+ public static CreateAliasRequestBody createFromSolrParams(SolrParams params)
{
+ final CreateAliasRequestBody createBody = new CreateAliasRequestBody();
+ createBody.name = params.required().get(NAME);
+
+ final String collections = params.get(COLLECTIONS);
+ createBody.collections =
+ StrUtils.isNullOrEmpty(collections) ? new ArrayList<>() :
StrUtils.split(collections, ',');
+ createBody.async = params.get(ASYNC);
+
+ // Handle routed-alias properties
+ final String typeStr = params.get(ROUTER_TYPE_NAME);
+ if (typeStr == null) {
+ return createBody; // non-routed aliases are being created
+ }
+
+ createBody.routers = new ArrayList<>();
+ if (typeStr.startsWith(RoutedAlias.DIMENSIONAL)) {
+ final String commaSeparatedDimensions =
+ typeStr.substring(RoutedAlias.DIMENSIONAL.length(), typeStr.length()
- 1);
+ final String[] dimensions = commaSeparatedDimensions.split(",");
+ if (dimensions.length > 2) {
+ throw new SolrException(
+ BAD_REQUEST,
+ "More than 2 dimensions is not supported yet. "
+ + "Please monitor SOLR-13628 for progress");
+ }
+
+ for (int i = 0; i < dimensions.length; i++) {
+ createBody.routers.add(
+ createFromSolrParams(dimensions[i], params, ROUTER_PREFIX + i +
"."));
+ }
+ } else {
+ createBody.routers.add(createFromSolrParams(typeStr, params,
ROUTER_PREFIX));
+ }
+
+ final SolrParams createCollectionParams =
+ getHierarchicalParametersByPrefix(params, CREATE_COLLECTION_PREFIX);
+ createBody.collCreationParameters =
+
CreateCollectionAPI.CreateCollectionRequestBody.fromV1Params(createCollectionParams,
false);
+
+ return createBody;
+ }
+
+ public static RoutedAliasProperties createFromSolrParams(
+ String type, SolrParams params, String propertyPrefix) {
+ final String typeLower = type.toLowerCase(Locale.ROOT);
+ if (typeLower.startsWith(TIME)) {
+ return TimeRoutedAliasProperties.createFromSolrParams(params,
propertyPrefix);
+ } else if (typeLower.startsWith(CATEGORY)) {
+ return CategoryRoutedAliasProperties.createFromSolrParams(params,
propertyPrefix);
+ } else {
+ throw new SolrException(
+ BAD_REQUEST,
+ "Router name: "
+ + type
+ + " is not in supported types, "
+ + Arrays.asList(RoutedAliasTypes.values()));
+ }
+ }
- public static final String V2_CREATE_ALIAS_CMD = "create-alias";
+ public static class CreateAliasRequestBody implements
JacksonReflectMapWriter {
+ @JsonProperty(required = true)
+ public String name;
- private final CollectionsHandler collectionsHandler;
+ @JsonProperty("collections")
+ public List<String> collections;
- public CreateAliasAPI(CollectionsHandler collectionsHandler) {
- this.collectionsHandler = collectionsHandler;
+ @JsonProperty(ASYNC)
+ public String async;
+
+ @JsonProperty("routers")
+ public List<RoutedAliasProperties> routers;
+
+ @JsonProperty("create-collection")
+ public CreateCollectionAPI.CreateCollectionRequestBody
collCreationParameters;
+
+ public void validate() {
+ SolrIdentifierValidator.validateAliasName(name);
+
+ if (CollectionUtil.isEmpty(collections) &&
CollectionUtil.isEmpty(routers)) {
+ throw new SolrException(
+ BAD_REQUEST,
+ "Alias creation requires either a list of either collections (for
creating a traditional alias) or routers (for creating a routed alias)");
+ }
+
+ if (CollectionUtil.isNotEmpty(routers)) {
+ routers.forEach(r -> r.validate());
+ if (CollectionUtil.isNotEmpty(collections)) {
+ throw new SolrException(
+ BAD_REQUEST, "Collections cannot be specified when creating a
routed alias.");
+ }
+
+ final CreateCollectionAPI.CreateCollectionRequestBody
createCollReqBody =
+ collCreationParameters;
+ if (createCollReqBody != null) {
+ if (createCollReqBody.name != null) {
+ throw new SolrException(
+ BAD_REQUEST,
+ "routed aliases calculate names for their "
+ + "dependent collections, you cannot specify the name.");
+ }
+ if (createCollReqBody.config == null) {
+ throw new SolrException(
+ SolrException.ErrorCode.BAD_REQUEST,
+ "Routed alias creation requires a configset name to use for
any collections created by the alias.");
+ }
+ }
+ }
+ }
}
- @Command(name = V2_CREATE_ALIAS_CMD)
- @SuppressWarnings("unchecked")
- public void createAlias(PayloadObj<CreateAliasPayload> obj) throws Exception
{
- final CreateAliasPayload v2Body = obj.get();
- final Map<String, Object> v1Params = v2Body.toMap(new HashMap<>());
+ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include =
JsonTypeInfo.As.PROPERTY, property = "type")
+ @JsonSubTypes({
+ @JsonSubTypes.Type(value = TimeRoutedAliasProperties.class, name = "time"),
+ @JsonSubTypes.Type(value = CategoryRoutedAliasProperties.class, name =
"category")
+ })
+ public abstract static class RoutedAliasProperties implements
JacksonReflectMapWriter {
+ @JsonProperty(required = true)
+ public String field;
+
+ public abstract void validate();
- v1Params.put(ACTION,
CollectionParams.CollectionAction.CREATEALIAS.toLower());
- if (v2Body.collections != null && !v2Body.collections.isEmpty()) {
- final String collectionsStr = String.join(",", v2Body.collections);
- v1Params.remove(V2ApiConstants.COLLECTIONS);
- v1Params.put(V2ApiConstants.COLLECTIONS, collectionsStr);
+ public abstract void addRemoteMessageProperties(
+ Map<String, Object> remoteMessage, String prefix);
+
+ protected void ensureRequiredFieldPresent(Object val, String name) {
+ if (val == null) {
+ throw new SolrException(
+ SolrException.ErrorCode.BAD_REQUEST, "Missing required parameter:
" + name);
+ }
}
- if (v2Body.router != null) {
- Map<String, Object> routerProperties =
- (Map<String, Object>) v1Params.remove(V2ApiConstants.ROUTER_KEY);
- flattenMapWithPrefix(routerProperties, v1Params, ROUTER_PREFIX);
+ }
+
+ public static class TimeRoutedAliasProperties extends RoutedAliasProperties {
+ // Expected to be a date/time in ISO format, or 'NOW'
+ @JsonProperty(required = true)
+ public String start;
+
+ // TODO Change this to 'timezone' or something less abbreviated
+ @JsonProperty("tz")
+ public String tz;
+
+ @JsonProperty(required = true)
+ public String interval;
+
+ @JsonProperty("maxFutureMs")
+ public Long maxFutureMs;
+
+ @JsonProperty("preemptiveCreateMath")
+ public String preemptiveCreateMath;
+
+ @JsonProperty("autoDeleteAge")
+ public String autoDeleteAge;
+
+ @Override
+ public void validate() {
+ ensureRequiredFieldPresent(field, "'field' on time routed alias");
+ ensureRequiredFieldPresent(start, "'start' on time routed alias");
+ ensureRequiredFieldPresent(interval, "'interval' on time routed alias");
+
+ // Ensures that provided 'start' and optional 'tz' are of the right
format.
+ TimeRoutedAlias.parseStringAsInstant(start,
TimeZoneUtils.parseTimezone(tz));
+
+ // maxFutureMs must be > 0 if provided
+ if (maxFutureMs != null && maxFutureMs < 0) {
+ throw new SolrException(BAD_REQUEST, ROUTER_MAX_FUTURE + " must be >=
0");
+ }
}
- if (v2Body.createCollectionParams != null &&
!v2Body.createCollectionParams.isEmpty()) {
- final Map<String, Object> createCollectionMap =
- (Map<String, Object>)
v1Params.remove(V2ApiConstants.CREATE_COLLECTION_KEY);
- convertV2CreateCollectionMapToV1ParamMap(createCollectionMap);
- flattenMapWithPrefix(createCollectionMap, v1Params,
CREATE_COLLECTION_PREFIX);
+
+ @Override
+ public void addRemoteMessageProperties(Map<String, Object> remoteMessage,
String prefix) {
+ remoteMessage.put(prefix + CoreAdminParams.NAME, TIME);
+ remoteMessage.put(prefix + "field", field);
+ remoteMessage.put(prefix + "start", start);
+ remoteMessage.put(prefix + "interval", interval);
+
+ if (tz != null) remoteMessage.put(prefix + "tz", tz);
+ if (maxFutureMs != null) remoteMessage.put(prefix + "maxFutureMs",
maxFutureMs);
+ if (preemptiveCreateMath != null)
+ remoteMessage.put(prefix + "preemptiveCreateMath",
preemptiveCreateMath);
+ if (autoDeleteAge != null) remoteMessage.put(prefix + "autoDeleteAge",
autoDeleteAge);
+ }
+
+ public static TimeRoutedAliasProperties createFromSolrParams(
+ SolrParams params, String propertyPrefix) {
+ final TimeRoutedAliasProperties timeRoutedProperties = new
TimeRoutedAliasProperties();
+ timeRoutedProperties.field = params.required().get(propertyPrefix +
"field");
+ timeRoutedProperties.start = params.required().get(propertyPrefix +
START);
+ timeRoutedProperties.interval = params.required().get(propertyPrefix +
"interval");
+
+ timeRoutedProperties.tz = params.get(propertyPrefix + "tz");
+ timeRoutedProperties.maxFutureMs = params.getLong(propertyPrefix +
"maxFutureMs");
+ timeRoutedProperties.preemptiveCreateMath =
+ params.get(propertyPrefix + "preemptiveCreateMath");
+ timeRoutedProperties.autoDeleteAge = params.get(propertyPrefix +
"autoDeleteAge");
+
+ return timeRoutedProperties;
+ }
+ }
+
+ public static class CategoryRoutedAliasProperties extends
RoutedAliasProperties {
+ @JsonProperty("maxCardinality")
+ public Long maxCardinality;
+
+ @JsonProperty("mustMatch")
+ public String mustMatch;
+
+ @Override
+ public void validate() {
+ ensureRequiredFieldPresent(field, "'field' on category routed alias");
+ }
+
+ @Override
+ public void addRemoteMessageProperties(Map<String, Object> remoteMessage,
String prefix) {
+ remoteMessage.put(prefix + CoreAdminParams.NAME, CATEGORY);
+ remoteMessage.put(prefix + "field", field);
+
+ if (maxCardinality != null) remoteMessage.put(prefix + "maxCardinality",
maxCardinality);
+ if (StrUtils.isNotBlank(mustMatch)) remoteMessage.put(prefix +
"mustMatch", mustMatch);
}
- collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(),
v1Params), obj.getResponse());
+ public static CategoryRoutedAliasProperties createFromSolrParams(
+ SolrParams params, String propertyPrefix) {
+ final CategoryRoutedAliasProperties categoryRoutedProperties =
+ new CategoryRoutedAliasProperties();
+ categoryRoutedProperties.field = params.required().get(propertyPrefix +
"field");
+
+ categoryRoutedProperties.maxCardinality = params.getLong(propertyPrefix
+ "maxCardinality");
+ categoryRoutedProperties.mustMatch = params.get(propertyPrefix +
"mustMatch");
+ return categoryRoutedProperties;
+ }
+ }
+
+ /**
+ * Returns a SolrParams object containing only those values whose keys match
a specified prefix
+ * (with that prefix removed)
+ *
+ * <p>Query-parameter based v1 APIs often mimic hierarchical parameters by
using a prefix in the
+ * query-param key to group similar parameters together. This function can
be used to identify all
+ * of the parameters "nested" in this way, with their prefix removed.
+ */
+ public static SolrParams getHierarchicalParametersByPrefix(
+ SolrParams paramSource, String prefix) {
+ final ModifiableSolrParams filteredParams = new ModifiableSolrParams();
+ paramSource.stream()
+ .filter(e -> e.getKey().startsWith(prefix))
+ .forEach(e ->
filteredParams.add(e.getKey().substring(prefix.length()), e.getValue()));
+ return filteredParams;
}
}
diff --git
a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionAPI.java
b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionAPI.java
index 1bfb176fd46..94ccfb9f183 100644
---
a/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionAPI.java
+++
b/solr/core/src/java/org/apache/solr/handler/admin/api/CreateCollectionAPI.java
@@ -49,6 +49,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
@@ -407,6 +408,14 @@ public class CreateCollectionAPI extends AdminAPIBase {
}
}
+ public void addToRemoteMessageWithPrefix(Map<String, Object>
remoteMessage, String prefix) {
+ final Map<String, Object> v1Params = toMap(new HashMap<>());
+ convertV2CreateCollectionMapToV1ParamMap(v1Params);
+ for (Map.Entry<String, Object> v1Param : v1Params.entrySet()) {
+ remoteMessage.put(prefix + v1Param.getKey(), v1Param.getValue());
+ }
+ }
+
/**
* Convert a map representing the v2 request body into v1-appropriate
query-parameters.
*
@@ -436,7 +445,9 @@ public class CreateCollectionAPI extends AdminAPIBase {
v2MapVals.put(CollectionAdminParams.COLL_CONF,
v2MapVals.remove(V2ApiConstants.CONFIG));
break;
case SHARD_NAMES:
- v2MapVals.put(SHARDS_PROP, v2MapVals.remove(SHARD_NAMES));
+ final String shardsValue =
+ String.join(",", (Collection<String>)
v2MapVals.remove(SHARD_NAMES));
+ v2MapVals.put(SHARDS_PROP, shardsValue);
break;
case V2ApiConstants.SHUFFLE_NODES:
v2MapVals.put(
diff --git a/solr/core/src/test/org/apache/solr/cloud/AliasIntegrationTest.java
b/solr/core/src/test/org/apache/solr/cloud/AliasIntegrationTest.java
index 3f55ee17f64..970f4d8478f 100644
--- a/solr/core/src/test/org/apache/solr/cloud/AliasIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/AliasIntegrationTest.java
@@ -892,7 +892,7 @@ public class AliasIntegrationTest extends SolrCloudTestCase
{
new V2Request.Builder("/aliases")
.withMethod(SolrRequest.METHOD.POST)
.withPayload(
- "{\"create-alias\": {\"name\": \"testalias6\",
collections:[\"collection2\",\"collection1\"]}}")
+ "{\"name\": \"testalias6\", \"collections\":
[\"collection2\",\"collection1\"]}")
.build()
.process(cluster.getSolrClient());
diff --git
a/solr/core/src/test/org/apache/solr/cloud/CreateRoutedAliasTest.java
b/solr/core/src/test/org/apache/solr/cloud/CreateRoutedAliasTest.java
index 1254552bf9c..69f8f1adc1e 100644
--- a/solr/core/src/test/org/apache/solr/cloud/CreateRoutedAliasTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/CreateRoutedAliasTest.java
@@ -94,45 +94,41 @@ public class CreateRoutedAliasTest extends
SolrCloudTestCase {
final String baseUrl =
cluster.getRandomJetty(random()).getBaseUrl().toString();
// TODO fix Solr test infra so that this /____v2/ becomes /api/
+ final String aliasJson =
+ "{\n"
+ + " \"name\": \""
+ + aliasName
+ + "\",\n"
+ + " \"routers\" : [{\n"
+ + " \"type\": \"time\",\n"
+ + " \"field\": \"evt_dt\",\n"
+ + " \"start\":\"NOW/DAY\",\n"
+ // small window for test failure once a day.
+ + " \"interval\":\"+2HOUR\",\n"
+ + " \"maxFutureMs\":\"14400000\"\n"
+ + " }],\n"
+ // TODO should we use "NOW=" param? Won't work with v2 and is
kinda a hack any way
+ // since intended for distributed search
+ + " \"create-collection\" : {\n"
+ + " \"router\": {\n"
+ + " \"name\":\"implicit\",\n"
+ + " \"field\":\"foo_s\"\n"
+ + " },\n"
+ + " \"shardNames\": [\"foo\", \"bar\"],\n"
+ + " \"config\":\"_default\",\n"
+ + " \"tlogReplicas\":1,\n"
+ + " \"pullReplicas\":1,\n"
+ + " \"nodeSet\": [\""
+ + createNode
+ + "\"],\n"
+ + " \"properties\" : {\n"
+ + " \"foobar\":\"bazbam\",\n"
+ + " \"foobar2\":\"bazbam2\"\n"
+ + " }\n"
+ + " }\n"
+ + " }\n";
HttpPost post = new HttpPost(baseUrl + "/____v2/aliases");
- post.setEntity(
- new StringEntity(
- "{\n"
- + " \"create-alias\" : {\n"
- + " \"name\": \""
- + aliasName
- + "\",\n"
- + " \"router\" : {\n"
- + " \"name\": \"time\",\n"
- + " \"field\": \"evt_dt\",\n"
- + " \"start\":\"NOW/DAY\",\n"
- + // small window for test failure once a day.
- " \"interval\":\"+2HOUR\",\n"
- + " \"maxFutureMs\":\"14400000\"\n"
- + " },\n"
- +
- // TODO should we use "NOW=" param? Won't work with v2 and is
kinda a hack any way
- // since intended for distributed search
- " \"create-collection\" : {\n"
- + " \"router\": {\n"
- + " \"name\":\"implicit\",\n"
- + " \"field\":\"foo_s\"\n"
- + " },\n"
- + " \"shards\":\"foo,bar\",\n"
- + " \"config\":\"_default\",\n"
- + " \"tlogReplicas\":1,\n"
- + " \"pullReplicas\":1,\n"
- + " \"nodeSet\": '"
- + createNode
- + "',\n"
- + " \"properties\" : {\n"
- + " \"foobar\":\"bazbam\",\n"
- + " \"foobar2\":\"bazbam2\"\n"
- + " }\n"
- + " }\n"
- + " }\n"
- + "}",
- ContentType.APPLICATION_JSON));
+ post.setEntity(new StringEntity(aliasJson, ContentType.APPLICATION_JSON));
assertSuccess(post);
Date startDate = DateMathParser.parseMath(new Date(), "NOW/DAY");
@@ -215,7 +211,7 @@ public class CreateRoutedAliasTest extends
SolrCloudTestCase {
}
@Test
- public void testUpdateRoudetedAliasDoesNotChangeCollectionList() throws
Exception {
+ public void testUpdateRoutedAliasDoesNotChangeCollectionList() throws
Exception {
final String aliasName = getSaferTestName();
Instant start = Instant.now().truncatedTo(ChronoUnit.HOURS); // mostly
make sure no millis
diff --git
a/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java
b/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java
index 3dd7182fb7a..611b7bd9d66 100644
--- a/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java
+++ b/solr/core/src/test/org/apache/solr/handler/admin/TestCollectionAPIs.java
@@ -89,13 +89,6 @@ public class TestCollectionAPIs extends SolrTestCaseJ4 {
apiBag.registerObject(clusterAPI.commands);
}
- compareOutput(
- apiBag,
- "/aliases",
- POST,
- "{create-alias:{name: aliasName , collections:[c1,c2] }}",
- "{operation : createalias, name: aliasName, collections:\"c1,c2\" }");
-
compareOutput(
apiBag, "/collections/collName", POST, "{reload:{}}", "{name:collName,
operation :reload}");
@@ -264,9 +257,6 @@ public class TestCollectionAPIs extends SolrTestCaseJ4 {
return null;
}
- @Override
- protected void copyFromClusterProp(Map<String, Object> props, String prop)
{}
-
@Override
void invokeAction(
SolrQueryRequest req,
diff --git
a/solr/core/src/test/org/apache/solr/handler/admin/V2CollectionsAPIMappingTest.java
b/solr/core/src/test/org/apache/solr/handler/admin/V2CollectionsAPIMappingTest.java
index a9bb20b999f..caa312e9d0a 100644
---
a/solr/core/src/test/org/apache/solr/handler/admin/V2CollectionsAPIMappingTest.java
+++
b/solr/core/src/test/org/apache/solr/handler/admin/V2CollectionsAPIMappingTest.java
@@ -18,10 +18,6 @@ package org.apache.solr.handler.admin;
import static org.apache.solr.common.params.CommonParams.ACTION;
-import java.util.Locale;
-import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.cloud.api.collections.CategoryRoutedAlias;
-import org.apache.solr.cloud.api.collections.RoutedAlias;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.CollectionAdminParams;
import org.apache.solr.common.params.CollectionParams;
@@ -30,7 +26,6 @@ import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.core.backup.BackupManager;
-import org.apache.solr.handler.admin.api.CreateAliasAPI;
import org.apache.solr.handler.admin.api.RestoreCollectionAPI;
import org.junit.Test;
@@ -52,7 +47,6 @@ public class V2CollectionsAPIMappingTest extends
V2ApiMappingTest<CollectionsHan
@Override
public void populateApiBag() {
- apiBag.registerObject(new CreateAliasAPI(getRequestHandler()));
apiBag.registerObject(new RestoreCollectionAPI(getRequestHandler()));
}
@@ -66,68 +60,6 @@ public class V2CollectionsAPIMappingTest extends
V2ApiMappingTest<CollectionsHan
return false;
}
- @Test
- public void testCreateAliasAllProperties() throws Exception {
- final SolrParams v1Params =
- captureConvertedV1Params(
- "/aliases",
- "POST",
- "{'create-alias': {"
- + "'name': 'aliasName', "
- + "'collections': ['techproducts1', 'techproducts2'], "
- + "'tz': 'someTimeZone', "
- + "'async': 'requestTrackingId', "
- + "'router': {"
- + " 'name': 'time', "
- + " 'field': 'date_dt', "
- + " 'interval': '+1HOUR', "
- + " 'maxFutureMs': 3600, "
- + " 'preemptiveCreateMath':
'somePreemptiveCreateMathString', "
- + " 'autoDeleteAge': 'someAutoDeleteAgeExpression', "
- + " 'maxCardinality': 36, "
- + " 'mustMatch': 'someRegex', "
- + "}, "
- + "'create-collection': {"
- + " 'numShards': 1, "
- + " 'properties': {'foo': 'bar', 'foo2': 'bar2'}, "
- + " 'replicationFactor': 3 "
- + "}"
- + "}}");
-
- assertEquals(CollectionParams.CollectionAction.CREATEALIAS.lowerName,
v1Params.get(ACTION));
- assertEquals("aliasName", v1Params.get(CommonParams.NAME));
- assertEquals("techproducts1,techproducts2", v1Params.get("collections"));
- assertEquals("someTimeZone",
v1Params.get(CommonParams.TZ.toLowerCase(Locale.ROOT)));
- assertEquals("requestTrackingId", v1Params.get(CommonAdminParams.ASYNC));
- assertEquals(
- "time",
v1Params.get(CollectionAdminRequest.CreateTimeRoutedAlias.ROUTER_TYPE_NAME));
- assertEquals(
- "date_dt",
v1Params.get(CollectionAdminRequest.CreateTimeRoutedAlias.ROUTER_FIELD));
- assertEquals(
- "+1HOUR",
v1Params.get(CollectionAdminRequest.CreateTimeRoutedAlias.ROUTER_INTERVAL));
- assertEquals(
- 3600,
-
v1Params.getPrimitiveInt(CollectionAdminRequest.CreateTimeRoutedAlias.ROUTER_MAX_FUTURE));
- assertEquals(
- "somePreemptiveCreateMathString",
-
v1Params.get(CollectionAdminRequest.CreateTimeRoutedAlias.ROUTER_PREEMPTIVE_CREATE_WINDOW));
- assertEquals(
- "someAutoDeleteAgeExpression",
-
v1Params.get(CollectionAdminRequest.CreateTimeRoutedAlias.ROUTER_AUTO_DELETE_AGE));
- assertEquals(36,
v1Params.getPrimitiveInt(CategoryRoutedAlias.ROUTER_MAX_CARDINALITY));
- assertEquals("someRegex",
v1Params.get(CategoryRoutedAlias.ROUTER_MUST_MATCH));
- assertEquals(
- 1,
- v1Params.getPrimitiveInt(
- RoutedAlias.CREATE_COLLECTION_PREFIX +
CollectionAdminParams.NUM_SHARDS));
- assertEquals("bar", v1Params.get(RoutedAlias.CREATE_COLLECTION_PREFIX +
"property.foo"));
- assertEquals("bar2", v1Params.get(RoutedAlias.CREATE_COLLECTION_PREFIX +
"property.foo2"));
- assertEquals(
- 3,
- v1Params.getPrimitiveInt(
- RoutedAlias.CREATE_COLLECTION_PREFIX +
ZkStateReader.REPLICATION_FACTOR));
- }
-
@Test
public void testRestoreAllProperties() throws Exception {
final SolrParams v1Params =
diff --git
a/solr/core/src/test/org/apache/solr/handler/admin/api/CreateAliasAPITest.java
b/solr/core/src/test/org/apache/solr/handler/admin/api/CreateAliasAPITest.java
new file mode 100644
index 00000000000..ce4b5b51544
--- /dev/null
+++
b/solr/core/src/test/org/apache/solr/handler/admin/api/CreateAliasAPITest.java
@@ -0,0 +1,348 @@
+/*
+ * 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.handler.admin.api;
+
+import static
org.apache.solr.client.solrj.request.beans.V2ApiConstants.COLLECTIONS;
+import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;
+import static org.apache.solr.common.params.CommonAdminParams.ASYNC;
+import static org.hamcrest.Matchers.containsString;
+
+import java.util.List;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.hamcrest.MatcherAssert;
+import org.junit.Test;
+
+/** Unit tests for {@link CreateAliasAPI} */
+public class CreateAliasAPITest extends SolrTestCaseJ4 {
+
+ @Test
+ public void testReportsErrorIfRequestBodyMissing() {
+ final SolrException thrown =
+ expectThrows(
+ SolrException.class,
+ () -> {
+ final var api = new CreateAliasAPI(null, null, null);
+ api.createAlias(null);
+ });
+
+ assertEquals(400, thrown.code());
+ assertEquals("Request body is required but missing", thrown.getMessage());
+ }
+
+ @Test
+ public void testReportsErrorIfAliasNameInvalid() {
+ final var requestBody = new CreateAliasAPI.CreateAliasRequestBody();
+ requestBody.name = "some@invalid$alias";
+ requestBody.collections = List.of("validColl1", "validColl2");
+
+ final var thrown = expectThrows(SolrException.class, () ->
requestBody.validate());
+ MatcherAssert.assertThat(thrown.getMessage(), containsString("Invalid
alias"));
+ MatcherAssert.assertThat(thrown.getMessage(),
containsString("some@invalid$alias"));
+ MatcherAssert.assertThat(
+ thrown.getMessage(),
+ containsString(
+ "alias names must consist entirely of periods, underscores,
hyphens, and alphanumerics"));
+ }
+
+ // Aliases can be normal or "routed', but not both.
+ @Test
+ public void
testReportsErrorIfExplicitCollectionsAndRoutingParamsBothProvided() {
+ final var requestBody = new CreateAliasAPI.CreateAliasRequestBody();
+ requestBody.name = "validName";
+ requestBody.collections = List.of("validColl1");
+ final var categoryRouter = new
CreateAliasAPI.CategoryRoutedAliasProperties();
+ categoryRouter.field = "someField";
+ requestBody.routers = List.of(categoryRouter);
+
+ final var thrown = expectThrows(SolrException.class, () ->
requestBody.validate());
+ assertEquals(
+ "Collections cannot be specified when creating a routed alias.",
thrown.getMessage());
+ }
+
+ @Test
+ public void
testReportsErrorIfNeitherExplicitCollectionsNorRoutingParamsProvided() {
+ final var requestBody = new CreateAliasAPI.CreateAliasRequestBody();
+ requestBody.name = "validName";
+
+ final var thrown = expectThrows(SolrException.class, () ->
requestBody.validate());
+ assertEquals(400, thrown.code());
+ assertEquals(
+ "Alias creation requires either a list of either collections (for
creating a traditional alias) or routers (for creating a routed alias)",
+ thrown.getMessage());
+ }
+
+ @Test
+ public void
testRoutedAliasesMustProvideAConfigsetToUseOnCreatedCollections() {
+ final var requestBody = new CreateAliasAPI.CreateAliasRequestBody();
+ requestBody.name = "validName";
+ final var categoryRouter = new
CreateAliasAPI.CategoryRoutedAliasProperties();
+ categoryRouter.field = "someField";
+ requestBody.routers = List.of(categoryRouter);
+ final var createParams = new
CreateCollectionAPI.CreateCollectionRequestBody();
+ createParams.numShards = 3;
+ requestBody.collCreationParameters = createParams;
+
+ final var thrown = expectThrows(SolrException.class, () ->
requestBody.validate());
+ assertEquals(400, thrown.code());
+ MatcherAssert.assertThat(
+ thrown.getMessage(), containsString("Routed alias creation requires a
configset name"));
+ }
+
+ @Test
+ public void testRoutedAliasesMustNotSpecifyANameInCollectionCreationParams()
{
+ final var requestBody = new CreateAliasAPI.CreateAliasRequestBody();
+ requestBody.name = "validName";
+ final var categoryRouter = new
CreateAliasAPI.CategoryRoutedAliasProperties();
+ categoryRouter.field = "someField";
+ requestBody.routers = List.of(categoryRouter);
+ final var createParams = new
CreateCollectionAPI.CreateCollectionRequestBody();
+ createParams.numShards = 3;
+ createParams.config = "someConfig";
+ // Not allowed since routed-aliases-created collections have semantically
meaningful names
+ // determined by the alias
+ createParams.name = "someCollectionName";
+ requestBody.collCreationParameters = createParams;
+
+ final var thrown = expectThrows(SolrException.class, () ->
requestBody.validate());
+
+ assertEquals(400, thrown.code());
+ MatcherAssert.assertThat(thrown.getMessage(), containsString("cannot
specify the name"));
+ }
+
+ @Test
+ public void
testReportsErrorIfCategoryRoutedAliasDoesntSpecifyAllRequiredParameters() {
+ final var requestBody = new CreateAliasAPI.CreateAliasRequestBody();
+ requestBody.name = "validName";
+ final var createParams = new
CreateCollectionAPI.CreateCollectionRequestBody();
+ createParams.numShards = 3;
+ createParams.config = "someConfig";
+ requestBody.collCreationParameters = createParams;
+ final var categoryRouter = new
CreateAliasAPI.CategoryRoutedAliasProperties();
+ categoryRouter.maxCardinality = 123L;
+ requestBody.routers = List.of(categoryRouter);
+
+ final var thrown = expectThrows(SolrException.class, () ->
requestBody.validate());
+
+ assertEquals(400, thrown.code());
+ assertEquals(
+ "Missing required parameter: 'field' on category routed alias",
thrown.getMessage());
+ }
+
+ @Test
+ public void
testReportsErrorIfTimeRoutedAliasDoesntSpecifyAllRequiredParameters() {
+ // No 'field' defined!
+ {
+ final var timeRouter = new CreateAliasAPI.TimeRoutedAliasProperties();
+ timeRouter.start = "NOW";
+ timeRouter.interval = "+5MINUTES";
+ final var requestBody = requestBodyWithProvidedRouter(timeRouter);
+
+ final var thrown = expectThrows(SolrException.class, () ->
requestBody.validate());
+
+ assertEquals(400, thrown.code());
+ assertEquals("Missing required parameter: 'field' on time routed alias",
thrown.getMessage());
+ }
+
+ // No 'start' defined!
+ {
+ final var timeRouter = new CreateAliasAPI.TimeRoutedAliasProperties();
+ timeRouter.field = "someField";
+ timeRouter.interval = "+5MINUTES";
+ final var requestBody = requestBodyWithProvidedRouter(timeRouter);
+
+ final var thrown = expectThrows(SolrException.class, () ->
requestBody.validate());
+
+ assertEquals(400, thrown.code());
+ assertEquals("Missing required parameter: 'start' on time routed alias",
thrown.getMessage());
+ }
+
+ // No 'interval' defined!
+ {
+ final var timeRouter = new CreateAliasAPI.TimeRoutedAliasProperties();
+ timeRouter.field = "someField";
+ timeRouter.start = "NOW";
+ final var requestBody = requestBodyWithProvidedRouter(timeRouter);
+
+ final var thrown = expectThrows(SolrException.class, () ->
requestBody.validate());
+
+ assertEquals(400, thrown.code());
+ assertEquals(
+ "Missing required parameter: 'interval' on time routed alias",
thrown.getMessage());
+ }
+ }
+
+ @Test
+ public void testRemoteMessageCreationForTraditionalAlias() {
+ final var requestBody = new CreateAliasAPI.CreateAliasRequestBody();
+ requestBody.name = "someAliasName";
+ requestBody.collections = List.of("validColl1", "validColl2");
+ requestBody.async = "someAsyncId";
+
+ final var remoteMessage =
+
CreateAliasAPI.createRemoteMessageForTraditionalAlias(requestBody).getProperties();
+
+ assertEquals(4, remoteMessage.size());
+ assertEquals("createalias", remoteMessage.get(QUEUE_OPERATION));
+ assertEquals("someAliasName", remoteMessage.get("name"));
+ assertEquals("validColl1,validColl2", remoteMessage.get(COLLECTIONS));
+ assertEquals("someAsyncId", remoteMessage.get(ASYNC));
+ }
+
+ @Test
+ public void testRemoteMessageCreationForCategoryRoutedAlias() {
+ final var requestBody = new CreateAliasAPI.CreateAliasRequestBody();
+ requestBody.name = "someAliasName";
+ final var categoryRouter = new
CreateAliasAPI.CategoryRoutedAliasProperties();
+ categoryRouter.field = "someField";
+ requestBody.routers = List.of(categoryRouter);
+ final var createParams = new
CreateCollectionAPI.CreateCollectionRequestBody();
+ createParams.numShards = 3;
+ createParams.config = "someConfig";
+ requestBody.collCreationParameters = createParams;
+
+ final var remoteMessage =
+
CreateAliasAPI.createRemoteMessageForRoutedAlias(requestBody).getProperties();
+
+ assertEquals(6, remoteMessage.size());
+ assertEquals("createalias", remoteMessage.get(QUEUE_OPERATION));
+ assertEquals("someAliasName", remoteMessage.get("name"));
+ assertEquals("category", remoteMessage.get("router.name"));
+ assertEquals("someField", remoteMessage.get("router.field"));
+ assertEquals(3, remoteMessage.get("create-collection.numShards"));
+ assertEquals("someConfig",
remoteMessage.get("create-collection.collection.configName"));
+ }
+
+ @Test
+ public void testRemoteMessageCreationForTimeRoutedAlias() {
+ final var requestBody = new CreateAliasAPI.CreateAliasRequestBody();
+ requestBody.name = "someAliasName";
+ final var timeRouter = new CreateAliasAPI.TimeRoutedAliasProperties();
+ timeRouter.field = "someField";
+ timeRouter.start = "NOW";
+ timeRouter.interval = "+1MONTH";
+ timeRouter.maxFutureMs = 123456L;
+ requestBody.routers = List.of(timeRouter);
+ final var createParams = new
CreateCollectionAPI.CreateCollectionRequestBody();
+ createParams.numShards = 3;
+ createParams.config = "someConfig";
+ requestBody.collCreationParameters = createParams;
+
+ final var remoteMessage =
+
CreateAliasAPI.createRemoteMessageForRoutedAlias(requestBody).getProperties();
+
+ assertEquals(9, remoteMessage.size());
+ assertEquals("createalias", remoteMessage.get(QUEUE_OPERATION));
+ assertEquals("someAliasName", remoteMessage.get("name"));
+ assertEquals("time", remoteMessage.get("router.name"));
+ assertEquals("someField", remoteMessage.get("router.field"));
+ assertEquals("NOW", remoteMessage.get("router.start"));
+ assertEquals("+1MONTH", remoteMessage.get("router.interval"));
+ assertEquals(Long.valueOf(123456L),
remoteMessage.get("router.maxFutureMs"));
+ assertEquals(3, remoteMessage.get("create-collection.numShards"));
+ assertEquals("someConfig",
remoteMessage.get("create-collection.collection.configName"));
+ }
+
+ @Test
+ public void testRemoteMessageCreationForMultiDimensionalRoutedAlias() {
+ final var requestBody = new CreateAliasAPI.CreateAliasRequestBody();
+ requestBody.name = "someAliasName";
+ final var timeRouter = new CreateAliasAPI.TimeRoutedAliasProperties();
+ timeRouter.field = "someField";
+ timeRouter.start = "NOW";
+ timeRouter.interval = "+1MONTH";
+ timeRouter.maxFutureMs = 123456L;
+ final var categoryRouter = new
CreateAliasAPI.CategoryRoutedAliasProperties();
+ categoryRouter.field = "someField";
+ requestBody.routers = List.of(timeRouter, categoryRouter);
+ final var createParams = new
CreateCollectionAPI.CreateCollectionRequestBody();
+ createParams.numShards = 3;
+ createParams.config = "someConfig";
+ requestBody.collCreationParameters = createParams;
+
+ final var remoteMessage =
+
CreateAliasAPI.createRemoteMessageForRoutedAlias(requestBody).getProperties();
+
+ assertEquals(11, remoteMessage.size());
+ assertEquals("createalias", remoteMessage.get(QUEUE_OPERATION));
+ assertEquals("someAliasName", remoteMessage.get("name"));
+ assertEquals("time", remoteMessage.get("router.0.name"));
+ assertEquals("someField", remoteMessage.get("router.0.field"));
+ assertEquals("NOW", remoteMessage.get("router.0.start"));
+ assertEquals("+1MONTH", remoteMessage.get("router.0.interval"));
+ assertEquals(Long.valueOf(123456L),
remoteMessage.get("router.0.maxFutureMs"));
+ assertEquals("category", remoteMessage.get("router.1.name"));
+ assertEquals("someField", remoteMessage.get("router.1.field"));
+ assertEquals(3, remoteMessage.get("create-collection.numShards"));
+ assertEquals("someConfig",
remoteMessage.get("create-collection.collection.configName"));
+ }
+
+ private CreateAliasAPI.CreateAliasRequestBody requestBodyWithProvidedRouter(
+ CreateAliasAPI.RoutedAliasProperties router) {
+ final var requestBody = new CreateAliasAPI.CreateAliasRequestBody();
+ requestBody.name = "validName";
+ final var createParams = new
CreateCollectionAPI.CreateCollectionRequestBody();
+ createParams.numShards = 3;
+ createParams.config = "someConfig";
+ requestBody.collCreationParameters = createParams;
+
+ requestBody.routers = List.of(router);
+
+ return requestBody;
+ }
+
+ @Test
+ public void testConvertsV1ParamsForMultiDimensionalAliasToV2RequestBody() {
+ final var v1Params = new ModifiableSolrParams();
+ v1Params.add("name", "someAliasName");
+ v1Params.add("router.name", "Dimensional[time,category]");
+ v1Params.add("router.0.field", "someField");
+ v1Params.add("router.0.start", "NOW");
+ v1Params.add("router.0.interval", "+1MONTH");
+ v1Params.add("router.0.maxFutureMs", "123456");
+ v1Params.add("router.1.field", "someOtherField");
+ v1Params.add("router.1.maxCardinality", "20");
+ v1Params.add("create-collection.numShards", "3");
+ v1Params.add("create-collection.collection.configName", "someConfig");
+
+ final var requestBody = CreateAliasAPI.createFromSolrParams(v1Params);
+
+ assertEquals("someAliasName", requestBody.name);
+ assertEquals(2, requestBody.routers.size());
+ assertTrue(
+ "Incorrect router type " + requestBody.routers.get(0) + " at index 0",
+ requestBody.routers.get(0) instanceof
CreateAliasAPI.TimeRoutedAliasProperties);
+ final var timeRouter = (CreateAliasAPI.TimeRoutedAliasProperties)
requestBody.routers.get(0);
+ assertEquals("someField", timeRouter.field);
+ assertEquals("NOW", timeRouter.start);
+ assertEquals("+1MONTH", timeRouter.interval);
+ assertEquals(Long.valueOf(123456L), timeRouter.maxFutureMs);
+ assertTrue(
+ "Incorrect router type " + requestBody.routers.get(1) + " at index 1",
+ requestBody.routers.get(1) instanceof
CreateAliasAPI.CategoryRoutedAliasProperties);
+ final var categoryRouter =
+ (CreateAliasAPI.CategoryRoutedAliasProperties)
requestBody.routers.get(1);
+ assertEquals("someOtherField", categoryRouter.field);
+ assertEquals(Long.valueOf(20), categoryRouter.maxCardinality);
+ final var createCollParams = requestBody.collCreationParameters;
+ assertEquals(Integer.valueOf(3), createCollParams.numShards);
+ assertEquals("someConfig", createCollParams.config);
+ }
+ // v1 -> v2 param conversion test
+}
diff --git
a/solr/solr-ref-guide/modules/deployment-guide/pages/alias-management.adoc
b/solr/solr-ref-guide/modules/deployment-guide/pages/alias-management.adoc
index f8093939515..2853a039e5a 100644
--- a/solr/solr-ref-guide/modules/deployment-guide/pages/alias-management.adoc
+++ b/solr/solr-ref-guide/modules/deployment-guide/pages/alias-management.adoc
@@ -45,7 +45,7 @@ While it is possible to send updates to an alias spanning
multiple collections,
*Routed aliases* are aliases with additional capabilities to act as a kind of
super-collection that route updates to the correct collection.
-Routing is data driven and may be based on a temporal field or on categories
specified in a field (normally string based).
+Routing is data driven and may be based on a temporal field or on categories
specified in a field (normally string based).
See xref:aliases.adoc#routed-aliases[Routed Aliases] for some important
high-level information before getting started.
[source,text]
@@ -115,7 +115,16 @@ Most routed alias parameters become _alias properties_
that can subsequently be
CREATEALIAS will validate against many (but not all) bad values, whereas
ALIASPROP blindly accepts any key or value you give it.
Some "valid" modifications allowed by CREATEALIAS may still be unwise, see
notes below. "Expert only" modifications are technically possible, but require
good understanding of how the code works and may require several precursor
operations.
-`router.name`::
+Routed aliases currently support up to two "dimensions" of routing, with each
dimension being either a "time" or "category"-based.
+Each dimension takes a number of parameters, which vary based on its type.
+
+On v1 requests, routing-dimension parameters are grouped together by
query-parameter prefix.
+A routed alias with only one dimension uses the `router.` prefix for its
parameters (e.g. `router.field`).
+Two-dimensional routed aliases add a number to this query-parameter prefix to
distinguish which routing-dimension the parameter belongs to (e.g.
`router.0.name`, `router.1.field`).
+
+On v2 requests, routing-dimensions are specified as individual objects within
a list (e.g. `[{"type": "category", "field": "manu_id_s"}]`).
+
+`router.name` (v1), `type` (v2)::
+
[%autowidth,frame=none]
|===
@@ -124,6 +133,7 @@ s|Required |Default: none |Modify: Do not change after
creation
+
The type of routing to use.
Presently only `time` and `category` and `Dimensional[]` are valid.
+v2 requests only allow `time` or `category` since dimensionality information
lives in the `routers` list unique to v2 requests (though the caveats below
about dimension ordering still apply).
+
In the case of a
xref:aliases.adoc#dimensional-routed-aliases[multi-dimensional routed alias]
(aka "DRA"), it is required to express all the dimensions in the same order
that they will appear in the dimension
array.
@@ -134,7 +144,7 @@ Careful design of dimensional routing is required to avoid
an explosion in the n
Solr Cloud may have difficulty managing more than a thousand collections.
See examples below for further clarification on how to configure individual
dimensions.
-`router.field`::
+`router.field` (v1), `field` (v2)::
+
[%autowidth,frame=none]
|===
@@ -156,9 +166,11 @@ All other fields are identical in requirements and naming
except that we insist
The configset must be created beforehand, either uploaded or copied and
modified.
It's probably a bad idea to use "data driven" mode as schema mutations might
happen concurrently leading to errors.
+On v2 requests, `create-collection` takes a JSON object containing all
provided collection-creation parameters (e.g. `"create-collection": {
"numShards": 3, "config": "_default"}`).
+
==== Time Routed Alias Parameters
-`router.start`::
+`router.start` (v2), `start` (v2)::
+
[%autowidth,frame=none]
|===
@@ -172,7 +184,7 @@ If a document is submitted with an earlier value for
`router.field` then the ear
This date/time MUST NOT have a milliseconds component other than 0.
Particularly, this means `NOW` will fail 999 times out of 1000, though
`NOW/SECOND`, `NOW/MINUTE`, etc., will work just fine.
-`TZ`::
+`TZ` (v1), `tz` (v2)::
+
[%autowidth,frame=none]
|===
@@ -186,7 +198,7 @@ as an alias property.
If GMT-4 is supplied for this value then a document dated
2018-01-14T21:00:00:01.2345Z would be stored in the myAlias_2018-01-15_01
collection (assuming an interval of +1HOUR).
-`router.interval`::
+`router.interval` (v1), `interval` (v2)::
+
[%autowidth,frame=none]
|===
@@ -196,7 +208,7 @@ s|Required |Default: none | Modify: Yes
A date math expression that will be appended to a timestamp to determine the
next collection in the series.
Any date math expression that can be evaluated if appended to a timestamp of
the form 2018-01-15T16:17:18 will work here.
-`router.maxFutureMs`::
+`router.maxFutureMs` (v1), `maxFutureMs` (v2)::
+
[%autowidth,frame=none]
|===
@@ -206,7 +218,7 @@ Any date math expression that can be evaluated if appended
to a timestamp of the
The maximum milliseconds into the future that a document is allowed to have in
`router.field` for it to be accepted without error.
If there was no limit, then an erroneous value could trigger many collections
to be created.
-`router.preemptiveCreateMath`::
+`router.preemptiveCreateMath` (v1), `preemptiveCreateMath` (v2)::
+
[%autowidth,frame=none]
|===
@@ -233,7 +245,7 @@ Example: `90MINUTES`.
+
This property is empty by default indicating just-in-time, synchronous
creation of new collections.
-`router.autoDeleteAge`::
+`router.autoDeleteAge` (v1), `autoDeleteAge` (v2)::
+
[%autowidth,frame=none]
|===
@@ -251,7 +263,7 @@ The default is not to delete.
==== Category Routed Alias Parameters
-`router.maxCardinality`::
+`router.maxCardinality` (v1), `maxCardinality` (v2)::
+
[%autowidth,frame=none]
|===
@@ -261,7 +273,7 @@ The default is not to delete.
The maximum number of categories allowed for this alias.
This setting safeguards against the inadvertent creation of an infinite number
of collections in the event of bad data.
-`router.mustMatch`::
+`router.mustMatch` (v1), `mustMatch` (v2)::
+
[%autowidth,frame=none]
|===
@@ -277,14 +289,14 @@ Overly complex patterns will produce CPU or garbage
collection overhead during i
==== Dimensional Routed Alias Parameters
-`router.#.`::
+`router.#.` (v1)::
+
[%autowidth,frame=none]
|===
|Optional |Default: none | Modify: As per above
|===
+
-This prefix denotes which position in the dimension array is being referred to
for purposes of dimension configuration.
+A prefix used on v1 request parameters to associate the parameter with a
particular dimensional, in multi-dimensional aliases.
+
For example in a `Dimensional[time,category]` alias, `router.0.start` would be
used to set the start time for the time dimension.
@@ -332,12 +344,10 @@
http://localhost:8983/solr/admin/collections?action=CREATEALIAS&name=testalias&c
[source,bash]
----
-curl -X POST http://localhost:8983/api/collections -H 'Content-Type:
application/json' -d '
+curl -X POST http://localhost:8983/api/aliases -H 'Content-Type:
application/json' -d '
{
- "create-alias":{
- "name":"testalias",
- "collections":["foo","bar"]
- }
+ "name":"testalias",
+ "collections":["foo","bar"]
}
'
----
@@ -408,32 +418,32 @@
http://localhost:8983/solr/admin/collections?action=CREATEALIAS
[source,bash]
----
-curl -X POST http://localhost:8983/api/collections -H 'Content-Type:
application/json' -d '
+curl -X POST http://localhost:8983/api/aliases -H 'Content-Type:
application/json' -d '
{
- "create-alias" : {
"name": "somethingTemporalThisWayComes",
- "router" : {
- "name": "time",
- "field": "evt_dt",
- "start":"NOW/MINUTE",
- "interval":"+2HOUR",
- "maxFutureMs":"14400000"
- },
+ "routers" : [
+ {
+ "type": "time",
+ "field": "evt_dt",
+ "start":"NOW/MINUTE",
+ "interval":"+2HOUR",
+ "maxFutureMs":"14400000"
+ }
+ ]
"create-collection" : {
"config":"_default",
"router": {
"name":"implicit",
"field":"foo_s"
},
- "shards":"foo,bar,baz",
+ "shardNames": ["foo", "bar", "baz"],
"numShards": 3,
"tlogReplicas":1,
"pullReplicas":1,
"properties" : {
"foobar":"bazbam"
}
- }
- }
+ }
}
'
----
@@ -502,26 +512,26 @@
http://localhost:8983/solr/admin/collections?action=CREATEALIAS
[source,bash]
----
-curl -X POST http://localhost:8983/api/collections -H 'Content-Type:
application/json' -d '
+curl -X POST http://localhost:8983/api/aliases -H 'Content-Type:
application/json' -d '
{
- "create-alias":{
- "name":"dra_test1",
- "router": {
- "name": "Dimensional[time,category]",
- "routerList" : [ {
- "field":"myDate_tdt",
- "start":"2019-01-01T00:00:00Z",
- "interval":"+1MONTH",
- "maxFutureMs":600000
- },{
- "field":"myCategory_s",
- "maxCardinality":20
- }]
+ "name":"dra_test1",
+ "routers": [
+ {
+ "type": "time",
+ "field":"myDate_tdt",
+ "start":"2019-01-01T00:00:00Z",
+ "interval":"+1MONTH",
+ "maxFutureMs":600000
},
- "create-collection": {
- "config":"_default",
- "numShards":2
+ {
+ "type": "category",
+ "field":"myCategory_s",
+ "maxCardinality":20
}
+ ]
+ "create-collection": {
+ "config":"_default",
+ "numShards":2
}
}
'
diff --git
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/CreateAliasPayload.java
b/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/CreateAliasPayload.java
deleted file mode 100644
index b34e6e56dc5..00000000000
---
a/solr/solrj/src/java/org/apache/solr/client/solrj/request/beans/CreateAliasPayload.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.client.solrj.request.beans;
-
-import static
org.apache.solr.client.solrj.request.beans.V2ApiConstants.CREATE_COLLECTION_KEY;
-
-import java.util.List;
-import java.util.Map;
-import org.apache.solr.common.annotation.JsonProperty;
-import org.apache.solr.common.util.ReflectMapWriter;
-
-public class CreateAliasPayload implements ReflectMapWriter {
- @JsonProperty(required = true)
- public String name;
-
- @JsonProperty public List<String> collections;
-
- @JsonProperty public AliasRouter router;
-
- @JsonProperty public String tz;
-
- @JsonProperty(CREATE_COLLECTION_KEY)
- public Map<String, Object> createCollectionParams;
-
- @JsonProperty public String async;
-
- public static class AliasRouter implements ReflectMapWriter {
- @JsonProperty(required = true)
- public String name;
-
- @JsonProperty public String field;
-
- @JsonProperty public String start;
-
- @JsonProperty public String interval;
-
- @JsonProperty public Long maxFutureMs;
-
- @JsonProperty public String preemptiveCreateMath;
-
- @JsonProperty public String autoDeleteAge;
-
- @JsonProperty public Integer maxCardinality;
-
- @JsonProperty public String mustMatch;
-
- @JsonProperty public List<Map<String, Object>> routerList;
- }
-}