/* * 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; import java.io.File; import java.io.FileOutputStream; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; import java.util.regex.Pattern; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableMap; import org.apache.commons.io.FileUtils; import org.apache.lucene.util.IOUtils; import org.apache.solr.SolrTestCaseJ4; import org.apache.solr.common.SolrException; import org.apache.solr.handler.admin.CollectionsHandler; import org.apache.solr.handler.admin.ConfigSetsHandler; import org.apache.solr.handler.admin.CoreAdminHandler; import org.apache.solr.handler.admin.InfoHandler; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.xml.sax.SAXParseException; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsInstanceOf.instanceOf; import static org.junit.matchers.JUnitMatchers.containsString; public class TestCoreContainer extends SolrTestCaseJ4 { private static String oldSolrHome; private static final String SOLR_HOME_PROP = "solr.solr.home"; @BeforeClass public static void beforeClass() throws Exception { oldSolrHome = System.getProperty(SOLR_HOME_PROP); System.setProperty("configsets", getFile("solr/configsets").getAbsolutePath()); } @AfterClass public static void afterClass() { if (oldSolrHome != null) { System.setProperty(SOLR_HOME_PROP, oldSolrHome); } else { System.clearProperty(SOLR_HOME_PROP); } } private CoreContainer init(String xml) throws Exception { Path solrHomeDirectory = createTempDir(); return init(solrHomeDirectory, xml); } private CoreContainer init(Path homeDirectory, String xml) throws Exception { SolrResourceLoader loader = new SolrResourceLoader(homeDirectory); CoreContainer ret = new CoreContainer(SolrXmlConfig.fromString(loader, xml)); ret.load(); return ret; } @Test public void testShareSchema() throws Exception { System.setProperty("shareSchema", "true"); CoreContainer cores = init(CONFIGSETS_SOLR_XML); try { SolrCore core1 = cores.create("core1", ImmutableMap.of("configSet", "minimal")); SolrCore core2 = cores.create("core2", ImmutableMap.of("configSet", "minimal")); assertSame(core1.getLatestSchema(), core2.getLatestSchema()); } finally { cores.shutdown(); System.clearProperty("shareSchema"); } } @Test public void testReloadSequential() throws Exception { final CoreContainer cc = init(CONFIGSETS_SOLR_XML); try { cc.create("core1", ImmutableMap.of("configSet", "minimal")); cc.reload("core1"); cc.reload("core1"); cc.reload("core1"); cc.reload("core1"); } finally { cc.shutdown(); } } @Test public void testReloadThreaded() throws Exception { final CoreContainer cc = init(CONFIGSETS_SOLR_XML); cc.create("core1", ImmutableMap.of("configSet", "minimal")); class TestThread extends Thread { @Override public void run() { cc.reload("core1"); } } List<Thread> threads = new ArrayList<>(); int numThreads = 4; for (int i = 0; i < numThreads; i++) { threads.add(new TestThread()); } for (Thread thread : threads) { thread.start(); } for (Thread thread : threads) { thread.join(); } cc.shutdown(); } @Test public void testNoCores() throws Exception { CoreContainer cores = init(CONFIGSETS_SOLR_XML); try { //assert zero cores assertEquals("There should not be cores", 0, cores.getCores().size()); //add a new core cores.create("core1", ImmutableMap.of("configSet", "minimal")); //assert one registered core assertEquals("There core registered", 1, cores.getCores().size()); cores.unload("core1"); //assert cero cores assertEquals("There should not be cores", 0, cores.getCores().size()); // try and remove a core that does not exist try { cores.unload("non_existent_core"); fail("Should have thrown an exception when unloading a non-existent core"); } catch (SolrException e) { assertThat(e.getMessage(), containsString("Cannot unload non-existent core [non_existent_core]")); } // try and remove a null core try { cores.unload(null); fail("Should have thrown an exception when unloading a null core"); } catch (Exception e) { if (!(e instanceof SolrException)) { fail("Should not have thrown SolrException but got " + e); } assertThat(e.getMessage(), containsString("Cannot unload non-existent core [null]")); } } finally { cores.shutdown(); } } @Test public void testLogWatcherEnabledByDefault() throws Exception { CoreContainer cc = init("<solr></solr>"); try { assertNotNull(cc.getLogging()); } finally { cc.shutdown(); } } @Test public void testDeleteBadCores() throws Exception { MockCoresLocator cl = new MockCoresLocator(); SolrResourceLoader resourceLoader = new SolrResourceLoader(createTempDir()); System.setProperty("configsets", getFile("solr/configsets").getAbsolutePath()); final CoreContainer cc = new CoreContainer(SolrXmlConfig.fromString(resourceLoader, CONFIGSETS_SOLR_XML), new Properties(), cl); Path corePath = resourceLoader.getInstancePath().resolve("badcore"); CoreDescriptor badcore = new CoreDescriptor("badcore", corePath, cc.getContainerProperties(), cc.isZooKeeperAware(), "configSet", "nosuchconfigset"); cl.add(badcore); try { cc.load(); assertThat(cc.getCoreInitFailures().size(), is(1)); assertThat(cc.getCoreInitFailures().get("badcore").exception.getMessage(), containsString("nosuchconfigset")); cc.unload("badcore", true, true, true); assertThat(cc.getCoreInitFailures().size(), is(0)); // can we create the core now with a good config? SolrCore core = cc.create("badcore", ImmutableMap.of("configSet", "minimal")); assertThat(core, not(nullValue())); } finally { cc.shutdown(); } } @Test public void testClassLoaderHierarchy() throws Exception { final CoreContainer cc = init(CONFIGSETS_SOLR_XML); try { ClassLoader sharedLoader = cc.loader.getClassLoader(); ClassLoader contextLoader = Thread.currentThread().getContextClassLoader(); assertSame(contextLoader, sharedLoader.getParent()); SolrCore core1 = cc.create("core1", ImmutableMap.of("configSet", "minimal")); ClassLoader coreLoader = core1.getResourceLoader().getClassLoader(); assertSame(sharedLoader, coreLoader.getParent()); } finally { cc.shutdown(); } } @Test public void testSharedLib() throws Exception { Path tmpRoot = createTempDir("testSharedLib"); File lib = new File(tmpRoot.toFile(), "lib"); lib.mkdirs(); JarOutputStream jar1 = new JarOutputStream(new FileOutputStream(new File(lib, "jar1.jar"))); jar1.putNextEntry(new JarEntry("defaultSharedLibFile")); jar1.closeEntry(); jar1.close(); File customLib = new File(tmpRoot.toFile(), "customLib"); customLib.mkdirs(); JarOutputStream jar2 = new JarOutputStream(new FileOutputStream(new File(customLib, "jar2.jar"))); jar2.putNextEntry(new JarEntry("customSharedLibFile")); jar2.closeEntry(); jar2.close(); final CoreContainer cc1 = init(tmpRoot, "<solr></solr>"); try { cc1.loader.openResource("defaultSharedLibFile").close(); } finally { cc1.shutdown(); } final CoreContainer cc2 = init(tmpRoot, "<solr><str name=\"sharedLib\">lib</str></solr>"); try { cc2.loader.openResource("defaultSharedLibFile").close(); } finally { cc2.shutdown(); } final CoreContainer cc3 = init(tmpRoot, "<solr><str name=\"sharedLib\">customLib</str></solr>"); try { cc3.loader.openResource("customSharedLibFile").close(); } finally { cc3.shutdown(); } } private static final String CONFIGSETS_SOLR_XML ="<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + "<solr>\n" + "<str name=\"configSetBaseDir\">${configsets:configsets}</str>\n" + "<str name=\"shareSchema\">${shareSchema:false}</str>\n" + "</solr>"; private static final String CUSTOM_HANDLERS_SOLR_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + "<solr>" + " <str name=\"collectionsHandler\">" + CustomCollectionsHandler.class.getName() + "</str>" + " <str name=\"infoHandler\">" + CustomInfoHandler.class.getName() + "</str>" + " <str name=\"adminHandler\">" + CustomCoreAdminHandler.class.getName() + "</str>" + " <str name=\"configSetsHandler\">" + CustomConfigSetsHandler.class.getName() + "</str>" + "</solr>"; public static class CustomCollectionsHandler extends CollectionsHandler { public CustomCollectionsHandler(CoreContainer cc) { super(cc); } } public static class CustomInfoHandler extends InfoHandler { public CustomInfoHandler(CoreContainer cc) { super(cc); } } public static class CustomCoreAdminHandler extends CoreAdminHandler { public CustomCoreAdminHandler(CoreContainer cc) { super(cc); } } public static class CustomConfigSetsHandler extends ConfigSetsHandler { public CustomConfigSetsHandler(CoreContainer cc) { super(cc); } } @Test public void testCustomHandlers() throws Exception { CoreContainer cc = init(CUSTOM_HANDLERS_SOLR_XML); try { assertThat(cc.getCollectionsHandler(), is(instanceOf(CustomCollectionsHandler.class))); assertThat(cc.getInfoHandler(), is(instanceOf(CustomInfoHandler.class))); assertThat(cc.getMultiCoreHandler(), is(instanceOf(CustomCoreAdminHandler.class))); } finally { cc.shutdown(); } } private static class MockCoresLocator implements CoresLocator { List<CoreDescriptor> cores = new ArrayList<>(); void add(CoreDescriptor cd) { cores.add(cd); } @Override public void create(CoreContainer cc, CoreDescriptor... coreDescriptors) { // noop } @Override public void persist(CoreContainer cc, CoreDescriptor... coreDescriptors) { } @Override public void delete(CoreContainer cc, CoreDescriptor... coreDescriptors) { } @Override public void rename(CoreContainer cc, CoreDescriptor oldCD, CoreDescriptor newCD) { } @Override public void swap(CoreContainer cc, CoreDescriptor cd1, CoreDescriptor cd2) { } @Override public List<CoreDescriptor> discover(CoreContainer cc) { return cores; } } @Test public void testCoreInitFailuresFromEmptyContainer() throws Exception { // reused state Map<String,CoreContainer.CoreLoadFailure> failures = null; Collection<String> cores = null; Exception fail = null; // ---- // init the CoreContainer CoreContainer cc = init(CONFIGSETS_SOLR_XML); // check that we have the cores we expect cores = cc.getLoadedCoreNames(); assertNotNull("core names is null", cores); assertEquals("wrong number of cores", 0, cores.size()); // check that we have the failures we expect failures = cc.getCoreInitFailures(); assertNotNull("core failures is a null map", failures); assertEquals("wrong number of core failures", 0, failures.size()); // ----- // try to add a collection with a configset that doesn't exist try { ignoreException(Pattern.quote("bogus_path")); cc.create("bogus", ImmutableMap.of("configSet", "bogus_path")); fail("bogus inst dir failed to trigger exception from create"); } catch (SolrException e) { Throwable cause = Throwables.getRootCause(e); assertTrue("init exception doesn't mention bogus dir: " + cause.getMessage(), 0 < cause.getMessage().indexOf("bogus_path")); } // check that we have the cores we expect cores = cc.getLoadedCoreNames(); assertNotNull("core names is null", cores); assertEquals("wrong number of cores", 0, cores.size()); // check that we have the failures we expect failures = cc.getCoreInitFailures(); assertNotNull("core failures is a null map", failures); assertEquals("wrong number of core failures", 1, failures.size()); fail = failures.get("bogus").exception; assertNotNull("null failure for test core", fail); assertTrue("init failure doesn't mention problem: " + fail.getMessage(), 0 < fail.getMessage().indexOf("bogus_path")); // check that we get null accessing a non-existent core assertNull(cc.getCore("does_not_exist")); // check that we get a 500 accessing the core with an init failure try { SolrCore c = cc.getCore("bogus"); fail("Failed to get Exception on accessing core with init failure"); } catch (SolrException ex) { assertEquals(500, ex.code()); String cause = Throwables.getRootCause(ex).getMessage(); assertTrue("getCore() ex cause doesn't mention init fail: " + cause, 0 < cause.indexOf("bogus_path")); } cc.shutdown(); } @Test public void testCoreInitFailuresOnReload() throws Exception { // reused state Map<String,CoreContainer.CoreLoadFailure> failures = null; Collection<String> cores = null; Exception fail = null; // ----- // init the CoreContainer with the mix of ok/bad cores MockCoresLocator cl = new MockCoresLocator(); SolrResourceLoader resourceLoader = new SolrResourceLoader(createTempDir()); System.setProperty("configsets", getFile("solr/configsets").getAbsolutePath()); final CoreContainer cc = new CoreContainer(SolrXmlConfig.fromString(resourceLoader, CONFIGSETS_SOLR_XML), new Properties(), cl); cl.add(new CoreDescriptor("col_ok", resourceLoader.getInstancePath().resolve("col_ok"), cc.getContainerProperties(), cc.isZooKeeperAware(), "configSet", "minimal")); cl.add(new CoreDescriptor("col_bad", resourceLoader.getInstancePath().resolve("col_bad"), cc.getContainerProperties(), cc.isZooKeeperAware(), "configSet", "bad-mergepolicy")); cc.load(); // check that we have the cores we expect cores = cc.getLoadedCoreNames(); assertNotNull("core names is null", cores); assertEquals("wrong number of cores", 1, cores.size()); assertTrue("col_ok not found", cores.contains("col_ok")); // check that we have the failures we expect failures = cc.getCoreInitFailures(); assertNotNull("core failures is a null map", failures); assertEquals("wrong number of core failures", 1, failures.size()); fail = failures.get("col_bad").exception; assertNotNull("null failure for test core", fail); assertTrue("init failure doesn't mention problem: " + fail.getMessage(), 0 < fail.getMessage().indexOf("DummyMergePolicy")); // check that we get null accessing a non-existent core assertNull(cc.getCore("does_not_exist")); // check that we get a 500 accessing the core with an init failure try { SolrCore c = cc.getCore("col_bad"); fail("Failed to get Exception on accessing core with init failure"); } catch (SolrException ex) { assertEquals(500, ex.code()); // double wrapped String cause = ex.getCause().getCause().getMessage(); assertTrue("getCore() ex cause doesn't mention init fail: " + cause, 0 < cause.indexOf("DummyMergePolicy")); } // ----- // "fix" the bad collection FileUtils.copyFile(getFile("solr/collection1/conf/solrconfig-defaults.xml"), FileUtils.getFile(cc.getSolrHome(), "col_bad", "conf", "solrconfig.xml")); FileUtils.copyFile(getFile("solr/collection1/conf/schema-minimal.xml"), FileUtils.getFile(cc.getSolrHome(), "col_bad", "conf", "schema.xml")); cc.create("col_bad", ImmutableMap.of()); // check that we have the cores we expect cores = cc.getLoadedCoreNames(); assertNotNull("core names is null", cores); assertEquals("wrong number of cores", 2, cores.size()); assertTrue("col_ok not found", cores.contains("col_ok")); assertTrue("col_bad not found", cores.contains("col_bad")); // check that we have the failures we expect failures = cc.getCoreInitFailures(); assertNotNull("core failures is a null map", failures); assertEquals("wrong number of core failures", 0, failures.size()); // ----- // try to add a collection with a path that doesn't exist try { ignoreException(Pattern.quote("bogus_path")); cc.create("bogus", ImmutableMap.of("configSet", "bogus_path")); fail("bogus inst dir failed to trigger exception from create"); } catch (SolrException e) { assertTrue("init exception doesn't mention bogus dir: " + e.getCause().getCause().getMessage(), 0 < e.getCause().getCause().getMessage().indexOf("bogus_path")); } // check that we have the cores we expect cores = cc.getLoadedCoreNames(); assertNotNull("core names is null", cores); assertEquals("wrong number of cores", 2, cores.size()); assertTrue("col_ok not found", cores.contains("col_ok")); assertTrue("col_bad not found", cores.contains("col_bad")); // check that we have the failures we expect failures = cc.getCoreInitFailures(); assertNotNull("core failures is a null map", failures); assertEquals("wrong number of core failures", 1, failures.size()); fail = failures.get("bogus").exception; assertNotNull("null failure for test core", fail); assertTrue("init failure doesn't mention problem: " + fail.getMessage(), 0 < fail.getMessage().indexOf("bogus_path")); // check that we get null accessing a non-existent core assertNull(cc.getCore("does_not_exist")); // check that we get a 500 accessing the core with an init failure try { SolrCore c = cc.getCore("bogus"); fail("Failed to get Exception on accessing core with init failure"); } catch (SolrException ex) { assertEquals(500, ex.code()); // double wrapped String cause = ex.getCause().getMessage(); assertTrue("getCore() ex cause doesn't mention init fail: " + cause, 0 < cause.indexOf("bogus_path")); } // ----- // break col_bad's config and try to RELOAD to add failure final long col_bad_old_start = getCoreStartTime(cc, "col_bad"); FileUtils.write (FileUtils.getFile(cc.getSolrHome(), "col_bad", "conf", "solrconfig.xml"), "This is giberish, not valid XML <", IOUtils.UTF_8); try { ignoreException(Pattern.quote("SAX")); cc.reload("col_bad"); fail("corrupt solrconfig.xml failed to trigger exception from reload"); } catch (SolrException e) { Throwable rootException = getWrappedException(e); assertTrue("We're supposed to have a wrapped SAXParserException here, but we don't", rootException instanceof SAXParseException); SAXParseException se = (SAXParseException) rootException; assertTrue("reload exception doesn't refer to slrconfig.xml " + se.getSystemId(), 0 < se.getSystemId().indexOf("solrconfig.xml")); } assertEquals("Failed core reload should not have changed start time", col_bad_old_start, getCoreStartTime(cc, "col_bad")); // check that we have the cores we expect cores = cc.getLoadedCoreNames(); assertNotNull("core names is null", cores); assertEquals("wrong number of cores", 2, cores.size()); assertTrue("col_ok not found", cores.contains("col_ok")); assertTrue("col_bad not found", cores.contains("col_bad")); // check that we have the failures we expect failures = cc.getCoreInitFailures(); assertNotNull("core failures is a null map", failures); assertEquals("wrong number of core failures", 2, failures.size()); Throwable ex = getWrappedException(failures.get("col_bad").exception); assertNotNull("null failure for test core", ex); assertTrue("init failure isn't SAXParseException", ex instanceof SAXParseException); SAXParseException saxEx = (SAXParseException) ex; assertTrue("init failure doesn't mention problem: " + saxEx.toString(), saxEx.getSystemId().contains("solrconfig.xml")); // ---- // fix col_bad's config (again) and RELOAD to fix failure FileUtils.copyFile(getFile("solr/collection1/conf/solrconfig-defaults.xml"), FileUtils.getFile(cc.getSolrHome(), "col_bad", "conf", "solrconfig.xml")); cc.reload("col_bad"); assertTrue("Core reload should have changed start time", col_bad_old_start < getCoreStartTime(cc, "col_bad")); // check that we have the cores we expect cores = cc.getLoadedCoreNames(); assertNotNull("core names is null", cores); assertEquals("wrong number of cores", 2, cores.size()); assertTrue("col_ok not found", cores.contains("col_ok")); assertTrue("col_bad not found", cores.contains("col_bad")); // check that we have the failures we expect failures = cc.getCoreInitFailures(); assertNotNull("core failures is a null map", failures); assertEquals("wrong number of core failures", 1, failures.size()); cc.shutdown(); } private long getCoreStartTime(final CoreContainer cc, final String name) { try (SolrCore tmp = cc.getCore(name)) { return tmp.getStartTimeStamp().getTime(); } } }