/*
* Copyright 2014 Alexey Plotnik
*
* Licensed 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.stem.db;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.Predicate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.stem.utils.BBUtils;
import org.stem.utils.Utils;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.*;
public class MountPoint {
private static final Logger logger = LoggerFactory.getLogger(MountPoint.class);
public static final String ID_FILENAME = "id";
public UUID uuid;
public String path;
public long totalSpace;
public long usedSpace;
private Map<Integer, FatFile> fatFiles = new TreeMap<Integer, FatFile>(); // TODO: initial capacity
private DataTracker dataTracker;
public UUID getId() {
return uuid;
}
public DataTracker getDataTracker() {
return dataTracker;
}
private MountPoint(String path, long totalSpace, DataTracker dataTracker) {
this.path = path;
this.totalSpace = totalSpace;
this.dataTracker = dataTracker;
}
@VisibleForTesting
public MountPoint(UUID uuid, String path, long totalSpace, DataTracker dataTracker) {
this.uuid = uuid;
this.path = path;
this.totalSpace = totalSpace;
this.dataTracker = dataTracker;
}
public static MountPoint open(String path, DataTracker dataTracker) throws IOException {
return open(path, dataTracker, true);
}
public static MountPoint open(String directoryPath, DataTracker dataTracker, boolean init) throws IOException {
File dir = BBUtils.getDirectory(directoryPath);
long capacity = dir.getFreeSpace(); // For small fat files
MountPoint mountPoint = new MountPoint(directoryPath, capacity, dataTracker);
if (init) {
mountPoint.init();
}
return mountPoint;
}
public FatFile getFatFile(int index) {
return fatFiles.get(index);
}
private void init() throws IOException {
File dir = BBUtils.getDirectory(path);
File[] allFiles = dir.listFiles();
File[] fatFiles = listFatFiles();
uuid = Utils.readUuid(path + File.separator + ID_FILENAME);
if (0 != fatFiles.length) {
if (null == uuid)
throw new IOException("Ident file not found for mount point " + path);
} else {
assert null != allFiles;
if (0 != allFiles.length)
throw new IOException("Mount point " + path + " is not empty");
uuid = UUID.randomUUID();
Utils.writeUuid(this.uuid, path + File.separator + ID_FILENAME);
long maxAllocation = 0 != StorageNodeDescriptor.getMaxAllocationInMb()
? StorageNodeDescriptor.getMaxAllocationInMb() * 1024 * 1024
: totalSpace;
if (StorageNodeDescriptor.getAutoAllocate()) {
FatFileAllocator.allocateDirectory(
path,
StorageNodeDescriptor.getFatFileSizeInMb(),
maxAllocation,
StorageNodeDescriptor.getMarkOnAllocate());
} // TODO: what should we do in auto-allocation is turned off?
}
// TODO: open descriptors
// TODO: check fat file size with pre-configured
fatFiles = listFatFiles();
for (File fatFile : fatFiles) {
logger.info("Opening {}", fatFile.getAbsolutePath());
FatFile file = FatFile.open(fatFile.getAbsolutePath(), dataTracker);
this.fatFiles.put(file.id, file);
}
// keep in mind ACTIVE fat files during computing used space
this.usedSpace = findBlankOrActive().size() * StorageNodeDescriptor.getFatFileSizeInMb();
}
private UUID generateIdent() {
return UUID.randomUUID();
}
private File[] listFatFiles() throws IOException {
File dir = BBUtils.getDirectory(path);
return dir.listFiles(new FilenameFilter() {
@Override
public boolean accept(File dir, String name) {
return name.matches(FatFileAllocator.FAT_FILE_NAME_REGEX);
}
});
}
public void close() {
for (FatFile fatFile : fatFiles.values()) {
fatFile.close();
}
}
public Collection<FatFile> findBlankOrActive() {
Collection<FatFile> collection = CollectionUtils.select(fatFiles.values(), new Predicate<FatFile>() {
@Override
public boolean evaluate(FatFile file) {
return file.isBlank() || file.isActive();
}
});
List<FatFile> sortedList = new ArrayList<FatFile>(collection);
// Sort, ACTIVE files is placed first
Collections.sort(sortedList, new Comparator<FatFile>() {
@Override
public int compare(FatFile f1, FatFile f2) {
if (f1.getState() == f2.getState())
return f1.id.compareTo(f2.id);
else {
Integer state1 = f1.getState().ordinal();
Integer state2 = f2.getState().ordinal();
return state1.compareTo(state2);
}
}
});
return sortedList;
}
public Collection<FatFile> findFullOrActive() {
Collection<FatFile> collection = CollectionUtils.select(fatFiles.values(), new Predicate<FatFile>() {
@Override
public boolean evaluate(FatFile file) {
return file.isFull() || file.isActive();
}
});
List<FatFile> sortedList = new ArrayList<FatFile>(collection);
// Sort, ACTIVE files is placed first
Collections.sort(sortedList, new Comparator<FatFile>() {
@Override
public int compare(FatFile f1, FatFile f2) {
return f1.id.compareTo(f2.id);
}
});
return sortedList;
}
public Collection<FatFile> findFull() {
Collection<FatFile> collection = CollectionUtils.select(fatFiles.values(), new Predicate<FatFile>() {
@Override
public boolean evaluate(FatFile file) {
return file.isFull();
}
});
List<FatFile> sortedList = new ArrayList<FatFile>(collection);
Collections.sort(sortedList, new Comparator<FatFile>() {
@Override
public int compare(FatFile f1, FatFile f2) {
if (f1.getState() == f2.getState())
return f1.id.compareTo(f2.id);
else {
Integer state1 = f1.getState().ordinal();
Integer state2 = f2.getState().ordinal();
return state1.compareTo(state2);
}
}
});
return sortedList;
}
public Collection<FatFile> findReadyForCompaction() {
Collection<FatFile> full = findFull();
return CollectionUtils.select(full, new Predicate<FatFile>() {
@Override
public boolean evaluate(FatFile file) {
return null != dataTracker.deletesPerFFCountMap.get(file.id);
}
});
}
public long getTotalSizeInBytes() {
return dataTracker.getTotalSizeInBytes();
}
public long getLiveSizeInBytes() {
return dataTracker.getLiveSizeInBytes();
}
public long getAllocatedSizeInBytes() {
return dataTracker.getAllocatedSizeInBytes();
}
public String getPath() {
return path;
}
}