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 2021/01/18 18:01:04 UTC

[lucene-solr] branch jira/solr-15055-2 updated: SOLR-15055: More unit tests + minor fixes.

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

ab pushed a commit to branch jira/solr-15055-2
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git


The following commit(s) were added to refs/heads/jira/solr-15055-2 by this push:
     new ecc50d1  SOLR-15055: More unit tests + minor fixes.
ecc50d1 is described below

commit ecc50d1c2b6d4638b6369cc6634450285fe28572
Author: Andrzej Bialecki <ab...@apache.org>
AuthorDate: Mon Jan 18 19:00:33 2021 +0100

    SOLR-15055: More unit tests + minor fixes.
---
 .../cloud/api/collections/DeleteCollectionCmd.java | 19 +++++
 .../cloud/api/collections/DeleteReplicaCmd.java    | 22 +++---
 .../placement/impl/ModificationRequestImpl.java    | 14 +++-
 .../impl/PlacementPluginIntegrationTest.java       | 85 +++++++++++++++++++++-
 4 files changed, 128 insertions(+), 12 deletions(-)

diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteCollectionCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteCollectionCmd.java
index d9b6679..7674577 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteCollectionCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteCollectionCmd.java
@@ -29,12 +29,18 @@ import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import org.apache.solr.cloud.Overseer;
+import org.apache.solr.cluster.placement.DeleteReplicasRequest;
+import org.apache.solr.cluster.placement.PlacementContext;
+import org.apache.solr.cluster.placement.PlacementPlugin;
+import org.apache.solr.cluster.placement.impl.ModificationRequestImpl;
+import org.apache.solr.cluster.placement.impl.PlacementContextImpl;
 import org.apache.solr.common.NonExistentCoreException;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.cloud.Aliases;
 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.cloud.Slice;
 import org.apache.solr.common.cloud.SolrZkClient;
 import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.cloud.ZkStateReader;
@@ -92,6 +98,19 @@ public class DeleteCollectionCmd implements OverseerCollectionMessageHandler.Cmd
       collection = extCollection;
     }
 
+    PlacementPlugin placementPlugin = ocmh.overseer.getCoreContainer().getPlacementPluginFactory().createPluginInstance();
+    if (placementPlugin != null) {
+      // verify the placement modifications caused by the deletion are allowed
+      PlacementContext placementContext = new PlacementContextImpl(ocmh.cloudManager);
+      DocCollection coll = state.getCollection(collection);
+      for (Slice slice : coll.getActiveSlices()) {
+        DeleteReplicasRequest deleteReplicasRequest = ModificationRequestImpl
+            .deleteReplicasRequest(coll, slice.getName(),
+                slice.getReplicas().stream().map(Replica::getName).collect(Collectors.toSet()));
+        placementPlugin.verifyAllowedModification(deleteReplicasRequest, placementContext);
+      }
+    }
+
     final boolean deleteHistory = message.getBool(CoreAdminParams.DELETE_METRICS_HISTORY, true);
 
     boolean removeCounterNode = true;
diff --git a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteReplicaCmd.java b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteReplicaCmd.java
index 93ababd..f5cf6db 100644
--- a/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteReplicaCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/api/collections/DeleteReplicaCmd.java
@@ -92,7 +92,7 @@ public class DeleteReplicaCmd implements Cmd {
         deleteReplicaBasedOnCount(clusterState, message, results, onComplete, parallel, placementPlugin, placementContext);
       } catch (PlacementException pe) {
         throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
-            "Delete replica(s) rejected by replica placement plugin: " + pe.getMessage(), pe);
+            "Delete replica(s) rejected by replica placement plugin: " + pe.toString(), pe);
       }
       return;
     }
@@ -122,7 +122,7 @@ public class DeleteReplicaCmd implements Cmd {
       deleteCore(coll, shard, replicaName, message, results, onComplete, parallel, placementPlugin, placementContext);
     } catch (PlacementException pe) {
       throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
-          "Delete replica rejected by replica placement plugin: " + pe.getMessage(), pe);
+          "Delete replica rejected by replica placement plugin: " + pe.toString(), pe);
     }
 
   }
@@ -170,14 +170,16 @@ public class DeleteReplicaCmd implements Cmd {
       }
     }
 
-    // verify that all replicas can be deleted
-    for (Map.Entry<Slice, Set<String>> entry : shardToReplicasMapping.entrySet()) {
-      Slice shardSlice = entry.getKey();
-      String shardId = shardSlice.getName();
-      Set<String> replicas = entry.getValue();
-      //verify all replicas
-      DeleteReplicasRequest deleteReplicasRequest = ModificationRequestImpl.deleteReplicasRequest(coll, shardId, replicas);
-      placementPlugin.verifyAllowedModification(deleteReplicasRequest, placementContext);
+    if (placementPlugin != null) {
+      // verify that all replicas can be deleted
+      for (Map.Entry<Slice, Set<String>> entry : shardToReplicasMapping.entrySet()) {
+        Slice shardSlice = entry.getKey();
+        String shardId = shardSlice.getName();
+        Set<String> replicas = entry.getValue();
+        //verify all replicas
+        DeleteReplicasRequest deleteReplicasRequest = ModificationRequestImpl.deleteReplicasRequest(coll, shardId, replicas);
+        placementPlugin.verifyAllowedModification(deleteReplicasRequest, placementContext);
+      }
     }
 
     for (Map.Entry<Slice, Set<String>> entry : shardToReplicasMapping.entrySet()) {
diff --git a/solr/core/src/java/org/apache/solr/cluster/placement/impl/ModificationRequestImpl.java b/solr/core/src/java/org/apache/solr/cluster/placement/impl/ModificationRequestImpl.java
index 8ae4935..7a62e28 100644
--- a/solr/core/src/java/org/apache/solr/cluster/placement/impl/ModificationRequestImpl.java
+++ b/solr/core/src/java/org/apache/solr/cluster/placement/impl/ModificationRequestImpl.java
@@ -29,10 +29,15 @@ import java.util.HashSet;
 import java.util.Set;
 
 /**
- *
+ * Helper class to create modification request instances.
  */
 public class ModificationRequestImpl {
 
+  /**
+   * Create a delete replicas request.
+   * @param collection collection to delete replicas from
+   * @param replicas replicas to delete
+   */
   public static DeleteReplicasRequest deleteReplicasRequest(SolrCollection collection, Set<Replica> replicas) {
     return new DeleteReplicasRequest() {
       @Override
@@ -53,6 +58,13 @@ public class ModificationRequestImpl {
     };
   }
 
+  /**
+   * Create a delete replicas request using the internal Solr API.
+   * @param docCollection Solr collection
+   * @param shardName shard name
+   * @param replicaNames replica names (aka. core-node names)
+   * @return
+   */
   public static DeleteReplicasRequest deleteReplicasRequest(DocCollection docCollection, String shardName, Set<String> replicaNames) {
     SolrCollection solrCollection = SimpleClusterAbstractionsImpl.SolrCollectionImpl.fromDocCollection(docCollection);
     Shard shard = solrCollection.getShard(shardName);
diff --git a/solr/core/src/test/org/apache/solr/cluster/placement/impl/PlacementPluginIntegrationTest.java b/solr/core/src/test/org/apache/solr/cluster/placement/impl/PlacementPluginIntegrationTest.java
index 6967550..e0dfc46 100644
--- a/solr/core/src/test/org/apache/solr/cluster/placement/impl/PlacementPluginIntegrationTest.java
+++ b/solr/core/src/test/org/apache/solr/cluster/placement/impl/PlacementPluginIntegrationTest.java
@@ -55,6 +55,7 @@ import org.slf4j.LoggerFactory;
 import java.lang.invoke.MethodHandles;
 import java.util.Arrays;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -71,7 +72,7 @@ import static java.util.Collections.singletonMap;
 public class PlacementPluginIntegrationTest extends SolrCloudTestCase {
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
-  private static final String COLLECTION = PlacementPluginIntegrationTest.class.getName() + "_collection";
+  private static final String COLLECTION = PlacementPluginIntegrationTest.class.getSimpleName() + "_collection";
 
   private static SolrCloudManager cloudManager;
   private static CoreContainer cc;
@@ -232,6 +233,88 @@ public class PlacementPluginIntegrationTest extends SolrCloudTestCase {
   }
 
   @Test
+  public void testWithCollectionIntegration() throws Exception {
+    PlacementPluginFactory<? extends PlacementPluginConfig> pluginFactory = cc.getPlacementPluginFactory();
+    assertTrue("wrong type " + pluginFactory.getClass().getName(), pluginFactory instanceof DelegatingPlacementPluginFactory);
+    DelegatingPlacementPluginFactory wrapper = (DelegatingPlacementPluginFactory) pluginFactory;
+
+    int version = wrapper.getVersion();
+    log.debug("--initial version={}", version);
+
+    Set<String> nodeSet = new HashSet<>();
+    for (String node : cloudManager.getClusterStateProvider().getLiveNodes()) {
+      if (nodeSet.size() > 1) {
+        break;
+      }
+      nodeSet.add(node);
+    }
+
+    String SECONDARY_COLLECTION = COLLECTION + "_secondary";
+    PluginMeta plugin = new PluginMeta();
+    plugin.name = PlacementPluginFactory.PLUGIN_NAME;
+    plugin.klass = AffinityPlacementFactory.class.getName();
+    plugin.config = new AffinityPlacementConfig(1, 2, Map.of(COLLECTION, SECONDARY_COLLECTION));
+    V2Request req = new V2Request.Builder("/cluster/plugin")
+        .forceV2(true)
+        .POST()
+        .withPayload(singletonMap("add", plugin))
+        .build();
+    req.process(cluster.getSolrClient());
+
+    version = waitForVersionChange(version, wrapper, 10);
+
+    CollectionAdminResponse rsp = CollectionAdminRequest.createCollection(SECONDARY_COLLECTION, "conf", 1, 3)
+        .process(cluster.getSolrClient());
+    assertTrue(rsp.isSuccess());
+    cluster.waitForActiveCollection(SECONDARY_COLLECTION, 1, 3);
+    DocCollection secondary = cloudManager.getClusterStateProvider().getClusterState().getCollection(SECONDARY_COLLECTION);
+    Set<String> secondaryNodes = new HashSet<>();
+    secondary.forEachReplica((shard, replica) -> secondaryNodes.add(replica.getNodeName()));
+
+    rsp = CollectionAdminRequest.createCollection(COLLECTION, "conf", 2, 2)
+        .setCreateNodeSet(String.join(",", nodeSet))
+        .process(cluster.getSolrClient());
+    assertTrue(rsp.isSuccess());
+    cluster.waitForActiveCollection(COLLECTION, 2, 4);
+    // make sure the primary replicas were placed on the nodeset
+    DocCollection primary = cloudManager.getClusterStateProvider().getClusterState().getCollection(COLLECTION);
+    primary.forEachReplica((shard, replica) ->
+        assertTrue("primary replica not on secondary node!", nodeSet.contains(replica.getNodeName())));
+
+    // try deleting secondary replica from node without the primary replica
+    Optional<String> onlySecondaryReplica = secondary.getReplicas().stream()
+        .filter(replica -> !nodeSet.contains(replica.getNodeName()))
+        .map(replica -> replica.getName()).findFirst();
+    assertTrue("no secondary node without primary replica", onlySecondaryReplica.isPresent());
+
+    rsp = CollectionAdminRequest.deleteReplica(SECONDARY_COLLECTION, "shard1", onlySecondaryReplica.get())
+        .process(cluster.getSolrClient());
+    assertTrue("delete of a lone secondary replica should succeed", rsp.isSuccess());
+
+    // try deleting secondary replica from node WITH the primary replica - should fail
+    Optional<String> secondaryWithPrimaryReplica = secondary.getReplicas().stream()
+        .filter(replica -> nodeSet.contains(replica.getNodeName()))
+        .map(replica -> replica.getName()).findFirst();
+    assertTrue("no secondary node with primary replica", secondaryWithPrimaryReplica.isPresent());
+    try {
+      rsp = CollectionAdminRequest.deleteReplica(SECONDARY_COLLECTION, "shard1", secondaryWithPrimaryReplica.get())
+          .process(cluster.getSolrClient());
+      fail("should have failed: " + rsp);
+    } catch (Exception e) {
+      assertTrue(e.toString(), e.toString().contains("co-located with replicas"));
+    }
+
+    // try deleting secondary collection
+    try {
+      rsp = CollectionAdminRequest.deleteCollection(SECONDARY_COLLECTION)
+          .process(cluster.getSolrClient());
+      fail("should have failed: " + rsp);
+    } catch (Exception e) {
+      assertTrue(e.toString(), e.toString().contains("delete replica(s) rejected"));
+    }
+  }
+
+  @Test
   public void testAttributeFetcherImpl() throws Exception {
     CollectionAdminResponse rsp = CollectionAdminRequest.createCollection(COLLECTION, "conf", 2, 2)
         .process(cluster.getSolrClient());