You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by no...@apache.org on 2023/05/26 20:55:19 UTC

[solr] branch branch_9x updated: Revert "SOLR-16507: Refactor out ImplicitSnitch (#1625)"

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

noble pushed a commit to branch branch_9x
in repository https://gitbox.apache.org/repos/asf/solr.git


The following commit(s) were added to refs/heads/branch_9x by this push:
     new 859e67c3457 Revert "SOLR-16507: Refactor out ImplicitSnitch (#1625)"
859e67c3457 is described below

commit 859e67c345767dbcad5024df4138029945d00cf9
Author: Noble Paul <no...@gmail.com>
AuthorDate: Sat May 27 06:54:37 2023 +1000

    Revert "SOLR-16507: Refactor out ImplicitSnitch (#1625)"
    
    This reverts commit 8031f7ace15bff54db752e0a7a9333f2fd2bf129.
    
    compilation errors, will fix and commit
---
 .../placement/impl/AttributeFetcherImpl.java       |  15 +-
 .../cluster/placement/impl/NodeMetricImpl.java     |  11 +-
 .../solr/client/solrj/impl/NodeValueFetcher.java   | 170 ------------------
 .../solrj/impl/SolrClientNodeStateProvider.java    | 182 +++++++++++++++++---
 .../solr/common/cloud/rule/ImplicitSnitch.java     | 191 +++++++++++++++++++++
 .../solr/common/cloud/rule/RemoteCallback.java     |  23 +++
 .../org/apache/solr/common/cloud/rule/Snitch.java  |  29 ++++
 .../solr/common/cloud/rule/SnitchContext.java      | 103 +++++++++++
 .../solr/common/cloud/rule/package-info.java       |  19 ++
 .../solrj/request/CollectionAdminRequest.java      |   2 +
 10 files changed, 540 insertions(+), 205 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/AttributeFetcherImpl.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/AttributeFetcherImpl.java
index 251dd49759d..0beba2366dc 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/impl/AttributeFetcherImpl.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/AttributeFetcherImpl.java
@@ -27,7 +27,7 @@ import java.util.Set;
 import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 import org.apache.solr.client.solrj.cloud.SolrCloudManager;
-import org.apache.solr.client.solrj.impl.NodeValueFetcher;
+import org.apache.solr.client.solrj.impl.SolrClientNodeStateProvider;
 import org.apache.solr.cluster.Node;
 import org.apache.solr.cluster.SolrCollection;
 import org.apache.solr.cluster.placement.AttributeFetcher;
@@ -37,6 +37,7 @@ import org.apache.solr.cluster.placement.NodeMetric;
 import org.apache.solr.cluster.placement.ReplicaMetric;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.Replica;
+import org.apache.solr.common.cloud.rule.ImplicitSnitch;
 import org.apache.solr.core.SolrInfoBean;
 import org.apache.solr.metrics.SolrMetricManager;
 import org.slf4j.Logger;
@@ -115,7 +116,7 @@ public class AttributeFetcherImpl implements AttributeFetcher {
     for (NodeMetric<?> metric : requestedNodeMetricSnitchTags) {
       final Map<Node, Object> metricMap = new HashMap<>();
       metricSnitchToNodeToValue.put(metric, metricMap);
-      String metricSnitch = getMetricTag(metric);
+      String metricSnitch = getMetricSnitchTag(metric);
       allSnitchTagsToInsertion.put(
           metricSnitch, (node, value) -> metricMap.put(node, metric.convert(value)));
     }
@@ -230,23 +231,23 @@ public class AttributeFetcherImpl implements AttributeFetcher {
     }
   }
 
-  public static String getMetricTag(NodeMetric<?> metric) {
+  public static String getMetricSnitchTag(NodeMetric<?> metric) {
     if (metric.getRegistry() != NodeMetric.Registry.UNSPECIFIED) {
       // regular registry + metricName
-      return NodeValueFetcher.METRICS_PREFIX
+      return SolrClientNodeStateProvider.METRICS_PREFIX
           + SolrMetricManager.getRegistryName(getGroupFromMetricRegistry(metric.getRegistry()))
           + ":"
           + metric.getInternalName();
-    } else if (NodeValueFetcher.tags.contains(metric.getInternalName())) {
+    } else if (ImplicitSnitch.tags.contains(metric.getInternalName())) {
       // "special" well-known tag
       return metric.getInternalName();
     } else {
       // a fully-qualified metric key
-      return NodeValueFetcher.METRICS_PREFIX + metric.getInternalName();
+      return SolrClientNodeStateProvider.METRICS_PREFIX + metric.getInternalName();
     }
   }
 
   public static String getSystemPropertySnitchTag(String name) {
-    return NodeValueFetcher.SYSPROP + name;
+    return ImplicitSnitch.SYSPROP + name;
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/NodeMetricImpl.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/NodeMetricImpl.java
index b76013d1903..f5f9cd8dbd6 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/impl/NodeMetricImpl.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/NodeMetricImpl.java
@@ -19,8 +19,8 @@ package org.apache.solr.cluster.placement.impl;
 
 import java.util.Objects;
 import java.util.function.Function;
-import org.apache.solr.client.solrj.impl.NodeValueFetcher;
 import org.apache.solr.cluster.placement.NodeMetric;
+import org.apache.solr.common.cloud.rule.ImplicitSnitch;
 
 /**
  * Node metric identifier, corresponding to a node-level metric registry and the internal metric
@@ -40,17 +40,14 @@ public class NodeMetricImpl<T> extends MetricImpl<T> implements NodeMetric<T> {
 
   /** Number of all cores. */
   public static final NodeMetricImpl<Integer> NUM_CORES =
-      new NodeMetricImpl<>(NodeValueFetcher.CORES);
+      new NodeMetricImpl<>(ImplicitSnitch.CORES);
 
   public static final NodeMetricImpl<Double> HEAP_USAGE =
-      new NodeMetricImpl<>(NodeValueFetcher.Tags.HEAPUSAGE.tagName);
+      new NodeMetricImpl<>(ImplicitSnitch.HEAPUSAGE);
 
   /** System load average. */
   public static final NodeMetricImpl<Double> SYSLOAD_AVG =
-      new NodeMetricImpl<>(
-          NodeValueFetcher.Tags.SYSLOADAVG.tagName,
-          Registry.SOLR_JVM,
-          NodeValueFetcher.Tags.SYSLOADAVG.prefix);
+      new NodeMetricImpl<>("sysLoadAvg", Registry.SOLR_JVM, "os.systemLoadAverage");
 
   /** Number of available processors. */
   public static final NodeMetricImpl<Integer> AVAILABLE_PROCESSORS =
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/client/solrj/impl/NodeValueFetcher.java b/solr/solrj-zookeeper/src/java/org/apache/solr/client/solrj/impl/NodeValueFetcher.java
deleted file mode 100644
index c5832aa1432..00000000000
--- a/solr/solrj-zookeeper/src/java/org/apache/solr/client/solrj/impl/NodeValueFetcher.java
+++ /dev/null
@@ -1,170 +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.impl;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import org.apache.solr.client.solrj.response.SimpleSolrResponse;
-import org.apache.solr.common.SolrException;
-import org.apache.solr.common.params.CommonParams;
-import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.StrUtils;
-import org.apache.solr.common.util.Utils;
-
-/**
- * This class is responsible for fetching metrics and other attributes from a given node in Solr
- * cluster. This is a helper class that is used by {@link SolrClientNodeStateProvider}
- */
-public class NodeValueFetcher {
-  // well known tags
-  public static final String NODE = "node";
-  public static final String PORT = "port";
-  public static final String HOST = "host";
-  public static final String CORES = "cores";
-  public static final String SYSPROP = "sysprop.";
-  public static final Set<String> tags =
-      Set.of(NODE, PORT, HOST, CORES, Tags.FREEDISK.tagName, Tags.HEAPUSAGE.tagName);
-  public static final Pattern hostAndPortPattern = Pattern.compile("(?:https?://)?([^:]+):(\\d+)");
-  public static final String METRICS_PREFIX = "metrics:";
-
-  /** Various well known tags that can be fetched from a node */
-  public enum Tags {
-    FREEDISK(
-        "freedisk", "solr.node", "CONTAINER.fs.usableSpace", "solr.node/CONTAINER.fs.usableSpace"),
-    TOTALDISK(
-        "totaldisk", "solr.node", "CONTAINER.fs.totalSpace", "solr.node/CONTAINER.fs.totalSpace"),
-    CORES("cores", "solr.node", "CONTAINER.cores", null) {
-      @Override
-      public Object extractResult(Object root) {
-        NamedList<?> node = (NamedList<?>) Utils.getObjectByPath(root, false, "solr.node");
-        int count = 0;
-        for (String leafCoreMetricName : new String[] {"lazy", "loaded", "unloaded"}) {
-          Number n = (Number) node.get("CONTAINER.cores." + leafCoreMetricName);
-          if (n != null) count += n.intValue();
-        }
-        return count;
-      }
-    },
-    SYSLOADAVG("sysLoadAvg", "solr.jvm", "os.systemLoadAverage", "solr.jvm/os.systemLoadAverage"),
-    HEAPUSAGE("heapUsage", "solr.jvm", "memory.heap.usage", "solr.jvm/memory.heap.usage");
-    // the metrics group
-    public final String group;
-    // the metrics prefix
-    public final String prefix;
-    public final String tagName;
-    // the json path in the response
-    public final String path;
-
-    Tags(String name, String group, String prefix, String path) {
-      this.group = group;
-      this.prefix = prefix;
-      this.tagName = name;
-      this.path = path;
-    }
-
-    public Object extractResult(Object root) {
-      Object v = Utils.getObjectByPath(root, true, path);
-      return v == null ? null : convertVal(v);
-    }
-
-    public Object convertVal(Object val) {
-      if (val instanceof String) {
-        return Double.valueOf((String) val);
-      } else if (val instanceof Number) {
-        Number num = (Number) val;
-        return num.doubleValue();
-
-      } else {
-        throw new IllegalArgumentException("Unknown type : " + val);
-      }
-    }
-  }
-
-  private void getRemoteInfo(
-      String solrNode, Set<String> requestedTags, SolrClientNodeStateProvider.RemoteCallCtx ctx) {
-    if (!(ctx).isNodeAlive(solrNode)) return;
-    Map<String, Set<Object>> metricsKeyVsTag = new HashMap<>();
-    for (String tag : requestedTags) {
-      if (tag.startsWith(SYSPROP)) {
-        metricsKeyVsTag
-            .computeIfAbsent(
-                "solr.jvm:system.properties:" + tag.substring(SYSPROP.length()),
-                k -> new HashSet<>())
-            .add(tag);
-      } else if (tag.startsWith(METRICS_PREFIX)) {
-        metricsKeyVsTag
-            .computeIfAbsent(tag.substring(METRICS_PREFIX.length()), k -> new HashSet<>())
-            .add(tag);
-      }
-    }
-    if (!metricsKeyVsTag.isEmpty()) {
-      SolrClientNodeStateProvider.fetchReplicaMetrics(solrNode, ctx, metricsKeyVsTag);
-    }
-
-    Set<String> groups = new HashSet<>();
-    List<String> prefixes = new ArrayList<>();
-    for (Tags t : Tags.values()) {
-      if (requestedTags.contains(t.tagName)) {
-        groups.add(t.group);
-        prefixes.add(t.prefix);
-      }
-    }
-    if (groups.isEmpty() || prefixes.isEmpty()) return;
-
-    ModifiableSolrParams params = new ModifiableSolrParams();
-    params.add("group", StrUtils.join(groups, ','));
-    params.add("prefix", StrUtils.join(prefixes, ','));
-
-    try {
-      SimpleSolrResponse rsp = ctx.invokeWithRetry(solrNode, CommonParams.METRICS_PATH, params);
-      NamedList<?> metrics = (NamedList<?>) rsp.nl.get("metrics");
-      if (metrics != null) {
-        for (Tags t : Tags.values()) {
-          ctx.tags.put(t.tagName, t.extractResult(metrics));
-        }
-      }
-    } catch (Exception e) {
-      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error getting remote info", e);
-    }
-  }
-
-  public void getTags(
-      String solrNode, Set<String> requestedTags, SolrClientNodeStateProvider.RemoteCallCtx ctx) {
-    try {
-      if (requestedTags.contains(NODE)) ctx.tags.put(NODE, solrNode);
-      if (requestedTags.contains(HOST)) {
-        Matcher hostAndPortMatcher = hostAndPortPattern.matcher(solrNode);
-        if (hostAndPortMatcher.find()) ctx.tags.put(HOST, hostAndPortMatcher.group(1));
-      }
-      if (requestedTags.contains(PORT)) {
-        Matcher hostAndPortMatcher = hostAndPortPattern.matcher(solrNode);
-        if (hostAndPortMatcher.find()) ctx.tags.put(PORT, hostAndPortMatcher.group(2));
-      }
-      getRemoteInfo(solrNode, requestedTags, ctx);
-    } catch (Exception e) {
-      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
-    }
-  }
-}
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/client/solrj/impl/SolrClientNodeStateProvider.java b/solr/solrj-zookeeper/src/java/org/apache/solr/client/solrj/impl/SolrClientNodeStateProvider.java
index 1bc2aed24bc..4d59091db54 100644
--- a/solr/solrj-zookeeper/src/java/org/apache/solr/client/solrj/impl/SolrClientNodeStateProvider.java
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/client/solrj/impl/SolrClientNodeStateProvider.java
@@ -17,6 +17,8 @@
 
 package org.apache.solr.client.solrj.impl;
 
+import static java.util.Collections.emptyMap;
+
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
@@ -41,22 +43,28 @@ import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.cloud.ClusterState;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.Replica;
+import org.apache.solr.common.cloud.rule.ImplicitSnitch;
+import org.apache.solr.common.cloud.rule.SnitchContext;
 import org.apache.solr.common.params.CommonParams;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.Pair;
+import org.apache.solr.common.util.StrUtils;
 import org.apache.solr.common.util.Utils;
+import org.apache.zookeeper.KeeperException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /** The <em>real</em> {@link NodeStateProvider}, which communicates with Solr via SolrJ. */
 public class SolrClientNodeStateProvider implements NodeStateProvider, MapWriter {
+  public static final String METRICS_PREFIX = "metrics:";
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   private final CloudLegacySolrClient solrClient;
   protected final Map<String, Map<String, Map<String, List<Replica>>>>
       nodeVsCollectionVsShardVsReplicaInfo = new HashMap<>();
+  private Map<String, Object> snitchSession = new HashMap<>();
 
   @SuppressWarnings({"rawtypes"})
   private Map<String, Map> nodeVsTags = new HashMap<>();
@@ -113,10 +121,10 @@ public class SolrClientNodeStateProvider implements NodeStateProvider, MapWriter
   }
 
   protected Map<String, Object> fetchTagValues(String node, Collection<String> tags) {
-    NodeValueFetcher nodeValueFetcher = new NodeValueFetcher();
-    RemoteCallCtx ctx = new RemoteCallCtx(node, solrClient);
-    nodeValueFetcher.getTags(node, new HashSet<>(tags), ctx);
-    return ctx.tags;
+    MetricsFetchingSnitch snitch = new MetricsFetchingSnitch();
+    ClientSnitchCtx ctx = new ClientSnitchCtx(null, node, snitchSession, solrClient);
+    snitch.getTags(node, new HashSet<>(tags), ctx);
+    return ctx.getTags();
   }
 
   public void forEachReplica(String node, Consumer<Replica> consumer) {
@@ -180,13 +188,13 @@ public class SolrClientNodeStateProvider implements NodeStateProvider, MapWriter
     Map<String, Set<Object>> collect =
         metricsKeyVsTagReplica.entrySet().stream()
             .collect(Collectors.toMap(e -> e.getKey(), e -> Set.of(e.getKey())));
-    RemoteCallCtx ctx = new RemoteCallCtx(null, solrClient);
+    ClientSnitchCtx ctx = new ClientSnitchCtx(null, null, emptyMap(), solrClient);
     fetchReplicaMetrics(node, ctx, collect);
-    return ctx.tags;
+    return ctx.getTags();
   }
 
   static void fetchReplicaMetrics(
-      String solrNode, RemoteCallCtx ctx, Map<String, Set<Object>> metricsKeyVsTag) {
+      String solrNode, ClientSnitchCtx ctx, Map<String, Set<Object>> metricsKeyVsTag) {
     if (!ctx.isNodeAlive(solrNode)) return;
     ModifiableSolrParams params = new ModifiableSolrParams();
     params.add("key", metricsKeyVsTag.keySet().toArray(new String[0]));
@@ -199,9 +207,9 @@ public class SolrClientNodeStateProvider implements NodeStateProvider, MapWriter
               if (tag instanceof Function) {
                 @SuppressWarnings({"unchecked"})
                 Pair<String, Object> p = (Pair<String, Object>) ((Function) tag).apply(v);
-                ctx.tags.put(p.first(), p.second());
+                ctx.getTags().put(p.first(), p.second());
               } else {
-                if (v != null) ctx.tags.put(tag.toString(), v);
+                if (v != null) ctx.getTags().put(tag.toString(), v);
               }
             }
           });
@@ -213,22 +221,110 @@ public class SolrClientNodeStateProvider implements NodeStateProvider, MapWriter
   @Override
   public void close() throws IOException {}
 
+  // uses metrics API to get node information
+  static class MetricsFetchingSnitch extends ImplicitSnitch {
+    @Override
+    protected void getRemoteInfo(String solrNode, Set<String> requestedTags, SnitchContext ctx) {
+      if (!((ClientSnitchCtx) ctx).isNodeAlive(solrNode)) return;
+      ClientSnitchCtx snitchContext = (ClientSnitchCtx) ctx;
+      Map<String, Set<Object>> metricsKeyVsTag = new HashMap<>();
+      for (String tag : requestedTags) {
+        if (tag.startsWith(SYSPROP)) {
+          metricsKeyVsTag
+              .computeIfAbsent(
+                  "solr.jvm:system.properties:" + tag.substring(SYSPROP.length()),
+                  k -> new HashSet<>())
+              .add(tag);
+        } else if (tag.startsWith(METRICS_PREFIX)) {
+          metricsKeyVsTag
+              .computeIfAbsent(tag.substring(METRICS_PREFIX.length()), k -> new HashSet<>())
+              .add(tag);
+        }
+      }
+      if (!metricsKeyVsTag.isEmpty()) {
+        fetchReplicaMetrics(solrNode, snitchContext, metricsKeyVsTag);
+      }
+
+      Set<String> groups = new HashSet<>();
+      List<String> prefixes = new ArrayList<>();
+      if (requestedTags.contains(DISK)) {
+        groups.add("solr.node");
+        prefixes.add("CONTAINER.fs.usableSpace");
+      }
+      if (requestedTags.contains(Variable.TOTALDISK.tagName)) {
+        groups.add("solr.node");
+        prefixes.add("CONTAINER.fs.totalSpace");
+      }
+      if (requestedTags.contains(CORES)) {
+        groups.add("solr.node");
+        prefixes.add("CONTAINER.cores");
+      }
+      if (requestedTags.contains(SYSLOADAVG)) {
+        groups.add("solr.jvm");
+        prefixes.add("os.systemLoadAverage");
+      }
+      if (requestedTags.contains(HEAPUSAGE)) {
+        groups.add("solr.jvm");
+        prefixes.add("memory.heap.usage");
+      }
+      if (groups.isEmpty() || prefixes.isEmpty()) return;
+
+      ModifiableSolrParams params = new ModifiableSolrParams();
+      params.add("group", StrUtils.join(groups, ','));
+      params.add("prefix", StrUtils.join(prefixes, ','));
+
+      try {
+        SimpleSolrResponse rsp =
+            snitchContext.invokeWithRetry(solrNode, CommonParams.METRICS_PATH, params);
+        NamedList<?> metrics = (NamedList<?>) rsp.nl.get("metrics");
+        if (metrics != null) {
+          // metrics enabled
+          if (requestedTags.contains(Variable.FREEDISK.tagName)) {
+            Object n = Utils.getObjectByPath(metrics, true, "solr.node/CONTAINER.fs.usableSpace");
+            if (n != null)
+              ctx.getTags().put(Variable.FREEDISK.tagName, Variable.FREEDISK.convertVal(n));
+          }
+          if (requestedTags.contains(Variable.TOTALDISK.tagName)) {
+            Object n = Utils.getObjectByPath(metrics, true, "solr.node/CONTAINER.fs.totalSpace");
+            if (n != null)
+              ctx.getTags().put(Variable.TOTALDISK.tagName, Variable.TOTALDISK.convertVal(n));
+          }
+          if (requestedTags.contains(CORES)) {
+            NamedList<?> node = (NamedList<?>) metrics.get("solr.node");
+            int count = 0;
+            for (String leafCoreMetricName : new String[] {"lazy", "loaded", "unloaded"}) {
+              Number n = (Number) node.get("CONTAINER.cores." + leafCoreMetricName);
+              if (n != null) count += n.intValue();
+            }
+            ctx.getTags().put(CORES, count);
+          }
+          if (requestedTags.contains(SYSLOADAVG)) {
+            Number n =
+                (Number) Utils.getObjectByPath(metrics, true, "solr.jvm/os.systemLoadAverage");
+            if (n != null) ctx.getTags().put(SYSLOADAVG, n.doubleValue());
+          }
+          if (requestedTags.contains(HEAPUSAGE)) {
+            Number n = (Number) Utils.getObjectByPath(metrics, true, "solr.jvm/memory.heap.usage");
+            if (n != null) ctx.getTags().put(HEAPUSAGE, n.doubleValue() * 100.0d);
+          }
+        }
+      } catch (Exception e) {
+        throw new SolrException(
+            SolrException.ErrorCode.SERVER_ERROR, "Error getting remote info", e);
+      }
+    }
+  }
+
   @Override
   public String toString() {
     return Utils.toJSONString(this);
   }
 
-  /**
-   * Mostly an info object that stores all the values for a given session to fetch various values
-   * from a node
-   */
-  static class RemoteCallCtx {
+  static class ClientSnitchCtx extends SnitchContext {
+    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
     ZkClientClusterStateProvider zkClientClusterStateProvider;
     CloudLegacySolrClient solrClient;
-    public final Map<String, Object> tags = new HashMap<>();
-    private String node;
-    public Map<String, Object> session;
 
     public boolean isNodeAlive(String node) {
       if (zkClientClusterStateProvider != null) {
@@ -237,13 +333,35 @@ public class SolrClientNodeStateProvider implements NodeStateProvider, MapWriter
       return true;
     }
 
-    public RemoteCallCtx(String node, CloudLegacySolrClient solrClient) {
-      this.node = node;
+    public ClientSnitchCtx(
+        SnitchInfo perSnitch,
+        String node,
+        Map<String, Object> session,
+        CloudLegacySolrClient solrClient) {
+      super(perSnitch, node, session);
       this.solrClient = solrClient;
       this.zkClientClusterStateProvider =
           (ZkClientClusterStateProvider) solrClient.getClusterStateProvider();
     }
 
+    @Override
+    @SuppressWarnings({"unchecked"})
+    public Map<?, ?> getZkJson(String path) throws KeeperException, InterruptedException {
+      try {
+        byte[] bytes =
+            zkClientClusterStateProvider
+                .getZkStateReader()
+                .getZkClient()
+                .getData(path, null, null, true);
+        if (bytes != null && bytes.length > 0) {
+          return (Map<String, Object>) Utils.fromJSON(bytes);
+        }
+      } catch (KeeperException.NoNodeException e) {
+        return emptyMap();
+      }
+      return emptyMap();
+    }
+
     /**
      * Will attempt to call {@link #invoke(String, String, SolrParams)} up to five times, retrying
      * on any IO Exceptions
@@ -300,9 +418,31 @@ public class SolrClientNodeStateProvider implements NodeStateProvider, MapWriter
         return request.response;
       }
     }
+  }
+
+  public enum Variable {
+    FREEDISK("freedisk", null, Double.class),
+    TOTALDISK("totaldisk", null, Double.class),
+    CORE_IDX("INDEX.sizeInGB", "INDEX.sizeInBytes", Double.class);
+    public final String tagName, metricsAttribute;
+    public final Class<?> type;
+
+    Variable(String tagName, String metricsAttribute, Class<?> type) {
+      this.tagName = tagName;
+      this.metricsAttribute = metricsAttribute;
+      this.type = type;
+    }
 
-    public String getNode() {
-      return node;
+    public Object convertVal(Object val) {
+      if (val instanceof String) {
+        return Double.valueOf((String) val);
+      } else if (val instanceof Number) {
+        Number num = (Number) val;
+        return num.doubleValue();
+
+      } else {
+        throw new IllegalArgumentException("Unknown type : " + val);
+      }
     }
   }
 }
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/rule/ImplicitSnitch.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/rule/ImplicitSnitch.java
new file mode 100644
index 00000000000..8f84f34273a
--- /dev/null
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/rule/ImplicitSnitch.java
@@ -0,0 +1,191 @@
+/*
+ * 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.common.cloud.rule;
+
+import java.lang.invoke.MethodHandles;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.zookeeper.KeeperException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+// This is the client-side component of the snitch
+public class ImplicitSnitch extends Snitch {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  public static final Pattern hostAndPortPattern = Pattern.compile("(?:https?://)?([^:]+):(\\d+)");
+
+  // well known tags
+  public static final String NODE = "node";
+  public static final String PORT = "port";
+  public static final String HOST = "host";
+  public static final String CORES = "cores";
+  public static final String DISK = "freedisk";
+  public static final String ROLE = "role";
+  public static final String NODEROLE = "nodeRole";
+  public static final String SYSPROP = "sysprop.";
+  public static final String SYSLOADAVG = "sysLoadAvg";
+  public static final String HEAPUSAGE = "heapUsage";
+  public static final List<String> IP_SNITCHES =
+      Collections.unmodifiableList(Arrays.asList("ip_1", "ip_2", "ip_3", "ip_4"));
+  public static final Set<String> tags =
+      Set.of(NODE, PORT, HOST, CORES, DISK, ROLE, HEAPUSAGE, "ip_1", "ip_2", "ip_3", "ip_4");
+
+  @Override
+  public void getTags(String solrNode, Set<String> requestedTags, SnitchContext ctx) {
+    try {
+      if (requestedTags.contains(NODE)) ctx.getTags().put(NODE, solrNode);
+      if (requestedTags.contains(HOST)) {
+        Matcher hostAndPortMatcher = hostAndPortPattern.matcher(solrNode);
+        if (hostAndPortMatcher.find()) ctx.getTags().put(HOST, hostAndPortMatcher.group(1));
+      }
+      if (requestedTags.contains(PORT)) {
+        Matcher hostAndPortMatcher = hostAndPortPattern.matcher(solrNode);
+        if (hostAndPortMatcher.find()) ctx.getTags().put(PORT, hostAndPortMatcher.group(2));
+      }
+      if (requestedTags.contains(ROLE)) fillRole(solrNode, ctx, ROLE);
+      if (requestedTags.contains(NODEROLE))
+        fillRole(solrNode, ctx, NODEROLE); // for new policy framework
+
+      addIpTags(solrNode, requestedTags, ctx);
+
+      getRemoteInfo(solrNode, requestedTags, ctx);
+    } catch (Exception e) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
+    }
+  }
+
+  protected void getRemoteInfo(String solrNode, Set<String> requestedTags, SnitchContext ctx) {
+    HashMap<String, Object> params = new HashMap<>();
+    if (requestedTags.contains(CORES)) params.put(CORES, "1");
+    if (requestedTags.contains(DISK)) params.put(DISK, "1");
+    for (String tag : requestedTags) {
+      if (tag.startsWith(SYSPROP)) params.put(tag, tag.substring(SYSPROP.length()));
+    }
+
+    if (params.size() > 0) {
+      Map<String, Object> vals = ctx.getNodeValues(solrNode, params.keySet());
+      for (Map.Entry<String, Object> e : vals.entrySet()) {
+        if (e.getValue() != null) params.put(e.getKey(), e.getValue());
+      }
+    }
+    ctx.getTags().putAll(params);
+  }
+
+  private void fillRole(String solrNode, SnitchContext ctx, String key)
+      throws KeeperException, InterruptedException {
+    Map<?, ?> roles =
+        (Map<?, ?>) ctx.retrieve(ZkStateReader.ROLES); // we don't want to hit the ZK for each node
+    // so cache and reuse
+    try {
+      if (roles == null) roles = ctx.getZkJson(ZkStateReader.ROLES);
+      cacheRoles(solrNode, ctx, key, roles);
+    } catch (KeeperException.NoNodeException e) {
+      cacheRoles(solrNode, ctx, key, Collections.emptyMap());
+    }
+  }
+
+  private void cacheRoles(String solrNode, SnitchContext ctx, String key, Map<?, ?> roles) {
+    ctx.store(ZkStateReader.ROLES, roles);
+    if (roles != null) {
+      for (Map.Entry<?, ?> e : roles.entrySet()) {
+        if (e.getValue() instanceof List) {
+          if (((List<?>) e.getValue()).contains(solrNode)) {
+            ctx.getTags().put(key, e.getKey());
+            break;
+          }
+        }
+      }
+    }
+  }
+
+  private static final String HOST_FRAG_SEPARATOR_REGEX = "\\.";
+
+  @Override
+  public boolean isKnownTag(String tag) {
+    return tags.contains(tag) || tag.startsWith(SYSPROP);
+  }
+
+  private void addIpTags(String solrNode, Set<String> requestedTags, SnitchContext context) {
+
+    List<String> requestedHostTags = new ArrayList<>();
+    for (String tag : requestedTags) {
+      if (IP_SNITCHES.contains(tag)) {
+        requestedHostTags.add(tag);
+      }
+    }
+
+    if (requestedHostTags.isEmpty()) {
+      return;
+    }
+
+    String[] ipFragments = getIpFragments(solrNode);
+
+    if (ipFragments == null) {
+      return;
+    }
+
+    int ipSnitchCount = IP_SNITCHES.size();
+    for (int i = 0; i < ipSnitchCount; i++) {
+      String currentTagValue = ipFragments[i];
+      String currentTagKey = IP_SNITCHES.get(ipSnitchCount - i - 1);
+
+      if (requestedHostTags.contains(currentTagKey)) {
+        context.getTags().put(currentTagKey, currentTagValue);
+      }
+    }
+  }
+
+  private String[] getIpFragments(String solrNode) {
+    Matcher hostAndPortMatcher = hostAndPortPattern.matcher(solrNode);
+    if (hostAndPortMatcher.find()) {
+      String host = hostAndPortMatcher.group(1);
+      if (host != null) {
+        String ip = getHostIp(host);
+        if (ip != null) {
+          return ip.split(HOST_FRAG_SEPARATOR_REGEX); // IPv6 support will be provided by SOLR-8523
+        }
+      }
+    }
+
+    log.warn(
+        "Failed to match host IP address from node URL [{}] using regex [{}]",
+        solrNode,
+        hostAndPortPattern.pattern());
+    return null;
+  }
+
+  public String getHostIp(String host) {
+    try {
+      InetAddress address = InetAddress.getByName(host);
+      return address.getHostAddress();
+    } catch (Exception e) {
+      log.warn("Failed to get IP address from host [{}], with exception [{}] ", host, e);
+      return null;
+    }
+  }
+}
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/rule/RemoteCallback.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/rule/RemoteCallback.java
new file mode 100644
index 00000000000..a5a4e98d70f
--- /dev/null
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/rule/RemoteCallback.java
@@ -0,0 +1,23 @@
+/*
+ * 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.common.cloud.rule;
+
+import java.util.Map;
+
+public interface RemoteCallback {
+  void remoteCallback(SnitchContext ctx, Map<String, Object> returnedVal);
+}
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/rule/Snitch.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/rule/Snitch.java
new file mode 100644
index 00000000000..916a639f647
--- /dev/null
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/rule/Snitch.java
@@ -0,0 +1,29 @@
+/*
+ * 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.common.cloud.rule;
+
+import java.util.Collections;
+import java.util.Set;
+
+public abstract class Snitch {
+  public static final Set<Class<?>> WELL_KNOWN_SNITCHES =
+      Collections.singleton(ImplicitSnitch.class);
+
+  public abstract void getTags(String solrNode, Set<String> requestedTags, SnitchContext ctx);
+
+  public abstract boolean isKnownTag(String tag);
+}
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/rule/SnitchContext.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/rule/SnitchContext.java
new file mode 100644
index 00000000000..e63660feb5f
--- /dev/null
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/rule/SnitchContext.java
@@ -0,0 +1,103 @@
+/*
+ * 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.common.cloud.rule;
+
+import java.lang.invoke.MethodHandles;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.zookeeper.KeeperException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is the context provided to the snitches to interact with the system. This is a
+ * per-node-per-snitch instance.
+ */
+public abstract class SnitchContext implements RemoteCallback {
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+  private final Map<String, Object> tags = new HashMap<>();
+  private String node;
+  private Map<String, Object> session;
+  public final SnitchInfo snitchInfo;
+  public Exception exception;
+
+  public SnitchContext(SnitchInfo perSnitch, String node, Map<String, Object> session) {
+    this.snitchInfo = perSnitch;
+    this.node = node;
+    this.session = session;
+  }
+
+  public Map<String, Object> getTags() {
+    return tags;
+  }
+
+  public void store(String s, Object val) {
+    if (session != null) session.put(s, val);
+  }
+
+  public Object retrieve(String s) {
+    return session != null ? session.get(s) : null;
+  }
+
+  public Map<String, Object> getNodeValues(String node, Collection<String> tags) {
+    return Collections.emptyMap();
+  }
+
+  public abstract Map<?, ?> getZkJson(String path) throws KeeperException, InterruptedException;
+
+  public String getNode() {
+    return node;
+  }
+
+  /**
+   * make a call to solrnode/admin/cores with the given params and give a callback. This is designed
+   * to be asynchronous because the system would want to batch the calls made to any given node
+   *
+   * @param node The node for which this call is made
+   * @param params The params to be passed to the Snitch counterpart
+   * @param klas The name of the class to be invoked in the remote node
+   * @param callback The callback to be called when the response is obtained from remote node. If
+   *     this is passed as null the entire response map will be added as tags
+   */
+  @Deprecated
+  public void invokeRemote(
+      String node, ModifiableSolrParams params, String klas, RemoteCallback callback) {}
+  ;
+
+  @Override
+  public void remoteCallback(SnitchContext ctx, Map<String, Object> returnedVal) {
+    tags.putAll(returnedVal);
+  }
+
+  public String getErrMsg() {
+    return exception == null ? null : exception.getMessage();
+  }
+
+  public abstract static class SnitchInfo {
+    private final Map<String, Object> conf;
+
+    protected SnitchInfo(Map<String, Object> conf) {
+      this.conf = conf;
+    }
+
+    public abstract Set<String> getTagNames();
+  }
+}
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/rule/package-info.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/rule/package-info.java
new file mode 100644
index 00000000000..ba8b61c5d2f
--- /dev/null
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/rule/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+/** Classes for managing Replica placement strategy when operating in SolrCloud mode. */
+package org.apache.solr.common.cloud.rule;
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 83dbd1fae8b..0b2357dff0c 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
@@ -1343,6 +1343,8 @@ public abstract class CollectionAdminRequest<T extends CollectionAdminResponse>
       return this;
     }
 
+    // TODO support rule, snitch
+
     @Override
     public SolrParams getParams() {
       ModifiableSolrParams params = (ModifiableSolrParams) super.getParams();