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

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

commit 6b2da97f8286ab4a8dc937187a5eee6ad11fb99d
Author: Boyi Dai <[email protected]>
AuthorDate: Wed Oct 21 17:39:15 2026 -0400

    SOLR-18538: CREATESNAPSHOT reports success when a shard has no active 
replica (#5114)
    
    Replace the per-shard failure counting with the check that the existing 
comment describes: after collecting the successful core snapshots, every active 
slice must have at least one replica that reported a successful snapshot.
    
    (cherry picked from commit 20eed9a06ab527bf938134c15deff24e92469668)
---
 .../SOLR-18538-snapshot-false-success.yml          | 12 ++++
 .../cloud/api/collections/CreateSnapshotCmd.java   | 35 ++++-----
 .../TestSolrCloudSnapshotInactiveShard.java        | 83 ++++++++++++++++++++++
 3 files changed, 109 insertions(+), 21 deletions(-)

diff --git a/changelog/unreleased/SOLR-18538-snapshot-false-success.yml 
b/changelog/unreleased/SOLR-18538-snapshot-false-success.yml
new file mode 100644
index 00000000000..48bca4b08cb
--- /dev/null
+++ b/changelog/unreleased/SOLR-18538-snapshot-false-success.yml
@@ -0,0 +1,12 @@
+# See https://github.com/apache/solr/blob/main/dev-docs/changelog.adoc
+
+title: >
+  CREATESNAPSHOT now fails instead of reporting success when a shard has no 
active replica with a
+  core snapshot
+type: fixed
+authors:
+  - name: Boyi Dai
+    nick: SpookyJumpyBeans
+links:
+  - name: SOLR-18538
+    url: https://issues.apache.org/jira/browse/SOLR-18538
diff --git 
a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateSnapshotCmd.java
 
b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateSnapshotCmd.java
index db4918f7759..2c901cea406 100644
--- 
a/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateSnapshotCmd.java
+++ 
b/solr/core/src/java/org/apache/solr/cloud/api/collections/CreateSnapshotCmd.java
@@ -99,8 +99,9 @@ public class CreateSnapshotCmd implements 
CollApiCmds.CollectionApiCommand {
 
     final ShardRequestTracker shardRequestTracker =
         CollectionHandlingUtils.asyncRequestTracker(adminCmdContext, ccc);
-    for (Slice slice :
-        
ccc.getZkStateReader().getClusterState().getCollection(collectionName).getSlices())
 {
+    DocCollection collection =
+        ccc.getZkStateReader().getClusterState().getCollection(collectionName);
+    for (Slice slice : collection.getActiveSlices()) {
       for (Replica replica : slice.getReplicas()) {
         if (replica.getState() != State.ACTIVE) {
           if (log.isInfoEnabled()) {
@@ -166,26 +167,18 @@ public class CreateSnapshotCmd implements 
CollApiCmds.CollectionApiCommand {
           "Unable to create a snapshot with name {} for following cores {}",
           commitName,
           shardByCoreName.keySet());
+    }
 
-      // Count number of failures per shard.
-      Map<String, Integer> failuresByShardId = new HashMap<>();
-      for (Map.Entry<String, Slice> entry : shardByCoreName.entrySet()) {
-        int f = 0;
-        if (failuresByShardId.get(entry.getValue().getName()) != null) {
-          f = failuresByShardId.get(entry.getValue().getName());
-        }
-        failuresByShardId.put(entry.getValue().getName(), f + 1);
-      }
-
-      // Now that we know number of failures per shard, we can figure out
-      // if at-least one replica per shard was able to create a snapshot or 
not.
-      DocCollection collectionStatus =
-          
ccc.getZkStateReader().getClusterState().getCollection(collectionName);
-      for (Map.Entry<String, Integer> entry : failuresByShardId.entrySet()) {
-        int replicaCount = 
collectionStatus.getSlice(entry.getKey()).getReplicas().size();
-        if (replicaCount <= entry.getValue()) {
-          failedShards.add(entry.getKey());
-        }
+    // A shard counts as captured only if at least one of its replicas 
reported a snapshot. Shards
+    // whose replicas were all skipped as inactive never received a request, 
so they must be
+    // checked against the collection state rather than against the requests 
that were sent.
+    Set<String> shardsWithSnapshot = new HashSet<>();
+    for (CoreSnapshotMetaData replica : replicas) {
+      shardsWithSnapshot.add(replica.getShardId());
+    }
+    for (Slice slice : collection.getActiveSlices()) {
+      if (!shardsWithSnapshot.contains(slice.getName())) {
+        failedShards.add(slice.getName());
       }
     }
 
diff --git 
a/solr/core/src/test/org/apache/solr/core/snapshots/TestSolrCloudSnapshotInactiveShard.java
 
b/solr/core/src/test/org/apache/solr/core/snapshots/TestSolrCloudSnapshotInactiveShard.java
new file mode 100644
index 00000000000..a1dc72b7bcd
--- /dev/null
+++ 
b/solr/core/src/test/org/apache/solr/core/snapshots/TestSolrCloudSnapshotInactiveShard.java
@@ -0,0 +1,83 @@
+/*
+ * 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.snapshots;
+
+import java.util.Collection;
+import org.apache.solr.client.solrj.impl.CloudSolrClient;
+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.cloud.Replica;
+import org.apache.solr.common.cloud.ZkStateReader;
+import org.apache.solr.embedded.JettySolrRunner;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * A collection snapshot must not be recorded as successful when a shard has 
no active replica to
+ * take a core snapshot.
+ */
+public class TestSolrCloudSnapshotInactiveShard extends SolrCloudTestCase {
+
+  @BeforeClass
+  public static void setupClass() throws Exception {
+    useFactory("solr.StandardDirectoryFactory");
+    configureCluster(2)
+        .addConfig(
+            "conf1", 
TEST_PATH().resolve("configsets").resolve("cloud-minimal").resolve("conf"))
+        .configure();
+  }
+
+  @Test
+  public void testSnapshotFailsWhenShardHasNoActiveReplica() throws Exception {
+    CloudSolrClient solrClient = cluster.getSolrClient();
+    String collectionName = "snapshotInactiveShard";
+    JettySolrRunner hostingNode = cluster.getJettySolrRunner(1);
+
+    CollectionAdminRequest.createCollection(collectionName, "conf1", 1, 1)
+        .setCreateNodeSet(hostingNode.getNodeName())
+        .process(solrClient);
+    cluster.waitForActiveCollection(collectionName, 1, 1);
+
+    cluster.stopJettySolrRunner(hostingNode);
+    cluster.waitForJettyToStop(hostingNode);
+    waitForState(
+        "the only replica should not be active",
+        collectionName,
+        (liveNodes, collection) ->
+            collection != null
+                && collection.replicaStream().noneMatch(r -> r.getState() == 
Replica.State.ACTIVE));
+
+    String commitName = "snap";
+    SolrException e =
+        expectThrows(
+            SolrException.class,
+            () ->
+                new CollectionAdminRequest.CreateSnapshot(collectionName, 
commitName)
+                    .process(solrClient));
+    assertTrue(e.getMessage(), e.getMessage().contains("Failed to create 
snapshot on shards"));
+
+    Collection<CollectionSnapshotMetaData> snapshots =
+        SolrSnapshotManager.listSnapshots(
+            ZkStateReader.from(solrClient).getZkClient(), collectionName);
+    assertEquals(1, snapshots.size());
+    CollectionSnapshotMetaData meta = snapshots.iterator().next();
+    assertEquals(commitName, meta.getName());
+    assertEquals(CollectionSnapshotMetaData.SnapshotStatus.Failed, 
meta.getStatus());
+    assertTrue(meta.getReplicaSnapshots().isEmpty());
+  }
+}

Reply via email to