You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by st...@apache.org on 2023/08/16 18:34:46 UTC

[solr] branch main updated: Refactor ZkCmdExecutor, move all ensureExists methods to ZkMaintenanceUtils (#1840)

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

stillalex 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 c3aef6e8551 Refactor ZkCmdExecutor, move all ensureExists methods to ZkMaintenanceUtils (#1840)
c3aef6e8551 is described below

commit c3aef6e8551bd757605fc308c4b19d9c784cfe68
Author: Alex D <st...@apache.org>
AuthorDate: Wed Aug 16 11:34:39 2023 -0700

    Refactor ZkCmdExecutor, move all ensureExists methods to ZkMaintenanceUtils (#1840)
---
 .../java/org/apache/solr/cloud/DistributedMap.java |  5 +-
 .../java/org/apache/solr/cloud/LeaderElector.java  | 11 +--
 .../apache/solr/cloud/OverseerElectionContext.java |  5 +-
 .../solr/cloud/ShardLeaderElectionContextBase.java |  4 +-
 .../java/org/apache/solr/cloud/ZkController.java   | 17 ++--
 .../org/apache/solr/cloud/ZkDistributedQueue.java  |  6 +-
 .../handler/admin/api/CreateCollectionAPI.java     | 11 ++-
 .../solr/schema/ManagedIndexSchemaFactory.java     |  5 +-
 .../org/apache/solr/cloud/ZkSolrClientTest.java    | 10 +--
 .../apache/solr/common/cloud/ZkCmdExecutor.java    | 60 --------------
 .../solr/common/cloud/ZkMaintenanceUtils.java      | 94 ++++++++++++++++++----
 11 files changed, 108 insertions(+), 120 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/DistributedMap.java b/solr/core/src/java/org/apache/solr/cloud/DistributedMap.java
index ece2589310a..323c378f71d 100644
--- a/solr/core/src/java/org/apache/solr/cloud/DistributedMap.java
+++ b/solr/core/src/java/org/apache/solr/cloud/DistributedMap.java
@@ -22,7 +22,7 @@ import java.util.List;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.cloud.SolrZkClient;
-import org.apache.solr.common.cloud.ZkCmdExecutor;
+import org.apache.solr.common.cloud.ZkMaintenanceUtils;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.KeeperException.NodeExistsException;
@@ -42,9 +42,8 @@ public class DistributedMap {
   public DistributedMap(SolrZkClient zookeeper, String dir) {
     this.dir = dir;
 
-    ZkCmdExecutor cmdExecutor = new ZkCmdExecutor(zookeeper.getZkClientTimeout());
     try {
-      cmdExecutor.ensureExists(dir, zookeeper);
+      ZkMaintenanceUtils.ensureExists(dir, zookeeper);
     } catch (KeeperException e) {
       throw new SolrException(ErrorCode.SERVER_ERROR, e);
     } catch (InterruptedException e) {
diff --git a/solr/core/src/java/org/apache/solr/cloud/LeaderElector.java b/solr/core/src/java/org/apache/solr/cloud/LeaderElector.java
index 7b0de72eca5..6a003b19955 100644
--- a/solr/core/src/java/org/apache/solr/cloud/LeaderElector.java
+++ b/solr/core/src/java/org/apache/solr/cloud/LeaderElector.java
@@ -28,7 +28,7 @@ import org.apache.solr.cloud.ZkController.ContextKey;
 import org.apache.solr.common.AlreadyClosedException;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.SolrZkClient;
-import org.apache.solr.common.cloud.ZkCmdExecutor;
+import org.apache.solr.common.cloud.ZkMaintenanceUtils;
 import org.apache.solr.common.cloud.ZooKeeperException;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException;
@@ -61,8 +61,6 @@ public class LeaderElector {
 
   protected SolrZkClient zkClient;
 
-  private ZkCmdExecutor zkCmdExecutor;
-
   private volatile ElectionContext context;
 
   private ElectionWatcher watcher;
@@ -72,13 +70,11 @@ public class LeaderElector {
 
   public LeaderElector(SolrZkClient zkClient) {
     this.zkClient = zkClient;
-    zkCmdExecutor = new ZkCmdExecutor(zkClient.getZkClientTimeout());
   }
 
   public LeaderElector(
       SolrZkClient zkClient, ContextKey key, Map<ContextKey, ElectionContext> electionContexts) {
     this.zkClient = zkClient;
-    zkCmdExecutor = new ZkCmdExecutor(zkClient.getZkClientTimeout());
     this.electionContexts = electionContexts;
     this.contextKey = key;
   }
@@ -366,10 +362,11 @@ public class LeaderElector {
   public void setup(final ElectionContext context) throws InterruptedException, KeeperException {
     String electZKPath = context.electionPath + LeaderElector.ELECTION_NODE;
     if (context instanceof OverseerElectionContext) {
-      zkCmdExecutor.ensureExists(electZKPath, zkClient);
+      ZkMaintenanceUtils.ensureExists(electZKPath, zkClient);
     } else {
       // we use 2 param so that replica won't create /collection/{collection} if it doesn't exist
-      zkCmdExecutor.ensureExists(electZKPath, (byte[]) null, CreateMode.PERSISTENT, zkClient, 2);
+      ZkMaintenanceUtils.ensureExists(
+          electZKPath, (byte[]) null, CreateMode.PERSISTENT, zkClient, 2);
     }
 
     this.context = context;
diff --git a/solr/core/src/java/org/apache/solr/cloud/OverseerElectionContext.java b/solr/core/src/java/org/apache/solr/cloud/OverseerElectionContext.java
index 2d2669af2ce..8eb8a6d1386 100644
--- a/solr/core/src/java/org/apache/solr/cloud/OverseerElectionContext.java
+++ b/solr/core/src/java/org/apache/solr/cloud/OverseerElectionContext.java
@@ -23,7 +23,7 @@ import java.lang.invoke.MethodHandles;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.cloud.SolrZkClient;
-import org.apache.solr.common.cloud.ZkCmdExecutor;
+import org.apache.solr.common.cloud.ZkMaintenanceUtils;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.util.Utils;
 import org.apache.zookeeper.CreateMode;
@@ -43,8 +43,7 @@ final class OverseerElectionContext extends ElectionContext {
     this.overseer = overseer;
     this.zkClient = zkClient;
     try {
-      new ZkCmdExecutor(zkClient.getZkClientTimeout())
-          .ensureExists(Overseer.OVERSEER_ELECT, zkClient);
+      ZkMaintenanceUtils.ensureExists(Overseer.OVERSEER_ELECT, zkClient);
     } catch (KeeperException e) {
       throw new SolrException(ErrorCode.SERVER_ERROR, e);
     } catch (InterruptedException e) {
diff --git a/solr/core/src/java/org/apache/solr/cloud/ShardLeaderElectionContextBase.java b/solr/core/src/java/org/apache/solr/cloud/ShardLeaderElectionContextBase.java
index c6ac7421d95..32706698d8b 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ShardLeaderElectionContextBase.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ShardLeaderElectionContextBase.java
@@ -27,7 +27,6 @@ import org.apache.solr.common.cloud.PerReplicaStates;
 import org.apache.solr.common.cloud.PerReplicaStatesOps;
 import org.apache.solr.common.cloud.Replica;
 import org.apache.solr.common.cloud.SolrZkClient;
-import org.apache.solr.common.cloud.ZkCmdExecutor;
 import org.apache.solr.common.cloud.ZkMaintenanceUtils;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.cloud.ZkStateReader;
@@ -79,11 +78,10 @@ class ShardLeaderElectionContextBase extends ElectionContext {
     this.collection = collection;
 
     String parent = ZkMaintenanceUtils.getZkParent(leaderPath);
-    ZkCmdExecutor zcmd = new ZkCmdExecutor(30000);
     // only if /collections/{collection} exists already do we succeed in creating this path
     log.info("make sure parent is created {}", parent);
     try {
-      zcmd.ensureExists(parent, (byte[]) null, CreateMode.PERSISTENT, zkClient, 2);
+      ZkMaintenanceUtils.ensureExists(parent, (byte[]) null, CreateMode.PERSISTENT, zkClient, 2);
     } catch (KeeperException e) {
       throw new RuntimeException(e);
     } catch (InterruptedException e) {
diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkController.java b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
index 25f097cc8c8..c27e79c8274 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkController.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
@@ -87,10 +87,10 @@ import org.apache.solr.common.cloud.Slice;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.cloud.ZkACLProvider;
 import org.apache.solr.common.cloud.ZkClientConnectionStrategy;
-import org.apache.solr.common.cloud.ZkCmdExecutor;
 import org.apache.solr.common.cloud.ZkCoreNodeProps;
 import org.apache.solr.common.cloud.ZkCredentialsInjector;
 import org.apache.solr.common.cloud.ZkCredentialsProvider;
+import org.apache.solr.common.cloud.ZkMaintenanceUtils;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.cloud.ZooKeeperException;
@@ -915,20 +915,19 @@ public class ZkController implements Closeable {
    */
   public static void createClusterZkNodes(SolrZkClient zkClient)
       throws KeeperException, InterruptedException, IOException {
-    ZkCmdExecutor cmdExecutor = new ZkCmdExecutor(zkClient.getZkClientTimeout());
-    cmdExecutor.ensureExists(ZkStateReader.LIVE_NODES_ZKNODE, zkClient);
-    cmdExecutor.ensureExists(ZkStateReader.NODE_ROLES, zkClient);
+    ZkMaintenanceUtils.ensureExists(ZkStateReader.LIVE_NODES_ZKNODE, zkClient);
+    ZkMaintenanceUtils.ensureExists(ZkStateReader.NODE_ROLES, zkClient);
     for (NodeRoles.Role role : NodeRoles.Role.values()) {
-      cmdExecutor.ensureExists(NodeRoles.getZNodeForRole(role), zkClient);
+      ZkMaintenanceUtils.ensureExists(NodeRoles.getZNodeForRole(role), zkClient);
       for (String mode : role.supportedModes()) {
-        cmdExecutor.ensureExists(NodeRoles.getZNodeForRoleMode(role, mode), zkClient);
+        ZkMaintenanceUtils.ensureExists(NodeRoles.getZNodeForRoleMode(role, mode), zkClient);
       }
     }
 
-    cmdExecutor.ensureExists(ZkStateReader.COLLECTIONS_ZKNODE, zkClient);
-    cmdExecutor.ensureExists(ZkStateReader.ALIASES, zkClient);
+    ZkMaintenanceUtils.ensureExists(ZkStateReader.COLLECTIONS_ZKNODE, zkClient);
+    ZkMaintenanceUtils.ensureExists(ZkStateReader.ALIASES, zkClient);
     byte[] emptyJson = "{}".getBytes(StandardCharsets.UTF_8);
-    cmdExecutor.ensureExists(ZkStateReader.SOLR_SECURITY_CONF_PATH, emptyJson, zkClient);
+    ZkMaintenanceUtils.ensureExists(ZkStateReader.SOLR_SECURITY_CONF_PATH, emptyJson, zkClient);
     repairSecurityJson(zkClient);
   }
 
diff --git a/solr/core/src/java/org/apache/solr/cloud/ZkDistributedQueue.java b/solr/core/src/java/org/apache/solr/cloud/ZkDistributedQueue.java
index 39a1de01feb..57db14740c5 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkDistributedQueue.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkDistributedQueue.java
@@ -37,7 +37,7 @@ import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.cloud.ConnectionManager.IsClosed;
 import org.apache.solr.common.cloud.SolrZkClient;
-import org.apache.solr.common.cloud.ZkCmdExecutor;
+import org.apache.solr.common.cloud.ZkMaintenanceUtils;
 import org.apache.solr.common.util.Pair;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException;
@@ -134,10 +134,8 @@ public class ZkDistributedQueue implements DistributedQueue {
       IsClosed higherLevelIsClosed) {
     this.dir = dir;
 
-    ZkCmdExecutor cmdExecutor =
-        new ZkCmdExecutor(zookeeper.getZkClientTimeout(), higherLevelIsClosed);
     try {
-      cmdExecutor.ensureExists(dir, zookeeper);
+      ZkMaintenanceUtils.ensureExists(dir, zookeeper);
     } catch (KeeperException e) {
       throw new SolrException(ErrorCode.SERVER_ERROR, e);
     } catch (InterruptedException e) {
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 a33c4a51f3b..b8ae2dca46f 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
@@ -68,7 +68,7 @@ import org.apache.solr.client.solrj.util.SolrIdentifierValidator;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.ClusterProperties;
 import org.apache.solr.common.cloud.SolrZkClient;
-import org.apache.solr.common.cloud.ZkCmdExecutor;
+import org.apache.solr.common.cloud.ZkMaintenanceUtils;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.params.CollectionAdminParams;
@@ -265,9 +265,8 @@ public class CreateCollectionAPI extends AdminAPIBase {
   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(
+    ZkMaintenanceUtils.ensureExists(ZkStateReader.CONFIGS_ZKNODE, zk);
+    ZkMaintenanceUtils.ensureExists(
         ZkStateReader.CONFIGS_ZKNODE + "/" + CollectionAdminParams.SYSTEM_COLL, zk);
 
     try {
@@ -280,7 +279,7 @@ public class CreateCollectionAPI extends AdminAPIBase {
         data = inputStream.readAllBytes();
       }
       assert data != null && data.length > 0;
-      cmdExecutor.ensureExists(path, data, CreateMode.PERSISTENT, zk);
+      ZkMaintenanceUtils.ensureExists(path, data, CreateMode.PERSISTENT, zk);
       path =
           ZkStateReader.CONFIGS_ZKNODE
               + "/"
@@ -292,7 +291,7 @@ public class CreateCollectionAPI extends AdminAPIBase {
         data = inputStream.readAllBytes();
       }
       assert data != null && data.length > 0;
-      cmdExecutor.ensureExists(path, data, CreateMode.PERSISTENT, zk);
+      ZkMaintenanceUtils.ensureExists(path, data, CreateMode.PERSISTENT, zk);
     } catch (IOException e) {
       throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
     }
diff --git a/solr/core/src/java/org/apache/solr/schema/ManagedIndexSchemaFactory.java b/solr/core/src/java/org/apache/solr/schema/ManagedIndexSchemaFactory.java
index 9008b3e443e..73187383bd8 100644
--- a/solr/core/src/java/org/apache/solr/schema/ManagedIndexSchemaFactory.java
+++ b/solr/core/src/java/org/apache/solr/schema/ManagedIndexSchemaFactory.java
@@ -32,7 +32,7 @@ import org.apache.solr.cloud.ZkSolrResourceLoader;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.SolrException.ErrorCode;
 import org.apache.solr.common.cloud.SolrZkClient;
-import org.apache.solr.common.cloud.ZkCmdExecutor;
+import org.apache.solr.common.cloud.ZkMaintenanceUtils;
 import org.apache.solr.common.params.SolrParams;
 import org.apache.solr.common.util.IOUtils;
 import org.apache.solr.common.util.NamedList;
@@ -485,12 +485,11 @@ public class ManagedIndexSchemaFactory extends IndexSchemaFactory implements Sol
       // Rename the non-managed schema znode in ZooKeeper
       final String nonManagedSchemaPath = zkLoader.getConfigSetZkPath() + "/" + resourceName;
       try {
-        ZkCmdExecutor zkCmdExecutor = new ZkCmdExecutor(zkClient.getZkClientTimeout());
         if (zkController.pathExists(nonManagedSchemaPath)) {
           // First, copy the non-managed schema znode content to the upgraded schema znode
           byte[] bytes = zkController.getZkClient().getData(nonManagedSchemaPath, null, null, true);
           final String upgradedSchemaPath = nonManagedSchemaPath + UPGRADED_SCHEMA_EXTENSION;
-          zkCmdExecutor.ensureExists(upgradedSchemaPath, zkController.getZkClient());
+          ZkMaintenanceUtils.ensureExists(upgradedSchemaPath, zkController.getZkClient());
           zkController.getZkClient().setData(upgradedSchemaPath, bytes, true);
           // Then delete the non-managed schema znode
           if (zkController.getZkClient().exists(nonManagedSchemaPath, true)) {
diff --git a/solr/core/src/test/org/apache/solr/cloud/ZkSolrClientTest.java b/solr/core/src/test/org/apache/solr/cloud/ZkSolrClientTest.java
index a9ed66f70ff..4503c862d50 100644
--- a/solr/core/src/test/org/apache/solr/cloud/ZkSolrClientTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/ZkSolrClientTest.java
@@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.cloud.ZkCmdExecutor;
+import org.apache.solr.common.cloud.ZkMaintenanceUtils;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException;
 import org.apache.zookeeper.Op;
@@ -394,12 +395,11 @@ public class ZkSolrClientTest extends SolrTestCaseJ4 {
 
       zkClient.clean("/");
 
-      ZkCmdExecutor zkCmdExecutor = new ZkCmdExecutor(30000);
       expectThrows(
           KeeperException.NoNodeException.class,
           "We should not be able to create this path",
           () ->
-              zkCmdExecutor.ensureExists(
+              ZkMaintenanceUtils.ensureExists(
                   "/collection/collection/leader",
                   (byte[]) null,
                   CreateMode.PERSISTENT,
@@ -412,7 +412,7 @@ public class ZkSolrClientTest extends SolrTestCaseJ4 {
           KeeperException.NoNodeException.class,
           "We should not be able to create this path",
           () ->
-              zkCmdExecutor.ensureExists(
+              ZkMaintenanceUtils.ensureExists(
                   "/collections/collection/leader",
                   (byte[]) null,
                   CreateMode.PERSISTENT,
@@ -421,7 +421,7 @@ public class ZkSolrClientTest extends SolrTestCaseJ4 {
       zkClient.makePath("/collection/collection", true);
 
       byte[] bytes = new byte[10];
-      zkCmdExecutor.ensureExists(
+      ZkMaintenanceUtils.ensureExists(
           "/collection/collection", bytes, CreateMode.PERSISTENT, zkClient, 2);
 
       byte[] returnedBytes = zkClient.getData("/collection/collection", null, null, true);
@@ -430,7 +430,7 @@ public class ZkSolrClientTest extends SolrTestCaseJ4 {
 
       zkClient.makePath("/collection/collection/leader", true);
 
-      zkCmdExecutor.ensureExists(
+      ZkMaintenanceUtils.ensureExists(
           "/collection/collection/leader", (byte[]) null, CreateMode.PERSISTENT, zkClient, 2);
     }
   }
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkCmdExecutor.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkCmdExecutor.java
index 06134f1fbae..01924b3c044 100644
--- a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkCmdExecutor.java
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkCmdExecutor.java
@@ -19,9 +19,7 @@ package org.apache.solr.common.cloud;
 import java.lang.invoke.MethodHandles;
 import org.apache.solr.common.AlreadyClosedException;
 import org.apache.solr.common.cloud.ConnectionManager.IsClosed;
-import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException;
-import org.apache.zookeeper.KeeperException.NodeExistsException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -94,64 +92,6 @@ public class ZkCmdExecutor {
     return isClosed != null && isClosed.isClosed();
   }
 
-  /**
-   * Create a persistent znode with no data if it does not already exist
-   *
-   * @see #ensureExists(String, byte[], CreateMode, SolrZkClient, int)
-   */
-  public void ensureExists(String path, final SolrZkClient zkClient)
-      throws KeeperException, InterruptedException {
-    ensureExists(path, null, CreateMode.PERSISTENT, zkClient, 0);
-  }
-
-  /**
-   * Create a persistent znode with the given data if it does not already exist
-   *
-   * @see #ensureExists(String, byte[], CreateMode, SolrZkClient, int)
-   */
-  public void ensureExists(String path, final byte[] data, final SolrZkClient zkClient)
-      throws KeeperException, InterruptedException {
-    ensureExists(path, data, CreateMode.PERSISTENT, zkClient, 0);
-  }
-
-  /**
-   * Create a znode with the given mode and data if it does not already exist
-   *
-   * @see #ensureExists(String, byte[], CreateMode, SolrZkClient, int)
-   */
-  public void ensureExists(
-      String path, final byte[] data, CreateMode createMode, final SolrZkClient zkClient)
-      throws KeeperException, InterruptedException {
-    ensureExists(path, data, createMode, zkClient, 0);
-  }
-
-  /**
-   * Create a node if it does not exist
-   *
-   * @param path the path at which to create the znode
-   * @param data the optional data to set on the znode
-   * @param createMode the mode with which to create the znode
-   * @param zkClient the client to use to check and create
-   * @param skipPathParts how many path elements to skip
-   */
-  public void ensureExists(
-      final String path,
-      final byte[] data,
-      CreateMode createMode,
-      final SolrZkClient zkClient,
-      int skipPathParts)
-      throws KeeperException, InterruptedException {
-
-    if (zkClient.exists(path, true)) {
-      return;
-    }
-    try {
-      zkClient.makePath(path, data, createMode, null, true, true, skipPathParts);
-    } catch (NodeExistsException ignored) {
-      // it's okay if another beats us creating the node
-    }
-  }
-
   /**
    * Performs a retry delay if this is not the first attempt
    *
diff --git a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkMaintenanceUtils.java b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkMaintenanceUtils.java
index 7ade32ebf3d..34d770c7a12 100644
--- a/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkMaintenanceUtils.java
+++ b/solr/solrj-zookeeper/src/java/org/apache/solr/common/cloud/ZkMaintenanceUtils.java
@@ -37,6 +37,7 @@ import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.common.util.StrUtils;
 import org.apache.zookeeper.CreateMode;
 import org.apache.zookeeper.KeeperException;
+import org.apache.zookeeper.KeeperException.NodeExistsException;
 import org.apache.zookeeper.data.Stat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -589,28 +590,87 @@ public class ZkMaintenanceUtils {
     int lastDot = filePath.lastIndexOf('.');
     return lastDot >= 0 && USE_FORBIDDEN_FILE_TYPES.contains(filePath.substring(lastDot + 1));
   }
-}
 
-class ZkCopier implements ZkMaintenanceUtils.ZkVisitor {
+  /**
+   * Create a persistent znode with no data if it does not already exist
+   *
+   * @see #ensureExists(String, byte[], CreateMode, SolrZkClient, int)
+   */
+  public static void ensureExists(String path, final SolrZkClient zkClient)
+      throws KeeperException, InterruptedException {
+    ensureExists(path, null, CreateMode.PERSISTENT, zkClient, 0);
+  }
+
+  /**
+   * Create a persistent znode with the given data if it does not already exist
+   *
+   * @see #ensureExists(String, byte[], CreateMode, SolrZkClient, int)
+   */
+  public static void ensureExists(String path, final byte[] data, final SolrZkClient zkClient)
+      throws KeeperException, InterruptedException {
+    ensureExists(path, data, CreateMode.PERSISTENT, zkClient, 0);
+  }
+
+  /**
+   * Create a znode with the given mode and data if it does not already exist
+   *
+   * @see #ensureExists(String, byte[], CreateMode, SolrZkClient, int)
+   */
+  public static void ensureExists(
+      String path, final byte[] data, CreateMode createMode, final SolrZkClient zkClient)
+      throws KeeperException, InterruptedException {
+    ensureExists(path, data, createMode, zkClient, 0);
+  }
 
-  String source;
-  String dest;
-  SolrZkClient zkClient;
+  /**
+   * Create a node if it does not exist
+   *
+   * @param path the path at which to create the znode
+   * @param data the optional data to set on the znode
+   * @param createMode the mode with which to create the znode
+   * @param zkClient the client to use to check and create
+   * @param skipPathParts how many path elements to skip
+   */
+  public static void ensureExists(
+      final String path,
+      final byte[] data,
+      CreateMode createMode,
+      final SolrZkClient zkClient,
+      int skipPathParts)
+      throws KeeperException, InterruptedException {
 
-  ZkCopier(SolrZkClient zkClient, String source, String dest) {
-    this.source = source;
-    this.dest = dest;
-    if (dest.endsWith("/")) {
-      this.dest = dest.substring(0, dest.length() - 1);
+    if (zkClient.exists(path, true)) {
+      return;
+    }
+    try {
+      zkClient.makePath(path, data, createMode, null, true, true, skipPathParts);
+    } catch (NodeExistsException ignored) {
+      // it's okay if another beats us creating the node
     }
-    this.zkClient = zkClient;
   }
 
-  @Override
-  public void visit(String path) throws InterruptedException, KeeperException {
-    String finalDestination = dest;
-    if (path.equals(source) == false) finalDestination += "/" + path.substring(source.length() + 1);
-    zkClient.makePath(finalDestination, false, true);
-    zkClient.setData(finalDestination, zkClient.getData(path, null, null, true), true);
+  static class ZkCopier implements ZkMaintenanceUtils.ZkVisitor {
+
+    String source;
+    String dest;
+    SolrZkClient zkClient;
+
+    ZkCopier(SolrZkClient zkClient, String source, String dest) {
+      this.source = source;
+      this.dest = dest;
+      if (dest.endsWith("/")) {
+        this.dest = dest.substring(0, dest.length() - 1);
+      }
+      this.zkClient = zkClient;
+    }
+
+    @Override
+    public void visit(String path) throws InterruptedException, KeeperException {
+      String finalDestination = dest;
+      if (path.equals(source) == false)
+        finalDestination += "/" + path.substring(source.length() + 1);
+      zkClient.makePath(finalDestination, false, true);
+      zkClient.setData(finalDestination, zkClient.getData(path, null, null, true), true);
+    }
   }
 }