You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ab...@apache.org on 2019/09/18 17:19:07 UTC
[lucene-solr] branch master updated: SOLR-13763: Improve the
tracking of "freedisk" in autoscaling simulations.
This is an automated email from the ASF dual-hosted git repository.
ab pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git
The following commit(s) were added to refs/heads/master by this push:
new 6a8cfdd SOLR-13763: Improve the tracking of "freedisk" in autoscaling simulations.
6a8cfdd is described below
commit 6a8cfddf305718b21d64eebb144d18b330dfdb24
Author: Andrzej Bialecki <ab...@apache.org>
AuthorDate: Wed Sep 18 19:18:47 2019 +0200
SOLR-13763: Improve the tracking of "freedisk" in autoscaling simulations.
---
solr/CHANGES.txt | 2 +
.../cloud/autoscaling/sim/SimCloudManager.java | 6 +-
.../autoscaling/sim/SimClusterStateProvider.java | 106 +++++++++++++++++----
.../autoscaling/sim/SimNodeStateProvider.java | 18 ++++
.../cloud/autoscaling/sim/FakeDocIterator.java | 56 +++++++++++
.../autoscaling/sim/TestSimExtremeIndexing.java | 35 -------
.../cloud/autoscaling/sim/TestSimLargeCluster.java | 87 +++++++++++++++++
7 files changed, 253 insertions(+), 57 deletions(-)
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index 22f334f..55c1eb5 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -161,6 +161,8 @@ Improvements
* SOLR-9658: Max idle time support for SolrCache implementations. (hoss, ab)
+* SOLR-13763: Improve the tracking of "freedisk" in autoscaling simulations. (ab)
+
Bug Fixes
----------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/sim/SimCloudManager.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/sim/SimCloudManager.java
index 6e27599..a029ac3 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/sim/SimCloudManager.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/sim/SimCloudManager.java
@@ -155,7 +155,7 @@ public class SimCloudManager implements SolrCloudManager {
private boolean useSystemCollection = true;
private static int nodeIdPort = 10000;
- public static int DEFAULT_FREE_DISK = 1024; // 1000 GiB
+ public static int DEFAULT_FREE_DISK = 10240; // 10 TiB
public static int DEFAULT_TOTAL_DISK = 10240; // 10 TiB
public static long DEFAULT_IDX_SIZE_BYTES = 10240; // 10 kiB
@@ -382,6 +382,10 @@ public class SimCloudManager implements SolrCloudManager {
return values;
}
+ public void disableMetricsHistory() {
+ metricsHistoryHandler.close();
+ }
+
public String dumpClusterState(boolean withCollections) throws Exception {
StringBuilder sb = new StringBuilder();
sb.append("#######################################\n");
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/sim/SimClusterStateProvider.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/sim/SimClusterStateProvider.java
index a15e4a5..2e5be68 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/sim/SimClusterStateProvider.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/sim/SimClusterStateProvider.java
@@ -128,7 +128,7 @@ import static org.apache.solr.common.params.CommonParams.NAME;
public class SimClusterStateProvider implements ClusterStateProvider {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
- public static final long DEFAULT_DOC_SIZE_BYTES = 500;
+ public static final long DEFAULT_DOC_SIZE_BYTES = 2048;
private static final String BUFFERED_UPDATES = "__buffered_updates__";
@@ -1527,17 +1527,18 @@ public class SimClusterStateProvider implements ClusterStateProvider {
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Collection not set");
}
ensureSystemCollection(collection);
-
DocCollection coll = getClusterState().getCollection(collection);
DocRouter router = coll.getRouter();
List<String> deletes = req.getDeleteById();
+ Map<String, AtomicLong> freediskDeltaPerNode = new HashMap<>();
if (deletes != null && !deletes.isEmpty()) {
+ Map<String, AtomicLong> deletesPerShard = new HashMap<>();
+ Map<String, Number> indexSizePerShard = new HashMap<>();
for (String id : deletes) {
Slice s = router.getTargetSlice(id, null, null, req.getParams(), coll);
Replica leader = s.getLeader();
if (leader == null) {
- log.debug("-- no leader in " + s);
- continue;
+ throw new IOException("-- no leader in " + s);
}
cloudManager.getMetricManager().registry(createRegistryName(collection, s.getName(), leader)).counter("UPDATE./update.requests").inc();
ReplicaInfo ri = getReplicaInfo(leader);
@@ -1546,6 +1547,13 @@ public class SimClusterStateProvider implements ClusterStateProvider {
log.debug("-- attempting to delete nonexistent doc " + id + " from " + s.getLeader());
continue;
}
+
+ // this is somewhat wrong - we should wait until buffered updates are applied
+ // but this way the freedisk changes are much easier to track
+ s.getReplicas().forEach(r ->
+ freediskDeltaPerNode.computeIfAbsent(r.getNodeName(), node -> new AtomicLong(0))
+ .addAndGet(DEFAULT_DOC_SIZE_BYTES));
+
AtomicLong bufferedUpdates = (AtomicLong)sliceProperties.get(collection).get(s.getName()).get(BUFFERED_UPDATES);
if (bufferedUpdates != null) {
if (bufferedUpdates.get() > 0) {
@@ -1555,19 +1563,33 @@ public class SimClusterStateProvider implements ClusterStateProvider {
}
continue;
}
+ deletesPerShard.computeIfAbsent(s.getName(), slice -> new AtomicLong(0)).incrementAndGet();
+ Number indexSize = (Number)ri.getVariable(Type.CORE_IDX.metricsAttribute);
+ if (indexSize != null) {
+ indexSizePerShard.put(s.getName(), indexSize);
+ }
+ }
+ if (!deletesPerShard.isEmpty()) {
lock.lockInterruptibly();
try {
- simSetShardValue(collection, s.getName(), "SEARCHER.searcher.deletedDocs", 1, true, false);
- simSetShardValue(collection, s.getName(), "SEARCHER.searcher.numDocs", -1, true, false);
- Number indexSize = (Number)ri.getVariable(Type.CORE_IDX.metricsAttribute);
- if (indexSize != null && indexSize.longValue() > SimCloudManager.DEFAULT_IDX_SIZE_BYTES) {
- indexSize = indexSize.longValue() - DEFAULT_DOC_SIZE_BYTES;
- simSetShardValue(collection, s.getName(), Type.CORE_IDX.metricsAttribute,
- new AtomicLong(indexSize.longValue()), false, false);
- simSetShardValue(collection, s.getName(), Variable.coreidxsize,
- new AtomicDouble((Double)Type.CORE_IDX.convertVal(indexSize)), false, false);
- } else {
- throw new Exception("unexpected indexSize ri=" + ri);
+ for (Map.Entry<String, AtomicLong> entry : deletesPerShard.entrySet()) {
+ String shard = entry.getKey();
+ simSetShardValue(collection, shard, "SEARCHER.searcher.deletedDocs", entry.getValue().get(), true, false);
+ simSetShardValue(collection, shard, "SEARCHER.searcher.numDocs", -entry.getValue().get(), true, false);
+ Number indexSize = indexSizePerShard.get(shard);
+ long delSize = DEFAULT_DOC_SIZE_BYTES * entry.getValue().get();
+ if (indexSize != null) {
+ indexSize = indexSize.longValue() - delSize;
+ if (indexSize.longValue() < SimCloudManager.DEFAULT_IDX_SIZE_BYTES) {
+ indexSize = SimCloudManager.DEFAULT_IDX_SIZE_BYTES;
+ }
+ simSetShardValue(collection, shard, Type.CORE_IDX.metricsAttribute,
+ new AtomicLong(indexSize.longValue()), false, false);
+ simSetShardValue(collection, shard, Variable.coreidxsize,
+ new AtomicDouble((Double)Type.CORE_IDX.convertVal(indexSize)), false, false);
+ } else {
+ throw new Exception("unexpected indexSize for collection=" + collection + ", shard=" + shard + ": " + indexSize);
+ }
}
} catch (Exception e) {
throw new IOException(e);
@@ -1582,11 +1604,11 @@ public class SimClusterStateProvider implements ClusterStateProvider {
if (!"*:*".equals(q)) {
throw new UnsupportedOperationException("Only '*:*' query is supported in deleteByQuery");
}
+ //log.debug("-- req delByQ " + collection);
for (Slice s : coll.getSlices()) {
Replica leader = s.getLeader();
if (leader == null) {
- log.debug("-- no leader in " + s);
- continue;
+ throw new IOException("-- no leader in " + s);
}
cloudManager.getMetricManager().registry(createRegistryName(collection, s.getName(), leader)).counter("UPDATE./update.requests").inc();
@@ -1597,6 +1619,16 @@ public class SimClusterStateProvider implements ClusterStateProvider {
}
lock.lockInterruptibly();
try {
+ Number indexSize = (Number)ri.getVariable(Type.CORE_IDX.metricsAttribute);
+ if (indexSize != null) {
+ long delta = indexSize.longValue() < SimCloudManager.DEFAULT_IDX_SIZE_BYTES ? 0 :
+ indexSize.longValue() - SimCloudManager.DEFAULT_IDX_SIZE_BYTES;
+ s.getReplicas().forEach(r ->
+ freediskDeltaPerNode.computeIfAbsent(r.getNodeName(), node -> new AtomicLong(0))
+ .addAndGet(delta));
+ } else {
+ throw new RuntimeException("Missing index size in " + ri);
+ }
simSetShardValue(collection, s.getName(), "SEARCHER.searcher.deletedDocs", new AtomicLong(numDocs.longValue()), false, false);
simSetShardValue(collection, s.getName(), "SEARCHER.searcher.numDocs", new AtomicLong(0), false, false);
simSetShardValue(collection, s.getName(), Type.CORE_IDX.metricsAttribute,
@@ -1626,6 +1658,7 @@ public class SimClusterStateProvider implements ClusterStateProvider {
}
}
if (docCount > 0) {
+ //log.debug("-- req update " + collection + " / " + docCount);
// this approach to updating counters and metrics drastically increases performance
// of bulk updates, because simSetShardValue is relatively costly
@@ -1672,13 +1705,16 @@ public class SimClusterStateProvider implements ClusterStateProvider {
Slice s = slices[i];
Replica leader = s.getLeader();
if (leader == null) {
- log.debug("-- no leader in " + s);
- continue;
+ throw new IOException("-- no leader in " + s);
}
metricUpdates.computeIfAbsent(s.getName(), sh -> new HashMap<>())
.computeIfAbsent(leader.getCoreName(), cn -> new AtomicLong())
.addAndGet(perSlice[i]);
modified = true;
+ long perSliceCount = perSlice[i];
+ s.getReplicas().forEach(r ->
+ freediskDeltaPerNode.computeIfAbsent(r.getNodeName(), node -> new AtomicLong(0))
+ .addAndGet(-perSliceCount * DEFAULT_DOC_SIZE_BYTES));
AtomicLong bufferedUpdates = (AtomicLong)sliceProperties.get(collection).get(s.getName()).get(BUFFERED_UPDATES);
if (bufferedUpdates != null) {
bufferedUpdates.addAndGet(perSlice[i]);
@@ -1697,13 +1733,15 @@ public class SimClusterStateProvider implements ClusterStateProvider {
Slice s = coll.getRouter().getTargetSlice(id, doc, null, null, coll);
Replica leader = s.getLeader();
if (leader == null) {
- log.debug("-- no leader in " + s);
- continue;
+ throw new IOException("-- no leader in " + s);
}
metricUpdates.computeIfAbsent(s.getName(), sh -> new HashMap<>())
.computeIfAbsent(leader.getCoreName(), cn -> new AtomicLong())
.incrementAndGet();
modified = true;
+ s.getReplicas().forEach(r ->
+ freediskDeltaPerNode.computeIfAbsent(r.getNodeName(), node -> new AtomicLong())
+ .addAndGet(-DEFAULT_DOC_SIZE_BYTES));
AtomicLong bufferedUpdates = (AtomicLong)sliceProperties.get(collection).get(s.getName()).get(BUFFERED_UPDATES);
if (bufferedUpdates != null) {
bufferedUpdates.incrementAndGet();
@@ -1741,6 +1779,32 @@ public class SimClusterStateProvider implements ClusterStateProvider {
lock.unlock();
}
}
+ if (!freediskDeltaPerNode.isEmpty()) {
+ SimNodeStateProvider nodeStateProvider = cloudManager.getSimNodeStateProvider();
+ freediskDeltaPerNode.forEach((node, delta) -> {
+ if (delta.get() == 0) {
+ return;
+ }
+ try {
+ // this method does its own locking to prevent races
+ nodeStateProvider.simUpdateNodeValue(node, Type.FREEDISK.tagName, val -> {
+ if (val == null) {
+ throw new RuntimeException("no freedisk for node " + node);
+ }
+ double freedisk = ((Number) val).doubleValue();
+ double deltaGB = (Double) Type.FREEDISK.convertVal(delta.get());
+ freedisk += deltaGB;
+ if (freedisk < 0) {
+ log.warn("-- freedisk=" + freedisk + " - ran out of disk space on node " + node);
+ freedisk = 0;
+ }
+ return freedisk;
+ });
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ });
+ }
SolrParams params = req.getParams();
if (params != null && (params.getBool(UpdateParams.OPTIMIZE, false) || params.getBool(UpdateParams.EXPUNGE_DELETES, false))) {
lock.lockInterruptibly();
diff --git a/solr/core/src/java/org/apache/solr/cloud/autoscaling/sim/SimNodeStateProvider.java b/solr/core/src/java/org/apache/solr/cloud/autoscaling/sim/SimNodeStateProvider.java
index 9a5656e..e1df6fd 100644
--- a/solr/core/src/java/org/apache/solr/cloud/autoscaling/sim/SimNodeStateProvider.java
+++ b/solr/core/src/java/org/apache/solr/cloud/autoscaling/sim/SimNodeStateProvider.java
@@ -28,6 +28,7 @@ import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -81,6 +82,23 @@ public class SimNodeStateProvider implements NodeStateProvider {
}
/**
+ * Atomically update a node value.
+ * @param node node id
+ * @param key property name
+ * @param updater updater function
+ * @return previous property value or null if property or node didn't exist.
+ */
+ public Object simUpdateNodeValue(String node, String key, Function<Object, Object> updater) throws InterruptedException {
+ lock.lockInterruptibly();
+ try {
+ Map<String, Object> values = nodeValues.computeIfAbsent(node, n -> new ConcurrentHashMap<>());
+ return values.put(key, updater.apply(values.get(key)));
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ /**
* Set node values.
* NOTE: if values contain 'nodeRole' key then /roles.json is updated.
* @param node node id
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/FakeDocIterator.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/FakeDocIterator.java
new file mode 100644
index 0000000..fbe66ac
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/FakeDocIterator.java
@@ -0,0 +1,56 @@
+/*
+ * 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.autoscaling.sim;
+
+import java.util.Iterator;
+
+import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.SolrInputField;
+
+/**
+ * Lightweight generator of fake documents
+ * NOTE: this iterator only ever returns the same document N times, which works ok
+ * for our "bulk index update" simulation. Obviously don't use this for real indexing.
+ */
+public class FakeDocIterator implements Iterator<SolrInputDocument> {
+ final SolrInputDocument doc = new SolrInputDocument();
+ final SolrInputField idField = new SolrInputField("id");
+
+ final long start, count;
+
+ long current, max;
+
+ FakeDocIterator(long start, long count) {
+ this.start = start;
+ this.count = count;
+ current = start;
+ max = start + count;
+ doc.put("id", idField);
+ idField.setValue("foo");
+ }
+
+ @Override
+ public boolean hasNext() {
+ return current < max;
+ }
+
+ @Override
+ public SolrInputDocument next() {
+ current++;
+ return doc;
+ }
+}
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimExtremeIndexing.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimExtremeIndexing.java
index 654c29f..3ad4f72 100644
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimExtremeIndexing.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimExtremeIndexing.java
@@ -17,7 +17,6 @@
package org.apache.solr.cloud.autoscaling.sim;
import java.lang.invoke.MethodHandles;
-import java.util.Iterator;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
@@ -30,8 +29,6 @@ import org.apache.solr.cloud.CloudTestUtils.AutoScalingRequest;
import org.apache.solr.cloud.CloudUtil;
import org.apache.solr.cloud.autoscaling.ExecutePlanAction;
import org.apache.solr.common.SolrDocumentList;
-import org.apache.solr.common.SolrInputDocument;
-import org.apache.solr.common.SolrInputField;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.TimeSource;
@@ -144,36 +141,4 @@ public class TestSimExtremeIndexing extends SimSolrCloudTestCase {
solrClient.request(ureq);
}
- // lightweight generator of fake documents
- // NOTE: this iterator only ever returns the same document, which works ok
- // for our "index update" simulation. Obviously don't use this for real indexing.
- private static class FakeDocIterator implements Iterator<SolrInputDocument> {
- final SolrInputDocument doc = new SolrInputDocument();
- final SolrInputField idField = new SolrInputField("id");
-
- final long start, count;
-
- long current, max;
-
- FakeDocIterator(long start, long count) {
- this.start = start;
- this.count = count;
- current = start;
- max = start + count;
- doc.put("id", idField);
- idField.setValue("foo");
- }
-
- @Override
- public boolean hasNext() {
- return current < max;
- }
-
- @Override
- public SolrInputDocument next() {
- current++;
- return doc;
- }
- }
-
}
diff --git a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimLargeCluster.java b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimLargeCluster.java
index 1cf4f0b..adf2e67 100644
--- a/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimLargeCluster.java
+++ b/solr/core/src/test/org/apache/solr/cloud/autoscaling/sim/TestSimLargeCluster.java
@@ -19,7 +19,10 @@ package org.apache.solr.cloud.autoscaling.sim;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
@@ -29,6 +32,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
import org.apache.lucene.util.TestUtil;
@@ -36,7 +40,9 @@ import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.cloud.autoscaling.Suggester;
import org.apache.solr.client.solrj.cloud.autoscaling.TriggerEventProcessorStage;
import org.apache.solr.client.solrj.cloud.autoscaling.TriggerEventType;
+import org.apache.solr.client.solrj.cloud.autoscaling.Variable;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
+import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.cloud.CloudTestUtils;
import org.apache.solr.cloud.CloudUtil;
import org.apache.solr.cloud.autoscaling.ActionContext;
@@ -48,10 +54,13 @@ import org.apache.solr.cloud.autoscaling.TriggerActionBase;
import org.apache.solr.cloud.autoscaling.TriggerEvent;
import org.apache.solr.cloud.autoscaling.TriggerListenerBase;
import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.cloud.ClusterState;
+import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.params.CollectionParams;
import org.apache.solr.common.util.Pair;
import org.apache.solr.common.util.TimeSource;
+import org.apache.solr.common.util.Utils;
import org.apache.solr.util.LogLevel;
import org.apache.solr.util.TimeOut;
import org.junit.After;
@@ -88,6 +97,9 @@ public class TestSimLargeCluster extends SimSolrCloudTestCase {
public void setupTest() throws Exception {
configureCluster(NUM_NODES, TimeSource.get("simTime:" + SPEED));
+ // disable metrics history collection
+ cluster.disableMetricsHistory();
+
// disable .scheduled_maintenance (once it exists)
CloudTestUtils.waitForTriggerToBeScheduled(cluster, ".scheduled_maintenance");
CloudTestUtils.suspendTrigger(cluster, ".scheduled_maintenance");
@@ -752,4 +764,79 @@ public class TestSimLargeCluster extends SimSolrCloudTestCase {
assertEquals("shard1", hint.second());
});
}
+
+ @Test
+ public void testFreediskTracking() throws Exception {
+ int NUM_DOCS = 100000;
+ String collectionName = "testFreeDisk";
+ SolrClient solrClient = cluster.simGetSolrClient();
+ CollectionAdminRequest.Create create = CollectionAdminRequest.createCollection(collectionName,
+ "conf",2, 2);
+ create.process(solrClient);
+
+ CloudUtil.waitForState(cluster, "Timed out waiting for replicas of new collection to be active",
+ collectionName, CloudUtil.clusterShape(2, 2, false, true));
+ ClusterState clusterState = cluster.getClusterStateProvider().getClusterState();
+ DocCollection coll = clusterState.getCollection(collectionName);
+ Set<String> nodes = coll.getReplicas().stream()
+ .map(r -> r.getNodeName())
+ .collect(Collectors.toSet());
+ Map<String, Number> initialFreedisk = getFreeDiskPerNode(nodes);
+
+ // test small updates
+ for (int i = 0; i < NUM_DOCS; i++) {
+ SolrInputDocument doc = new SolrInputDocument("id", "id-" + i);
+ solrClient.add(collectionName, doc);
+ }
+ Map<String, Number> updatedFreedisk = getFreeDiskPerNode(nodes);
+ double delta = getDeltaFreeDiskBytes(initialFreedisk, updatedFreedisk);
+ // 2 replicas - twice as much delta
+ assertEquals(SimClusterStateProvider.DEFAULT_DOC_SIZE_BYTES * NUM_DOCS * 2, delta, delta * 0.1);
+
+ // test small deletes - delete half of docs
+ for (int i = 0; i < NUM_DOCS / 2; i++) {
+ solrClient.deleteById(collectionName, "id-" + i);
+ }
+ Map<String, Number> updatedFreedisk1 = getFreeDiskPerNode(nodes);
+ double delta1 = getDeltaFreeDiskBytes(initialFreedisk, updatedFreedisk1);
+ // 2 replicas but half the docs
+ assertEquals(SimClusterStateProvider.DEFAULT_DOC_SIZE_BYTES * NUM_DOCS * 2 / 2, delta1, delta1 * 0.1);
+
+ // test bulk delete
+ solrClient.deleteByQuery(collectionName, "*:*");
+ Map<String, Number> updatedFreedisk2 = getFreeDiskPerNode(nodes);
+ double delta2 = getDeltaFreeDiskBytes(initialFreedisk, updatedFreedisk2);
+ // 0 docs - initial freedisk
+ log.info(cluster.dumpClusterState(true));
+ assertEquals(0.0, delta2, delta2 * 0.1);
+
+ // test bulk update
+ UpdateRequest ureq = new UpdateRequest();
+ ureq.setDocIterator(new FakeDocIterator(0, NUM_DOCS));
+ ureq.process(solrClient, collectionName);
+ Map<String, Number> updatedFreedisk3 = getFreeDiskPerNode(nodes);
+ double delta3 = getDeltaFreeDiskBytes(initialFreedisk, updatedFreedisk3);
+ assertEquals(SimClusterStateProvider.DEFAULT_DOC_SIZE_BYTES * NUM_DOCS * 2, delta3, delta3 * 0.1);
+ }
+
+ private double getDeltaFreeDiskBytes(Map<String, Number> initial, Map<String, Number> updated) {
+ double deltaGB = 0;
+ for (String node : initial.keySet()) {
+ double before = initial.get(node).doubleValue();
+ double after = updated.get(node).doubleValue();
+ assertTrue("freedisk after=" + after + " not smaller than before=" + before, after <= before);
+ deltaGB += before - after;
+ }
+ return deltaGB * 1024.0 * 1024.0 * 1024.0;
+ }
+
+ private Map<String, Number> getFreeDiskPerNode(Collection<String> nodes) throws Exception {
+ Map<String, Number> freediskPerNode = new HashMap<>();
+ for (String node : nodes) {
+ Map<String, Object> values = cluster.getNodeStateProvider().getNodeValues(node, Arrays.asList(Variable.Type.FREEDISK.tagName));
+ freediskPerNode.put(node, (Number) values.get(Variable.Type.FREEDISK.tagName));
+ }
+ log.info("- freeDiskPerNode: " + Utils.toJSONString(freediskPerNode));
+ return freediskPerNode;
+ }
}