/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.server.store;
import com.foundationdb.NetworkOptions;
import com.foundationdb.directory.DirectoryLayer;
import com.foundationdb.directory.DirectorySubspace;
import com.foundationdb.server.error.ClusterFileNotReadableException;
import com.foundationdb.server.error.ClusterFileTooLargeException;
import com.foundationdb.server.error.NoClusterFileException;
import com.foundationdb.server.service.Service;
import com.foundationdb.server.service.config.ConfigurationService;
import com.foundationdb.Database;
import com.foundationdb.FDB;
import com.foundationdb.FDBException;
import com.foundationdb.TransactionContext;
import com.foundationdb.util.ArgumentValidation;
import com.google.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
public class FDBHolderImpl implements FDBHolder, Service {
private static final Logger LOG = LoggerFactory.getLogger(FDBHolderImpl.class);
private static final String CONFIG_API_VERSION = "fdbsql.fdb.api_version";
private static final String CONFIG_CLUSTER_FILE = "fdbsql.fdb.cluster_file";
private static final String CONFIG_TRACE_DIRECTORY = "fdbsql.fdb.trace_directory";
private static final String CONFIG_ROOT_DIR = "fdbsql.fdb.root_directory";
private static final String CONFIG_TLS_PLUGIN = "fdbsql.fdb.tls.plugin";
private static final String CONFIG_TLS_CERT_PATH = "fdbsql.fdb.tls.cert_path";
private static final String CONFIG_TLS_KEY_PATH = "fdbsql.fdb.tls.key_path";
private static final String CONFIG_TLS_VERIFY_PEERS = "fdbsql.fdb.tls.verify_peers";
private static final String CONFIG_KNOB_PREFIX = "fdbsql.fdb.knobs.";
private final ConfigurationService configService;
private int apiVersion;
private FDB fdb;
private Database db;
private DirectorySubspace rootDirectory;
@Inject
public FDBHolderImpl(ConfigurationService configService) {
this.configService = configService;
}
//
// Service
//
@Override
public void start() {
// Just one FDB for whole JVM and its dispose doesn't do anything.
if(fdb == null) {
apiVersion = Integer.parseInt(configService.getProperty(CONFIG_API_VERSION));
LOG.info("Starting with API Version {}", apiVersion);
fdb = FDB.selectAPIVersion(apiVersion);
// Legal to call more than once but will only apply the first time (network started once)
setOptions(fdb.options());
}
String clusterFile = configService.getProperty(CONFIG_CLUSTER_FILE);
boolean isDefault = clusterFile.isEmpty();
clusterFile = isDefault ? "DEFAULT" : clusterFile;
LOG.info("Opening cluster file {}", clusterFile);
try {
db = isDefault ? fdb.open() : fdb.open(clusterFile);
} catch (FDBException e) {
if (e.getCode() == 1515) { // no_cluster_file_found
throw new NoClusterFileException(clusterFile);
}
else if (e.getCode() == 1513) { // file_not_readable
throw new ClusterFileNotReadableException(clusterFile);
}
else if (e.getCode() == 1516) { // cluster_file_too_large
throw new ClusterFileTooLargeException(clusterFile);
}
throw e;
}
String rootDirName = configService.getProperty(CONFIG_ROOT_DIR);
List<String> rootDirPath = parseDirString(rootDirName);
rootDirectory = new DirectoryLayer().createOrOpen(getTransactionContext(), rootDirPath).get();
}
@Override
public void stop() {
if(db != null)
db.dispose();
db = null;
}
@Override
public void crash() {
stop();
}
//
// FDBHolder
//
@Override
public int getAPIVersion() {
return apiVersion;
}
@Override
public FDB getFDB() {
return fdb;
}
@Override
public Database getDatabase() {
return db;
}
@Override
public TransactionContext getTransactionContext() {
return db;
}
@Override
public DirectorySubspace getRootDirectory() {
return rootDirectory;
}
//
// Internal
//
private void setOptions(NetworkOptions options) {
String val = configService.getProperty(CONFIG_TRACE_DIRECTORY);
if (!val.isEmpty()) {
options.setTraceEnable(val);
}
val = configService.getProperty(CONFIG_TLS_PLUGIN);
if (!val.isEmpty()) {
options.setTLSPlugin(val);
}
val = configService.getProperty(CONFIG_TLS_CERT_PATH);
if (!val.isEmpty()) {
options.setTLSCertPath(val);
}
val = configService.getProperty(CONFIG_TLS_KEY_PATH);
if (!val.isEmpty()) {
options.setTLSKeyPath(val);
}
val = configService.getProperty(CONFIG_TLS_VERIFY_PEERS);
if (!val.isEmpty()) {
byte[] bytes = val.getBytes(Charset.forName("UTF8"));
options.setTLSVerifyPeers(bytes);
}
Properties knobs = configService.deriveProperties(CONFIG_KNOB_PREFIX);
for(String name : knobs.stringPropertyNames()) {
val = knobs.getProperty(name);
options.setKnob(String.format("%s=%s", name, val));
}
}
static List<String> parseDirString(String dirString) {
ArgumentValidation.notNull("dirString", dirString);
// Excess whitespace, ends with /, back to forward and deduplicate
String normalized = (dirString.trim() + "/").replace("\\", "/").replace("//", "/");
String[] parts = normalized.split("/");
return Arrays.asList(parts);
}
}