This is an automated email from the ASF dual-hosted git repository.
sigram 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 ad558424923 SOLR-17894: Support collection state.json compression when
Overseer is disabled. (#4905)
ad558424923 is described below
commit ad55842492387d0f0bb313c5619774cd2ac47510
Author: Andrzej BiaĆecki <[email protected]>
AuthorDate: Sat Sep 12 10:29:42 2026 +0200
SOLR-17894: Support collection state.json compression when Overseer is
disabled. (#4905)
---
.../solr-17894-distrib-update-compression.yml | 7 +
.../solr/cloud/DistributedClusterStateUpdater.java | 74 +++++--
.../src/java/org/apache/solr/cloud/Overseer.java | 11 +-
.../java/org/apache/solr/cloud/ZkController.java | 12 +-
.../cloud/DistributedClusterStateUpdaterTest.java | 229 +++++++++++++++++++++
.../test/org/apache/solr/cloud/OverseerTest.java | 2 +-
6 files changed, 310 insertions(+), 25 deletions(-)
diff --git a/changelog/unreleased/solr-17894-distrib-update-compression.yml
b/changelog/unreleased/solr-17894-distrib-update-compression.yml
new file mode 100644
index 00000000000..8b5d9fd2534
--- /dev/null
+++ b/changelog/unreleased/solr-17894-distrib-update-compression.yml
@@ -0,0 +1,7 @@
+title: Support collection state.json compression when Overseer is disabled.
+type: changed
+authors:
+ - name: Andrzej Bialecki
+links:
+ - name: SOLR-17894
+ url: https://issues.apache.org/jira/browse/SOLR-17894
diff --git
a/solr/core/src/java/org/apache/solr/cloud/DistributedClusterStateUpdater.java
b/solr/core/src/java/org/apache/solr/cloud/DistributedClusterStateUpdater.java
index 9a8e249ca3c..478b0fdc9ad 100644
---
a/solr/core/src/java/org/apache/solr/cloud/DistributedClusterStateUpdater.java
+++
b/solr/core/src/java/org/apache/solr/cloud/DistributedClusterStateUpdater.java
@@ -45,6 +45,7 @@ import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.util.Compressor;
import org.apache.solr.common.util.Pair;
import org.apache.solr.common.util.Utils;
import org.apache.zookeeper.CreateMode;
@@ -67,6 +68,9 @@ public class DistributedClusterStateUpdater {
*/
private final boolean useDistributedStateUpdate;
+ private final int minStateByteLenForCompression;
+ private final Compressor compressor;
+
/**
* Builds an instance with the specified behavior regarding distribution of
state updates,
* allowing to know distributed updates are not enabled (parameter {@code
@@ -76,9 +80,16 @@ public class DistributedClusterStateUpdater {
* @param useDistributedStateUpdate when this parameter is {@code false},
only method expected to
* ever be called on this instance is {@link #isDistributedStateUpdate},
and it will return
* {@code false}.
+ * @param minStateByteLenForCompression the minimum size of a state.json
file that should be
+ * compressed before being written to Zookeeper.
+ * @param compressor the compressor to use for compressing state.json files
before writing them to
+ * Zookeeper.
*/
- public DistributedClusterStateUpdater(boolean useDistributedStateUpdate) {
+ public DistributedClusterStateUpdater(
+ boolean useDistributedStateUpdate, int minStateByteLenForCompression,
Compressor compressor) {
this.useDistributedStateUpdate = useDistributedStateUpdate;
+ this.minStateByteLenForCompression = minStateByteLenForCompression;
+ this.compressor = compressor;
}
/**
@@ -93,7 +104,8 @@ public class DistributedClusterStateUpdater {
throw new IllegalStateException(
"Not expecting to create instances of StateChangeRecorder when not
using distributed state update");
}
- return new StateChangeRecorder(collectionName, isCollectionCreation);
+ return new StateChangeRecorder(
+ collectionName, isCollectionCreation, minStateByteLenForCompression,
compressor);
}
/** Syntactic sugar to allow a single change to the cluster state to be made
in a single call. */
@@ -109,7 +121,11 @@ public class DistributedClusterStateUpdater {
}
String collectionName = command.getCollectionName(message);
final StateChangeRecorder scr =
- new StateChangeRecorder(collectionName,
command.isCollectionCreation());
+ new StateChangeRecorder(
+ collectionName,
+ command.isCollectionCreation(),
+ minStateByteLenForCompression,
+ compressor);
scr.record(command, message);
scr.executeStateUpdates(scm, zkStateReader);
}
@@ -119,7 +135,8 @@ public class DistributedClusterStateUpdater {
throw new IllegalStateException(
"Not expecting to execute executeNodeDownStateUpdate when not using
distributed state update");
}
- CollectionNodeDownChangeCalculator.executeNodeDownStateUpdate(nodeName,
zkStateReader);
+ CollectionNodeDownChangeCalculator.executeNodeDownStateUpdate(
+ nodeName, zkStateReader, minStateByteLenForCompression, compressor);
}
/**
@@ -349,16 +366,29 @@ public class DistributedClusterStateUpdater {
private final ZkStateReader zkStateReader;
private final StateChangeCalculator updater;
-
- static void applyUpdate(ZkStateReader zkStateReader, StateChangeCalculator
updater)
+ private final int minStateByteLenForCompression;
+ private final Compressor compressor;
+
+ static void applyUpdate(
+ ZkStateReader zkStateReader,
+ StateChangeCalculator updater,
+ int minStateByteLenForCompression,
+ Compressor compressor)
throws KeeperException, InterruptedException {
- ZkUpdateApplicator zua = new ZkUpdateApplicator(zkStateReader, updater);
+ ZkUpdateApplicator zua =
+ new ZkUpdateApplicator(zkStateReader, updater,
minStateByteLenForCompression, compressor);
zua.applyUpdate();
}
- private ZkUpdateApplicator(ZkStateReader zkStateReader,
StateChangeCalculator updater) {
+ private ZkUpdateApplicator(
+ ZkStateReader zkStateReader,
+ StateChangeCalculator updater,
+ int minStateByteLenForCompression,
+ Compressor compressor) {
this.zkStateReader = zkStateReader;
this.updater = updater;
+ this.minStateByteLenForCompression = minStateByteLenForCompression;
+ this.compressor = compressor;
}
/**
@@ -546,6 +576,11 @@ public class DistributedClusterStateUpdater {
// Collection update or creation
DocCollection collection =
updatedState.getCollection(updater.getCollectionName());
byte[] stateJson = Utils.toJSON(Map.of(updater.getCollectionName(),
collection));
+ if (minStateByteLenForCompression > -1
+ && stateJson.length > minStateByteLenForCompression) {
+ // When compressing state.json, we expect at least a 10:1
compression ratio.
+ stateJson = compressor.compressBytes(stateJson, stateJson.length /
10);
+ }
if (updater.isCollectionCreation()) {
// The state.json file does not exist yet (more precisely it is
assumed not to exist)
@@ -627,7 +662,14 @@ public class DistributedClusterStateUpdater {
*/
boolean creationCommandRecorded = false;
- private StateChangeRecorder(String collectionName, boolean
isCollectionCreation) {
+ final int minStateByteLenForCompression;
+ final Compressor compressor;
+
+ private StateChangeRecorder(
+ String collectionName,
+ boolean isCollectionCreation,
+ int minStateByteLenForCompression,
+ Compressor compressor) {
if (collectionName == null) {
final String err =
"Internal bug. collectionName=null (isCollectionCreation=" +
isCollectionCreation + ")";
@@ -637,6 +679,8 @@ public class DistributedClusterStateUpdater {
mutations = new ArrayList<>();
this.collectionName = collectionName;
this.isCollectionCreation = isCollectionCreation;
+ this.minStateByteLenForCompression = minStateByteLenForCompression;
+ this.compressor = compressor;
}
/**
@@ -825,7 +869,8 @@ public class DistributedClusterStateUpdater {
RecordedMutationsPlayer mutationPlayer =
new RecordedMutationsPlayer(scm, collectionName,
isCollectionCreation, mutations);
- ZkUpdateApplicator.applyUpdate(zkStateReader, mutationPlayer);
+ ZkUpdateApplicator.applyUpdate(
+ zkStateReader, mutationPlayer, minStateByteLenForCompression,
compressor);
// TODO update stats here for the various commands executed successfully
or not?
// This would replace the stats about cluster state updates that the
Collection API currently
@@ -859,7 +904,11 @@ public class DistributedClusterStateUpdater {
* Entry point to mark all replicas of all collections present on a single
node as being DOWN
* (because the node is down)
*/
- public static void executeNodeDownStateUpdate(String nodeName,
ZkStateReader zkStateReader) {
+ public static void executeNodeDownStateUpdate(
+ String nodeName,
+ ZkStateReader zkStateReader,
+ int minStateByteLenForCompression,
+ Compressor compressor) {
// This code does a version of what NodeMutator.downNode() is doing. We
can't assume we have a
// cache of the collections, so we're going to read all of them from ZK,
fetch the state.json
// for each and if it has any replicas on the failed node, do an update
(conditional of
@@ -886,7 +935,8 @@ public class DistributedClusterStateUpdater {
for (String collectionName : collectionNames) {
CollectionNodeDownChangeCalculator collectionUpdater =
new CollectionNodeDownChangeCalculator(collectionName, nodeName);
- ZkUpdateApplicator.applyUpdate(zkStateReader, collectionUpdater);
+ ZkUpdateApplicator.applyUpdate(
+ zkStateReader, collectionUpdater, minStateByteLenForCompression,
compressor);
}
} catch (Exception e) {
if (e instanceof InterruptedException) {
diff --git a/solr/core/src/java/org/apache/solr/cloud/Overseer.java
b/solr/core/src/java/org/apache/solr/cloud/Overseer.java
index 9d35afb7161..019dd57b3e1 100644
--- a/solr/core/src/java/org/apache/solr/cloud/Overseer.java
+++ b/solr/core/src/java/org/apache/solr/cloud/Overseer.java
@@ -52,9 +52,7 @@ import org.apache.solr.common.util.Compressor;
import org.apache.solr.common.util.IOUtils;
import org.apache.solr.common.util.ObjectReleaseTracker;
import org.apache.solr.common.util.Pair;
-import org.apache.solr.common.util.StrUtils;
import org.apache.solr.common.util.Utils;
-import org.apache.solr.common.util.ZLibCompressor;
import org.apache.solr.core.CloudConfig;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.SolrInfoBean;
@@ -734,14 +732,7 @@ public class Overseer implements SolrCloseable {
createOverseerNode(reader.getZkClient());
// launch cluster state updater thread
ThreadGroup tg = new ThreadGroup("Overseer state updater.");
- String stateCompressionProviderClass = config.getStateCompressorClass();
- Compressor compressor =
- StrUtils.isNullOrEmpty(stateCompressionProviderClass)
- ? new ZLibCompressor()
- : zkController
- .getCoreContainer()
- .getResourceLoader()
- .newInstance(stateCompressionProviderClass, Compressor.class);
+ Compressor compressor = zkController.getCompressor();
updaterThread =
new OverseerThread(
tg,
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 808bd0d24f9..8a260c1d85d 100644
--- a/solr/core/src/java/org/apache/solr/cloud/ZkController.java
+++ b/solr/core/src/java/org/apache/solr/cloud/ZkController.java
@@ -225,6 +225,8 @@ public class ZkController implements Closeable {
private final CloudConfig cloudConfig;
private final NodesSysPropsCacher sysPropsCacher;
+ private final Compressor compressor;
+
private final DistributedClusterStateUpdater distributedClusterStateUpdater;
private final Optional<DistributedCollectionConfigSetCommandRunner>
distributedCommandRunner;
@@ -324,7 +326,7 @@ public class ZkController implements Closeable {
addOnReconnectListener(getConfigDirListener());
- final var compressor =
+ compressor =
loadPluginOrDefault(
Compressor.class, cloudConfig.getStateCompressorClass(), new
ZLibCompressor());
@@ -382,7 +384,9 @@ public class ZkController implements Closeable {
"The Overseer is disabled. Cluster commands & state updates will
happen on any/all nodes.");
}
// These "distributed" things replace the Overseer when that's disabled
- this.distributedClusterStateUpdater = new
DistributedClusterStateUpdater(!overseerEnabled);
+ this.distributedClusterStateUpdater =
+ new DistributedClusterStateUpdater(
+ !overseerEnabled, cloudConfig.getMinStateByteLenForCompression(),
compressor);
this.distributedCommandRunner =
!overseerEnabled
? Optional.of(new DistributedCollectionConfigSetCommandRunner(cc,
zkClient))
@@ -401,6 +405,10 @@ public class ZkController implements Closeable {
assert ObjectReleaseTracker.track(this);
}
+ public Compressor getCompressor() {
+ return compressor;
+ }
+
private void onDisconnect(boolean sessionExpired) {
try {
overseer.close();
diff --git
a/solr/core/src/test/org/apache/solr/cloud/DistributedClusterStateUpdaterTest.java
b/solr/core/src/test/org/apache/solr/cloud/DistributedClusterStateUpdaterTest.java
new file mode 100644
index 00000000000..39b44220064
--- /dev/null
+++
b/solr/core/src/test/org/apache/solr/cloud/DistributedClusterStateUpdaterTest.java
@@ -0,0 +1,229 @@
+/*
+ * 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.cloud;
+
+import java.util.Map;
+import org.apache.lucene.util.IOUtils;
+import org.apache.solr.SolrTestCase;
+import org.apache.solr.client.solrj.cloud.SolrCloudManager;
+import org.apache.solr.client.solrj.impl.CloudSolrClient;
+import org.apache.solr.client.solrj.impl.SolrClientCloudManager;
+import org.apache.solr.client.solrj.impl.ZkClientClusterStateProvider;
+import org.apache.solr.common.cloud.Replica;
+import org.apache.solr.common.cloud.SolrZkClient;
+import org.apache.solr.common.cloud.ZkNodeProps;
+import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.util.Compressor;
+import org.apache.solr.common.util.Utils;
+import org.apache.solr.common.util.ZLibCompressor;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+/**
+ * Verifies that {@link DistributedClusterStateUpdater} (used when the
Overseer is disabled)
+ * compresses {@code state.json} above the configured size threshold, the same
way {@link
+ * org.apache.solr.cloud.overseer.ZkStateWriter} does for the Overseer path.
See {@code
+ * ZkStateWriterTest#testSingleExternalCollectionCompressedState} for the
Overseer equivalent.
+ */
+public class DistributedClusterStateUpdaterTest extends SolrTestCase {
+
+ private static final int MIN_STATE_BYTE_LEN_FOR_COMPRESSION = 10_000;
+
+ private static ZkTestServer server;
+ private static SolrZkClient zkClient;
+
+ @BeforeClass
+ public static void startZkServer() throws Exception {
+ server = new
ZkTestServer(createTempDir("DistributedClusterStateUpdaterTest"));
+ server.run();
+ zkClient = new
SolrZkClient.Builder().withUrl(server.getZkAddress()).build();
+ ZkController.createClusterZkNodes(zkClient);
+ }
+
+ @AfterClass
+ public static void stopZkServer() throws Exception {
+ IOUtils.closeWhileHandlingException(zkClient);
+ if (server != null) {
+ server.shutdown();
+ }
+ }
+
+ public void testDistributedStateUpdateCompressesLargeStateJson() throws
Exception {
+ CloudSolrClient cloudSolrClient = null;
+
+ // default compressor impl
+ Compressor compressor = new ZLibCompressor();
+
+ try (ZkStateReader reader = new ZkStateReader(zkClient)) {
+ reader.createClusterStateWatchersAndUpdate();
+
+ cloudSolrClient =
+ new CloudSolrClient.Builder(new
ZkClientClusterStateProvider(reader)).build();
+ SolrCloudManager scm = new SolrClientCloudManager(cloudSolrClient, null);
+
+ DistributedClusterStateUpdater updater =
+ new DistributedClusterStateUpdater(true,
MIN_STATE_BYTE_LEN_FOR_COMPRESSION, compressor);
+
+ // small collection: state.json stays under the threshold and is not
compressed.
+ String smallCollection = "small";
+ zkClient.makePath(ZkStateReader.COLLECTIONS_ZKNODE + "/" +
smallCollection, true);
+ updater.doSingleStateUpdate(
+
DistributedClusterStateUpdater.MutatingCommand.ClusterCreateCollection,
+ new ZkNodeProps(CommonParams.NAME, smallCollection,
ZkStateReader.NUM_SHARDS_PROP, "1"),
+ scm,
+ reader);
+
+ byte[] smallStateJson =
+ zkClient
+ .getCuratorFramework()
+ .getData()
+ // make sure the Curator doesn't decompress it automatically
+ .undecompressed()
+ .forPath(ZkStateReader.COLLECTIONS_ZKNODE + "/" +
smallCollection + "/state.json");
+
+ assertFalse(
+ "small state.json should not be compressed",
+ compressor.isCompressedBytes(smallStateJson));
+ Map<?, ?> smallMap = (Map<?, ?>) Utils.fromJSON(smallStateJson);
+ assertNotNull(smallMap.get(smallCollection));
+
+ // large collection: enough replicas added in one batch to push
state.json past the
+ // compression threshold.
+ String bigCollection = "big";
+ zkClient.makePath(ZkStateReader.COLLECTIONS_ZKNODE + "/" +
bigCollection, true);
+ updater.doSingleStateUpdate(
+
DistributedClusterStateUpdater.MutatingCommand.ClusterCreateCollection,
+ new ZkNodeProps(CommonParams.NAME, bigCollection,
ZkStateReader.NUM_SHARDS_PROP, "1"),
+ scm,
+ reader);
+
+ DistributedClusterStateUpdater.StateChangeRecorder recorder =
+ updater.createStateChangeRecorder(bigCollection, false);
+ for (int i = 0; i < 300; i++) {
+ recorder.record(
+ DistributedClusterStateUpdater.MutatingCommand.SliceAddReplica,
+ new ZkNodeProps(
+ ZkStateReader.COLLECTION_PROP,
+ bigCollection,
+ ZkStateReader.SHARD_ID_PROP,
+ "shard1",
+ ZkStateReader.CORE_NODE_NAME_PROP,
+ "core_node" + i,
+ ZkStateReader.CORE_NAME_PROP,
+ "core_node" + i,
+ ZkStateReader.NODE_NAME_PROP,
+ "127.0.0.1:8983_solr",
+ ZkStateReader.STATE_PROP,
+ Replica.State.ACTIVE.toString(),
+ ZkStateReader.REPLICA_TYPE,
+ Replica.Type.NRT.toString()));
+ }
+ recorder.executeStateUpdates(scm, reader);
+
+ byte[] bigStateJsonRaw =
+ zkClient
+ .getCuratorFramework()
+ .getData()
+ .undecompressed()
+ .forPath(ZkStateReader.COLLECTIONS_ZKNODE + "/" + bigCollection
+ "/state.json");
+ assertTrue(
+ "big state.json should have been compressed by
DistributedClusterStateUpdater",
+ compressor.isCompressedBytes(bigStateJsonRaw));
+
+ Map<?, ?> bigMap = (Map<?, ?>)
Utils.fromJSON(compressor.decompressBytes(bigStateJsonRaw));
+ assertNotNull(bigMap.get(bigCollection));
+
+ // reading through the normal (automatically decompressing) path yields
the same, uncompressed
+ // JSON.
+ byte[] bigStateJsonDecoded =
+ zkClient.getData(
+ ZkStateReader.COLLECTIONS_ZKNODE + "/" + bigCollection +
"/state.json", null, null);
+ assertFalse(compressor.isCompressedBytes(bigStateJsonDecoded));
+ bigMap = (Map<?, ?>) Utils.fromJSON(bigStateJsonDecoded);
+ assertNotNull(bigMap.get(bigCollection));
+ } finally {
+ IOUtils.closeWhileHandlingException(cloudSolrClient);
+ }
+ }
+
+ public void testDistributedStateUpdateDisabledCompression() throws Exception
{
+ CloudSolrClient cloudSolrClient = null;
+
+ Compressor compressor = new ZLibCompressor();
+
+ try (ZkStateReader reader = new ZkStateReader(zkClient)) {
+ reader.createClusterStateWatchersAndUpdate();
+
+ cloudSolrClient =
+ new CloudSolrClient.Builder(new
ZkClientClusterStateProvider(reader)).build();
+ SolrCloudManager scm = new SolrClientCloudManager(cloudSolrClient, null);
+
+ // negative threshold: compression must never kick in, no matter the
state.json size.
+ DistributedClusterStateUpdater updater =
+ new DistributedClusterStateUpdater(true, -1, compressor);
+
+ String bigCollection = "bigNoCompression";
+ zkClient.makePath(ZkStateReader.COLLECTIONS_ZKNODE + "/" +
bigCollection, true);
+ updater.doSingleStateUpdate(
+
DistributedClusterStateUpdater.MutatingCommand.ClusterCreateCollection,
+ new ZkNodeProps(CommonParams.NAME, bigCollection,
ZkStateReader.NUM_SHARDS_PROP, "1"),
+ scm,
+ reader);
+
+ DistributedClusterStateUpdater.StateChangeRecorder recorder =
+ updater.createStateChangeRecorder(bigCollection, false);
+ for (int i = 0; i < 300; i++) {
+ recorder.record(
+ DistributedClusterStateUpdater.MutatingCommand.SliceAddReplica,
+ new ZkNodeProps(
+ ZkStateReader.COLLECTION_PROP,
+ bigCollection,
+ ZkStateReader.SHARD_ID_PROP,
+ "shard1",
+ ZkStateReader.CORE_NODE_NAME_PROP,
+ "core_node" + i,
+ ZkStateReader.CORE_NAME_PROP,
+ "core_node" + i,
+ ZkStateReader.NODE_NAME_PROP,
+ "127.0.0.1:8983_solr",
+ ZkStateReader.STATE_PROP,
+ Replica.State.ACTIVE.toString(),
+ ZkStateReader.REPLICA_TYPE,
+ Replica.Type.NRT.toString()));
+ }
+ recorder.executeStateUpdates(scm, reader);
+
+ byte[] bigStateJsonRaw =
+ zkClient
+ .getCuratorFramework()
+ .getData()
+ // make sure the Curator doesn't decompress it automatically
+ .undecompressed()
+ .forPath(ZkStateReader.COLLECTIONS_ZKNODE + "/" + bigCollection
+ "/state.json");
+ assertFalse(
+ "state.json must stay uncompressed when
minStateByteLenForCompression is negative,"
+ + " regardless of size",
+ compressor.isCompressedBytes(bigStateJsonRaw));
+
+ Map<?, ?> bigMap = (Map<?, ?>) Utils.fromJSON(bigStateJsonRaw);
+ assertNotNull(bigMap.get(bigCollection));
+ } finally {
+ IOUtils.closeWhileHandlingException(cloudSolrClient);
+ }
+ }
+}
diff --git a/solr/core/src/test/org/apache/solr/cloud/OverseerTest.java
b/solr/core/src/test/org/apache/solr/cloud/OverseerTest.java
index 15ae9733f7a..c29244590fe 100644
--- a/solr/core/src/test/org/apache/solr/cloud/OverseerTest.java
+++ b/solr/core/src/test/org/apache/solr/cloud/OverseerTest.java
@@ -1776,7 +1776,7 @@ public class OverseerTest extends SolrTestCaseJ4 {
when(zkController.getZkClient()).thenReturn(zkClient);
when(zkController.getZkStateReader()).thenReturn(reader);
when(zkController.getDistributedClusterStateUpdater())
- .thenReturn(new DistributedClusterStateUpdater(false));
+ .thenReturn(new DistributedClusterStateUpdater(false, -1, null));
// primitive support for CC.runAsync
doAnswer(
invocable -> {