<commit_before>package org.apache.solr.cloud;

/*
 * 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.
 */

import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.request.CoreAdminRequest;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.response.CoreAdminResponse;
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.Slice;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.MapSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.apache.solr.cloud.OverseerCollectionProcessor.NUM_SLICES;
import static org.apache.solr.cloud.OverseerCollectionProcessor.ONLY_IF_DOWN;
import static org.apache.solr.common.cloud.ZkNodeProps.makeMap;
import static org.apache.solr.common.cloud.ZkStateReader.MAX_SHARDS_PER_NODE;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETEREPLICA;

public class DeleteReplicaTest extends AbstractFullDistribZkTestBase {
  private CloudSolrClient client;
  
  @Override
  public void distribSetUp() throws Exception {
    super.distribSetUp();
    System.setProperty("numShards", Integer.toString(sliceCount));
    System.setProperty("solr.xml.persist", "true");
    client = createCloudClient(null);
  }

  @Override
  public void distribTearDown() throws Exception {
    super.distribTearDown();
    client.close();
  }

  protected String getSolrXml() {
    return "solr-no-core.xml";
  }

  public DeleteReplicaTest() {
    sliceCount = 2;
    checkCreatedVsState = false;
  }

  @Test
  @ShardsFixed(num = 4)
  public void deleteLiveReplicaTest() throws Exception {
    String collectionName = "delLiveColl";
    try (CloudSolrClient client = createCloudClient(null)) {
      createCollection(collectionName, client);
      
      waitForRecoveriesToFinish(collectionName, false);
      
      DocCollection testcoll = getCommonCloudSolrClient().getZkStateReader()
          .getClusterState().getCollection(collectionName);
      
      Slice shard1 = null;
      Replica replica1 = null;
      
      // Get an active replica
      for (Slice slice : testcoll.getSlices()) {
        if(replica1 != null)
          break;
        if ("active".equals(slice.getStr("state"))) {
          shard1 = slice;
          for (Replica replica : shard1.getReplicas()) {
            if ("active".equals(replica.getStr("state"))) {
              replica1 = replica;
              break;
            }
          }
        }
      }

      if (replica1 == null) fail("no active replicas found");

      String dataDir = null;
      try (HttpSolrClient replica1Client = new HttpSolrClient(replica1.getStr("base_url"))) {
        CoreAdminResponse status = CoreAdminRequest.getStatus(replica1.getStr("core"), replica1Client);
        NamedList<Object> coreStatus = status.getCoreStatus(replica1.getStr("core"));
        dataDir = (String) coreStatus.get("dataDir");
      }
      try {
        // Should not be able to delete a replica that is up if onlyIfDown=true.
        tryToRemoveOnlyIfDown(collectionName, client, replica1, shard1.getName());
        fail("Should have thrown an exception here because the replica is NOT down");
      } catch (SolrException se) {
        assertEquals("Should see 400 here ", se.code(), 400);
        assertTrue("Should have had a good message here", se.getMessage().contains("with onlyIfDown='true', but state is 'active'"));
        // This bit is a little weak in that if we're screwing up and actually deleting the replica, we might get back
        // here _before_ the datadir is deleted. But I'd rather not introduce a delay here.
        assertTrue("dataDir for " + replica1.getName() + " should NOT have been deleted by deleteReplica API with onlyIfDown='true'",
            new File(dataDir).exists());
      }

      removeAndWaitForReplicaGone(collectionName, client, replica1, shard1.getName());
      assertFalse("dataDir for " + replica1.getName() + " should have been deleted by deleteReplica API", new File(dataDir).exists());
    }
  }

  protected void tryToRemoveOnlyIfDown(String collectionName, CloudSolrClient client, Replica replica, String shard) throws IOException, SolrServerException {
    Map m = makeMap("collection", collectionName,
        "action", DELETEREPLICA.toLower(),
        "shard", shard,
        "replica", replica.getName(),
        ONLY_IF_DOWN, "true");
    SolrParams params = new MapSolrParams(m);
    SolrRequest request = new QueryRequest(params);
    request.setPath("/admin/collections");
    client.request(request);
  }

  static void removeAndWaitForReplicaGone(String COLL_NAME,
      CloudSolrClient client, Replica replica, String shard)
      throws SolrServerException, IOException, InterruptedException {
    Map m = makeMap("collection", COLL_NAME, "action", DELETEREPLICA.toLower(), "shard",
        shard, "replica", replica.getName());
    SolrParams params = new MapSolrParams(m);
    SolrRequest request = new QueryRequest(params);<commit_msg>SOLR-6956: A better message for DeleteReplicaTest failures<commit_after>package org.apache.solr.cloud;

/*
 * 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.
 */

import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.request.CoreAdminRequest;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.response.CoreAdminResponse;
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.Slice;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.MapSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.junit.BeforeClass;
import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.apache.solr.cloud.OverseerCollectionProcessor.NUM_SLICES;
import static org.apache.solr.cloud.OverseerCollectionProcessor.ONLY_IF_DOWN;
import static org.apache.solr.common.cloud.ZkNodeProps.makeMap;
import static org.apache.solr.common.cloud.ZkStateReader.MAX_SHARDS_PER_NODE;
import static org.apache.solr.common.params.CollectionParams.CollectionAction.DELETEREPLICA;

public class DeleteReplicaTest extends AbstractFullDistribZkTestBase {
  private CloudSolrClient client;
  
  @Override
  public void distribSetUp() throws Exception {
    super.distribSetUp();
    System.setProperty("numShards", Integer.toString(sliceCount));
    System.setProperty("solr.xml.persist", "true");
    client = createCloudClient(null);
  }

  @Override
  public void distribTearDown() throws Exception {
    super.distribTearDown();
    client.close();
  }

  protected String getSolrXml() {
    return "solr-no-core.xml";
  }

  public DeleteReplicaTest() {
    sliceCount = 2;
    checkCreatedVsState = false;
  }

  @Test
  @ShardsFixed(num = 4)
  public void deleteLiveReplicaTest() throws Exception {
    String collectionName = "delLiveColl";
    try (CloudSolrClient client = createCloudClient(null)) {
      createCollection(collectionName, client);
      
      waitForRecoveriesToFinish(collectionName, false);
      
      DocCollection testcoll = getCommonCloudSolrClient().getZkStateReader()
          .getClusterState().getCollection(collectionName);
      
      Slice shard1 = null;
      Replica replica1 = null;
      
      // Get an active replica
      for (Slice slice : testcoll.getSlices()) {
        if(replica1 != null)
          break;
        if ("active".equals(slice.getStr("state"))) {
          shard1 = slice;
          for (Replica replica : shard1.getReplicas()) {
            if ("active".equals(replica.getStr("state"))) {
              replica1 = replica;
              break;
            }
          }
        }
      }

      if (replica1 == null) fail("no active replicas found");

      String dataDir = null;
      try (HttpSolrClient replica1Client = new HttpSolrClient(replica1.getStr("base_url"))) {
        CoreAdminResponse status = CoreAdminRequest.getStatus(replica1.getStr("core"), replica1Client);
        NamedList<Object> coreStatus = status.getCoreStatus(replica1.getStr("core"));
        dataDir = (String) coreStatus.get("dataDir");
      }
      try {
        // Should not be able to delete a replica that is up if onlyIfDown=true.
        tryToRemoveOnlyIfDown(collectionName, client, replica1, shard1.getName());
        fail("Should have thrown an exception here because the replica is NOT down");
      } catch (SolrException se) {
        assertEquals("Should see 400 here ", se.code(), 400);
        assertTrue("Expected DeleteReplica to fail because node state is 'active' but returned message was: " + se.getMessage(), se.getMessage().contains("with onlyIfDown='true', but state is 'active'"));
        // This bit is a little weak in that if we're screwing up and actually deleting the replica, we might get back
        // here _before_ the datadir is deleted. But I'd rather not introduce a delay here.
        assertTrue("dataDir for " + replica1.getName() + " should NOT have been deleted by deleteReplica API with onlyIfDown='true'",
            new File(dataDir).exists());
      }

      removeAndWaitForReplicaGone(collectionName, client, replica1, shard1.getName());
      assertFalse("dataDir for " + replica1.getName() + " should have been deleted by deleteReplica API", new File(dataDir).exists());
    }
  }

  protected void tryToRemoveOnlyIfDown(String collectionName, CloudSolrClient client, Replica replica, String shard) throws IOException, SolrServerException {
    Map m = makeMap("collection", collectionName,
        "action", DELETEREPLICA.toLower(),
        "shard", shard,
        "replica", replica.getName(),
        ONLY_IF_DOWN, "true");
    SolrParams params = new MapSolrParams(m);
    SolrRequest request = new QueryRequest(params);
    request.setPath("/admin/collections");
    client.request(request);
  }

  static void removeAndWaitForReplicaGone(String COLL_NAME,
      CloudSolrClient client, Replica replica, String shard)
      throws SolrServerException, IOException, InterruptedException {
    Map m = makeMap("collection", COLL_NAME, "action", DELETEREPLICA.toLower(), "shard",
        shard, "replica", replica.getName());
    SolrParams params = new MapSolrParams(m);
    SolrRequest request = new QueryRequest(params);
{'fraction_non_alphanumeric': 0.10056701842809891, 'fraction_numerical': 0.005040163805323673, 'mean_word_length': 3.7884615384615383, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 4}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2142229', 'n_tokens_mistral': 3760, 'n_tokens_neox': 3681, 'n_words': 964}