/* * 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.schema; import java.io.File; import java.io.FileInputStream; import java.lang.invoke.MethodHandles; import java.nio.file.Files; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.regex.Pattern; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.solr.common.SolrException; import org.apache.solr.common.params.CoreAdminParams; import org.apache.solr.common.util.NamedList; import org.apache.solr.core.AbstractBadConfigTestBase; import org.apache.solr.core.CoreContainer; import org.apache.solr.handler.admin.CoreAdminHandler; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.response.SolrQueryResponse; import org.junit.After; import org.junit.Before; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class TestManagedSchema extends AbstractBadConfigTestBase { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private static File tmpSolrHome; private static File tmpConfDir; private static final String collection = "collection1"; private static final String confDir = collection + "/conf"; @Before private void initManagedSchemaCore() throws Exception { tmpSolrHome = createTempDir().toFile(); tmpConfDir = new File(tmpSolrHome, confDir); File testHomeConfDir = new File(TEST_HOME(), confDir); FileUtils.copyFileToDirectory(new File(testHomeConfDir, "solrconfig-managed-schema.xml"), tmpConfDir); FileUtils.copyFileToDirectory(new File(testHomeConfDir, "solrconfig-basic.xml"), tmpConfDir); FileUtils.copyFileToDirectory(new File(testHomeConfDir, "solrconfig-managed-schema-test.xml"), tmpConfDir); FileUtils.copyFileToDirectory(new File(testHomeConfDir, "solrconfig.snippet.randomindexconfig.xml"), tmpConfDir); FileUtils.copyFileToDirectory(new File(testHomeConfDir, "schema-one-field-no-dynamic-field.xml"), tmpConfDir); FileUtils.copyFileToDirectory(new File(testHomeConfDir, "schema-one-field-no-dynamic-field-unique-key.xml"), tmpConfDir); FileUtils.copyFileToDirectory(new File(testHomeConfDir, "schema-minimal.xml"), tmpConfDir); FileUtils.copyFileToDirectory(new File(testHomeConfDir, "schema_codec.xml"), tmpConfDir); FileUtils.copyFileToDirectory(new File(testHomeConfDir, "schema-bm25.xml"), tmpConfDir); // initCore will trigger an upgrade to managed schema, since the solrconfig has // <schemaFactory class="ManagedIndexSchemaFactory" ... /> System.setProperty("managed.schema.mutable", "false"); System.setProperty("enable.update.log", "false"); initCore("solrconfig-managed-schema.xml", "schema-minimal.xml", tmpSolrHome.getPath()); } @After private void afterClass() throws Exception { deleteCore(); System.clearProperty("managed.schema.mutable"); System.clearProperty("enable.update.log"); } public void testUpgrade() throws Exception { File managedSchemaFile = new File(tmpConfDir, "managed-schema"); assertTrue(managedSchemaFile.exists()); String managedSchema = FileUtils.readFileToString(managedSchemaFile, "UTF-8"); assertTrue(managedSchema.contains("DO NOT EDIT")); File upgradedOriginalSchemaFile = new File(tmpConfDir, "schema-minimal.xml.bak"); assertTrue(upgradedOriginalSchemaFile.exists()); assertSchemaResource(collection, "managed-schema"); } public void testUpgradeThenRestart() throws Exception { assertSchemaResource(collection, "managed-schema"); deleteCore(); File nonManagedSchemaFile = new File(tmpConfDir, "schema-minimal.xml"); assertFalse(nonManagedSchemaFile.exists()); initCore("solrconfig-managed-schema.xml", "schema-minimal.xml", tmpSolrHome.getPath()); File managedSchemaFile = new File(tmpConfDir, "managed-schema"); assertTrue(managedSchemaFile.exists()); String managedSchema = FileUtils.readFileToString(managedSchemaFile, "UTF-8"); assertTrue(managedSchema.contains("DO NOT EDIT")); File upgradedOriginalSchemaFile = new File(tmpConfDir, "schema-minimal.xml.bak"); assertTrue(upgradedOriginalSchemaFile.exists()); assertSchemaResource(collection, "managed-schema"); } public void testUpgradeThenRestartNonManaged() throws Exception { deleteCore(); // After upgrade to managed schema, fail to restart when solrconfig doesn't contain // <schemaFactory class="ManagedIndexSchemaFactory">...</schemaFactory> assertConfigs("solrconfig-basic.xml", "schema-minimal.xml", tmpSolrHome.getPath(), "Can't find resource 'schema-minimal.xml'"); } public void testUpgradeThenRestartNonManagedAfterPuttingBackNonManagedSchema() throws Exception { assertSchemaResource(collection, "managed-schema"); deleteCore(); File nonManagedSchemaFile = new File(tmpConfDir, "schema-minimal.xml"); assertFalse(nonManagedSchemaFile.exists()); File upgradedOriginalSchemaFile = new File(tmpConfDir, "schema-minimal.xml.bak"); assertTrue(upgradedOriginalSchemaFile.exists()); // After upgrade to managed schema, downgrading to non-managed should work after putting back the non-managed schema. FileUtils.moveFile(upgradedOriginalSchemaFile, nonManagedSchemaFile); initCore("solrconfig-basic.xml", "schema-minimal.xml", tmpSolrHome.getPath()); assertSchemaResource(collection, "schema-minimal.xml"); } public void testDefaultSchemaFactory() throws Exception { deleteCore(); initCore("solrconfig-managed-schema-test.xml", "schema-minimal.xml", tmpSolrHome.getPath()); final CoreContainer cores = h.getCoreContainer(); final CoreAdminHandler admin = new CoreAdminHandler(cores); SolrQueryRequest request = req(CoreAdminParams.ACTION, CoreAdminParams.CoreAdminAction.STATUS.toString()); SolrQueryResponse response = new SolrQueryResponse(); admin.handleRequestBody(request, response); assertNull("Exception on create", response.getException()); assertSchemaResource(collection, "managed-schema"); } private void assertSchemaResource(String collection, String expectedSchemaResource) throws Exception { final CoreContainer cores = h.getCoreContainer(); final CoreAdminHandler admin = new CoreAdminHandler(cores); SolrQueryRequest request = req(CoreAdminParams.ACTION, CoreAdminParams.CoreAdminAction.STATUS.toString()); SolrQueryResponse response = new SolrQueryResponse(); admin.handleRequestBody(request, response); assertNull("Exception on create", response.getException()); NamedList responseValues = response.getValues(); NamedList status = (NamedList)responseValues.get("status"); NamedList collectionStatus = (NamedList)status.get(collection); String collectionSchema = (String)collectionStatus.get(CoreAdminParams.SCHEMA); assertEquals("Schema resource name differs from expected name", expectedSchemaResource, collectionSchema); } public void testAddFieldWhenNotMutable() throws Exception { assertSchemaResource(collection, "managed-schema"); String errString = "This ManagedIndexSchema is not mutable."; ignoreException(Pattern.quote(errString)); try { IndexSchema oldSchema = h.getCore().getLatestSchema(); String fieldName = "new_field"; String fieldType = "string"; Map<String,?> options = Collections.emptyMap(); SchemaField newField = oldSchema.newField(fieldName, fieldType, options); IndexSchema newSchema = oldSchema.addField(newField); h.getCore().setLatestSchema(newSchema); fail(); } catch (Exception e) { for (Throwable t = e; t != null; t = t.getCause()) { // short circuit out if we found what we expected if (t.getMessage() != null && -1 != t.getMessage().indexOf(errString)) return; } // otherwise, rethrow it, possibly completely unrelated throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unexpected error, expected error matching: " + errString, e); } finally { resetExceptionIgnores(); } } public void testAddFieldPersistence() throws Exception { assertSchemaResource(collection, "managed-schema"); deleteCore(); File managedSchemaFile = new File(tmpConfDir, "managed-schema"); Files.delete(managedSchemaFile.toPath()); // Delete managed-schema so it won't block parsing a new schema System.setProperty("managed.schema.mutable", "true"); initCore("solrconfig-managed-schema.xml", "schema-one-field-no-dynamic-field.xml", tmpSolrHome.getPath()); assertTrue(managedSchemaFile.exists()); String managedSchemaContents = FileUtils.readFileToString(managedSchemaFile, "UTF-8"); assertFalse(managedSchemaContents.contains("\"new_field\"")); Map<String,Object> options = new HashMap<>(); options.put("stored", "false"); IndexSchema oldSchema = h.getCore().getLatestSchema(); String fieldName = "new_field"; String fieldType = "string"; SchemaField newField = oldSchema.newField(fieldName, fieldType, options); IndexSchema newSchema = oldSchema.addField(newField); h.getCore().setLatestSchema(newSchema); assertTrue(managedSchemaFile.exists()); FileInputStream stream = new FileInputStream(managedSchemaFile); managedSchemaContents = IOUtils.toString(stream, "UTF-8"); stream.close(); // Explicitly close so that Windows can delete this file assertTrue(managedSchemaContents.contains("<field name=\"new_field\" type=\"string\" stored=\"false\"/>")); } public void testAddedFieldIndexableAndQueryable() throws Exception { assertSchemaResource(collection, "managed-schema"); deleteCore(); File managedSchemaFile = new File(tmpConfDir, "managed-schema"); Files.delete(managedSchemaFile.toPath()); // Delete managed-schema so it won't block parsing a new schema System.setProperty("managed.schema.mutable", "true"); initCore("solrconfig-managed-schema.xml", "schema-one-field-no-dynamic-field.xml", tmpSolrHome.getPath()); assertTrue(managedSchemaFile.exists()); String managedSchemaContents = FileUtils.readFileToString(managedSchemaFile, "UTF-8"); assertFalse(managedSchemaContents.contains("\"new_field\"")); clearIndex(); String errString = "unknown field 'new_field'"; ignoreException(Pattern.quote(errString)); try { assertU(adoc("new_field", "thing1 thing2", "str", "X")); fail(); } catch (Exception e) { for (Throwable t = e; t != null; t = t.getCause()) { // short circuit out if we found what we expected if (t.getMessage() != null && -1 != t.getMessage().indexOf(errString)) return; } // otherwise, rethrow it, possibly completely unrelated throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unexpected error, expected error matching: " + errString, e); } finally { resetExceptionIgnores(); } assertU(commit()); assertQ(req("new_field:thing1"), "//*[@numFound='0']"); Map<String,Object> options = new HashMap<>(); options.put("stored", "false"); IndexSchema oldSchema = h.getCore().getLatestSchema(); String fieldName = "new_field"; String fieldType = "text"; SchemaField newField = oldSchema.newField(fieldName, fieldType, options); IndexSchema newSchema = oldSchema.addField(newField); h.getCore().setLatestSchema(newSchema); assertU(adoc("new_field", "thing1 thing2", "str", "X")); assertU(commit()); assertQ(req("new_field:thing1"), "//*[@numFound='1']"); } public void testAddFieldWhenItAlreadyExists() throws Exception{ deleteCore(); File managedSchemaFile = new File(tmpConfDir, "managed-schema"); Files.delete(managedSchemaFile.toPath()); // Delete managed-schema so it won't block parsing a new schema System.setProperty("managed.schema.mutable", "true"); initCore("solrconfig-managed-schema.xml", "schema-one-field-no-dynamic-field.xml", tmpSolrHome.getPath()); assertNotNull("Field 'str' is not present in the schema", h.getCore().getLatestSchema().getFieldOrNull("str")); String errString = "Field 'str' already exists."; ignoreException(Pattern.quote(errString)); try { Map<String,Object> options = new HashMap<>(); IndexSchema oldSchema = h.getCore().getLatestSchema(); String fieldName = "str"; String fieldType = "string"; SchemaField newField = oldSchema.newField(fieldName, fieldType, options); IndexSchema newSchema = oldSchema.addField(newField); h.getCore().setLatestSchema(newSchema); fail("Should fail when adding a field that already exists"); } catch (Exception e) { for (Throwable t = e; t != null; t = t.getCause()) { // short circuit out if we found what we expected if (t.getMessage() != null && -1 != t.getMessage().indexOf(errString)) return; } // otherwise, rethrow it, possibly completely unrelated throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unexpected error, expected error matching: " + errString, e); } finally { resetExceptionIgnores(); } } public void testAddSameFieldTwice() throws Exception{ deleteCore(); File managedSchemaFile = new File(tmpConfDir, "managed-schema"); Files.delete(managedSchemaFile.toPath()); // Delete managed-schema so it won't block parsing a new schema System.setProperty("managed.schema.mutable", "true"); initCore("solrconfig-managed-schema.xml", "schema-one-field-no-dynamic-field.xml", tmpSolrHome.getPath()); Map<String,Object> options = new HashMap<>(); options.put("stored", "false"); IndexSchema oldSchema = h.getCore().getLatestSchema(); String fieldName = "new_field"; String fieldType = "text"; SchemaField newField = oldSchema.newField(fieldName, fieldType, options); IndexSchema newSchema = oldSchema.addField(newField); h.getCore().setLatestSchema(newSchema); String errString = "Field 'new_field' already exists."; ignoreException(Pattern.quote(errString)); try { newSchema = newSchema.addField(newField); h.getCore().setLatestSchema(newSchema); fail("Should fail when adding the same field twice"); } catch (Exception e) { for (Throwable t = e; t != null; t = t.getCause()) { // short circuit out if we found what we expected if (t.getMessage() != null && -1 != t.getMessage().indexOf(errString)) return; } // otherwise, rethrow it, possibly completely unrelated throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unexpected error, expected error matching: " + errString, e); } finally { resetExceptionIgnores(); } } public void testAddDynamicField() throws Exception{ deleteCore(); File managedSchemaFile = new File(tmpConfDir, "managed-schema"); Files.delete(managedSchemaFile.toPath()); // Delete managed-schema so it won't block parsing a new schema System.setProperty("managed.schema.mutable", "true"); initCore("solrconfig-managed-schema.xml", "schema-one-field-no-dynamic-field.xml", tmpSolrHome.getPath()); assertNull("Field '*_s' is present in the schema", h.getCore().getLatestSchema().getFieldOrNull("*_s")); String errString = "Can't add dynamic field '*_s'."; ignoreException(Pattern.quote(errString)); try { Map<String,Object> options = new HashMap<>(); IndexSchema oldSchema = h.getCore().getLatestSchema(); String fieldName = "*_s"; String fieldType = "string"; SchemaField newField = oldSchema.newField(fieldName, fieldType, options); IndexSchema newSchema = oldSchema.addField(newField); h.getCore().setLatestSchema(newSchema); fail("Should fail when adding a dynamic field"); } catch (Exception e) { for (Throwable t = e; t != null; t = t.getCause()) { // short circuit out if we found what we expected if (t.getMessage() != null && -1 != t.getMessage().indexOf(errString)) return; } // otherwise, rethrow it, possibly completely unrelated throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unexpected error, expected error matching: " + errString, e); } finally { resetExceptionIgnores(); } } public void testAddWithSchemaCodecFactory() throws Exception { deleteCore(); File managedSchemaFile = new File(tmpConfDir, "managed-schema"); Files.delete(managedSchemaFile.toPath()); // Delete managed-schema so it won't block parsing a new schema System.setProperty("managed.schema.mutable", "true"); initCore("solrconfig-managed-schema.xml", "schema_codec.xml", tmpSolrHome.getPath()); String uniqueKey = "string_f"; assertNotNull("Unique key field '" + uniqueKey + "' is not present in the schema", h.getCore().getLatestSchema().getFieldOrNull(uniqueKey)); String fieldName = "string_disk_new_field"; assertNull("Field '" + fieldName + "' is present in the schema", h.getCore().getLatestSchema().getFieldOrNull(fieldName)); Map<String,Object> options = new HashMap<>(); IndexSchema oldSchema = h.getCore().getLatestSchema(); String fieldType = "string_disk"; SchemaField newField = oldSchema.newField(fieldName, fieldType, options); IndexSchema newSchema = oldSchema.addField(newField); h.getCore().setLatestSchema(newSchema); assertU(adoc(fieldName, "thing", uniqueKey, "aBc")); assertU(commit()); assertQ(req(fieldName + ":thing"), "//*[@numFound='1']"); } public void testAddWithSchemaSimilarityFactory() throws Exception { deleteCore(); File managedSchemaFile = new File(tmpConfDir, "managed-schema"); Files.delete(managedSchemaFile.toPath()); // Delete managed-schema so it won't block parsing a new schema System.setProperty("managed.schema.mutable", "true"); initCore("solrconfig-managed-schema.xml", "schema-bm25.xml", tmpSolrHome.getPath()); String uniqueKey = "id"; assertNotNull("Unique key field '" + uniqueKey + "' is not present in the schema", h.getCore().getLatestSchema().getFieldOrNull(uniqueKey)); String fieldName = "new_text_field"; assertNull("Field '" + fieldName + "' is present in the schema", h.getCore().getLatestSchema().getFieldOrNull(fieldName)); Map<String,Object> options = new HashMap<>(); IndexSchema oldSchema = h.getCore().getLatestSchema(); String fieldType = "text"; SchemaField newField = oldSchema.newField(fieldName, fieldType, options); IndexSchema newSchema = oldSchema.addField(newField); h.getCore().setLatestSchema(newSchema); assertU(adoc(fieldName, "thing", uniqueKey, "123")); assertU(commit()); assertQ(req(fieldName + ":thing"), "//*[@numFound='1']"); } public void testPersistUniqueKey() throws Exception { assertSchemaResource(collection, "managed-schema"); deleteCore(); File managedSchemaFile = new File(tmpConfDir, "managed-schema"); Files.delete(managedSchemaFile.toPath()); // Delete managed-schema so it won't block parsing a new schema System.setProperty("managed.schema.mutable", "true"); initCore("solrconfig-managed-schema.xml", "schema-one-field-no-dynamic-field-unique-key.xml", tmpSolrHome.getPath()); assertTrue(managedSchemaFile.exists()); String managedSchemaContents = FileUtils.readFileToString(managedSchemaFile, "UTF-8"); assertFalse(managedSchemaContents.contains("\"new_field\"")); Map<String,Object> options = new HashMap<>(); options.put("stored", "false"); IndexSchema oldSchema = h.getCore().getLatestSchema(); assertEquals("str", oldSchema.getUniqueKeyField().getName()); String fieldName = "new_field"; String fieldType = "string"; SchemaField newField = oldSchema.newField(fieldName, fieldType, options); IndexSchema newSchema = oldSchema.addField(newField); assertEquals("str", newSchema.getUniqueKeyField().getName()); h.getCore().setLatestSchema(newSchema); log.info("####close harness"); h.close(); log.info("####close harness end"); initCore(); assertTrue(managedSchemaFile.exists()); FileInputStream stream = new FileInputStream(managedSchemaFile); managedSchemaContents = IOUtils.toString(stream, "UTF-8"); stream.close(); // Explicitly close so that Windows can delete this file assertTrue(managedSchemaContents.contains("<field name=\"new_field\" type=\"string\" stored=\"false\"/>")); IndexSchema newNewSchema = h.getCore().getLatestSchema(); assertNotNull(newNewSchema.getUniqueKeyField()); assertEquals("str", newNewSchema.getUniqueKeyField().getName()); } public void testAddFieldThenReload() throws Exception { deleteCore(); File managedSchemaFile = new File(tmpConfDir, "managed-schema"); Files.delete(managedSchemaFile.toPath()); // Delete managed-schema so it won't block parsing a new schema System.setProperty("managed.schema.mutable", "true"); initCore("solrconfig-managed-schema.xml", "schema-one-field-no-dynamic-field.xml", tmpSolrHome.getPath()); String fieldName = "new_text_field"; assertNull("Field '" + fieldName + "' is present in the schema", h.getCore().getLatestSchema().getFieldOrNull(fieldName)); Map<String,Object> options = new HashMap<>(); IndexSchema oldSchema = h.getCore().getLatestSchema(); String fieldType = "text"; SchemaField newField = oldSchema.newField(fieldName, fieldType, options); IndexSchema newSchema = oldSchema.addField(newField); h.getCore().setLatestSchema(newSchema); h.reload(); } }