You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by yo...@apache.org on 2020/02/28 02:11:47 UTC

[lucene-solr] branch jira/SOLR-13101 updated: SOLR-14213: Configuring Solr Cloud to use Shared Storage (#1223)

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

yonik pushed a commit to branch jira/SOLR-13101
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git


The following commit(s) were added to refs/heads/jira/SOLR-13101 by this push:
     new d46803f  SOLR-14213: Configuring Solr Cloud to use Shared Storage (#1223)
d46803f is described below

commit d46803f4fe9844f5e09f7b7d4548457e446933f0
Author: Andy Vuong <an...@users.noreply.github.com>
AuthorDate: Thu Feb 27 18:11:35 2020 -0800

    SOLR-14213: Configuring Solr Cloud to use Shared Storage (#1223)
    
    * Add SharedStoreConfig for initiating shared store support and refactor tests setup
    
    * Add missing condition
    
    * Fix test failure
    
    * Initialize fields in constructor and fix tests
    
    * load shared store manager vs corecontainer
    
    * Undo change
---
 .../cloud/api/collections/CreateCollectionCmd.java |  7 ++
 .../java/org/apache/solr/core/CoreContainer.java   | 25 +++++-
 .../src/java/org/apache/solr/core/NodeConfig.java  | 18 +++-
 .../org/apache/solr/core/SharedStoreConfig.java    | 44 ++++++++++
 .../java/org/apache/solr/core/SolrXmlConfig.java   | 21 +++--
 .../store/blob/metadata/ServerSideMetadata.java    |  8 +-
 .../solr/store/blob/process/BlobProcessUtil.java   | 18 ++--
 .../solr/store/blob/process/CorePullTask.java      | 39 +++++----
 .../solr/store/blob/process/CorePullerFeeder.java  | 22 ++---
 .../solr/store/blob/process/CoreSyncFeeder.java    | 10 +--
 .../shared/SharedCoreConcurrencyController.java    |  7 +-
 .../solr/store/shared/SharedStoreManager.java      | 95 +++++++++++-----------
 solr/core/src/test-files/solr/solr-sharedstore.xml | 58 +++++++++++++
 .../SharedStorageShardMetadataTest.java            | 16 +---
 .../SimpleSharedStorageCollectionTest.java         | 50 ++++++------
 .../solr/store/blob/SharedStorageSplitTest.java    | 14 +---
 .../store/shared/SharedCoreConcurrencyTest.java    | 23 ++----
 .../store/shared/SharedStoreMissingCoreTest.java   | 35 ++------
 .../shared/SimpleSharedStoreEndToEndPullTest.java  | 18 ----
 .../shared/SimpleSharedStoreEndToEndPushTest.java  | 19 +----
 .../store/shared/SolrCloudSharedStoreTestCase.java | 39 ++++++++-
 .../SharedShardMetadataControllerTest.java         |  8 +-
 .../DistributedZkUpdateProcessorTest.java          | 22 -----
 23 files changed, 342 insertions(+), 274 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateCollectionCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateCollectionCmd.java
index 872068d..9197932 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateCollectionCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateCollectionCmd.java
@@ -109,11 +109,18 @@ public class CreateCollectionCmd implements OverseerCollectionMessageHandler.Cmd
     if (ocmh.zkStateReader.aliasesManager != null) { // not a mock ZkStateReader
       ocmh.zkStateReader.aliasesManager.update();
     }
+    final boolean sharedIndex = message.getBool(SHARED_INDEX, false);
     final Aliases aliases = ocmh.zkStateReader.getAliases();
     final String collectionName = message.getStr(NAME);
     final boolean waitForFinalState = message.getBool(WAIT_FOR_FINAL_STATE, false);
     final String alias = message.getStr(ALIAS, collectionName);
     log.info("Create collection {}", collectionName);
+    
+    if (sharedIndex && !ocmh.overseer.getCoreContainer().isSharedStoreEnabled()) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, 
+          "Collection " + collectionName + " cannot be created because shared storage is not enabled on this cluster."
+              + " Check solr.xml for the correct configurations");
+    }
     if (clusterState.hasCollection(collectionName)) {
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "collection already exists: " + collectionName);
     }
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 0bc662d..7e911be 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -229,6 +229,8 @@ public class CoreContainer {
   protected volatile AutoscalingHistoryHandler autoscalingHistoryHandler;
   
   protected SharedStoreManager sharedStoreManager;
+  
+  private volatile boolean discoveredSharedCollection = false;
 
 
   // Bits for the state variable.
@@ -736,7 +738,12 @@ public class CoreContainer {
 
     if (isZooKeeperAware()) {
       metricManager.loadClusterReporters(metricReporters, this);
-      sharedStoreManager = new SharedStoreManager(getZkController());
+      
+      if (cfg.getSharedStoreConfig() != null) {
+        log.info("Shared storage is enabled in Solr Cloud. Initiating SharedStoreManager.");
+        sharedStoreManager = new SharedStoreManager(this);
+        sharedStoreManager.load();
+      }
     }
 
     // setup executor to load cores in parallel
@@ -756,6 +763,12 @@ public class CoreContainer {
         // is set to true for them
         // TODO: should this go behind some config?
         List<CoreDescriptor> additionalCoreDescriptors = discoverAdditionalCoreDescriptorsForSharedReplicas(cds);
+        if (!isSharedStoreEnabled() && discoveredSharedCollection) {
+          throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, 
+              "Core discovery found cores belonging to shared collections but shared storage is not enabled on "
+              + "this cluster! Solr is aborting startup. Check solr.xml for the correct configurations.");
+        }
+        
         if (!additionalCoreDescriptors.isEmpty()) {
           // enhance the list of discovered cores with the additional ones we just discovered/created
           cds = new ArrayList<>(cds);
@@ -864,6 +877,7 @@ public class CoreContainer {
             // skip non-shared collections
             continue;
           }
+          discoveredSharedCollection = true;
 
           // TODO: if shard activation(including post split) can guarantee core existence locally we can skip inactive shards
           // go over collection's replicas belonging to this node
@@ -1075,8 +1089,7 @@ public class CoreContainer {
         }
         
         if (sharedStoreManager != null) {
-          sharedStoreManager.getBlobProcessManager().shutdown();
-          sharedStoreManager.getBlobDeleteManager().shutdown();
+          sharedStoreManager.shutdown();
         }
       }
 
@@ -2101,7 +2114,7 @@ public class CoreContainer {
    * the SharedCoreConcurrencyController cache; see SOLR-14134
    */
   public void evictSharedCoreMetadata(CoreDescriptor cd) {
-    if (!isZooKeeperAware()) {
+    if (!isZooKeeperAware() || !isSharedStoreEnabled()) {
       return;
     }
     
@@ -2116,6 +2129,10 @@ public class CoreContainer {
           " and shard " + shardId + " from shared core concurrency cache");
     }
   }
+  
+  public boolean isSharedStoreEnabled() {
+    return getSharedStoreManager() != null;
+  }
 
   static {
     ExecutorUtil.addThreadLocalProvider(SolrRequestInfo.getInheritableThreadLocalProvider());
diff --git a/solr/core/src/java/org/apache/solr/core/NodeConfig.java b/solr/core/src/java/org/apache/solr/core/NodeConfig.java
index 62c2a98..c273f1a 100644
--- a/solr/core/src/java/org/apache/solr/core/NodeConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/NodeConfig.java
@@ -78,6 +78,8 @@ public class NodeConfig {
   private final PluginInfo transientCacheConfig;
 
   private final PluginInfo tracerConfig;
+  
+  private final SharedStoreConfig sharedStoreConfig;
 
   private NodeConfig(String nodeName, Path coreRootDirectory, Path solrDataHome, Integer booleanQueryMaxClauseCount,
                      Path configSetBaseDirectory, String sharedLibDirectory,
@@ -87,7 +89,8 @@ public class NodeConfig {
                      LogWatcherConfig logWatcherConfig, CloudConfig cloudConfig, Integer coreLoadThreads, int replayUpdatesThreads,
                      int transientCacheSize, boolean useSchemaCache, String managementPath, SolrResourceLoader loader,
                      Properties solrProperties, PluginInfo[] backupRepositoryPlugins,
-                     MetricsConfig metricsConfig, PluginInfo transientCacheConfig, PluginInfo tracerConfig) {
+                     MetricsConfig metricsConfig, PluginInfo transientCacheConfig, PluginInfo tracerConfig,
+                     SharedStoreConfig sharedStoreConfig) {
     this.nodeName = nodeName;
     this.coreRootDirectory = coreRootDirectory;
     this.solrDataHome = solrDataHome;
@@ -114,6 +117,7 @@ public class NodeConfig {
     this.metricsConfig = metricsConfig;
     this.transientCacheConfig = transientCacheConfig;
     this.tracerConfig = tracerConfig;
+    this.sharedStoreConfig = sharedStoreConfig;
 
     if (this.cloudConfig != null && this.getCoreLoadThreadCount(false) < 2) {
       throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
@@ -232,6 +236,10 @@ public class NodeConfig {
   public PluginInfo getTracerConfiguratorPluginInfo() {
     return tracerConfig;
   }
+  
+  public SharedStoreConfig getSharedStoreConfig() {
+    return sharedStoreConfig;
+  }
 
   public static class NodeConfigBuilder {
 
@@ -261,6 +269,7 @@ public class NodeConfig {
     private MetricsConfig metricsConfig;
     private PluginInfo transientCacheConfig;
     private PluginInfo tracerConfig;
+    private SharedStoreConfig sharedStoreConfig;
 
     private final SolrResourceLoader loader;
     private final String nodeName;
@@ -422,13 +431,18 @@ public class NodeConfig {
       this.tracerConfig = tracerConfig;
       return this;
     }
+    
+    public NodeConfigBuilder setSharedStoreConfig(SharedStoreConfig sharedStoreConfig) {
+      this.sharedStoreConfig = sharedStoreConfig;
+      return this;
+    }
 
     public NodeConfig build() {
       return new NodeConfig(nodeName, coreRootDirectory, solrDataHome, booleanQueryMaxClauseCount,
                             configSetBaseDirectory, sharedLibDirectory, shardHandlerFactoryConfig,
                             updateShardHandlerConfig, coreAdminHandlerClass, collectionsAdminHandlerClass, healthCheckHandlerClass, infoHandlerClass, configSetsHandlerClass,
                             logWatcherConfig, cloudConfig, coreLoadThreads, replayUpdatesThreads, transientCacheSize, useSchemaCache, managementPath, loader, solrProperties,
-                            backupRepositoryPlugins, metricsConfig, transientCacheConfig, tracerConfig);
+                            backupRepositoryPlugins, metricsConfig, transientCacheConfig, tracerConfig, sharedStoreConfig);
     }
   }
 }
diff --git a/solr/core/src/java/org/apache/solr/core/SharedStoreConfig.java b/solr/core/src/java/org/apache/solr/core/SharedStoreConfig.java
new file mode 100644
index 0000000..05c698d
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/core/SharedStoreConfig.java
@@ -0,0 +1,44 @@
+/*
+ * 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.core;
+
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.SolrXmlConfig;
+
+/**
+ * Configuration class representing the configuration of Solr Cloud using shared storage
+ * to persist index files. The configuration properties come from solr.xml and are loaded
+ * via {@link SolrXmlConfig} at Solr startup. The presence of sharedStore section in the solr.xml
+ * file of a Solr Cloud mode cluster indicates the user's intention to enable shared storage 
+ * capabilities in the cluster and starts the necessary components required to create/support
+ * shared-type Solr collections.
+ * 
+ * TODO: This class is bare bones until we convert the many hard coded configuration values
+ * into configurable fields that can be specified under this section. The current responsibility
+ * of this class is to ensure shared storage is enabled on the cluster and initiate the necessary
+ * processes associated with it.
+ */
+public class SharedStoreConfig {
+  
+  public static SharedStoreConfig loadSharedStoreConfig(NamedList<Object> nl) {
+    if (nl == null) {
+      // shared store is not configured
+      return null;
+    }
+    return new SharedStoreConfig();
+  }
+}
diff --git a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
index 3be075f..54358a7 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
@@ -16,10 +16,8 @@
  */
 package org.apache.solr.core;
 
-import javax.management.MBeanServer;
-import javax.xml.xpath.XPath;
-import javax.xml.xpath.XPathConstants;
-import javax.xml.xpath.XPathExpressionException;
+import static org.apache.solr.common.params.CommonParams.NAME;
+
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
 import java.lang.invoke.MethodHandles;
@@ -34,7 +32,11 @@ import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 
-import com.google.common.base.Strings;
+import javax.management.MBeanServer;
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+
 import org.apache.commons.io.IOUtils;
 import org.apache.solr.client.solrj.impl.HttpClientUtil;
 import org.apache.solr.common.SolrException;
@@ -51,7 +53,7 @@ import org.w3c.dom.Node;
 import org.w3c.dom.NodeList;
 import org.xml.sax.InputSource;
 
-import static org.apache.solr.common.params.CommonParams.NAME;
+import com.google.common.base.Strings;
 
 
 /**
@@ -95,6 +97,12 @@ public class SolrXmlConfig {
       }
       updateConfig = deprecatedUpdateConfig;
     }
+    
+    SharedStoreConfig sharedStoreConfig = null;
+    if (config.getNodeList("solr/sharedStore", false).getLength() > 0) {
+      sharedStoreConfig = SharedStoreConfig.loadSharedStoreConfig(
+          readNodeListAsNamedList(config, "solr/sharedStore/*[@name]", "<sharedStore>"));
+    }
 
     NodeConfig.NodeConfigBuilder configBuilder = new NodeConfig.NodeConfigBuilder(nodeName, config.getResourceLoader());
     configBuilder.setUpdateShardHandlerConfig(updateConfig);
@@ -107,6 +115,7 @@ public class SolrXmlConfig {
       configBuilder.setCloudConfig(cloudConfig);
     configBuilder.setBackupRepositoryPlugins(getBackupRepositoryPluginInfos(config));
     configBuilder.setMetricsConfig(getMetricsConfig(config));
+    configBuilder.setSharedStoreConfig(sharedStoreConfig);
     return fillSolrSection(configBuilder, entries);
   }
 
diff --git a/solr/core/src/java/org/apache/solr/store/blob/metadata/ServerSideMetadata.java b/solr/core/src/java/org/apache/solr/store/blob/metadata/ServerSideMetadata.java
index d65139a..f18d97f 100644
--- a/solr/core/src/java/org/apache/solr/store/blob/metadata/ServerSideMetadata.java
+++ b/solr/core/src/java/org/apache/solr/store/blob/metadata/ServerSideMetadata.java
@@ -107,10 +107,10 @@ public class ServerSideMetadata {
    *
    * @throws Exception if core corresponding to <code>coreName</code> can't be found.
    */
-  public ServerSideMetadata(String coreName, CoreContainer container, boolean reserveCommit) throws Exception {
+  public ServerSideMetadata(String coreName, CoreContainer coreContainer, boolean reserveCommit) throws Exception {
     this.coreName = coreName;
-    this.container = container;
-    this.core = container.getCore(coreName);
+    this.container = coreContainer;
+    this.core = coreContainer.getCore(coreName);
 
     if (core == null) {
       throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Can't find core " + coreName);
@@ -193,7 +193,7 @@ public class ServerSideMetadata {
   }
 
   public CoreContainer getCoreContainer() {
-    return this.container;
+    return container;
   }
 
   public long getGeneration() {
diff --git a/solr/core/src/java/org/apache/solr/store/blob/process/BlobProcessUtil.java b/solr/core/src/java/org/apache/solr/store/blob/process/BlobProcessUtil.java
index 7df8aa4..9cf5d02 100644
--- a/solr/core/src/java/org/apache/solr/store/blob/process/BlobProcessUtil.java
+++ b/solr/core/src/java/org/apache/solr/store/blob/process/BlobProcessUtil.java
@@ -18,10 +18,13 @@ package org.apache.solr.store.blob.process;
 
 import java.lang.invoke.MethodHandles;
 
-import org.apache.solr.core.CoreContainer;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.store.shared.SharedStoreManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.annotations.VisibleForTesting;
+
 /**
  * Utils related to blob background process e.g. init/shutdown of blob's background processes
  */
@@ -33,18 +36,19 @@ public class BlobProcessUtil {
   /*
    * Start the Blob store async core pull machinery
    */
-  public BlobProcessUtil(CoreContainer coreContainer) {
-    this(coreContainer, new CorePullerFeeder(coreContainer));
+  public void load(SharedStoreManager storeManager) {
+    load(new CorePullerFeeder(storeManager));
   }
   
-  /*
-   * Start the Blob store async core pull machinery
-   */
-  public BlobProcessUtil(CoreContainer coreContainer, CorePullerFeeder cpf) {    
+  @VisibleForTesting
+  public void load(CorePullerFeeder cpf) {
     runningFeeder = initializeCorePullerFeeder(cpf);
   }
   
   public CorePullerFeeder getCorePullerFeeder() {
+    if (runningFeeder == null) {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "BlobProcessUtil has not been initialized yet!");
+    }
     return runningFeeder;
   }
   
diff --git a/solr/core/src/java/org/apache/solr/store/blob/process/CorePullTask.java b/solr/core/src/java/org/apache/solr/store/blob/process/CorePullTask.java
index 956741d..6c65e65 100644
--- a/solr/core/src/java/org/apache/solr/store/blob/process/CorePullTask.java
+++ b/solr/core/src/java/org/apache/solr/store/blob/process/CorePullTask.java
@@ -23,8 +23,6 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Throwables;
 import org.apache.solr.cloud.ZkController;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.Replica;
@@ -44,11 +42,15 @@ import org.apache.solr.store.blob.util.DeduplicatingList;
 import org.apache.solr.store.shared.SharedCoreConcurrencyController;
 import org.apache.solr.store.shared.SharedCoreConcurrencyController.SharedCoreStage;
 import org.apache.solr.store.shared.SharedCoreConcurrencyController.SharedCoreVersionMetadata;
+import org.apache.solr.store.shared.SharedStoreManager;
 import org.apache.solr.store.shared.metadata.SharedShardMetadataController;
 import org.apache.solr.store.shared.metadata.SharedShardMetadataController.SharedShardVersionMetadata;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Throwables;
+
 /**
  * Code for pulling updates on a specific core to the Blob store.
  */
@@ -63,7 +65,7 @@ public class CorePullTask implements DeduplicatingList.Deduplicatable<String> {
    */
   private static final long MIN_RETRY_DELAY_MS = 20000;
 
-  private final CoreContainer coreContainer;
+  private final SharedStoreManager storeManager;
   private final PullCoreInfo pullCoreInfo;
   
   /**
@@ -78,14 +80,14 @@ public class CorePullTask implements DeduplicatingList.Deduplicatable<String> {
   private long lastAttemptTimestamp;
   private final PullCoreCallback callback;
 
-  CorePullTask(CoreContainer coreContainer, PullCoreInfo pullCoreInfo, PullCoreCallback callback, Set<String> coresCreatedNotPulledYet) {
-    this(coreContainer, pullCoreInfo, BlobStoreUtils.getCurrentTimeMs(), 0, 0L, callback, coresCreatedNotPulledYet);
+  CorePullTask(SharedStoreManager storeManager, PullCoreInfo pullCoreInfo, PullCoreCallback callback, Set<String> coresCreatedNotPulledYet) {
+    this(storeManager, pullCoreInfo, BlobStoreUtils.getCurrentTimeMs(), 0, 0L, callback, coresCreatedNotPulledYet);
   }
 
   @VisibleForTesting
-  CorePullTask(CoreContainer coreContainer, PullCoreInfo pullCoreInfo, long queuedTimeMs, int attempts,
+  CorePullTask(SharedStoreManager storeManager, PullCoreInfo pullCoreInfo, long queuedTimeMs, int attempts,
       long lastAttemptTimestamp, PullCoreCallback callback, Set<String> coresCreatedNotPulledYet) {
-    this.coreContainer = coreContainer;
+    this.storeManager = storeManager;
     this.pullCoreInfo = pullCoreInfo;
     this.queuedTimeMs = queuedTimeMs;
     this.attempts = attempts;
@@ -117,7 +119,7 @@ public class CorePullTask implements DeduplicatingList.Deduplicatable<String> {
     @Override
     public CorePullTask merge(CorePullTask task1, CorePullTask task2) {
       // The asserts below are not guaranteed by construction but we know that's the case
-      assert task1.coreContainer == task2.coreContainer;
+      assert task1.storeManager == task2.storeManager;
       assert task1.callback == task2.callback;
 
       int mergedAttempts;
@@ -149,7 +151,7 @@ public class CorePullTask implements DeduplicatingList.Deduplicatable<String> {
       }
       
       // We merge the tasks.
-      return new CorePullTask(task1.coreContainer, mergedPullCoreInfo,
+      return new CorePullTask(task1.storeManager, mergedPullCoreInfo,
           Math.min(task1.queuedTimeMs, task2.queuedTimeMs), mergedAttempts, mergedLatAttemptsTimestamp,
           task1.callback, task1.coresCreatedNotPulledYet);
     }
@@ -184,7 +186,7 @@ public class CorePullTask implements DeduplicatingList.Deduplicatable<String> {
   }
 
   public CoreContainer getCoreContainer() {
-    return coreContainer;
+    return storeManager.getCoreContainer();
   }
 
   /**
@@ -194,7 +196,7 @@ public class CorePullTask implements DeduplicatingList.Deduplicatable<String> {
    */
   void pullCoreFromBlob(boolean isLeaderPulling) throws InterruptedException {
     BlobCoreMetadata blobMetadata = null;
-    if (coreContainer.isShutDown()) {
+    if (storeManager.getCoreContainer().isShutDown()) {
       this.callback.finishedPull(this, blobMetadata, CoreSyncStatus.SHUTTING_DOWN, null);
       // TODO could throw InterruptedException here or interrupt ourselves if we wanted to signal to
       // CorePullerThread to stop everything.
@@ -213,20 +215,20 @@ public class CorePullTask implements DeduplicatingList.Deduplicatable<String> {
       }
     }
 
-    SharedCoreConcurrencyController concurrencyController = coreContainer.getSharedStoreManager().getSharedCoreConcurrencyController();
+    SharedCoreConcurrencyController concurrencyController = storeManager.getSharedCoreConcurrencyController();
     CoreSyncStatus syncStatus = CoreSyncStatus.FAILURE;
     // Auxiliary information related to pull outcome. It can be metadata resolver message which can be null or exception detail in case of failure 
     String message = null;
     try {
       // Do the sequence of actions required to pull a core from the Blob store.
-      BlobStorageProvider blobProvider = coreContainer.getSharedStoreManager().getBlobStorageProvider(); 
+      BlobStorageProvider blobProvider = storeManager.getBlobStorageProvider(); 
       CoreStorageClient blobClient = blobProvider.getClient();
 
       SharedCoreVersionMetadata coreVersionMetadata = concurrencyController.getCoreVersionMetadata(pullCoreInfo.getCollectionName(),
           pullCoreInfo.getShardName(),
           pullCoreInfo.getCoreName());
 
-      SharedShardMetadataController metadataController = coreContainer.getSharedStoreManager().getSharedShardMetadataController();
+      SharedShardMetadataController metadataController = storeManager.getSharedShardMetadataController();
       SharedShardVersionMetadata shardVersionMetadata =  metadataController.readMetadataValue(pullCoreInfo.getCollectionName(), pullCoreInfo.getShardName());
       
       if(concurrencyController.areVersionsEqual(coreVersionMetadata, shardVersionMetadata)) {
@@ -284,7 +286,7 @@ public class CorePullTask implements DeduplicatingList.Deduplicatable<String> {
       }
 
       // Get local metadata + resolve with blob metadata. Given we're doing a pull, don't need to reserve commit point
-      ServerSideMetadata serverMetadata = new ServerSideMetadata(pullCoreInfo.getCoreName(), coreContainer, false);
+      ServerSideMetadata serverMetadata = new ServerSideMetadata(pullCoreInfo.getCoreName(), storeManager.getCoreContainer(), false);
       SharedMetadataResolutionResult resolutionResult = SharedStoreResolutionUtil.resolveMetadata(
           serverMetadata, blobMetadata);
       
@@ -292,7 +294,7 @@ public class CorePullTask implements DeduplicatingList.Deduplicatable<String> {
       // If we call pullUpdateFromBlob with an empty list of files to pull, we'll see an NPE down the line.
       // TODO: might be better to handle this error in CorePushPull.pullUpdateFromBlob
       if (resolutionResult.getFilesToPull().size() > 0) {
-        BlobDeleteManager deleteManager = coreContainer.getSharedStoreManager().getBlobDeleteManager();
+        BlobDeleteManager deleteManager = storeManager.getBlobDeleteManager();
         CorePushPull cp = new CorePushPull(blobClient, deleteManager, pullCoreInfo, resolutionResult, serverMetadata, blobMetadata);
         // TODO: we are computing/tracking attempts but we are not passing it along
         cp.pullUpdateFromBlob(/* waitForSearcher */ true);
@@ -365,9 +367,9 @@ public class CorePullTask implements DeduplicatingList.Deduplicatable<String> {
   private boolean coreExists(String coreName) {
 
     SolrCore core = null;
-    File coreIndexDir = new File(coreContainer.getCoreRootDirectory() + "/" + coreName);
+    File coreIndexDir = new File(storeManager.getCoreContainer().getCoreRootDirectory() + "/" + coreName);
     if (coreIndexDir.exists()) {
-      core = coreContainer.getCore(coreName);
+      core = storeManager.getCoreContainer().getCore(coreName);
     }
 
     log.info("Core " + coreName + " expected in dir " + coreIndexDir.getAbsolutePath() + " exists=" + coreIndexDir.exists()
@@ -389,6 +391,7 @@ public class CorePullTask implements DeduplicatingList.Deduplicatable<String> {
 
     log.info("About to create local core " + pci.getCoreName());
 
+    CoreContainer coreContainer = storeManager.getCoreContainer();
     ZkController controller = coreContainer.getZkController();
     DocCollection collection = controller.getZkStateReader().
         getClusterState().getCollection(pci.getCollectionName());
diff --git a/solr/core/src/java/org/apache/solr/store/blob/process/CorePullerFeeder.java b/solr/core/src/java/org/apache/solr/store/blob/process/CorePullerFeeder.java
index 93c07ce..f4b66ee 100644
--- a/solr/core/src/java/org/apache/solr/store/blob/process/CorePullerFeeder.java
+++ b/solr/core/src/java/org/apache/solr/store/blob/process/CorePullerFeeder.java
@@ -20,16 +20,18 @@ import java.lang.invoke.MethodHandles;
 import java.util.Locale;
 import java.util.Set;
 
-import com.google.common.collect.Sets;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.store.blob.client.BlobCoreMetadata;
 import org.apache.solr.store.blob.metadata.BlobCoreSyncer;
 import org.apache.solr.store.blob.metadata.PushPullData;
 import org.apache.solr.store.blob.util.BlobStoreUtils;
 import org.apache.solr.store.blob.util.DeduplicatingList;
+import org.apache.solr.store.shared.SharedStoreManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.collect.Sets;
+
 /**
  * A pull version of {@link CoreSyncFeeder} then will continually ({@link #feedTheMonsters()}) to load up a work queue (
  * {@link #pullTaskQueue}) with such tasks {@link CorePullTask} to keep the created threads busy :) The tasks will be
@@ -57,8 +59,8 @@ public class CorePullerFeeder extends CoreSyncFeeder {
    */
   private final Set<String> coresCreatedNotPulledYet = Sets.newHashSet();
 
-  protected CorePullerFeeder(CoreContainer cores) {
-    super(cores, numPullerThreads);
+  protected CorePullerFeeder(SharedStoreManager storeManager) {
+    super(storeManager, numPullerThreads);
     this.pullTaskQueue = new DeduplicatingList<>(ALMOST_MAX_WORKER_QUEUE_SIZE, new CorePullTask.PullTaskMerger());
     this.callback = new CorePullResult();
   }
@@ -95,15 +97,7 @@ public class CorePullerFeeder extends CoreSyncFeeder {
 
   @Override
   void feedTheMonsters() throws InterruptedException {
-    while (cores.getSharedStoreManager() == null) {
-      // todo: Fix cyclic initialization sequence
-      // if thread starts early it will be killed since the initialization of sharedStoreManager has triggered the
-      // creation of this thread and following line will throw NPE.
-      if (Thread.interrupted()) {
-        throw new InterruptedException();
-      }
-    }
-    CorePullTracker tracker = cores.getSharedStoreManager().getCorePullTracker();
+    CorePullTracker tracker = storeManager.getCorePullTracker();
     final long minMsBetweenLogs = 15000;
     long lastLoggedTimestamp = 0L;
     long syncsEnqueuedSinceLastLog = 0; // This is the non-deduped count
@@ -112,7 +106,7 @@ public class CorePullerFeeder extends CoreSyncFeeder {
       PullCoreInfo pci = tracker.getCoreToPull();
 
       // Add the core to the list consumed by the thread doing the actual work
-      CorePullTask pt = new CorePullTask(cores, pci, getCorePullTaskCallback(), coresCreatedNotPulledYet);
+      CorePullTask pt = new CorePullTask(storeManager, pci, getCorePullTaskCallback(), coresCreatedNotPulledYet);
       pullTaskQueue.addDeduplicated(pt, /* isReenqueue */ false);
       syncsEnqueuedSinceLastLog++;
 
@@ -227,7 +221,7 @@ public class CorePullerFeeder extends CoreSyncFeeder {
           log.warn(String.format(Locale.ROOT, "Pulling core %s failed. Giving up. Last status=%s attempts=%s . %s",
               pullCoreInfo.getSharedStoreName(), status, pullTask.getAttempts(), message == null ? "" : message));
         }
-        BlobCoreSyncer syncer = cores.getSharedStoreManager().getBlobCoreSyncer();
+        BlobCoreSyncer syncer = storeManager.getBlobCoreSyncer();
         syncer.finishedPull(pullCoreInfo.getSharedStoreName(), status, blobMetadata, message);
       } catch (InterruptedException ie) {
         close();
diff --git a/solr/core/src/java/org/apache/solr/store/blob/process/CoreSyncFeeder.java b/solr/core/src/java/org/apache/solr/store/blob/process/CoreSyncFeeder.java
index 29fd22f..985fb07 100644
--- a/solr/core/src/java/org/apache/solr/store/blob/process/CoreSyncFeeder.java
+++ b/solr/core/src/java/org/apache/solr/store/blob/process/CoreSyncFeeder.java
@@ -25,7 +25,7 @@ import java.util.Set;
 import org.apache.commons.lang3.exception.ExceptionUtils;
 //import com.force.commons.util.concurrent.NamedThreadFactory; difference?
 import org.apache.lucene.util.NamedThreadFactory;
-import org.apache.solr.core.CoreContainer;
+import org.apache.solr.store.shared.SharedStoreManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -37,7 +37,7 @@ public abstract class CoreSyncFeeder implements Runnable, Closeable {
 
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
-  protected final CoreContainer cores;
+  protected final SharedStoreManager storeManager;
 
   /**
    * Maximum number of elements in the queue, NOT counting re-inserts after failures. Total queue size might therefore
@@ -64,9 +64,9 @@ public abstract class CoreSyncFeeder implements Runnable, Closeable {
   private volatile Thread executionThread;
   private volatile boolean closed = false;
 
-  protected CoreSyncFeeder(CoreContainer cores, int numSyncThreads) {
+  protected CoreSyncFeeder(SharedStoreManager storeManager, int numSyncThreads) {
     this.numSyncThreads = numSyncThreads;
-    this.cores = cores;
+    this.storeManager = storeManager;
   }
 
   @Override
@@ -115,7 +115,7 @@ public abstract class CoreSyncFeeder implements Runnable, Closeable {
   }
 
   boolean shouldContinueRunning() {
-    return !this.cores.isShutDown();
+    return !this.storeManager.getCoreContainer().isShutDown();
   }
 
   @Override
diff --git a/solr/core/src/java/org/apache/solr/store/shared/SharedCoreConcurrencyController.java b/solr/core/src/java/org/apache/solr/store/shared/SharedCoreConcurrencyController.java
index 77c0034..b542260 100644
--- a/solr/core/src/java/org/apache/solr/store/shared/SharedCoreConcurrencyController.java
+++ b/solr/core/src/java/org/apache/solr/store/shared/SharedCoreConcurrencyController.java
@@ -132,15 +132,15 @@ public class SharedCoreConcurrencyController {
    */
   public static int MAX_ATTEMPTS_INDEXING_PULL_WRITE_LOCK = 10;
 
-  private final CoreContainer cores;
+  private final SharedShardMetadataController metadataController;
   /**
    * This cache maintains the shared store version the each core is at or ahead of(core has to sometimes be ahead of
    * shared store given indexing first happens locally before being propagated to shared store).
    */
   private final ConcurrentHashMap<String, SharedCoreVersionMetadata> coresVersionMetadata;
 
-  public SharedCoreConcurrencyController(CoreContainer cores) {
-    this.cores = cores;
+  public SharedCoreConcurrencyController(SharedShardMetadataController metadataController) {
+    this.metadataController = metadataController;
     coresVersionMetadata = buildMetadataCache();
   }
 
@@ -301,7 +301,6 @@ public class SharedCoreConcurrencyController {
 
   @VisibleForTesting
   protected void ensureShardVersionMetadataNodeExists(String collectionName, String shardName) {
-    SharedShardMetadataController metadataController = cores.getSharedStoreManager().getSharedShardMetadataController();
     try {
       // creates the metadata node if it doesn't exist
       metadataController.ensureMetadataNodeExists(collectionName, shardName);
diff --git a/solr/core/src/java/org/apache/solr/store/shared/SharedStoreManager.java b/solr/core/src/java/org/apache/solr/store/shared/SharedStoreManager.java
index 9fec3f4..1295dd2 100644
--- a/solr/core/src/java/org/apache/solr/store/shared/SharedStoreManager.java
+++ b/solr/core/src/java/org/apache/solr/store/shared/SharedStoreManager.java
@@ -16,8 +16,8 @@
  */
 package org.apache.solr.store.shared;
 
-import com.google.common.annotations.VisibleForTesting;
 import org.apache.solr.cloud.ZkController;
+import org.apache.solr.core.CoreContainer;
 import org.apache.solr.store.blob.metadata.BlobCoreSyncer;
 import org.apache.solr.store.blob.process.BlobDeleteManager;
 import org.apache.solr.store.blob.process.BlobProcessUtil;
@@ -25,6 +25,8 @@ import org.apache.solr.store.blob.process.CorePullTracker;
 import org.apache.solr.store.blob.provider.BlobStorageProvider;
 import org.apache.solr.store.shared.metadata.SharedShardMetadataController;
 
+import com.google.common.annotations.VisibleForTesting;
+
 /**
  * Provides access to Shared Store processes. Note that this class is meant to be 
  * more generic in the future and provide a cleaner API but for now we'll expose
@@ -32,7 +34,7 @@ import org.apache.solr.store.shared.metadata.SharedShardMetadataController;
  */
 public class SharedStoreManager {
   
-  private ZkController zkController;
+  private CoreContainer coreContainer;
   private SharedShardMetadataController sharedShardMetadataController;
   private BlobStorageProvider blobStorageProvider;
   private BlobDeleteManager blobDeleteManager;
@@ -41,71 +43,44 @@ public class SharedStoreManager {
   private BlobCoreSyncer blobCoreSyncer;
   private SharedCoreConcurrencyController sharedCoreConcurrencyController;
 
-  public SharedStoreManager(ZkController controller) {
-    zkController = controller;
-    // initialize BlobProcessUtil with the SharedStoreManager for background processes to be ready
-    blobProcessUtil = new BlobProcessUtil(zkController.getCoreContainer());
+  public SharedStoreManager(CoreContainer coreContainer) {
+    this.coreContainer = coreContainer;
+    ZkController zkController = coreContainer.getZkController();
+    
+    blobStorageProvider = new BlobStorageProvider();
+    blobDeleteManager = new BlobDeleteManager(getBlobStorageProvider().getClient());
+    corePullTracker = new CorePullTracker();
+    sharedShardMetadataController = new SharedShardMetadataController(zkController.getSolrCloudManager());
+    sharedCoreConcurrencyController = new SharedCoreConcurrencyController(sharedShardMetadataController);
     blobCoreSyncer = new BlobCoreSyncer();
-    sharedCoreConcurrencyController = new SharedCoreConcurrencyController(zkController.getCoreContainer());
+    blobProcessUtil = new BlobProcessUtil();
   }
   
-  @VisibleForTesting
-  public void initBlobStorageProvider(BlobStorageProvider blobStorageProvider) {
-    this.blobStorageProvider = blobStorageProvider;
-  }
-  
-  @VisibleForTesting
-  public void initBlobProcessUtil(BlobProcessUtil processUtil) {
-    if (blobProcessUtil != null) {
-      blobProcessUtil.shutdown();
-    }
-    blobProcessUtil = processUtil;
-  }
-  
-  /*
-   * Initiates a SharedShardMetadataController if it doesn't exist and returns one 
+  /**
+   * Start blob processes that depend on an initiated {@link SharedStoreManager} in {@link CoreContainer}
    */
+  public void load() {
+    blobProcessUtil.load(this);
+  }
+
   public SharedShardMetadataController getSharedShardMetadataController() {
-    if (sharedShardMetadataController != null) {
-      return sharedShardMetadataController;
-    }
-    sharedShardMetadataController = new SharedShardMetadataController(zkController.getSolrCloudManager());
     return sharedShardMetadataController;
   }
   
-  /*
-   * Initiates a BlobStorageProvider if it doesn't exist and returns one 
-   */
   public BlobStorageProvider getBlobStorageProvider() {
-    if (blobStorageProvider != null) {
-      return blobStorageProvider;
-    }
-    blobStorageProvider = new BlobStorageProvider();
     return blobStorageProvider;
   }
   
   public BlobDeleteManager getBlobDeleteManager() {
-    if (blobDeleteManager != null) {
-      return blobDeleteManager;
-    }
-    blobDeleteManager = new BlobDeleteManager(getBlobStorageProvider().getClient());
     return blobDeleteManager;
   }
   
   public BlobProcessUtil getBlobProcessManager() {
-    if (blobProcessUtil != null) {
-      return blobProcessUtil;
-    }
-    blobProcessUtil = new BlobProcessUtil(zkController.getCoreContainer());
     return blobProcessUtil;
   }
   
   public CorePullTracker getCorePullTracker() {
-    if (corePullTracker != null) {
-      return corePullTracker ;
-    }
-    corePullTracker = new CorePullTracker();
-    return corePullTracker ;
+    return corePullTracker;
   }
   
   public BlobCoreSyncer getBlobCoreSyncer() {
@@ -115,6 +90,19 @@ public class SharedStoreManager {
   public SharedCoreConcurrencyController getSharedCoreConcurrencyController() {
     return sharedCoreConcurrencyController;
   }
+  
+  public CoreContainer getCoreContainer() {
+    return coreContainer;
+  }
+  
+  public void shutdown() {
+    if (blobProcessUtil != null) {
+      blobProcessUtil.shutdown();
+    }
+    if (blobDeleteManager != null) {
+      blobDeleteManager.shutdown();
+    }
+  }
 
   @VisibleForTesting
   public void initConcurrencyController(SharedCoreConcurrencyController concurrencyController) {
@@ -122,9 +110,22 @@ public class SharedStoreManager {
   }
   
   @VisibleForTesting
+  public void initBlobStorageProvider(BlobStorageProvider blobStorageProvider) {
+    this.blobStorageProvider = blobStorageProvider;
+  }
+  
+  @VisibleForTesting
+  public void initBlobProcessUtil(BlobProcessUtil processUtil) {
+    if (blobProcessUtil != null) {
+      blobProcessUtil.shutdown();
+    }
+    blobProcessUtil = processUtil;
+  }
+  
+  @VisibleForTesting
   public void initBlobDeleteManager(BlobDeleteManager blobDeleteManager) {
     if (this.blobDeleteManager != null) {
-      blobDeleteManager.shutdown();
+      this.blobDeleteManager.shutdown();
     }
     this.blobDeleteManager = blobDeleteManager;
   }
diff --git a/solr/core/src/test-files/solr/solr-sharedstore.xml b/solr/core/src/test-files/solr/solr-sharedstore.xml
new file mode 100644
index 0000000..746d0c7
--- /dev/null
+++ b/solr/core/src/test-files/solr/solr-sharedstore.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ 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.
+-->
+
+<!--
+ All (relative) paths are relative to the installation path
+-->
+<solr> 
+  <str name="shareSchema">${shareSchema:false}</str> 
+  <str name="configSetBaseDir">${configSetBaseDir:configsets}</str> 
+  <str name="coreRootDirectory">${coreRootDirectory:.}</str> 
+  <str name="collectionsHandler">${collectionsHandler:solr.CollectionsHandler}</str> 
+	
+	<shardHandlerFactory name="shardHandlerFactory" class="HttpShardHandlerFactory"> 
+    <str name="urlScheme">${urlScheme:}</str> 
+    <int name="socketTimeout">${socketTimeout:90000}</int> 
+    <int name="connTimeout">${connTimeout:15000}</int> 
+    <str name="shardsWhitelist">${SOLR_TESTS_SHARDS_WHITELIST:}</str> 
+  </shardHandlerFactory> 
+	
+	<solrcloud> 
+		<str name="host">127.0.0.1</str> 
+		<int name="hostPort">${hostPort:8983}</int> 
+		<str name="hostContext">${hostContext:solr}</str> 
+		<int name="zkClientTimeout">${solr.zkclienttimeout:30000}</int> 
+		<bool name="genericCoreNodeNames">${genericCoreNodeNames:true}</bool> 
+		<int name="leaderVoteWait">${leaderVoteWait:10000}</int> 
+		<int name="distribUpdateConnTimeout">${distribUpdateConnTimeout:45000}</int> 
+		<int name="distribUpdateSoTimeout">${distribUpdateSoTimeout:340000}</int> 
+		<str name="zkCredentialsProvider">${zkCredentialsProvider:org.apache.solr.common.cloud.DefaultZkCredentialsProvider}</str>  
+		<str name="zkACLProvider">${zkACLProvider:org.apache.solr.common.cloud.DefaultZkACLProvider}</str>  
+	</solrcloud> 
+	
+	<sharedStore>
+	 <!-- The presence of this config section enables shared storage capabilities for the entire cluster if it
+	 is in Solr Cloud mode -->
+	</sharedStore>
+	
+	<metrics> 
+		<reporter name="default" class="org.apache.solr.metrics.reporters.SolrJmxReporter"> 
+		  <str name="rootName">solr_${hostPort:8983}</str> 
+		</reporter> 
+	</metrics> 
+</solr>
diff --git a/solr/core/src/test/org/apache/solr/cloud/api/collections/SharedStorageShardMetadataTest.java b/solr/core/src/test/org/apache/solr/cloud/api/collections/SharedStorageShardMetadataTest.java
index 595ed86..182db52 100644
--- a/solr/core/src/test/org/apache/solr/cloud/api/collections/SharedStorageShardMetadataTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/api/collections/SharedStorageShardMetadataTest.java
@@ -16,17 +16,14 @@
  */
 package org.apache.solr.cloud.api.collections;
 
-import java.nio.file.Path;
 import java.util.Map;
 
-import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.Replica.Type;
 import org.apache.solr.common.cloud.Slice;
 import org.apache.solr.common.cloud.ZkStateReader;
-import org.apache.solr.store.blob.client.CoreStorageClient;
 import org.apache.solr.store.shared.SolrCloudSharedStoreTestCase;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
@@ -41,20 +38,9 @@ public class SharedStorageShardMetadataTest extends SolrCloudSharedStoreTestCase
   
   @BeforeClass
   public static void setupCluster() throws Exception {    
-    configureCluster(3)
-      .addConfig("conf", configset("cloud-minimal"))
-      .configure();
-    
-    // we don't use this in testing
-    Path sharedStoreRootPath = createTempDir("tempDir");
-    CoreStorageClient storageClient = setupLocalBlobStoreClient(sharedStoreRootPath, DEFAULT_BLOB_DIR_NAME);
-    // configure same client for each runner, this isn't a concurrency test so this is fine
-    for (JettySolrRunner runner : cluster.getJettySolrRunners()) {
-      setupTestSharedClientForNode(getBlobStorageProviderTestInstance(storageClient), runner);
-    }
+    setupCluster(3);
   }
   
-  
   @AfterClass
   public static void teardownTest() throws Exception {
     shutdownCluster();
diff --git a/solr/core/src/test/org/apache/solr/cloud/api/collections/SimpleSharedStorageCollectionTest.java b/solr/core/src/test/org/apache/solr/cloud/api/collections/SimpleSharedStorageCollectionTest.java
index 5d000fc..fad695d 100644
--- a/solr/core/src/test/org/apache/solr/cloud/api/collections/SimpleSharedStorageCollectionTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/api/collections/SimpleSharedStorageCollectionTest.java
@@ -16,47 +16,34 @@
  */
 package org.apache.solr.cloud.api.collections;
 
-import java.nio.file.Path;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import org.apache.commons.io.FileUtils;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.UpdateRequest;
+import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.Replica;
 import org.apache.solr.common.cloud.Replica.Type;
 import org.apache.solr.common.cloud.ZkStateReader;
-import org.apache.solr.store.blob.client.CoreStorageClient;
 import org.apache.solr.store.shared.SharedCoreConcurrencyController;
 import org.apache.solr.store.shared.SharedCoreConcurrencyController.SharedCoreVersionMetadata;
 import org.apache.solr.store.shared.SolrCloudSharedStoreTestCase;
 import org.junit.After;
-import org.junit.BeforeClass;
 import org.junit.Test;
 
 /**
  * Tests related to shared storage based collections, i.e. collections having only replicas of type {@link Type#SHARED}.
  */
 public class SimpleSharedStorageCollectionTest extends SolrCloudSharedStoreTestCase {
-
-  private static Path sharedStoreRootPath;
-  
-  @BeforeClass
-  public static void setupClass() throws Exception {
-    sharedStoreRootPath = createTempDir("tempDir");    
-  }
   
   @After
   public void teardownTest() throws Exception {
     if (cluster != null) {
       cluster.shutdown();
     }
-    // clean up the shared store after each test. The temp dir should clean up itself after the
-    // test class finishes
-    FileUtils.cleanDirectory(sharedStoreRootPath.toFile());
   }
   
   /**
@@ -66,7 +53,6 @@ public class SimpleSharedStorageCollectionTest extends SolrCloudSharedStoreTestC
   @Test
   public void testCreateCollection() throws Exception {
     setupCluster(3);
-    setupSolrNodes();
     String collectionName = "BlobBasedCollectionName1";
     CloudSolrClient cloudClient = cluster.getSolrClient();
     
@@ -76,6 +62,28 @@ public class SimpleSharedStorageCollectionTest extends SolrCloudSharedStoreTestC
     waitForState("Timed-out wait for collection to be created", collectionName, clusterShape(1, 1));
     assertTrue(cloudClient.getZkStateReader().getZkClient().exists(ZkStateReader.COLLECTIONS_ZKNODE + "/" + collectionName, false));
   }
+  
+  /**
+   * Test that verifies that a collection creation command for a "shared" type collection fails
+   * if the cluster was not enabled with shared storage
+   */
+  @Test
+  public void testCreateCollectionSharedDisabled() throws Exception {
+    setupClusterSharedDisable(1);
+    String collectionName = "BlobBasedCollectionName1";
+    CloudSolrClient cloudClient = cluster.getSolrClient();
+    
+    CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName, 1, 0).setSharedIndex(true).setSharedReplicas(1);
+    try {
+      create.process(cloudClient);
+      fail("Request should have failed");
+    } catch (SolrException ex) {
+      assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, ex.code());
+      assertTrue(ex.getMessage().contains("shared storage is not enabled"));
+    } catch (Exception ex) {
+      fail("Unexpected exception thrown " + ex.getMessage());
+    }
+  }
 
   /**
    * Test that verifies that adding a NRT replica to a shared collection fails but adding a SHARED replica
@@ -84,7 +92,6 @@ public class SimpleSharedStorageCollectionTest extends SolrCloudSharedStoreTestC
   @Test
   public void testAddReplica() throws Exception {
     setupCluster(3);
-    setupSolrNodes();
     String collectionName = "BlobBasedCollectionName2";
     CloudSolrClient cloudClient = cluster.getSolrClient();
     
@@ -123,7 +130,6 @@ public class SimpleSharedStorageCollectionTest extends SolrCloudSharedStoreTestC
     String shardNames = "shard1";
     
     // setup testing components
-    setupSolrNodes();
     AtomicInteger evictionCount = new AtomicInteger(0);
     SharedCoreConcurrencyController concurrencyController = 
         configureTestSharedConcurrencyControllerForNode(cluster.getJettySolrRunner(0), evictionCount);
@@ -161,18 +167,12 @@ public class SimpleSharedStorageCollectionTest extends SolrCloudSharedStoreTestC
     assertEquals(null, scvm.getMetadataSuffix());
     assertEquals(null, scvm.getBlobCoreMetadata());
   }
-  
-  private void setupSolrNodes() throws Exception {
-    for (JettySolrRunner process : cluster.getJettySolrRunners()) {
-      CoreStorageClient storageClient = setupLocalBlobStoreClient(sharedStoreRootPath, DEFAULT_BLOB_DIR_NAME);
-      setupTestSharedClientForNode(getBlobStorageProviderTestInstance(storageClient), process);
-    }
-  }
 
   private SharedCoreConcurrencyController configureTestSharedConcurrencyControllerForNode(JettySolrRunner runner,
       AtomicInteger evictionCount) {
     SharedCoreConcurrencyController concurrencyController = 
-        new SharedCoreConcurrencyController(runner.getCoreContainer()) {
+        new SharedCoreConcurrencyController(runner.getCoreContainer().getSharedStoreManager()
+            .getSharedShardMetadataController()) {
       
       @Override
       public boolean removeCoreVersionMetadataIfPresent(String coreName) {
diff --git a/solr/core/src/test/org/apache/solr/store/blob/SharedStorageSplitTest.java b/solr/core/src/test/org/apache/solr/store/blob/SharedStorageSplitTest.java
index fe51cac..43aa8a7 100644
--- a/solr/core/src/test/org/apache/solr/store/blob/SharedStorageSplitTest.java
+++ b/solr/core/src/test/org/apache/solr/store/blob/SharedStorageSplitTest.java
@@ -17,7 +17,6 @@
 package org.apache.solr.store.blob;
 
 import java.lang.invoke.MethodHandles;
-import java.nio.file.Path;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -43,7 +42,6 @@ import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.Replica;
 import org.apache.solr.common.cloud.Slice;
 import org.apache.solr.common.cloud.ZkStateReader;
-import org.apache.solr.store.blob.client.CoreStorageClient;
 import org.apache.solr.store.shared.SolrCloudSharedStoreTestCase;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
@@ -60,22 +58,14 @@ public class SharedStorageSplitTest extends SolrCloudSharedStoreTestCase  {
   static Map<String, Map<String, CountDownLatch>> solrProcessesTaskTracker = new HashMap<>();
   
   @BeforeClass
-  public static void setupCluster() throws Exception {    
-    configureCluster(2)
-      .addConfig("conf", configset("cloud-minimal"))
-      .configure();
+  public static void setupCluster() throws Exception {
+    setupCluster(2);
     
-    // we don't use this in testing
-    Path sharedStoreRootPath = createTempDir("tempDir");
-    CoreStorageClient storageClient = setupLocalBlobStoreClient(sharedStoreRootPath, DEFAULT_BLOB_DIR_NAME);
-    // configure same client for each runner, this isn't a concurrency test so this is fine
     for (JettySolrRunner runner : cluster.getJettySolrRunners()) {
-      setupTestSharedClientForNode(getBlobStorageProviderTestInstance(storageClient), runner);
       solrProcessesTaskTracker.put(runner.getNodeName(), configureTestBlobProcessForNode(runner));
     }
   }
   
-  
   @AfterClass
   public static void teardownTest() throws Exception {
     shutdownCluster();
diff --git a/solr/core/src/test/org/apache/solr/store/shared/SharedCoreConcurrencyTest.java b/solr/core/src/test/org/apache/solr/store/shared/SharedCoreConcurrencyTest.java
index 7753d9f..6d0c725 100644
--- a/solr/core/src/test/org/apache/solr/store/shared/SharedCoreConcurrencyTest.java
+++ b/solr/core/src/test/org/apache/solr/store/shared/SharedCoreConcurrencyTest.java
@@ -18,7 +18,6 @@ package org.apache.solr.store.shared;
 
 import java.io.File;
 import java.lang.invoke.MethodHandles;
-import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
@@ -33,8 +32,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.stream.Collectors;
 
-import com.google.common.base.Throwables;
-import com.google.common.collect.Lists;
 import org.apache.commons.io.FileUtils;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
@@ -46,15 +43,16 @@ import org.apache.solr.common.cloud.DocCollection;
 import org.apache.solr.common.cloud.Replica;
 import org.apache.solr.common.cloud.Slice;
 import org.apache.solr.common.params.ModifiableSolrParams;
-import org.apache.solr.store.blob.client.CoreStorageClient;
 import org.apache.solr.store.shared.SharedCoreConcurrencyController.SharedCoreStage;
 import org.junit.After;
 import org.junit.Before;
-import org.junit.BeforeClass;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Throwables;
+import com.google.common.collect.Lists;
+
 /**
  * Tests around synchronization of concurrent indexing, pushes and pulls
  * happening on a core of a shared collection {@link DocCollection#getSharedIndex()}
@@ -95,21 +93,12 @@ public class SharedCoreConcurrencyTest extends SolrCloudSharedStoreTestCase {
    * Minimum time between each failover.
    */
   private static int DELAY_MS_BETWEEN_EACH_FAILOVER_ITERATION = 500;
-  /**
-   * Path for local shared store
-   */
-  private static Path sharedStoreRootPath;
 
   /**
    * Manages test state from start to end.
    */
   private TestState testState;
 
-  @BeforeClass
-  public static void setupClass() throws Exception {
-    sharedStoreRootPath = createTempDir("tempDir");
-  }
-
   @Before
   public void setupTest() throws Exception {
     int numNodes = 4;
@@ -132,7 +121,6 @@ public class SharedCoreConcurrencyTest extends SolrCloudSharedStoreTestCase {
     if (cluster != null) {
       cluster.shutdown();
     }
-    FileUtils.cleanDirectory(sharedStoreRootPath.toFile());
   }
 
   /**
@@ -583,8 +571,6 @@ public class SharedCoreConcurrencyTest extends SolrCloudSharedStoreTestCase {
    * Setup solr process for test(process is one life of a node, restarts starts a new life).
    */
   private void setupSolrProcess(JettySolrRunner solrProcess) throws Exception {
-    CoreStorageClient storageClient = setupLocalBlobStoreClient(sharedStoreRootPath, DEFAULT_BLOB_DIR_NAME);
-    setupTestSharedClientForNode(getBlobStorageProviderTestInstance(storageClient), solrProcess);
     Map<String, CountDownLatch> corePullTracker = configureTestBlobProcessForNode(solrProcess);
     ConcurrentHashMap<String, ConcurrentLinkedQueue<String>> coreConcurrencyStagesTracker = new ConcurrentHashMap<>();
     configureTestSharedConcurrencyControllerForProcess(solrProcess, coreConcurrencyStagesTracker);
@@ -604,7 +590,8 @@ public class SharedCoreConcurrencyTest extends SolrCloudSharedStoreTestCase {
    */
   private void configureTestSharedConcurrencyControllerForProcess(
       JettySolrRunner solrProcess, ConcurrentHashMap<String, ConcurrentLinkedQueue<String>> coreConcurrencyStagesMap) {
-    SharedCoreConcurrencyController concurrencyController = new SharedCoreConcurrencyController(solrProcess.getCoreContainer()) {
+    SharedCoreConcurrencyController concurrencyController = new SharedCoreConcurrencyController
+        (solrProcess.getCoreContainer().getSharedStoreManager().getSharedShardMetadataController()) {
       @Override
       public void recordState(String collectionName, String shardName, String coreName, SharedCoreStage stage) {
         super.recordState(collectionName, shardName, coreName, stage);
diff --git a/solr/core/src/test/org/apache/solr/store/shared/SharedStoreMissingCoreTest.java b/solr/core/src/test/org/apache/solr/store/shared/SharedStoreMissingCoreTest.java
index d28411e..ef94806 100644
--- a/solr/core/src/test/org/apache/solr/store/shared/SharedStoreMissingCoreTest.java
+++ b/solr/core/src/test/org/apache/solr/store/shared/SharedStoreMissingCoreTest.java
@@ -18,7 +18,6 @@ package org.apache.solr.store.shared;
 
 import java.io.File;
 import java.io.IOException;
-import java.nio.file.Path;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -26,7 +25,6 @@ import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
-import com.google.common.collect.Lists;
 import org.apache.commons.io.FileUtils;
 import org.apache.solr.client.solrj.SolrServerException;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
@@ -39,11 +37,11 @@ import org.apache.solr.common.cloud.Replica.State;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.SolrCore;
-import org.apache.solr.store.blob.client.CoreStorageClient;
 import org.junit.After;
-import org.junit.BeforeClass;
 import org.junit.Test;
 
+import com.google.common.collect.Lists;
+
 /**
  * Tests for missing shared core. Missing core refers to a case in which shard index exists on the source-of-truth for
  * shared collections, the shared store, but is missing locally on the solr node. The metadata for the index shard and 
@@ -51,19 +49,9 @@ import org.junit.Test;
  */
 public class SharedStoreMissingCoreTest extends SolrCloudSharedStoreTestCase {
 
-  private static Path sharedStoreRootPath;
-
-  @BeforeClass
-  public static void setupClass() throws Exception {
-    sharedStoreRootPath = createTempDir("tempDir");
-  }
-
   @After
   public void teardownTest() throws Exception {
     shutdownCluster();
-    // clean up the shared store after each test. The temp dir should clean up itself after the
-    // test class finishes
-    FileUtils.cleanDirectory(sharedStoreRootPath.toFile());
   }
 
   /**
@@ -75,10 +63,6 @@ public class SharedStoreMissingCoreTest extends SolrCloudSharedStoreTestCase {
     setupCluster(1);
     CloudSolrClient cloudClient = cluster.getSolrClient();
 
-    // setup the test harness
-    CoreStorageClient storageClient = setupLocalBlobStoreClient(sharedStoreRootPath, DEFAULT_BLOB_DIR_NAME);
-    setupTestSharedClientForNode(getBlobStorageProviderTestInstance(storageClient), cluster.getJettySolrRunner(0));
-
     String collectionName = "sharedCollection";
     int maxShardsPerNode = 1;
     int numReplicas = 1;
@@ -92,7 +76,7 @@ public class SharedStoreMissingCoreTest extends SolrCloudSharedStoreTestCase {
     updateReq.add("id", "1");
     updateReq.commit(cloudClient, collectionName);
 
-    Map<String, CountDownLatch> asyncPullLatches = stopSolrRemoveCoreRestartSolr(cloudClient, storageClient, collectionName);
+    Map<String, CountDownLatch> asyncPullLatches = stopSolrRemoveCoreRestartSolr(cloudClient, collectionName);
     queryAndWaitForPullToFinish(cloudClient, collectionName, asyncPullLatches);
     // verify the documents are present
     assertQueryReturnsAllDocs(cloudClient, collectionName, Lists.newArrayList("1"));
@@ -106,7 +90,7 @@ public class SharedStoreMissingCoreTest extends SolrCloudSharedStoreTestCase {
     assertQueryReturnsAllDocs(cloudClient, collectionName, Lists.newArrayList("1", "2"));
 
     // verify that they made it to shared store by a clean pull
-    asyncPullLatches = stopSolrRemoveCoreRestartSolr(cloudClient, storageClient, collectionName);
+    asyncPullLatches = stopSolrRemoveCoreRestartSolr(cloudClient, collectionName);
     queryAndWaitForPullToFinish(cloudClient, collectionName, asyncPullLatches);
     assertQueryReturnsAllDocs(cloudClient, collectionName, Lists.newArrayList("1", "2"));
   }
@@ -121,10 +105,6 @@ public class SharedStoreMissingCoreTest extends SolrCloudSharedStoreTestCase {
     setupCluster(1);
     CloudSolrClient cloudClient = cluster.getSolrClient();
 
-    // setup the test harness
-    CoreStorageClient storageClient = setupLocalBlobStoreClient(sharedStoreRootPath, DEFAULT_BLOB_DIR_NAME);
-    setupTestSharedClientForNode(getBlobStorageProviderTestInstance(storageClient), cluster.getJettySolrRunner(0));
-
     String collectionName = "sharedCollection";
     int maxShardsPerNode = 1;
     int numReplicas = 1;
@@ -138,7 +118,7 @@ public class SharedStoreMissingCoreTest extends SolrCloudSharedStoreTestCase {
     updateReq.add("id", "1");
     updateReq.commit(cloudClient, collectionName);
 
-    stopSolrRemoveCoreRestartSolr(cloudClient, storageClient, collectionName);
+    stopSolrRemoveCoreRestartSolr(cloudClient, collectionName);
 
     // send another update to the collection
     updateReq = new UpdateRequest();
@@ -149,7 +129,7 @@ public class SharedStoreMissingCoreTest extends SolrCloudSharedStoreTestCase {
     assertQueryReturnsAllDocs(cloudClient, collectionName, Lists.newArrayList("1", "2"));
 
     // verify that new state made it to shared store by doing a clean pull
-    Map<String, CountDownLatch> asyncPullLatches = stopSolrRemoveCoreRestartSolr(cloudClient, storageClient, collectionName);
+    Map<String, CountDownLatch> asyncPullLatches = stopSolrRemoveCoreRestartSolr(cloudClient, collectionName);
     queryAndWaitForPullToFinish(cloudClient, collectionName, asyncPullLatches);
     assertQueryReturnsAllDocs(cloudClient, collectionName, Lists.newArrayList("1", "2"));
   }
@@ -166,7 +146,7 @@ public class SharedStoreMissingCoreTest extends SolrCloudSharedStoreTestCase {
     assertTrue("Timed-out waiting for pull to finish", latch.await(120, TimeUnit.SECONDS));
   }
 
-  private Map<String, CountDownLatch> stopSolrRemoveCoreRestartSolr(CloudSolrClient cloudClient, CoreStorageClient storageClient, String collectionName) throws Exception {
+  private Map<String, CountDownLatch> stopSolrRemoveCoreRestartSolr(CloudSolrClient cloudClient, String collectionName) throws Exception {
     // get the replica
     DocCollection collection = cloudClient.getZkStateReader().getClusterState().getCollection(collectionName);
     Replica shardLeaderReplica = collection.getLeader("shard1");
@@ -194,7 +174,6 @@ public class SharedStoreMissingCoreTest extends SolrCloudSharedStoreTestCase {
     // start up the node again
     runner = cluster.startJettySolrRunner(runner, true);
     cluster.waitForNode(runner, /* seconds */ 30);
-    setupTestSharedClientForNode(getBlobStorageProviderTestInstance(storageClient), cluster.getJettySolrRunner(0));
     Map<String, CountDownLatch> asyncPullLatches = configureTestBlobProcessForNode(cluster.getJettySolrRunner(0));
 
     collection = cloudClient.getZkStateReader().getClusterState().getCollection(collectionName);
diff --git a/solr/core/src/test/org/apache/solr/store/shared/SimpleSharedStoreEndToEndPullTest.java b/solr/core/src/test/org/apache/solr/store/shared/SimpleSharedStoreEndToEndPullTest.java
index eb018ca..425ee8b 100644
--- a/solr/core/src/test/org/apache/solr/store/shared/SimpleSharedStoreEndToEndPullTest.java
+++ b/solr/core/src/test/org/apache/solr/store/shared/SimpleSharedStoreEndToEndPullTest.java
@@ -16,13 +16,11 @@
  */
 package org.apache.solr.store.shared;
 
-import java.nio.file.Path;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-import org.apache.commons.io.FileUtils;
 import org.apache.solr.client.solrj.SolrClient;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
@@ -33,9 +31,7 @@ import org.apache.solr.common.cloud.Replica;
 import org.apache.solr.common.params.ModifiableSolrParams;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.SolrCore;
-import org.apache.solr.store.blob.client.CoreStorageClient;
 import org.junit.After;
-import org.junit.BeforeClass;
 import org.junit.Test;
 
 /**
@@ -43,21 +39,11 @@ import org.junit.Test;
  */
 public class SimpleSharedStoreEndToEndPullTest extends SolrCloudSharedStoreTestCase {
   
-  private static Path sharedStoreRootPath;
-  
-  @BeforeClass
-  public static void setupClass() throws Exception {
-    sharedStoreRootPath = createTempDir("tempDir");    
-  }
-  
   @After
   public void teardownTest() throws Exception {
     if (cluster != null) {
       cluster.shutdown();
     }
-    // clean up the shared store after each test. The temp dir should clean up itself after the
-    // test class finishes
-    FileUtils.cleanDirectory(sharedStoreRootPath.toFile());
   }
   
   /**
@@ -73,13 +59,9 @@ public class SimpleSharedStoreEndToEndPullTest extends SolrCloudSharedStoreTestC
     Map<String, Map<String, CountDownLatch>> solrProcessesTaskTracker = new HashMap<>();
     
     JettySolrRunner solrProcess1 = cluster.getJettySolrRunner(0);
-    CoreStorageClient storageClient1 = setupLocalBlobStoreClient(sharedStoreRootPath, DEFAULT_BLOB_DIR_NAME);
-    setupTestSharedClientForNode(getBlobStorageProviderTestInstance(storageClient1), solrProcess1);
     Map<String, CountDownLatch> asyncPullLatches1 = configureTestBlobProcessForNode(solrProcess1);
     
     JettySolrRunner solrProcess2 = cluster.getJettySolrRunner(1);
-    CoreStorageClient storageClient2 = setupLocalBlobStoreClient(sharedStoreRootPath, DEFAULT_BLOB_DIR_NAME);
-    setupTestSharedClientForNode(getBlobStorageProviderTestInstance(storageClient2), solrProcess2);
     Map<String, CountDownLatch> asyncPullLatches2 = configureTestBlobProcessForNode(solrProcess2);
     
     solrProcessesTaskTracker.put(solrProcess1.getNodeName(), asyncPullLatches1);
diff --git a/solr/core/src/test/org/apache/solr/store/shared/SimpleSharedStoreEndToEndPushTest.java b/solr/core/src/test/org/apache/solr/store/shared/SimpleSharedStoreEndToEndPushTest.java
index 039e5ad..2d6bb5c 100644
--- a/solr/core/src/test/org/apache/solr/store/shared/SimpleSharedStoreEndToEndPushTest.java
+++ b/solr/core/src/test/org/apache/solr/store/shared/SimpleSharedStoreEndToEndPushTest.java
@@ -16,10 +16,8 @@
  */
 package org.apache.solr.store.shared;
 
-import java.nio.file.Path;
 import java.util.Map;
 
-import org.apache.commons.io.FileUtils;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.request.QueryRequest;
@@ -36,7 +34,6 @@ import org.apache.solr.store.blob.util.BlobStoreUtils;
 import org.apache.solr.store.shared.metadata.SharedShardMetadataController;
 import org.apache.solr.store.shared.metadata.SharedShardMetadataController.SharedShardVersionMetadata;
 import org.junit.After;
-import org.junit.BeforeClass;
 import org.junit.Test;
 
 /**
@@ -44,21 +41,11 @@ import org.junit.Test;
  */
 public class SimpleSharedStoreEndToEndPushTest extends SolrCloudSharedStoreTestCase {
   
-  private static Path sharedStoreRootPath;
-  
-  @BeforeClass
-  public static void setupClass() throws Exception {
-    sharedStoreRootPath = createTempDir("tempDir");    
-  }
-  
   @After
   public void teardownTest() throws Exception {
     if (cluster != null) {
       cluster.shutdown();
     }
-    // clean up the shared store after each test. The temp dir should clean up itself after the
-    // test class finishes
-    FileUtils.cleanDirectory(sharedStoreRootPath.toFile());
   }
   
   /**
@@ -79,14 +66,9 @@ public class SimpleSharedStoreEndToEndPushTest extends SolrCloudSharedStoreTestC
   }
 
   public void testUpdatePushesToBlob(boolean withCommit) throws Exception {
-
     setupCluster(1);
     CloudSolrClient cloudClient = cluster.getSolrClient();
     
-    // setup the test harness
-    CoreStorageClient storageClient = setupLocalBlobStoreClient(sharedStoreRootPath, DEFAULT_BLOB_DIR_NAME);
-    setupTestSharedClientForNode(getBlobStorageProviderTestInstance(storageClient), cluster.getJettySolrRunner(0));
-    
     String collectionName = "sharedCollection";
     int maxShardsPerNode = 1;
     int numReplicas = 1;
@@ -122,6 +104,7 @@ public class SimpleSharedStoreEndToEndPushTest extends SolrCloudSharedStoreTestC
       String sharedShardName = (String) props.get(ZkStateReader.SHARED_SHARD_NAME);
       String blobCoreMetadataName = BlobStoreUtils.buildBlobStoreMetadataName(shardMetadata.getMetadataSuffix());
       
+      CoreStorageClient storageClient = leaderCC.getSharedStoreManager().getBlobStorageProvider().getClient();
       // verify that we pushed the core to blob
       assertTrue(storageClient.coreMetadataExists(sharedShardName, blobCoreMetadataName));
       
diff --git a/solr/core/src/test/org/apache/solr/store/shared/SolrCloudSharedStoreTestCase.java b/solr/core/src/test/org/apache/solr/store/shared/SolrCloudSharedStoreTestCase.java
index 909443c..9324cbc 100644
--- a/solr/core/src/test/org/apache/solr/store/shared/SolrCloudSharedStoreTestCase.java
+++ b/solr/core/src/test/org/apache/solr/store/shared/SolrCloudSharedStoreTestCase.java
@@ -21,6 +21,7 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 
+import org.apache.commons.io.FileUtils;
 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.cloud.MiniSolrCloudCluster;
@@ -35,6 +36,8 @@ import org.apache.solr.store.blob.process.CorePullTask.PullCoreCallback;
 import org.apache.solr.store.blob.process.CorePullerFeeder;
 import org.apache.solr.store.blob.process.CoreSyncStatus;
 import org.apache.solr.store.blob.provider.BlobStorageProvider;
+import org.junit.After;
+import org.junit.BeforeClass;
 
 /**
  * Base class for SolrCloud tests with a few additional utilities for testing with a shared store
@@ -58,6 +61,20 @@ public class SolrCloudSharedStoreTestCase extends SolrCloudTestCase {
   
   public static String DEFAULT_BLOB_DIR_NAME = "LocalBlobStore/";
   
+  public static Path blobDir;
+  
+  @BeforeClass
+  public static void setupBlobDirectory() throws Exception {
+    blobDir = createTempDir("tempDir");
+  }
+  
+  @After
+  public void cleanupBlobDirectory() throws Exception {
+    if (blobDir != null) {
+      FileUtils.cleanDirectory(blobDir.toFile());
+    }
+  }
+  
   protected static void setupSharedCollectionWithShardNames(String collectionName, 
       int maxShardsPerNode, int numReplicas, String shardNames) throws Exception {
     CollectionAdminRequest.Create create = CollectionAdminRequest
@@ -72,7 +89,24 @@ public class SolrCloudSharedStoreTestCase extends SolrCloudTestCase {
     waitForState("Timed-out wait for collection to be created", collectionName, clusterShape(numShards, numShards*numReplicas));
   }
 
+  /**
+   * Spin up a {@link MiniSolrCloudCluster} with shared storage enabled and 
+   * the local FS as the shared storage provider
+   */
   protected static void setupCluster(int nodes) throws Exception {
+    System.setProperty(LocalStorageClient.BLOB_STORE_LOCAL_FS_ROOT_DIR_PROPERTY, 
+        blobDir.resolve(DEFAULT_BLOB_DIR_NAME).toString());
+    
+    configureCluster(nodes)
+      .withSolrXml(TEST_PATH().resolve("solr-sharedstore.xml"))
+      .addConfig("conf", configset("cloud-minimal"))
+      .configure();
+  }
+  
+  /**
+   * Spin up a {@link MiniSolrCloudCluster} with shared storage disabled
+   */
+  protected static void setupClusterSharedDisable(int nodes) throws Exception {
     configureCluster(nodes)
       .addConfig("conf", configset("cloud-minimal"))
       .configure();
@@ -147,14 +181,15 @@ public class SolrCloudSharedStoreTestCase extends SolrCloudTestCase {
       }
     };
     
-    CorePullerFeeder cpf = new CorePullerFeeder(runner.getCoreContainer()) {  
+    CorePullerFeeder cpf = new CorePullerFeeder(runner.getCoreContainer().getSharedStoreManager()) {  
       @Override
       protected CorePullTask.PullCoreCallback getCorePullTaskCallback() {
         return callback;
       }
     };
 
-    BlobProcessUtil testUtil = new BlobProcessUtil(runner.getCoreContainer(), cpf);
+    BlobProcessUtil testUtil = new BlobProcessUtil();
+    testUtil.load(cpf);
     setupTestBlobProcessUtilForNode(testUtil, runner);
     return asyncPullTracker;
   }
diff --git a/solr/core/src/test/org/apache/solr/store/shared/metadata/SharedShardMetadataControllerTest.java b/solr/core/src/test/org/apache/solr/store/shared/metadata/SharedShardMetadataControllerTest.java
index 6e67b5c..2c65ad9 100644
--- a/solr/core/src/test/org/apache/solr/store/shared/metadata/SharedShardMetadataControllerTest.java
+++ b/solr/core/src/test/org/apache/solr/store/shared/metadata/SharedShardMetadataControllerTest.java
@@ -22,9 +22,9 @@ import java.util.NoSuchElementException;
 import org.apache.solr.client.solrj.cloud.SolrCloudManager;
 import org.apache.solr.client.solrj.cloud.autoscaling.BadVersionException;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
-import org.apache.solr.cloud.SolrCloudTestCase;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.util.Utils;
+import org.apache.solr.store.shared.SolrCloudSharedStoreTestCase;
 import org.apache.solr.store.shared.metadata.SharedShardMetadataController.SharedShardVersionMetadata;
 import org.apache.zookeeper.data.Stat;
 import org.junit.After;
@@ -34,7 +34,7 @@ import org.junit.Test;
 /**
  * Tests for {@link SharedShardMetadataController}
  */
-public class SharedShardMetadataControllerTest extends SolrCloudTestCase {
+public class SharedShardMetadataControllerTest extends SolrCloudSharedStoreTestCase {
 
   static final String TEST_COLLECTION_NAME = "testCollectionName1";
   static final String TEST_SHARD_NAME = "testShardName";
@@ -47,9 +47,7 @@ public class SharedShardMetadataControllerTest extends SolrCloudTestCase {
   @BeforeClass
   public static void setupCluster() throws Exception {
     assumeWorkingMockito();
-    configureCluster(1)
-      .addConfig("conf", configset("cloud-minimal"))
-      .configure();
+    setupCluster(1);
     
     cloudManager = cluster.getJettySolrRunner(0).getCoreContainer().
         getZkController().getSolrCloudManager();
diff --git a/solr/core/src/test/org/apache/solr/update/processor/DistributedZkUpdateProcessorTest.java b/solr/core/src/test/org/apache/solr/update/processor/DistributedZkUpdateProcessorTest.java
index 20fda76..b6f088f 100644
--- a/solr/core/src/test/org/apache/solr/update/processor/DistributedZkUpdateProcessorTest.java
+++ b/solr/core/src/test/org/apache/solr/update/processor/DistributedZkUpdateProcessorTest.java
@@ -20,11 +20,6 @@ import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 
-import java.io.File;
-import java.nio.file.Path;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.solr.client.solrj.embedded.JettySolrRunner;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.request.CollectionAdminRequest;
 import org.apache.solr.client.solrj.request.UpdateRequest;
@@ -41,7 +36,6 @@ import org.apache.solr.core.SolrCore;
 import org.apache.solr.request.LocalSolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.response.SolrQueryResponse;
-import org.apache.solr.store.blob.client.CoreStorageClient;
 import org.apache.solr.store.blob.process.CoreUpdateTracker;
 import org.apache.solr.store.shared.SolrCloudSharedStoreTestCase;
 import org.apache.solr.update.AddUpdateCommand;
@@ -55,25 +49,16 @@ import org.mockito.Mockito;
  * Test for the {@link DistributedZkUpdateProcessor}.
  */
 public class DistributedZkUpdateProcessorTest extends SolrCloudSharedStoreTestCase {    
-
-  private static Path sharedStoreRootPath;
-  private static CoreStorageClient storageClient;
   
   @BeforeClass
   public static void setupTestClass() throws Exception {
     assumeWorkingMockito();
-    sharedStoreRootPath = createTempDir("tempDir");
-    storageClient = setupLocalBlobStoreClient(sharedStoreRootPath, DEFAULT_BLOB_DIR_NAME);
-    
-    assumeWorkingMockito();
   }
   
   @After
   public void teardownTest() throws Exception {
     cluster.deleteAllCollections();
     shutdownCluster();
-    File blobPath = sharedStoreRootPath.toFile();
-    FileUtils.cleanDirectory(blobPath);
   }
   
   /**
@@ -84,7 +69,6 @@ public class DistributedZkUpdateProcessorTest extends SolrCloudSharedStoreTestCa
   @Test
   public void testNonLeaderSharedReplicaFailsOnForwardedCommit() throws Exception {
     setupCluster(1);
-    setupTestSharedClientForNode(getBlobStorageProviderTestInstance(storageClient), cluster.getJettySolrRunner(0));
 
     String collectionName = "sharedCollection";
     CloudSolrClient cloudClient = cluster.getSolrClient();
@@ -175,7 +159,6 @@ public class DistributedZkUpdateProcessorTest extends SolrCloudSharedStoreTestCa
    */
   private void testReplicaUpdatesZk(Replica.Type replicaType, boolean isUpdateAnIsolatedCommit, boolean isWriteToSharedStoreExpected) throws Exception {
     setupCluster(1);
-    setupTestSharedClientForNode(getBlobStorageProviderTestInstance(storageClient), cluster.getJettySolrRunner(0));
 
     // Set collection name and create client
     String collectionName = "testCollection";
@@ -258,11 +241,6 @@ public class DistributedZkUpdateProcessorTest extends SolrCloudSharedStoreTestCa
   public void testSharedReplicaSimpleUpdateOnLeaderSuccess() throws Exception {
     setupCluster(3);
     
-    // configure same client for each runner, this isn't a concurrency test so this is fine
-    for (JettySolrRunner runner : cluster.getJettySolrRunners()) {
-      setupTestSharedClientForNode(getBlobStorageProviderTestInstance(storageClient), runner);
-    }
-    
     String collectionName = "sharedCollection";
     CloudSolrClient cloudClient = cluster.getSolrClient();