You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by gu...@apache.org on 2019/01/28 23:07:40 UTC

[lucene-solr] 05/05: SOLR-13148 Factory for routed aliases, remove CollectionHandler deps on TimeRoutedAlias, minor cleanup

This is an automated email from the ASF dual-hosted git repository.

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

commit c78e1b61fb48d6bf5f24ddd93a6592d5e1dd3e32
Author: Gus Heck <gu...@apache.org>
AuthorDate: Mon Jan 28 16:02:08 2019 -0500

    SOLR-13148 Factory for routed aliases, remove CollectionHandler deps
    on TimeRoutedAlias, minor cleanup
---
 .../cloud/api/collections/CategoryRoutedAlias.java |  20 ++-
 .../solr/cloud/api/collections/CreateAliasCmd.java |  48 +++---
 .../solr/cloud/api/collections/RoutedAlias.java    |  81 ++++++++--
 .../cloud/api/collections/TimeRoutedAlias.java     | 166 ++++++++++-----------
 .../solr/handler/admin/CollectionsHandler.java     |  41 +++--
 .../processor/RoutedAliasUpdateProcessor.java      |  20 +--
 .../apache/solr/cloud/CreateRoutedAliasTest.java   |   2 +-
 7 files changed, 216 insertions(+), 162 deletions(-)

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 30b0747..5a352a6 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
@@ -18,8 +18,11 @@
 package org.apache.solr.cloud.api.collections;
 
 import java.time.Instant;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 
 import org.apache.solr.cloud.ZkController;
 import org.apache.solr.common.cloud.Aliases;
@@ -29,10 +32,9 @@ public class CategoryRoutedAlias implements RoutedAlias {
   private final String aliasName;
   private final Map<String, String> aliasProperties;
 
-  public CategoryRoutedAlias(String aliasName, Map<String, String> aliasProperties) {
-
+  CategoryRoutedAlias(String aliasName, Map<String, String> aliasMetadata) {
     this.aliasName = aliasName;
-    this.aliasProperties = aliasProperties;
+    this.aliasProperties = aliasMetadata;
   }
 
   @Override
@@ -61,7 +63,7 @@ public class CategoryRoutedAlias implements RoutedAlias {
   }
 
   @Override
-  public String computeInitialCollectionName(String collection) {
+  public Optional<String> computeInitialCollectionName() {
     return null;
   }
 
@@ -69,4 +71,14 @@ public class CategoryRoutedAlias implements RoutedAlias {
   public Map<String, String> getAliasMetadata() {
     return aliasProperties;
   }
+
+  @Override
+  public Set<String> getRequiredParams() {
+    return new HashSet<>();
+  }
+
+  @Override
+  public Set<String> getOptionalParams() {
+    return new HashSet<>();
+  }
 }
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 182eda3..320a4ae 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
@@ -19,11 +19,12 @@ package org.apache.solr.cloud.api.collections;
 
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
-import java.util.function.BiFunction;
 import java.util.stream.Collectors;
 
 import org.apache.solr.common.SolrException;
@@ -97,36 +98,29 @@ public class CreateAliasCmd extends AliasCmd {
   }
 
   private void callCreateRoutedAlias(ZkNodeProps message, String aliasName, ZkStateReader zkStateReader, ClusterState state) throws Exception {
-    // Validate we got everything we need
+    // Validate we got a basic minimum
     if (!message.getProperties().keySet().containsAll(RoutedAlias.MINIMAL_REQUIRED_PARAMS)) {
       throw new SolrException(BAD_REQUEST, "A routed alias requires these params: " + RoutedAlias.MINIMAL_REQUIRED_PARAMS
       + " plus some create-collection prefixed ones.");
     }
 
-    String aliasType = message.getStr(RoutedAlias.ROUTER_TYPE_NAME);
-
-    BiFunction<String, ZkNodeProps, RoutedAlias> aliasConstructor = RoutedAlias.constructorFactory.get(aliasType);
-
-    if (aliasConstructor == null) {
-      throw new SolrException(BAD_REQUEST, "Router name: " + aliasType + " is not in supported types, "
-          + String.join(", ", RoutedAlias.constructorFactory.keySet()));
-    }
-
-
-
-    RoutedAlias routedAlias = aliasConstructor.apply(aliasName, message);
-
-    String start = message.getStr(RoutedAlias.ROUTER_START);
-    if (start != null) {
-      String initialCollectionName = routedAlias.computeInitialCollectionName(start);
-      if (initialCollectionName != null) {
-        ensureCollection(aliasName, zkStateReader, state, routedAlias.getAliasMetadata(), initialCollectionName);
-        // Create/update the alias
-        zkStateReader.aliasesManager.applyModificationAndExportToZk(aliases -> aliases
-            .cloneWithCollectionAlias(aliasName, initialCollectionName)
-            .cloneWithCollectionAliasProperties(aliasName, routedAlias.getAliasMetadata()));
-        return;
-      }
+    // convert values to strings
+    Map<String, String> props = new LinkedHashMap<>();
+    message.getProperties().forEach((key, value) -> props.put(key, String.valueOf(value)));
+
+    // Further validation happens here
+    RoutedAlias routedAlias = RoutedAlias.fromProps(aliasName, props);
+
+    // If we can, create the first collection.
+    Optional<String> initialCollectionName = routedAlias.computeInitialCollectionName();
+    if (initialCollectionName.isPresent()) {
+      String initialColl = initialCollectionName.get();
+      ensureAliasCollection(aliasName, zkStateReader, state, routedAlias.getAliasMetadata(), initialColl);
+      // Create/update the alias
+      zkStateReader.aliasesManager.applyModificationAndExportToZk(aliases -> aliases
+          .cloneWithCollectionAlias(aliasName, initialColl)
+          .cloneWithCollectionAliasProperties(aliasName, routedAlias.getAliasMetadata()));
+      return;
     }
 
     // Create/update the alias
@@ -134,7 +128,7 @@ public class CreateAliasCmd extends AliasCmd {
         .cloneWithCollectionAliasProperties(aliasName, routedAlias.getAliasMetadata()));
   }
 
-  private void ensureCollection(String aliasName, ZkStateReader zkStateReader, ClusterState state, Map<String, String> aliasProperties, String initialCollectionName) throws Exception {
+  private void ensureAliasCollection(String aliasName, ZkStateReader zkStateReader, ClusterState state, Map<String, String> aliasProperties, String initialCollectionName) throws Exception {
     // Create the collection
     createCollectionAndWait(state, aliasName, aliasProperties, initialCollectionName, ocmh);
     validateAllCollectionsExistAndNoDups(Collections.singletonList(initialCollectionName), zkStateReader);
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 1a607ba..5e7df68 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
@@ -18,30 +18,35 @@
 package org.apache.solr.cloud.api.collections;
 
 import java.time.Instant;
+import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
-import java.util.function.BiFunction;
 
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Sets;
 import org.apache.solr.cloud.ZkController;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.Aliases;
-import org.apache.solr.common.cloud.ZkNodeProps;
 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;
+
 public interface RoutedAlias {
 
-  Map<String, BiFunction<String, ZkNodeProps, RoutedAlias>> constructorFactory = ImmutableMap.<String, BiFunction<String, ZkNodeProps, RoutedAlias>>builder()
-      .put("time", TimeRoutedAlias::fromZkProps)
-      .build();
+  /**
+   * Types supported. Every entry here must have a case in the switch statement in {@link #fromProps(String, Map)}
+   */
+  enum SupportedRouterTypes {
+    TIME,
+    CATEGORY
+  }
 
   String ROUTER_PREFIX = "router.";
   String ROUTER_TYPE_NAME = ROUTER_PREFIX + "name";
   String ROUTER_FIELD = ROUTER_PREFIX + "field";
-  String ROUTER_AUTO_DELETE_AGE = ROUTER_PREFIX + "autoDeleteAge";
-  String ROUTER_START = ROUTER_PREFIX + "start";
   String CREATE_COLLECTION_PREFIX = "create-collection.";
   Set<String> MINIMAL_REQUIRED_PARAMS = Sets.newHashSet(ROUTER_TYPE_NAME, ROUTER_FIELD);
   String ROUTED_ALIAS_NAME_CORE_PROP = "routedAliasName"; // core prop
@@ -52,6 +57,42 @@ public interface RoutedAlias {
   }
 
   /**
+   * Factory method for implementations of this interface. There should be no reason to construct instances
+   * elsewhere, and routed alias types are encouraged to have package private constructors.
+   *
+   * @param aliasName The alias name (will be returned by {@link #getAliasName()}
+   * @param props     The properties from an overseer message.
+   * @return An implementation appropriate for the supplied properties, or null if no type is specified.
+   * @throws SolrException If the properties are invalid or the router type is unknown.
+   */
+  static RoutedAlias fromProps(String aliasName, Map<String, String> props) throws SolrException {
+
+    String typeStr = props.get(ROUTER_TYPE_NAME);
+    if (typeStr == null) {
+      return null;
+    }
+    SupportedRouterTypes routerType;
+    try {
+       routerType = SupportedRouterTypes.valueOf(typeStr.toUpperCase(Locale.ENGLISH));
+    } catch (IllegalArgumentException e) {
+      throw new SolrException(BAD_REQUEST, "Router name: " + typeStr + " is not in supported types, "
+          + Arrays.asList(SupportedRouterTypes.values()));
+    }
+    switch (routerType) {
+      case TIME:
+        return new TimeRoutedAlias(aliasName, props);
+      case CATEGORY:
+        return new CategoryRoutedAlias(aliasName, props);
+      default:
+        // if we got a type not handled by the switch there's been a bogus implementation.
+        throw new SolrException(SERVER_ERROR, "Router " + routerType + " is not fully implemented. If you see this" +
+            "error in an official release please file a bug report. Available types were:"
+            + Arrays.asList(SupportedRouterTypes.values()));
+
+    }
+  }
+
+  /**
    * Ensure our parsed version of the alias collection list is up to date. If it was modified, return true.
    * Note that this will return true if some other alias was modified or if properties were modified. These
    * are spurious and the caller should be written to be tolerant of no material changes.
@@ -59,19 +100,28 @@ public interface RoutedAlias {
   boolean updateParsedCollectionAliases(ZkController zkController);
 
   /**
+   * Create the initial collection for this RoutedAlias if applicable.
+   *
+   * Routed Aliases do not aggregate existing collections, instead they create collections on the fly. If the initial
+   * collection can be determined from initialization parameters it should be calculated here.
    *
-   * @param startParam the start parameter passed to create alias cmd
    * @return optional string of initial collection name
    */
-  String computeInitialCollectionName(String startParam);
+  Optional<String> computeInitialCollectionName();
+
 
+  /**
+   * The name of the alias. This name is used in place of a collection name for both queries and updates.
+   *
+   * @return The name of the Alias.
+   */
   String getAliasName();
 
   /**
    * Parses the elements of the collection list. Result is returned them in sorted order (desc) if there
    * is a natural order for this type of routed alias
    */
-  List<Map.Entry<Instant,String>> parseCollections(Aliases aliases);
+  List<Map.Entry<Instant, String>> parseCollections(Aliases aliases);
 
   /**
    * Check that the value we will be routing on is legal for this type of routed alias.
@@ -85,13 +135,16 @@ public interface RoutedAlias {
    *
    * @param cmd The command that might cause collection creation
    * @return The name of the proper destination collection for the document which may or may not be a
-   *         newly created collection
+   * newly created collection
    */
-  String createCollectionsIfRequired( AddUpdateCommand cmd);
+  String createCollectionsIfRequired(AddUpdateCommand cmd);
 
   /**
-   *
    * @return get alias related metadata
    */
   Map<String, String> getAliasMetadata();
+
+  Set<String> getRequiredParams();
+
+  Set<String> getOptionalParams();
 }
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 c8b0ebb..afc9ce4 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
@@ -30,19 +30,19 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Date;
-import java.util.LinkedHashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
 import java.util.TimeZone;
 import java.util.concurrent.TimeUnit;
-import java.util.function.Predicate;
 
 import com.google.common.base.Objects;
 import org.apache.solr.cloud.ZkController;
 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.CommonParams;
 import org.apache.solr.common.params.MapSolrParams;
 import org.apache.solr.common.params.RequiredSolrParams;
@@ -58,7 +58,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import static org.apache.commons.lang3.StringUtils.isNotBlank;
-import static org.apache.solr.cloud.api.collections.TimeRoutedAlias.CreationType.*;
+import static org.apache.solr.cloud.api.collections.TimeRoutedAlias.CreationType.ASYNC_PREEMPTIVE;
+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.CommonParams.TZ;
 
@@ -86,33 +88,30 @@ public class TimeRoutedAlias implements RoutedAlias {
   public static final String ROUTER_START = ROUTER_PREFIX + "start";
   public static final String ROUTER_INTERVAL = ROUTER_PREFIX + "interval";
   public static final String ROUTER_MAX_FUTURE = ROUTER_PREFIX + "maxFutureMs";
+  public static final String ROUTER_AUTO_DELETE_AGE = ROUTER_PREFIX + "autoDeleteAge";
   public static final String ROUTER_PREEMPTIVE_CREATE_MATH = ROUTER_PREFIX + "preemptiveCreateMath";
   // plus TZ and NAME
 
   /**
    * Parameters required for creating a routed alias
    */
-  public static final List<String> REQUIRED_ROUTER_PARAMS = Collections.unmodifiableList(Arrays.asList(
+  public static final Set<String> REQUIRED_ROUTER_PARAMS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
       CommonParams.NAME,
       ROUTER_TYPE_NAME,
       ROUTER_FIELD,
       ROUTER_START,
-      ROUTER_INTERVAL));
+      ROUTER_INTERVAL)));
 
   /**
    * Optional parameters for creating a routed alias excluding parameters for collection creation.
    */
   //TODO lets find a way to remove this as it's harder to maintain than required list
-  public static final List<String> OPTIONAL_ROUTER_PARAMS = Collections.unmodifiableList(Arrays.asList(
+  public static final Set<String> OPTIONAL_ROUTER_PARAMS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
       ROUTER_MAX_FUTURE,
       ROUTER_AUTO_DELETE_AGE,
       ROUTER_PREEMPTIVE_CREATE_MATH,
-      TZ)); // kinda special
+      TZ))); // kinda special
 
-  static Predicate<String> PARAM_IS_PROP =
-      key -> key.equals(TZ) ||
-          (key.startsWith(ROUTER_PREFIX) && !key.equals(ROUTER_START)) || //TODO reconsider START special case
-          key.startsWith(CREATE_COLLECTION_PREFIX);
 
   // This format must be compatible with collection name limitations
   private static final DateTimeFormatter DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
@@ -122,58 +121,6 @@ public class TimeRoutedAlias implements RoutedAlias {
       .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0)
       .toFormatter(Locale.ROOT).withZone(ZoneOffset.UTC); // deliberate -- collection names disregard TZ
 
-  @Override
-  public String computeInitialCollectionName(String dateStr) {
-    return formatCollectionNameFromInstant(aliasName, parseStringAsInstant(dateStr, timeZone));
-  }
-
-  public static Instant parseInstantFromCollectionName(String aliasName, String collection) {
-    final String dateTimePart = collection.substring(aliasName.length() + 1);
-    return DATE_TIME_FORMATTER.parse(dateTimePart, Instant::from);
-  }
-
-  public static String formatCollectionNameFromInstant(String aliasName, Instant timestamp) {
-    String nextCollName = DATE_TIME_FORMATTER.format(timestamp);
-    for (int i = 0; i < 3; i++) { // chop off seconds, minutes, hours
-      if (nextCollName.endsWith("_00")) {
-        nextCollName = nextCollName.substring(0, nextCollName.length()-3);
-      }
-    }
-    assert DATE_TIME_FORMATTER.parse(nextCollName, Instant::from).equals(timestamp);
-    return aliasName + "_" + nextCollName;
-  }
-
-  Instant parseStringAsInstant(String str, TimeZone zone) {
-    Instant start = DateMathParser.parseMath(new Date(), str, zone).toInstant();
-    checkMilis(start);
-    return start;
-  }
-
-  private void checkMilis(Instant date) {
-    if (!date.truncatedTo(ChronoUnit.SECONDS).equals(date)) {
-      throw new SolrException(BAD_REQUEST,
-          "Date or date math for start time includes milliseconds, which is not supported. " +
-              "(Hint: 'NOW' used without rounding always has this problem)");
-    }
-  }
-
-
-  @Override
-  public boolean updateParsedCollectionAliases(ZkController zkController) {
-    final Aliases aliases = zkController.getZkStateReader().getAliases(); // note: might be different from last request
-    if (this.parsedCollectionsAliases != aliases) {
-      if (this.parsedCollectionsAliases != null) {
-        log.debug("Observing possibly updated alias: {}", getAliasName());
-      }
-      this.parsedCollectionsDesc = parseCollections(aliases );
-      this.parsedCollectionsAliases = aliases;
-      return true;
-    }
-    return false;
-  }
-
-
-
   //
   // Instance data and methods
   //
@@ -186,11 +133,20 @@ public class TimeRoutedAlias implements RoutedAlias {
   private final String preemptiveCreateMath;
   private final String autoDeleteAgeMath; // ex: /DAY-30DAYS  *optional*
   private final TimeZone timeZone;
+  private String start;
+
+  TimeRoutedAlias(String aliasName, Map<String, String> aliasMetadata) throws SolrException {
+    // Validate we got everything we need
+    if (!aliasMetadata.keySet().containsAll(TimeRoutedAlias.REQUIRED_ROUTER_PARAMS)) {
+      throw new SolrException(BAD_REQUEST, "A time routed alias requires these params: " + TimeRoutedAlias.REQUIRED_ROUTER_PARAMS
+          + " plus some create-collection prefixed ones.");
+    }
 
-  public TimeRoutedAlias(String aliasName, Map<String, String> aliasMetadata) {
-    this.aliasName = aliasName;
     this.aliasMetadata = aliasMetadata;
-    final MapSolrParams params = new MapSolrParams(aliasMetadata); // for convenience
+
+    this.start = this.aliasMetadata.get(ROUTER_START);
+    this.aliasName = aliasName;
+    final MapSolrParams params = new MapSolrParams(this.aliasMetadata); // for convenience
     final RequiredSolrParams required = params.required();
     if (!"time".equals(required.get(ROUTER_TYPE_NAME))) {
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Only 'time' routed aliases is supported right now.");
@@ -204,7 +160,7 @@ public class TimeRoutedAlias implements RoutedAlias {
     String pcmTmp = params.get(ROUTER_PREEMPTIVE_CREATE_MATH);
     preemptiveCreateMath = pcmTmp != null ? (pcmTmp.startsWith("-") ? pcmTmp : "-" + pcmTmp) : null;
     autoDeleteAgeMath = params.get(ROUTER_AUTO_DELETE_AGE); // no default
-    timeZone = TimeZoneUtils.parseTimezone(aliasMetadata.get(CommonParams.TZ));
+    timeZone = TimeZoneUtils.parseTimezone(this.aliasMetadata.get(CommonParams.TZ));
 
     // More validation:
 
@@ -243,6 +199,55 @@ public class TimeRoutedAlias implements RoutedAlias {
   }
 
   @Override
+  public Optional<String> computeInitialCollectionName() {
+    return Optional.of(formatCollectionNameFromInstant(aliasName, parseStringAsInstant(this.start, timeZone)));
+  }
+
+  public static Instant parseInstantFromCollectionName(String aliasName, String collection) {
+    final String dateTimePart = collection.substring(aliasName.length() + 1);
+    return DATE_TIME_FORMATTER.parse(dateTimePart, Instant::from);
+  }
+
+  public static String formatCollectionNameFromInstant(String aliasName, Instant timestamp) {
+    String nextCollName = DATE_TIME_FORMATTER.format(timestamp);
+    for (int i = 0; i < 3; i++) { // chop off seconds, minutes, hours
+      if (nextCollName.endsWith("_00")) {
+        nextCollName = nextCollName.substring(0, nextCollName.length()-3);
+      }
+    }
+    assert DATE_TIME_FORMATTER.parse(nextCollName, Instant::from).equals(timestamp);
+    return aliasName + "_" + nextCollName;
+  }
+
+  Instant parseStringAsInstant(String str, TimeZone zone) {
+    Instant start = DateMathParser.parseMath(new Date(), str, zone).toInstant();
+    checkMilis(start);
+    return start;
+  }
+
+  private void checkMilis(Instant date) {
+    if (!date.truncatedTo(ChronoUnit.SECONDS).equals(date)) {
+      throw new SolrException(BAD_REQUEST,
+          "Date or date math for start time includes milliseconds, which is not supported. " +
+              "(Hint: 'NOW' used without rounding always has this problem)");
+    }
+  }
+
+  @Override
+  public boolean updateParsedCollectionAliases(ZkController zkController) {
+    final Aliases aliases = zkController.getZkStateReader().getAliases(); // note: might be different from last request
+    if (this.parsedCollectionsAliases != aliases) {
+      if (this.parsedCollectionsAliases != null) {
+        log.debug("Observing possibly updated alias: {}", getAliasName());
+      }
+      this.parsedCollectionsDesc = parseCollections(aliases );
+      this.parsedCollectionsAliases = aliases;
+      return true;
+    }
+    return false;
+  }
+
+  @Override
   public String getAliasName() {
     return aliasName;
   }
@@ -321,8 +326,6 @@ public class TimeRoutedAlias implements RoutedAlias {
     }
   }
 
-
-
   @Override
   public String createCollectionsIfRequired(AddUpdateCommand cmd) {
     SolrQueryRequest req = cmd.getReq();
@@ -381,19 +384,14 @@ public class TimeRoutedAlias implements RoutedAlias {
     return aliasMetadata;
   }
 
-  static RoutedAlias fromZkProps(String aliasName, ZkNodeProps aliasProps) throws SolrException {
-    // Validate we got everything we need
-    if (!aliasProps.getProperties().keySet().containsAll(TimeRoutedAlias.REQUIRED_ROUTER_PARAMS)) {
-      throw new SolrException(BAD_REQUEST, "A time routed alias requires these params: " + TimeRoutedAlias.REQUIRED_ROUTER_PARAMS
-          + " plus some create-collection prefixed ones.");
-    }
-
-    Map<String, String> aliasProperties = new LinkedHashMap<>();
-    aliasProps.getProperties().entrySet().stream()
-        .filter(entry -> TimeRoutedAlias.PARAM_IS_PROP.test(entry.getKey()))
-        .forEach(entry -> aliasProperties.put(entry.getKey(), (String) entry.getValue())); // way easier than .collect
+  @Override
+  public Set<String> getRequiredParams() {
+    return REQUIRED_ROUTER_PARAMS;
+  }
 
-    return new TimeRoutedAlias(aliasName, aliasProperties); // validates as well
+  @Override
+  public Set<String> getOptionalParams() {
+    return OPTIONAL_ROUTER_PARAMS;
   }
 
   /**
@@ -470,8 +468,6 @@ public class TimeRoutedAlias implements RoutedAlias {
     }
   }
 
-
-
   private void preemptiveAsync(Runnable r, SolrCore core) {
     preemptiveCreateOnceAlready = true;
     core.runAsync(r);
@@ -558,6 +554,4 @@ public class TimeRoutedAlias implements RoutedAlias {
     ASYNC_PREEMPTIVE,
     SYNCHRONOUS
   }
-
-
 }
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 725d2bd..3f39808 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
@@ -23,6 +23,7 @@ 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;
@@ -52,6 +53,7 @@ import org.apache.solr.cloud.OverseerTaskQueue.QueueEvent;
 import org.apache.solr.cloud.ZkController.NotInClusterStateException;
 import org.apache.solr.cloud.ZkController;
 import org.apache.solr.cloud.ZkShardTerms;
+import org.apache.solr.cloud.api.collections.RoutedAlias;
 import org.apache.solr.cloud.overseer.SliceMutator;
 import org.apache.solr.cloud.rule.ReplicaAssigner;
 import org.apache.solr.cloud.rule.Rule;
@@ -115,9 +117,7 @@ import static org.apache.solr.cloud.api.collections.OverseerCollectionMessageHan
 import static org.apache.solr.cloud.api.collections.OverseerCollectionMessageHandler.REQUESTID;
 import static org.apache.solr.cloud.api.collections.OverseerCollectionMessageHandler.SHARDS_PROP;
 import static org.apache.solr.cloud.api.collections.OverseerCollectionMessageHandler.SHARD_UNIQUE;
-import static org.apache.solr.cloud.api.collections.TimeRoutedAlias.CREATE_COLLECTION_PREFIX;
-import static org.apache.solr.cloud.api.collections.TimeRoutedAlias.OPTIONAL_ROUTER_PARAMS;
-import static org.apache.solr.cloud.api.collections.TimeRoutedAlias.REQUIRED_ROUTER_PARAMS;
+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.DocCollection.DOC_ROUTER;
 import static org.apache.solr.common.cloud.DocCollection.RULE;
@@ -551,20 +551,39 @@ public class CollectionsHandler extends RequestHandlerBase implements Permission
       String alias = req.getParams().get(NAME);
       SolrIdentifierValidator.validateAliasName(alias);
       String collections = req.getParams().get("collections");
-      Map<String, Object> result = copy(req.getParams(), null, REQUIRED_ROUTER_PARAMS);
-      copy(req.getParams(), result, OPTIONAL_ROUTER_PARAMS);
+      RoutedAlias routedAlias = null;
+      Exception ex = null;
+      try {
+        // note that RA specific validation occurs here.
+        routedAlias = RoutedAlias.fromProps(alias, req.getParams().toMap(new HashMap<>()));
+      } catch (SolrException e) {
+        // we'll throw this later if we are in fact creating a routed alias.
+        ex = e;
+      }
       if (collections != null) {
-        if (result.size() > 1) { // (NAME should be there, and if it's not we will fail below)
+        if (routedAlias != null) {
           throw new SolrException(BAD_REQUEST, "Collections cannot be specified when creating a time routed alias.");
+        } else {
+          //////////////////////////////////////
+          // Regular alias creation indicated //
+          //////////////////////////////////////
+          return copy(req.getParams().required(), null, NAME, "collections");
         }
-        // regular alias creation...
-        return copy(req.getParams().required(), null, NAME, "collections");
       }
 
-      // Ok so we are creating a time routed alias from here
+      /////////////////////////////////////////////////
+      // 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
+      Map<String, Object> result = copy(req.getParams(), null, routedAlias.getRequiredParams());
+      copy(req.getParams(), result, routedAlias.getOptionalParams());
 
-      // for validation....
-      copy(req.getParams().required(), null, REQUIRED_ROUTER_PARAMS);
       ModifiableSolrParams createCollParams = new ModifiableSolrParams(); // without prefix
 
       // add to result params that start with "create-collection.".
diff --git a/solr/core/src/java/org/apache/solr/update/processor/RoutedAliasUpdateProcessor.java b/solr/core/src/java/org/apache/solr/update/processor/RoutedAliasUpdateProcessor.java
index 5929e94..ab83e22 100644
--- a/solr/core/src/java/org/apache/solr/update/processor/RoutedAliasUpdateProcessor.java
+++ b/solr/core/src/java/org/apache/solr/update/processor/RoutedAliasUpdateProcessor.java
@@ -25,9 +25,7 @@ import java.util.Map;
 import java.util.stream.Collectors;
 
 import org.apache.solr.cloud.ZkController;
-import org.apache.solr.cloud.api.collections.CategoryRoutedAlias;
 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.SolrInputDocument;
 import org.apache.solr.common.cloud.Aliases;
@@ -95,23 +93,7 @@ public class RoutedAliasUpdateProcessor extends UpdateRequestProcessor {
       return next;
     } else {
       try {
-        final Map<String, String> aliasProperties = getAliasProps(req, aliasName);
-        String routerType = aliasProperties.get("router.name");
-        RoutedAlias alias;
-        switch (routerType) {
-          case "time": {
-            log.debug("Time Routed Alias detected for {}", aliasName );
-            alias = new TimeRoutedAlias(aliasName, aliasProperties);
-            break;
-          }
-          case "category":{
-            log.debug("Category Routed Alias detected for {}", aliasName );
-            alias = new CategoryRoutedAlias(aliasName, aliasProperties);
-            break;
-          }
-          default:
-            throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown router type " + routerType);
-        }
+        RoutedAlias alias = RoutedAlias.fromProps(aliasName, getAliasProps(req, aliasName));
         return new RoutedAliasUpdateProcessor(req, next, aliasDistribPhase, alias);
       } catch (Exception e) { // ensure we throw SERVER_ERROR not BAD_REQUEST at this stage
         throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Routed alias has invalid properties: " + e, e);
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 2e18d18..85a755a 100644
--- a/solr/core/src/test/org/apache/solr/cloud/CreateRoutedAliasTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/CreateRoutedAliasTest.java
@@ -282,7 +282,7 @@ public class CreateRoutedAliasTest extends SolrCloudTestCase {
         "&router.interval=%2B30MINUTE" +
         "&create-collection.collection.configName=_default" +
         "&create-collection.numShards=1");
-    assertFailure(get, "Only 'time' routed aliases is supported right now");
+    assertFailure(get, " is not in supported types, ");
   }
 
   @Test