blob: 4f2a5051285952cc08c7c5dd67f132df453a7ca2 [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.core;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.lang.invoke.MethodHandles;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitOption;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.common.collect.Lists;
import org.apache.solr.common.SolrException;
import org.apache.solr.util.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Persists CoreDescriptors as properties files
*/
public class CorePropertiesLocator implements CoresLocator {
public static final String PROPERTIES_FILENAME = "core.properties";
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
private final Path rootDirectory;
public CorePropertiesLocator(Path coreDiscoveryRoot) {
this.rootDirectory = coreDiscoveryRoot;
log.debug("Config-defined core root directory: {}", this.rootDirectory);
}
@Override
public void create(CoreContainer cc, CoreDescriptor... coreDescriptors) {
for (CoreDescriptor cd : coreDescriptors) {
Path propertiesFile = cd.getInstanceDir().resolve(PROPERTIES_FILENAME);
if (Files.exists(propertiesFile))
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Could not create a new core in " + cd.getInstanceDir()
+ " as another core is already defined there");
writePropertiesFile(cd, propertiesFile);
}
}
// TODO, this isn't atomic! If we crash in the middle of a rename, we
// could end up with two cores with identical names, in which case one of
// them won't start up. Are we happy with this?
@Override
public void persist(CoreContainer cc, CoreDescriptor... coreDescriptors) {
for (CoreDescriptor cd : coreDescriptors) {
Path propFile = cd.getInstanceDir().resolve(PROPERTIES_FILENAME);
writePropertiesFile(cd, propFile);
}
}
private void writePropertiesFile(CoreDescriptor cd, Path propfile) {
Properties p = buildCoreProperties(cd);
try {
FileUtils.createDirectories(propfile.getParent()); // Handling for symlinks.
try (Writer os = new OutputStreamWriter(Files.newOutputStream(propfile), StandardCharsets.UTF_8)) {
p.store(os, "Written by CorePropertiesLocator");
}
}
catch (IOException e) {
log.error("Couldn't persist core properties to {}: ", propfile, e);
throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
"Couldn't persist core properties to " + propfile.toAbsolutePath().toString() + " : " + e.getMessage());
}
}
@Override
public void delete(CoreContainer cc, CoreDescriptor... coreDescriptors) {
if (coreDescriptors == null) {
return;
}
for (CoreDescriptor cd : coreDescriptors) {
if (cd == null) continue;
Path propfile = cd.getInstanceDir().resolve(PROPERTIES_FILENAME);
try {
Files.deleteIfExists(propfile);
} catch (IOException e) {
log.warn("Couldn't delete core properties file {}: ", propfile, e);
}
}
}
@Override
public void rename(CoreContainer cc, CoreDescriptor oldCD, CoreDescriptor newCD) {
String oldName = newCD.getPersistableStandardProperties().getProperty(CoreDescriptor.CORE_NAME);
String newName = newCD.coreProperties.getProperty(CoreDescriptor.CORE_NAME);
if (oldName == null ||
(newName != null && oldName.equals(newName) == false)) {
newCD.getPersistableStandardProperties().put(CoreDescriptor.CORE_NAME, newName);
}
persist(cc, newCD);
}
@Override
public void swap(CoreContainer cc, CoreDescriptor cd1, CoreDescriptor cd2) {
persist(cc, cd1, cd2);
}
@Override
public List<CoreDescriptor> discover(final CoreContainer cc) {
log.debug("Looking for core definitions underneath {}", rootDirectory);
final List<CoreDescriptor> cds = Lists.newArrayList();
try {
Set<FileVisitOption> options = new HashSet<>();
options.add(FileVisitOption.FOLLOW_LINKS);
final int maxDepth = 256;
Files.walkFileTree(this.rootDirectory, options, maxDepth, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (file.getFileName().toString().equals(PROPERTIES_FILENAME)) {
CoreDescriptor cd = buildCoreDescriptor(file, cc);
if (cd != null) {
if (log.isDebugEnabled()) {
log.debug("Found core {} in {}", cd.getName(), cd.getInstanceDir());
}
cds.add(cd);
}
return FileVisitResult.SKIP_SIBLINGS;
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
// if we get an error on the root, then fail the whole thing
// otherwise, log a warning and continue to try and load other cores
if (file.equals(rootDirectory)) {
log.error("Error reading core root directory {}: {}", file, exc);
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Error reading core root directory");
}
log.warn("Error visiting {}: {}", file, exc);
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Couldn't walk file tree under " + this.rootDirectory, e);
}
if (log.isInfoEnabled()) {
log.info("Found {} core definitions underneath {}", cds.size(), rootDirectory);
}
if (cds.size() > 0) {
if (log.isInfoEnabled()) {
log.info("Cores are: {}", cds.stream().map(CoreDescriptor::getName).collect(Collectors.toList()));
}
}
return cds;
}
protected CoreDescriptor buildCoreDescriptor(Path propertiesFile, CoreContainer cc) {
Path instanceDir = propertiesFile.getParent();
Properties coreProperties = new Properties();
try (InputStream fis = Files.newInputStream(propertiesFile)) {
coreProperties.load(new InputStreamReader(fis, StandardCharsets.UTF_8));
String name = createName(coreProperties, instanceDir);
Map<String, String> propMap = new HashMap<>();
for (String key : coreProperties.stringPropertyNames()) {
propMap.put(key, coreProperties.getProperty(key));
}
CoreDescriptor ret = new CoreDescriptor(name, instanceDir, propMap, cc.getContainerProperties(), cc.getZkController());
ret.loadExtraProperties();
return ret;
}
catch (IOException e) {
log.error("Couldn't load core descriptor from {}:", propertiesFile, e);
return null;
}
}
protected static String createName(Properties p, Path instanceDir) {
return p.getProperty(CoreDescriptor.CORE_NAME, instanceDir.getFileName().toString());
}
protected Properties buildCoreProperties(CoreDescriptor cd) {
Properties p = new Properties();
p.putAll(cd.getPersistableStandardProperties());
p.putAll(cd.getPersistableUserProperties());
return p;
}
}