/*
* 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.drill.exec.store.sys.store;
import static org.apache.drill.exec.ExecConstants.DRILL_SYS_FILE_SUFFIX;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.annotation.Nullable;
import org.apache.commons.io.IOUtils;
import org.apache.drill.common.collections.ImmutableEntry;
import org.apache.drill.common.concurrent.AutoCloseableLock;
import org.apache.drill.common.config.DrillConfig;
import org.apache.drill.exec.exception.VersionMismatchException;
import org.apache.drill.exec.store.dfs.DrillFileSystem;
import org.apache.drill.exec.store.sys.BasePersistentStore;
import org.apache.drill.exec.store.sys.PersistentStoreConfig;
import org.apache.drill.exec.store.sys.PersistentStoreMode;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LocalPersistentStore<V> extends BasePersistentStore<V> {
private static final Logger logger = LoggerFactory.getLogger(LocalPersistentStore.class);
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final AutoCloseableLock readLock = new AutoCloseableLock(readWriteLock.readLock());
private final AutoCloseableLock writeLock = new AutoCloseableLock(readWriteLock.writeLock());
private final Path basePath;
private final PersistentStoreConfig<V> config;
private final DrillFileSystem fs;
private int version = -1;
public LocalPersistentStore(DrillFileSystem fs, Path base, PersistentStoreConfig<V> config) {
super();
this.basePath = new Path(base, config.getName());
this.config = config;
this.fs = fs;
try {
if (!fs.mkdirs(basePath)) {
version++;
}
} catch (IOException e) {
throw new RuntimeException("Failure setting pstore configuration path.");
}
}
@Override
public PersistentStoreMode getMode() {
return PersistentStoreMode.PERSISTENT;
}
public static Path getLogDir() {
String drillLogDir = System.getenv("DRILL_LOG_DIR");
if (drillLogDir == null) {
drillLogDir = System.getProperty("drill.log.dir");
}
if (drillLogDir == null) {
drillLogDir = "/var/log/drill";
}
return new Path(new File(drillLogDir).getAbsoluteFile().toURI());
}
public static DrillFileSystem getFileSystem(DrillConfig config, Path root) throws IOException {
Path blobRoot = root == null ? getLogDir() : root;
Configuration fsConf = new Configuration();
if (blobRoot.toUri().getScheme() != null) {
fsConf.set(FileSystem.FS_DEFAULT_NAME_KEY, blobRoot.toUri().toString());
}
DrillFileSystem fs = new DrillFileSystem(fsConf);
fs.mkdirs(blobRoot);
return fs;
}
@Override
public Iterator<Map.Entry<String, V>> getRange(int skip, int take) {
try (AutoCloseableLock lock = readLock.open()) {
try {
List<FileStatus> f = fs.list(false, basePath);
if (f == null || f.isEmpty()) {
return Collections.emptyIterator();
}
List<String> files = Lists.newArrayList();
for (FileStatus stat : f) {
String s = stat.getPath().getName();
if (s.endsWith(DRILL_SYS_FILE_SUFFIX)) {
files.add(s.substring(0, s.length() - DRILL_SYS_FILE_SUFFIX.length()));
}
}
Collections.sort(files);
return Iterables.transform(Iterables.limit(Iterables.skip(files, skip), take), new Function<String, Entry<String, V>>() {
@Nullable
@Override
public Entry<String, V> apply(String key) {
return new ImmutableEntry<>(key, get(key));
}
}).iterator();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private Path makePath(String name) {
Preconditions.checkArgument(
!name.contains("/") &&
!name.contains(":") &&
!name.contains(".."));
return new Path(basePath, name + DRILL_SYS_FILE_SUFFIX);
}
@Override
public boolean contains(String key) {
return contains(key, null);
}
@Override
public boolean contains(String key, DataChangeVersion dataChangeVersion) {
try (AutoCloseableLock lock = readLock.open()) {
try {
Path path = makePath(key);
boolean exists = fs.exists(path);
if (exists && dataChangeVersion != null) {
dataChangeVersion.setVersion(version);
}
return exists;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Override
public V get(String key) {
return get(key, null);
}
@Override
public V get(String key, DataChangeVersion dataChangeVersion) {
try (AutoCloseableLock lock = readLock.open()) {
try {
if (dataChangeVersion != null) {
dataChangeVersion.setVersion(version);
}
Path path = makePath(key);
if (!fs.exists(path)) {
return null;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
final Path path = makePath(key);
try (InputStream is = fs.open(path)) {
return config.getSerializer().deserialize(IOUtils.toByteArray(is));
} catch (IOException e) {
throw new RuntimeException("Unable to deserialize \"" + path + "\"", e);
}
}
}
@Override
public void put(String key, V value) {
put(key, value, null);
}
@Override
public void put(String key, V value, DataChangeVersion dataChangeVersion) {
try (AutoCloseableLock lock = writeLock.open()) {
if (dataChangeVersion != null && dataChangeVersion.getVersion() != version) {
throw new VersionMismatchException("Version mismatch detected", dataChangeVersion.getVersion());
}
try (OutputStream os = fs.create(makePath(key))) {
IOUtils.write(config.getSerializer().serialize(value), os);
version++;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Override
public boolean putIfAbsent(String key, V value) {
try (AutoCloseableLock lock = writeLock.open()) {
try {
Path p = makePath(key);
if (fs.exists(p)) {
return false;
} else {
try (OutputStream os = fs.create(makePath(key))) {
IOUtils.write(config.getSerializer().serialize(value), os);
version++;
}
return true;
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void delete(String key) {
try (AutoCloseableLock lock = writeLock.open()) {
try {
fs.delete(makePath(key), false);
version++;
} catch (IOException e) {
logger.error("Unable to delete data from storage.", e);
throw new RuntimeException(e);
}
}
}
@Override
public void close() {
}
}