Merge branch 'moshebla-SOLR-13152' into solr-13131
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/CategoryRoutedAlias.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/CategoryRoutedAlias.java
index 4193ec0..7d73149 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/CategoryRoutedAlias.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/CategoryRoutedAlias.java
@@ -29,6 +29,7 @@
import org.apache.solr.cloud.ZkController;
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.params.CommonParams;
import org.apache.solr.core.CoreContainer;
@@ -39,6 +40,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import static org.apache.solr.common.SolrException.ErrorCode.BAD_REQUEST;
+
public class CategoryRoutedAlias implements RoutedAlias {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private static final String COLLECTION_INFIX = "__CRA__";
@@ -78,11 +81,13 @@
private Aliases parsedAliases; // a cached reference to the source of what we parse into parsedCollectionsDesc
private final String aliasName;
private final Map<String, String> aliasMetadata;
+ private final Integer maxCardinality;
private final Pattern mustMatch;
CategoryRoutedAlias(String aliasName, Map<String, String> aliasMetadata) {
this.aliasName = aliasName;
this.aliasMetadata = aliasMetadata;
+ this.maxCardinality = parseMaxCardinality(aliasMetadata.get(ROUTER_MAX_CARDINALITY));
final String mustMatch = this.aliasMetadata.get(ROUTER_MUST_MATCH);
this.mustMatch = mustMatch == null? null: compileMustMatch(mustMatch);
}
@@ -113,17 +118,19 @@
@Override
public void validateRouteValue(AddUpdateCommand cmd) throws SolrException {
- //Mostly this will be filled out by SOLR-13150 and SOLR-13151
- String maxCardinality = aliasMetadata.get(ROUTER_MAX_CARDINALITY);
- if(maxCardinality == null) {
- return;
- }
-
if (this.parsedAliases == null) {
updateParsedCollectionAliases(cmd.getReq().getCore().getCoreContainer().getZkController());
}
- String dataValue = String.valueOf(cmd.getSolrInputDocument().getFieldValue(getRouteField()));
+ Object fieldValue = cmd.getSolrInputDocument().getFieldValue(getRouteField());
+ // possible future enhancement: allow specification of an "unknown" category name to where we can send
+ // docs that are uncategorized.
+ if (fieldValue == null) {
+ throw new SolrException(BAD_REQUEST,"Route value is null");
+ }
+
+ String dataValue = String.valueOf(fieldValue);
+
String candidateCollectionName = buildCollectionNameFromValue(dataValue);
List<String> cols = getCollectionList(this.parsedAliases);
@@ -131,14 +138,22 @@
return;
}
- if (mustMatch != null && !mustMatch.matcher(candidateCollectionName).matches()) {
- throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Collection name " + candidateCollectionName
+ // this check will become very important for future work
+ int infix = candidateCollectionName.indexOf(COLLECTION_INFIX);
+ int valueStart = infix + COLLECTION_INFIX.length();
+ if (candidateCollectionName.substring(valueStart).contains(COLLECTION_INFIX)) {
+ throw new SolrException(BAD_REQUEST, "No portion of the route value may resolve to the 7 character sequence " +
+ "__CRA__");
+ }
+
+ if (mustMatch != null && !mustMatch.matcher(dataValue).matches()) {
+ throw new SolrException(BAD_REQUEST, "Route value " + dataValue
+ " does not match " + ROUTER_MUST_MATCH + ": " + mustMatch);
}
if (cols.stream()
- .filter(x -> !x.contains(UNINITIALIZED)).count() >= Integer.valueOf(maxCardinality)) {
- throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Max cardinality " + maxCardinality
+ .filter(x -> !x.contains(UNINITIALIZED)).count() >= maxCardinality) {
+ throw new SolrException(BAD_REQUEST, "Max cardinality " + maxCardinality
+ " reached for Category Routed Alias: " + getAliasName());
}
}
@@ -194,7 +209,7 @@
// we should see some sort of update to our aliases
if (!updateParsedCollectionAliases(coreContainer.getZkController())) { // thus we didn't make progress...
// this is not expected, even in known failure cases, but we check just in case
- throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
+ throw new SolrException(ErrorCode.SERVER_ERROR,
"We need to create a new category routed collection but for unknown reasons were unable to do so.");
}
}
@@ -202,7 +217,16 @@
} catch (SolrException e) {
throw e;
} catch (Exception e) {
- throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
+ throw new SolrException(ErrorCode.SERVER_ERROR, e);
+ }
+ }
+
+ private Integer parseMaxCardinality(String maxCardinality) {
+ try {
+ return Integer.valueOf(maxCardinality);
+ } catch (NumberFormatException e) {
+ throw new SolrException(BAD_REQUEST, ROUTER_MAX_CARDINALITY + " must be a valid Integer"
+ + ", instead got: " + maxCardinality);
}
}
@@ -210,7 +234,7 @@
try {
return Pattern.compile(mustMatch);
} catch (PatternSyntaxException e) {
- throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, ROUTER_MUST_MATCH + " must be a valid regular"
+ throw new SolrException(BAD_REQUEST, ROUTER_MUST_MATCH + " must be a valid regular"
+ " expression, instead got: " + mustMatch);
}
}
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 4951003..1ae3229 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
@@ -38,9 +38,8 @@
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
-import com.google.common.base.Objects;
-import org.apache.solr.cloud.ZkController;
import com.google.common.base.MoreObjects;
+import org.apache.solr.cloud.ZkController;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.Aliases;
import org.apache.solr.common.params.CommonParams;
diff --git a/solr/core/src/test/org/apache/solr/update/processor/CategoryRoutedAliasUpdateProcessorTest.java b/solr/core/src/test/org/apache/solr/update/processor/CategoryRoutedAliasUpdateProcessorTest.java
index fcf3232..77ecb22 100644
--- a/solr/core/src/test/org/apache/solr/update/processor/CategoryRoutedAliasUpdateProcessorTest.java
+++ b/solr/core/src/test/org/apache/solr/update/processor/CategoryRoutedAliasUpdateProcessorTest.java
@@ -126,9 +126,9 @@
retrievedConfigSetNames.size() >= expectedConfigSetNames.size());
assertTrue("ConfigNames should include :" + expectedConfigSetNames, retrievedConfigSetNames.containsAll(expectedConfigSetNames));
- CollectionAdminRequest.createCategoryRoutedAlias(getAlias(), categoryField,
+ CollectionAdminRequest.createCategoryRoutedAlias(getAlias(), categoryField, 20,
CollectionAdminRequest.createCollection("_unused_", configName, 1, 1)
- .setMaxShardsPerNode(2)).setMaxCardinality(20)
+ .setMaxShardsPerNode(2))
.process(solrClient);
addDocsAndCommit(true,
newDoc(somethingInChinese),
@@ -181,9 +181,9 @@
retrievedConfigSetNames.size() >= expectedConfigSetNames.size());
assertTrue("ConfigNames should include :" + expectedConfigSetNames, retrievedConfigSetNames.containsAll(expectedConfigSetNames));
- CollectionAdminRequest.createCategoryRoutedAlias(getAlias(), categoryField,
+ CollectionAdminRequest.createCategoryRoutedAlias(getAlias(), categoryField, 20,
CollectionAdminRequest.createCollection("_unused_", configName, 1, 1)
- .setMaxShardsPerNode(2)).setMaxCardinality(Integer.MAX_VALUE)
+ .setMaxShardsPerNode(2))
.process(solrClient);
// now we index a document
@@ -200,6 +200,12 @@
newDoc(SHIPS[4]));
assertInvariants(colVogon, colHoG, colStunt, colArk, colBistro);
+
+ // make sure we fail if we have no value to route on.
+ testFailedDocument(newDoc(null), "Route value is null");
+ testFailedDocument(newDoc("foo__CRA__bar"), "7 character sequence __CRA__");
+ testFailedDocument(newDoc("fóóCRAóóbar"), "7 character sequence __CRA__");
+
}
private String noSpaces(String ship) {
@@ -211,43 +217,22 @@
private String noDollar(String ship) {
return ship.replaceAll("\\$", "_");
}
- @Slow
- @Test
- public void testMissingRequiredParams() throws Exception {
- String configName = getSaferTestName();
- createConfigSet(configName);
-
- List<String> retrievedConfigSetNames = new ConfigSetAdminRequest.List().process(solrClient).getConfigSets();
- List<String> expectedConfigSetNames = Arrays.asList("_default", configName);
-
- // config sets leak between tests so we can't be any more specific than this on the next 2 asserts
- assertTrue("We expect at least 2 configSets",
- retrievedConfigSetNames.size() >= expectedConfigSetNames.size());
- assertTrue("ConfigNames should include :" + expectedConfigSetNames, retrievedConfigSetNames.containsAll(expectedConfigSetNames));
-
- SolrException e = expectThrows(SolrException.class, () -> CollectionAdminRequest.createCategoryRoutedAlias(getAlias(), categoryField,
- CollectionAdminRequest.createCollection("_unused_", configName, 1, 1)
- .setMaxShardsPerNode(2))
- .process(solrClient)
- );
- assertTrue("Create Alias should fail since not all required params were supplied", e.getMessage().contains("Not all required params were supplied"));
- }
@Slow
@Test
public void testMustMatch() throws Exception {
String configName = getSaferTestName();
createConfigSet(configName);
- final String mustMatchRegex = ".+_solr";
+ final String mustMatchRegex = "HHS\\s.+_solr";
final int maxCardinality = Integer.MAX_VALUE; // max cardinality for current test
// Start with one collection manually created (and use higher numShards & replicas than we'll use for others)
// This tests we may pre-create the collection and it's acceptable.
- final String colVogon = getAlias() + "__CRA__" + noSpaces(SHIPS[0]) + "_solr";
+ final String colVogon = getAlias() + "__CRA__" + noSpaces("HHS "+ SHIPS[0]) + "_solr";
// we expect changes ensuring a legal collection name.
- final String colHoG = getAlias() + "__CRA__" + noSpaces(SHIPS[1]) + "_solr";
+ final String colHoG = getAlias() + "__CRA__" + noSpaces("HHS "+ SHIPS[1]) + "_solr";
List<String> retrievedConfigSetNames = new ConfigSetAdminRequest.List().process(solrClient).getConfigSets();
List<String> expectedConfigSetNames = Arrays.asList("_default", configName);
@@ -257,21 +242,20 @@
retrievedConfigSetNames.size() >= expectedConfigSetNames.size());
assertTrue("ConfigNames should include :" + expectedConfigSetNames, retrievedConfigSetNames.containsAll(expectedConfigSetNames));
- CollectionAdminRequest.createCategoryRoutedAlias(getAlias(), categoryField,
+ CollectionAdminRequest.createCategoryRoutedAlias(getAlias(), categoryField, maxCardinality,
CollectionAdminRequest.createCollection("_unused_", configName, 1, 1)
.setMaxShardsPerNode(2))
- .setMaxCardinality(maxCardinality)
.setMustMatch(mustMatchRegex)
.process(solrClient);
// now we index a document
- addDocsAndCommit(true, newDoc(SHIPS[0] + "_solr"));
+ addDocsAndCommit(true, newDoc("HHS " + SHIPS[0] + "_solr"));
//assertDocRoutedToCol(lastDocId, col23rd);
String uninitialized = getAlias() + "__CRA__" + CategoryRoutedAlias.UNINITIALIZED;
assertInvariants(colVogon, uninitialized);
- addDocsAndCommit(true, newDoc(SHIPS[1] + "_solr"));
+ addDocsAndCommit(true, newDoc("HHS "+ SHIPS[1] + "_solr"));
assertInvariants(colVogon, colHoG);
@@ -298,10 +282,9 @@
retrievedConfigSetNames.size() >= expectedConfigSetNames.size());
assertTrue("ConfigNames should include :" + expectedConfigSetNames, retrievedConfigSetNames.containsAll(expectedConfigSetNames));
- SolrException e = expectThrows(SolrException.class, () -> CollectionAdminRequest.createCategoryRoutedAlias(getAlias(), categoryField,
+ SolrException e = expectThrows(SolrException.class, () -> CollectionAdminRequest.createCategoryRoutedAlias(getAlias(), categoryField, maxCardinality,
CollectionAdminRequest.createCollection("_unused_", configName, 1, 1)
.setMaxShardsPerNode(2))
- .setMaxCardinality(maxCardinality)
.setMustMatch(mustMatchRegex)
.process(solrClient)
);
@@ -333,9 +316,9 @@
retrievedConfigSetNames.size() >= expectedConfigSetNames.size());
assertTrue("ConfigNames should include :" + expectedConfigSetNames, retrievedConfigSetNames.containsAll(expectedConfigSetNames));
- CollectionAdminRequest.createCategoryRoutedAlias(getAlias(), categoryField,
+ CollectionAdminRequest.createCategoryRoutedAlias(getAlias(), categoryField, maxCardinality,
CollectionAdminRequest.createCollection("_unused_", configName, 1, 1)
- .setMaxShardsPerNode(2)).setMaxCardinality(maxCardinality)
+ .setMaxShardsPerNode(2))
.process(solrClient);
// now we index a document
@@ -372,9 +355,9 @@
// 4 of which are leaders, and 8 of which should fail this test.
final int numShards = 1 + random().nextInt(4);
final int numReplicas = 1 + random().nextInt(3);
- CollectionAdminRequest.createCategoryRoutedAlias(getAlias(), categoryField,
+ CollectionAdminRequest.createCategoryRoutedAlias(getAlias(), categoryField, 20,
CollectionAdminRequest.createCollection("_unused_", configName, numShards, numReplicas)
- .setMaxShardsPerNode(numReplicas)).setMaxCardinality(20)
+ .setMaxShardsPerNode(numReplicas))
.process(solrClient);
// cause some collections to be created
@@ -447,9 +430,14 @@
}
private SolrInputDocument newDoc(String routedValue) {
- return sdoc("id", Integer.toString(++lastDocId),
- categoryField, routedValue,
- intField, "0"); // always 0
+ if (routedValue != null) {
+ return sdoc("id", Integer.toString(++lastDocId),
+ categoryField, routedValue,
+ intField, "0"); // always 0
+ } else {
+ return sdoc("id", Integer.toString(++lastDocId),
+ intField, "0"); // always 0
+ }
}
@Override
@@ -477,7 +465,8 @@
final UpdateResponse resp = solrClient.add(getAlias(), sdoc);
// if we have a TolerantUpdateProcessor then we see it there)
final Object errors = resp.getResponseHeader().get("errors"); // Tolerant URP
- assertTrue(errors != null && errors.toString().contains(errorMsg));
+ assertNotNull(errors);
+ assertTrue("Expected to find " + errorMsg + " in errors: " + errors.toString(),errors.toString().contains(errorMsg));
} catch (SolrException e) {
assertTrue(e.getMessage().contains(errorMsg));
}
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 7ef507c..de7f307 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
@@ -1619,13 +1619,15 @@
*
* @param aliasName the name of the alias to create.
* @param routerField the document field to contain the timestamp to route on
+ * @param maxCardinality the maximum number of collections under this CRA
* @param createCollTemplate Holds options to create a collection. The "name" is ignored.
*/
public static CreateCategoryRoutedAlias createCategoryRoutedAlias(String aliasName,
String routerField,
+ int maxCardinality,
Create createCollTemplate) {
- return new CreateCategoryRoutedAlias(aliasName, routerField, createCollTemplate);
+ return new CreateCategoryRoutedAlias(aliasName, routerField, maxCardinality, createCollTemplate);
}
public static class CreateCategoryRoutedAlias extends AsyncCollectionAdminRequest {
@@ -1642,16 +1644,12 @@
private final Create createCollTemplate;
- public CreateCategoryRoutedAlias(String aliasName, String routerField, Create createCollTemplate) {
+ public CreateCategoryRoutedAlias(String aliasName, String routerField, int maxCardinality, Create createCollTemplate) {
super(CollectionAction.CREATEALIAS);
this.aliasName = aliasName;
this.routerField = routerField;
- this.createCollTemplate = createCollTemplate;
- }
-
- public CreateCategoryRoutedAlias setMaxCardinality(int maxCardinality) {
this.maxCardinality = maxCardinality;
- return this;
+ this.createCollTemplate = createCollTemplate;
}
public CreateCategoryRoutedAlias setMustMatch(String regex) {
@@ -1665,10 +1663,7 @@
params.add(CommonParams.NAME, aliasName);
params.add(ROUTER_TYPE_NAME, "category");
params.add(ROUTER_FIELD, routerField);
-
- if (maxCardinality != null) {
- params.add(ROUTER_MAX_CARDINALITY, maxCardinality.toString());
- }
+ params.add(ROUTER_MAX_CARDINALITY, maxCardinality.toString());
if (mustMatch != null) {
params.add(ROUTER_MUST_MATCH, mustMatch);
diff --git a/solr/solrj/src/resources/apispec/collections.Commands.json b/solr/solrj/src/resources/apispec/collections.Commands.json
index dc8e251..f24fe72 100644
--- a/solr/solrj/src/resources/apispec/collections.Commands.json
+++ b/solr/solrj/src/resources/apispec/collections.Commands.json
@@ -175,6 +175,14 @@
"autoDeleteAge": {
"type": "string",
"description": "A date math expressions yielding a time in the past. Collections covering a period of time entirely before this age will be automatically deleted."
+ },
+ "maxCardinality": {
+ "type": "integer",
+ "description": "The maximum number of categories allowed for this alias."
+ },
+ "mustMatch": {
+ "type": "string",
+ "description": "A regular expression that the value of the field specified by `router.field` must match before a corresponding collection will be created."
}
}
},