blob: 20cab54919e51bee1fb744f7ea6244312cf21153 [file] [log] [blame]
/*
* 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.api.collections;
import org.apache.curator.shaded.com.google.common.collect.ImmutableList;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.TestUtil;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.SolrTestUtil;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.client.solrj.impl.BaseHttpSolrClient;
import org.apache.solr.client.solrj.impl.Http2SolrClient;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.CoreAdminRequest;
import org.apache.solr.client.solrj.request.CoreStatus;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.client.solrj.response.CollectionAdminResponse;
import org.apache.solr.client.solrj.response.CoreAdminResponse;
import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.cloud.ZkController;
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.ZkNodeProps;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.TimeSource;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.SolrCore;
import org.apache.solr.core.SolrInfoBean.Category;
import org.apache.solr.util.TestInjection;
import org.apache.solr.util.TimeOut;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.lang.management.ManagementFactory;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
/**
* Tests the Cloud Collections API.
*/
public class CollectionsAPIDistClusterPerZkTest extends SolrCloudTestCase {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
protected static volatile String configSet = "cloud-minimal";
protected static String getConfigSet() {
return configSet;
}
@BeforeClass
public static void beforeCollectionsAPIDistClusterPerZkTest() throws Exception {
useFactory(null);
// we don't want this test to have zk timeouts
System.setProperty("zkClientTimeout", "60000");
if (TEST_NIGHTLY) {
System.setProperty("createCollectionWaitTimeTillActive", "100");
TestInjection.randomDelayInCoreCreation = "true:5";
} else {
System.setProperty("createCollectionWaitTimeTillActive", "100");
}
configureCluster(TEST_NIGHTLY ? 4 : 2)
.addConfig("conf", SolrTestUtil.configset(getConfigSet()))
.addConfig("conf2", SolrTestUtil.configset(getConfigSet()))
.withSolrXml(SolrTestUtil.TEST_PATH().resolve("solr.xml"))
.configure();
}
@AfterClass
public static void afterCollectionsAPIDistClusterPerZkTest() throws Exception {
shutdownCluster();
}
@Test
@Ignore
public void deleteCollectionOnlyInZk() throws Exception {
final String collectionName = "onlyinzk";
// create the collections node
CollectionAdminRequest.createCollection(collectionName, "conf", 2, 1)
.process(cluster.getSolrClient());
// delete via API - should remove collections node
CollectionAdminRequest.deleteCollection(collectionName).process(cluster.getSolrClient());
assertFalse(CollectionAdminRequest.listCollections(cluster.getSolrClient()).contains(collectionName));
// now creating that collection should work
CollectionAdminRequest.createCollection(collectionName, "conf", 2, 1)
.process(cluster.getSolrClient());
assertTrue(CollectionAdminRequest.listCollections(cluster.getSolrClient()).contains(collectionName));
}
@Test
@Ignore // MRM TODO: we can speed this up, TJP ~ WIP: fails
public void testCreateShouldFailOnExistingCore() throws Exception {
assertEquals(0, CollectionAdminRequest.createCollection("halfcollectionblocker", "conf", 1, 1)
.setCreateNodeSet("")
.process(cluster.getSolrClient()).getStatus());
assertTrue(CollectionAdminRequest.addReplicaToShard("halfcollectionblocker", "shard1")
.setNode(cluster.getJettySolrRunner(0).getNodeName())
.setCoreName("halfcollection_shard1_replica_n1")
.process(cluster.getSolrClient()).isSuccess());
assertEquals(0, CollectionAdminRequest.createCollection("halfcollectionblocker2", "conf",1, 1)
.setCreateNodeSet("")
.process(cluster.getSolrClient()).getStatus());
assertTrue(CollectionAdminRequest.addReplicaToShard("halfcollectionblocker2", "shard1")
.setNode(cluster.getJettySolrRunner(1).getNodeName())
.setCoreName("halfcollection_shard1_replica_n1")
.process(cluster.getSolrClient()).isSuccess());
String nn1 = cluster.getJettySolrRunner(0).getNodeName();
String nn2 = cluster.getJettySolrRunner(1).getNodeName();
LuceneTestCase.expectThrows(BaseHttpSolrClient.RemoteSolrException.class, () -> {
CollectionAdminResponse resp = CollectionAdminRequest.createCollection("halfcollection", "conf", 2, 1)
.setCreateNodeSet(nn1 + "," + nn2)
.process(cluster.getSolrClient());
});
}
@Test
@LuceneTestCase.Nightly // needs 4 nodes
public void testCoresAreDistributedAcrossNodes() throws Exception {
CollectionAdminRequest.createCollection("nodes_used_collection", "conf", 2, 2)
.process(cluster.getSolrClient());
Set<String> liveNodes = cluster.getSolrClient().getZkStateReader().getLiveNodes();
List<String> createNodeList = new ArrayList<>(liveNodes);
DocCollection collection = getCollectionState("nodes_used_collection");
for (Slice slice : collection.getSlices()) {
for (Replica replica : slice.getReplicas()) {
createNodeList.remove(replica.getNodeName());
}
}
assertEquals(createNodeList.toString(), 0, createNodeList.size());
}
@Test
public void testSpecificConfigsets() throws Exception {
CollectionAdminRequest.createCollection("withconfigset2", "conf2", 1, 1).process(cluster.getSolrClient());
byte[] data = zkClient().getData(ZkStateReader.COLLECTIONS_ZKNODE + "/" + "withconfigset2", null, null);
assertNotNull(data);
ZkNodeProps props = ZkNodeProps.load(data);
String configName = props.getStr(ZkController.CONFIGNAME_PROP);
assertEquals("conf2", configName);
}
@Test
public void testCreateNodeSet() throws Exception {
JettySolrRunner jetty1 = null;
JettySolrRunner jetty2 = null;
final List<JettySolrRunner> runners = cluster.getJettySolrRunners();
if (runners.size() == 2) {
jetty1 = runners.get(0);
jetty2 = runners.get(1);
} else if (runners.size() > 2) {
jetty1 = cluster.getRandomJetty(random());
jetty2 = cluster.getRandomJetty(random(), jetty1);
} else {
fail("This test requires at least 2 Jetty runners!");
}
List<String> baseUrls = ImmutableList.of(jetty1.getCoreContainer().getZkController().getNodeName(), jetty2.getCoreContainer().getZkController().getNodeName());
CollectionAdminRequest.createCollection("nodeset_collection", "conf", 2, 1)
.setCreateNodeSet(baseUrls.get(0) + "," + baseUrls.get(1))
.process(cluster.getSolrClient());
DocCollection collectionState = getCollectionState("nodeset_collection");
for (Replica replica : collectionState.getReplicas()) {
String node = replica.getNodeName();
boolean matchingJetty = false;
for (String jettyNode : baseUrls) {
if (node.equals(jettyNode)) {
matchingJetty = true;
}
}
if (matchingJetty == false) {
fail("Expected replica to be on " + baseUrls + " but was on " + node);
}
}
}
@Test
//28-June-2018 @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028")
// See: https://issues.apache.org/jira/browse/SOLR-12028 Tests cannot remove files on Windows machines occasionally
// commented out on: 24-Dec-2018 @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // added 09-Aug-2018 SOLR-12028
@Ignore // MRM TODO:
public void testCollectionsAPI() throws Exception {
// create new collections rapid fire
int cnt = random().nextInt(TEST_NIGHTLY ? 3 : 1) + 1;
CollectionAdminRequest.Create[] createRequests = new CollectionAdminRequest.Create[cnt];
class Coll {
String name;
int numShards;
int replicationFactor;
}
List<Coll> colls = new ArrayList<>();
for (int i = 0; i < cnt; i++) {
int numShards = TestUtil.nextInt(random(), 0, cluster.getJettySolrRunners().size()) + 1;
int replicationFactor = TestUtil.nextInt(random(), 0, 3) + 1;
int maxShardsPerNode = 100;
createRequests[i]
= CollectionAdminRequest.createCollection("awhollynewcollection_" + i, "conf2", numShards, replicationFactor)
.setMaxShardsPerNode(maxShardsPerNode);
createRequests[i].processAsync(cluster.getSolrClient());
Coll coll = new Coll();
coll.name = "awhollynewcollection_" + i;
coll.numShards = numShards;
coll.replicationFactor = replicationFactor;
colls.add(coll);
}
for (CollectionAdminRequest.Create create : createRequests) {
if (create == null) continue;
try {
cluster.waitForActiveCollection(create.getCollectionName(), create.getNumShards(), create.getNumShards() * create.getTotaleReplicaCount());
} catch(Exception e) {
throw new RuntimeException(create.getParams().toString(), e);
}
}
for (int i = 0; i < cluster.getJettySolrRunners().size(); i++) {
checkInstanceDirs(cluster.getJettySolrRunner(i));
}
String collectionName = createRequests[random().nextInt(createRequests.length)].getCollectionName();
new UpdateRequest()
.add("id", "6")
.add("id", "7")
.add("id", "8")
.commit(cluster.getSolrClient(), collectionName);
long numFound = 0;
TimeOut timeOut = new TimeOut(10, TimeUnit.SECONDS, TimeSource.NANO_TIME);
while (!timeOut.hasTimedOut()) {
numFound = cluster.getSolrClient().query(collectionName, new SolrQuery("*:*")).getResults().getNumFound();
if (numFound == 3) {
break;
}
Thread.sleep(100);
}
if (timeOut.hasTimedOut()) {
fail("Timeout waiting to see 3 found, instead saw " + numFound + " for collection " + collectionName);
}
// checkNoTwoShardsUseTheSameIndexDir();
}
@Test
public void testCollectionReload() throws Exception {
final String collectionName = "reloaded_collection";
CollectionAdminRequest.createCollection(collectionName, "conf", 2, 2).setMaxShardsPerNode(10).process(cluster.getSolrClient());
CollectionAdminRequest.reloadCollection(collectionName).process(cluster.getSolrClient());
}
private void checkInstanceDirs(JettySolrRunner jetty) throws IOException {
CoreContainer cores = jetty.getCoreContainer();
Collection<SolrCore> theCores = cores.getCores();
for (SolrCore core : theCores) {
// look for core props file
Path instancedir = core.getInstancePath();
assertTrue("Could not find expected core.properties file", Files.exists(instancedir.resolve("core.properties")));
Path expected = Paths.get(jetty.getSolrHome()).toAbsolutePath().resolve(core.getName());
if (Files.exists(expected) && Files.exists(instancedir)) {
assertTrue("Expected: " + expected + "\nFrom core stats: " + instancedir, Files.isSameFile(expected, instancedir));
}
}
}
private boolean waitForReloads(String collectionName, Map<String,Long> urlToTimeBefore) throws SolrServerException, IOException {
TimeOut timeout = new TimeOut(5, TimeUnit.SECONDS, TimeSource.NANO_TIME);
boolean allTimesAreCorrect = false;
while (! timeout.hasTimedOut()) {
Map<String,Long> urlToTimeAfter = new HashMap<>();
collectStartTimes(collectionName, urlToTimeAfter);
boolean retry = false;
Set<Entry<String,Long>> entries = urlToTimeBefore.entrySet();
for (Entry<String,Long> entry : entries) {
Long beforeTime = entry.getValue();
Long afterTime = urlToTimeAfter.get(entry.getKey());
assertNotNull(afterTime);
if (afterTime <= beforeTime) {
retry = true;
break;
}
}
if (!retry) {
allTimesAreCorrect = true;
break;
}
}
return allTimesAreCorrect;
}
private void collectStartTimes(String collectionName, Map<String,Long> urlToTime)
throws SolrServerException, IOException {
DocCollection collectionState = getCollectionState(collectionName);
if (collectionState != null) {
for (Slice shard : collectionState) {
for (Replica replica : shard) {
Replica coreProps = replica;
CoreStatus coreStatus;
try (Http2SolrClient server = SolrTestCaseJ4.getHttpSolrClient(coreProps.getBaseUrl())) {
coreStatus = CoreAdminRequest.getCoreStatus(coreProps.getName(), false, server);
}
long before = coreStatus.getCoreStartTime().getTime();
urlToTime.put(coreProps.getCoreUrl(), before);
}
}
} else {
throw new IllegalArgumentException("Could not find collection " + collectionName);
}
}
private void checkNoTwoShardsUseTheSameIndexDir() {
Map<String, Set<String>> indexDirToShardNamesMap = new HashMap<>();
List<MBeanServer> servers = new LinkedList<>();
servers.add(ManagementFactory.getPlatformMBeanServer());
servers.addAll(MBeanServerFactory.findMBeanServer(null));
for (final MBeanServer server : servers) {
Set<ObjectName> mbeans = new HashSet<>(server.queryNames(null, null));
for (final ObjectName mbean : mbeans) {
try {
Map<String, String> props = mbean.getKeyPropertyList();
String category = props.get("category");
String name = props.get("name");
if ((category != null && category.equals(Category.CORE.toString())) &&
(name != null && name.equals("indexDir"))) {
String indexDir = server.getAttribute(mbean, "Value").toString();
String key = props.get("dom2") + "." + props.get("dom3") + "." + props.get("dom4");
if (!indexDirToShardNamesMap.containsKey(indexDir)) {
indexDirToShardNamesMap.put(indexDir, new HashSet<>());
}
indexDirToShardNamesMap.get(indexDir).add(key);
}
} catch (Exception e) {
// ignore, just continue - probably a "Value" attribute
// not found
}
}
}
assertTrue(
"Something is broken in the assert for no shards using the same indexDir - probably something was changed in the attributes published in the MBean of "
+ SolrCore.class.getSimpleName() + " : " + indexDirToShardNamesMap,
indexDirToShardNamesMap.size() > 0);
for (Entry<String,Set<String>> entry : indexDirToShardNamesMap.entrySet()) {
if (entry.getValue().size() > 1) {
fail("We have shards using the same indexDir. E.g. shards "
+ entry.getValue().toString() + " all use indexDir "
+ entry.getKey());
}
}
}
@Test
@LuceneTestCase.Nightly
public void addReplicaTest() throws Exception {
String collectionName = "addReplicaColl";
CollectionAdminRequest.createCollection(collectionName, "conf", 2, 2)
.setMaxShardsPerNode(6)
.process(cluster.getSolrClient());
ArrayList<String> nodeList
= new ArrayList<>(cluster.getSolrClient().getZkStateReader().getLiveNodes());
Collections.shuffle(nodeList, random());
CollectionAdminRequest.AddReplica req = CollectionAdminRequest.addReplicaToShard(collectionName, "s1").setNode(nodeList.get(0));
req.setWaitForFinalState(true);
CollectionAdminResponse response = req.process(cluster.getSolrClient());
Replica newReplica = grabNewReplica(response, getCollectionState(collectionName));
assertEquals("Replica should be created on the right node",
cluster.getSolrClient().getZkStateReader().getBaseUrlForNodeName(nodeList.get(0)),
newReplica.getBaseUrl());
Path instancePath = SolrTestUtil.createTempDir();
req = CollectionAdminRequest.addReplicaToShard(collectionName, "s1").withProperty(CoreAdminParams.INSTANCE_DIR, instancePath.toString());
req.setWaitForFinalState(true);
response = req.process(cluster.getSolrClient());
String replicaName = response.getCollectionCoresStatus().keySet().iterator().next();
AtomicReference<Replica> theReplica = new AtomicReference<>();
try {
cluster.getSolrClient().getZkStateReader().waitForState(collectionName, 15, TimeUnit.SECONDS, (liveNodes, collectionState) -> {
if (collectionState == null) {
return false;
}
Replica replica = collectionState.getReplica(replicaName);
if (replica != null) {
theReplica.set(replica);
return true;
}
return false;
});
} catch (TimeoutException e) {
log.error("timeout",e);
throw new TimeoutException("timeout waiting to see " + replicaName);
}
newReplica = theReplica.get();
assertNotNull(newReplica);
try (Http2SolrClient coreclient = SolrTestCaseJ4.getHttpSolrClient(newReplica.getBaseUrl())) {
CoreAdminResponse status = CoreAdminRequest.getStatus(newReplica.getName(), coreclient);
NamedList<Object> coreStatus = status.getCoreStatus(newReplica.getName());
String instanceDirStr = (String) coreStatus.get("instanceDir");
assertEquals(instanceDirStr, instancePath.toString());
}
// Check that specifying property.name works. DO NOT remove this when the "name" property is deprecated
// for ADDREPLICA, this is "property.name". See SOLR-7132
req = CollectionAdminRequest.addReplicaToShard(collectionName, "s1")
.withProperty(CoreAdminParams.NAME, "propertyDotName");
req.setWaitForFinalState(true);
response = req.process(cluster.getSolrClient());
AtomicReference<Replica> theReplica2 = new AtomicReference<>();
cluster.getSolrClient().getZkStateReader().waitForState(collectionName, 15, TimeUnit.SECONDS, (liveNodes, collectionState) -> {
if (collectionState == null) {
return false;
}
Replica replica = collectionState.getReplica(replicaName);
if (replica != null) {
theReplica2.set(replica);
return true;
}
return false;
});
newReplica = theReplica2.get();
assertNotNull(theReplica2);
// MRM TODO: do we really want to support this anymore? We really should control core names for cloud
// assertEquals("'core' should be 'propertyDotName' " + newReplica.getName(), "propertyDotName", newReplica.getName());
}
private Replica grabNewReplica(CollectionAdminResponse response, DocCollection docCollection) {
String replicaName = response.getCollectionCoresStatus().keySet().iterator().next();
Optional<Replica> optional = docCollection.getReplicas().stream()
.filter(replica -> replicaName.equals(replica.getName()))
.findAny();
if (optional.isPresent()) {
return optional.get();
}
throw new AssertionError("Can not find " + replicaName + " from " + docCollection);
}
}