/**
*
*/
package org.jscsi.target.storage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
/*
import org.jclouds.ContextBuilder;
import org.jclouds.blobstore.BlobStore;
import org.jclouds.blobstore.BlobStoreContext;
import org.jclouds.blobstore.domain.Blob;
import org.jclouds.domain.Location;
import org.jclouds.filesystem.reference.FilesystemConstants;
*/
import com.google.common.annotations.Beta;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
/**
* JClouds-Binding to store blocks as buckets in clouds-backends. This class
* utilizes caching as well as multithreaded writing to improve performance.
*
* @author Sebastian Graf, University of Konstanz
*
*/
@Beta
public class JCloudsStorageModule implements IStorageModule {
// // START DEBUG CODE
// private final static File writeFile = new
// File("/Users/sebi/Desktop/writeaccess.txt");
// private final static File readFile = new
// File("/Users/sebi/Desktop/readaccess.txt");
// private final static File uploadFile = new
// File("/Users/sebi/Desktop/uploadaccess.txt");
// private final static File downloadFile = new
// File("/Users/sebi/Desktop/downloadaccess.txt");
// static final FileWriter writer;
// static final FileWriter reader;
// static final FileWriter upload;
// static final FileWriter download;
// static {
// try {
// writer = new FileWriter(writeFile);
// reader = new FileWriter(readFile);
// upload = new FileWriter(uploadFile);
// download = new FileWriter(downloadFile);
// } catch (IOException e) {
// throw new RuntimeException(e);
// }
// }
/** Number of Blocks in one Cluster. */
public static final int BLOCK_IN_CLUSTER = 512;
private static final int BUCKETS_TO_PREFETCH = 3;
private static final boolean ENCRYPT = false;
private static final String ALGO = "AES";
private static byte[] keyValue = new byte[] {
'k', 'k', 'k', 'k', 'k', 'k', 'k', 'k', 'k', 'k', 'k', 'k', 'k', 'k', 'k', 'k'
};
private static final Key KEY = new SecretKeySpec(keyValue, "AES");
/** Number of Bytes in Bucket. */
public final static int SIZE_PER_BUCKET = BLOCK_IN_CLUSTER * DEFAULT_BLOCK_SIZE;
public final static String CONTAINERNAME = "grave9283708";
private final long mNumberOfCluster;
/*
private final String mContainerName;
private final BlobStore mStore;
private final BlobStoreContext mContext;
*/
private final Cache<Integer, byte[]> mByteCache;
private int lastIndexWritten;
private byte[] lastBlobWritten;
private final CompletionService<Integer> mWriterService;
private final CompletionService<Map.Entry<Integer, byte[]>> mReaderService;
private final ConcurrentHashMap<Integer, Future<Integer>> mRunningWriteTasks;
private final ConcurrentHashMap<Integer, Future<Map.Entry<Integer, byte[]>>> mRunningReadTasks;
/**
* Creates a new {@link JCloudsStorageModule} backed by the specified file.
* If no such file exists, a {@link FileNotFoundException} will be thrown.
*
* @param pSizeInBlocks
* blocksize for this module
* @param pFile
* local storage, not used over here
*
*/
public JCloudsStorageModule(final long pSizeInBlocks, final File pFile) {
// number * 512 = size in bytes
// 4gig, bench for iozone and bonnie++
// mNumberOfCluster = 8388608 / BLOCK_IN_CLUSTER;
// 512m, bench for fio
mNumberOfCluster = 1048576 / BLOCK_IN_CLUSTER;
/*
mContainerName = CONTAINERNAME;
String[] credentials = getCredentials();
if (credentials.length == 0) {
Properties properties = new Properties();
properties.setProperty(FilesystemConstants.PROPERTY_BASEDIR, pFile.getAbsolutePath());
mContext =
ContextBuilder.newBuilder("filesystem").overrides(properties).credentials("testUser",
"testPass").buildView(BlobStoreContext.class);
} else {
mContext =
ContextBuilder.newBuilder("aws-s3").credentials(getCredentials()[0], getCredentials()[1])
.buildView(BlobStoreContext.class);
}
// Create Container
mStore = mContext.getBlobStore();
if (!mStore.containerExists(mContainerName)) {
Location locToSet = null;
for (Location loc : mStore.listAssignableLocations()) {
if (loc.getId().equals("eu-west-1")) {
locToSet = loc;
break;
}
}
// System.out.println(locToSet);
mStore.createContainerInLocation(locToSet, mContainerName);
}
*/
final ExecutorService writerService = Executors.newFixedThreadPool(20);
final ExecutorService readerService = Executors.newFixedThreadPool(20);
mRunningWriteTasks = new ConcurrentHashMap<Integer, Future<Integer>>();
mRunningReadTasks = new ConcurrentHashMap<Integer, Future<Map.Entry<Integer, byte[]>>>();
mReaderService = new ExecutorCompletionService<Map.Entry<Integer, byte[]>>(readerService);
final Thread readHashmapCleaner = new Thread(new ReadFutureCleaner());
readHashmapCleaner.setDaemon(true);
readHashmapCleaner.start();
mWriterService = new ExecutorCompletionService<Integer>(writerService);
final Thread writeHashmapCleaner = new Thread(new WriteFutureCleaner());
writeHashmapCleaner.setDaemon(true);
writeHashmapCleaner.start();
mByteCache = CacheBuilder.newBuilder().maximumSize(100).build();
lastIndexWritten = -1;
}
/**
* {@inheritDoc}
*/
@Override
public int checkBounds(long logicalBlockAddress, int transferLengthInBlocks) {
if (logicalBlockAddress < 0 || logicalBlockAddress >= getSizeInBlocks()) {
return 1;
} else
// if the logical block address is in bounds but the transferlength
// either exceeds
// the device size or is faulty return 2
if (transferLengthInBlocks < 0 || logicalBlockAddress + transferLengthInBlocks > getSizeInBlocks()) {
return 2;
} else {
return 0;
}
}
/**
* {@inheritDoc}
*/
@Override
public long getSizeInBlocks() {
return mNumberOfCluster * BLOCK_IN_CLUSTER;
}
/**
* {@inheritDoc}
*/
@Override
public int getBlockSize() {
return DEFAULT_BLOCK_SIZE;
}
/**
* {@inheritDoc}
*/
@Override
public synchronized void read(ByteBuffer bytes, long storageIndex) throws IOException {
final int bucketIndex = (int)(storageIndex / SIZE_PER_BUCKET);
final int bucketOffset = (int)(storageIndex % SIZE_PER_BUCKET);
try {
storeBucket(-1, null);
// // DEBUG CODE
// reader.write(bucketIndex + "," + storageIndex + "," +
// bucketOffset + "," + bytes.length +
// "\n");
// reader.flush();
byte[] data = mByteCache.getIfPresent(bucketIndex);
if (data == null) {
data = getAndprefetchBuckets(bucketIndex);
}
final ByteArrayDataOutput output = ByteStreams.newDataOutput(bytes.capacity());
int length = -1;
if (bucketOffset + bytes.capacity() > SIZE_PER_BUCKET) {
length = SIZE_PER_BUCKET - bucketOffset;
} else {
length = bytes.capacity();
}
output.write(data, bucketOffset, length);
if (bucketOffset + bytes.capacity() > SIZE_PER_BUCKET) {
data = mByteCache.getIfPresent(bucketIndex + 1);
if (data == null) {
data = getAndprefetchBuckets(bucketIndex + 1);
}
output.write(data, 0, bytes.capacity() - (SIZE_PER_BUCKET - bucketOffset));
}
// OODRIVE : de-activate ByteBuffer direct or re-coded with channel
System.arraycopy(output.toByteArray(), 0, bytes.array(), 0, bytes.capacity());
} catch (Exception exc) {
throw new IOException(exc);
}
}
private final byte[] getAndprefetchBuckets(final int pBucketStartId) throws InterruptedException,
ExecutionException {
byte[] returnval = null;
Future<Map.Entry<Integer, byte[]>> startTask = null;
for (int i = pBucketStartId; i < pBucketStartId + BUCKETS_TO_PREFETCH; i++) {
Future<Map.Entry<Integer, byte[]>> currentTask = mRunningReadTasks.remove(i);
if (currentTask == null) {
currentTask = mReaderService.submit(new ReadTask(i));
mRunningReadTasks.put(i, currentTask);
}
if (i == pBucketStartId) {
startTask = currentTask;
}
}
returnval = startTask.get().getValue();
return returnval;
}
private final void storeBucket(int pBucketId, byte[] pData) throws InterruptedException,
ExecutionException {
if (lastIndexWritten != pBucketId && lastBlobWritten != null) {
Future<Integer> writeTask = mRunningWriteTasks.remove(lastIndexWritten);
if (writeTask != null) {
writeTask.cancel(false);
}
mRunningWriteTasks.put(lastIndexWritten, mWriterService.submit(new WriteTask(lastBlobWritten,
lastIndexWritten)));
}
lastIndexWritten = pBucketId;
lastBlobWritten = pData;
}
/**
* {@inheritDoc}
*
* @throws Exception
*/
@Override
public synchronized void write(ByteBuffer bytes, long storageIndex) throws IOException {
final int bucketIndex = (int)(storageIndex / SIZE_PER_BUCKET);
final int bucketOffset = (int)(storageIndex % SIZE_PER_BUCKET);
try {
// // DEBUG CODE
// writer.write(bucketIndex + "," + storageIndex + "," +
// bucketOffset + "," + bytes.length +
// "\n");
// writer.flush();
byte[] data = mByteCache.getIfPresent(bucketIndex);
if (data == null) {
data = getAndprefetchBuckets(bucketIndex);
}
System.arraycopy(bytes, 0, data, bucketOffset, bytes.capacity() + bucketOffset > SIZE_PER_BUCKET
? SIZE_PER_BUCKET - bucketOffset : bytes.capacity());
storeBucket(bucketIndex, data);
mByteCache.put(bucketIndex, data);
if (bucketOffset + bytes.capacity() > SIZE_PER_BUCKET) {
data = mByteCache.getIfPresent(bucketIndex + 1);
if (data == null) {
data = getAndprefetchBuckets(bucketIndex + 1);
}
System.arraycopy(bytes.array(), SIZE_PER_BUCKET - bucketOffset, data, 0, bytes.capacity()
- (SIZE_PER_BUCKET - bucketOffset));
storeBucket(bucketIndex + 1, data);
mByteCache.put(bucketIndex + 1, data);
}
} catch (Exception exc) {
throw new IOException(exc);
}
}
/**
* {@inheritDoc}
*/
@Override
public void close() throws IOException {
/*
mContext.close();
*/
}
/**
* Getting credentials for aws from homedir/.credentials
*
* @return a two-dimensional String[] with login and password
*/
/*
private static String[] getCredentials() {
return new String[0];
// File userStore =
// new File(System.getProperty("user.home"), new StringBuilder(".credentials")
// .append(File.separator).append("aws.properties").toString());
// if (!userStore.exists()) {
// return new String[0];
// } else {
// Properties props = new Properties();
// try {
// props.load(new FileReader(userStore));
// return new String[] {
// props.getProperty("access"), props.getProperty("secret")
// };
//
// } catch (IOException exc) {
// throw new RuntimeException(exc);
// }
// }
}
*/
/**
* Single task to write data to the cloud.
*
* @author Sebastian Graf, University of Konstanz
*
*/
class ReadTask implements Callable<Map.Entry<Integer, byte[]>> {
final Cipher mCipher;
/**
* Bucket ID to be read.
*/
final int mBucketId;
ReadTask(final int pBucketId) {
if (ENCRYPT) {
try {
mCipher = Cipher.getInstance(ALGO);
mCipher.init(Cipher.DECRYPT_MODE, KEY);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
throw new RuntimeException(e);
}
} else {
mCipher = null;
}
this.mBucketId = pBucketId;
}
@Override
public Map.Entry<Integer, byte[]> call() throws Exception {
/*
byte[] data = mByteCache.getIfPresent(mBucketId);
if (data == null) {
// long time = System.currentTimeMillis();
Blob blob = mStore.getBlob(mContainerName, Integer.toString(mBucketId));
if (blob == null) {
data = new byte[SIZE_PER_BUCKET];
// // DEBUG CODE
// download.write(Integer.toString(mBucketId) + ", empty, "
// + (System.currentTimeMillis() - time) + "\n");
// download.flush();
} else {
data = ByteStreams.toByteArray(blob.getPayload().getInput());
// download.write(Integer.toString(mBucketId) + "," +
// data.length + " , "
// + (System.currentTimeMillis() - time) + "\n");
// download.flush();
while (data.length == 0) {
blob = mStore.getBlob(mContainerName, Integer.toString(mBucketId));
data = ByteStreams.toByteArray(blob.getPayload().getInput());
// // DEBUG CODE
// download.write(Integer.toString(mBucketId) + "," +
// data.length + " , "
// + (System.currentTimeMillis() - time) + "\n");
// download.flush();
}
if (ENCRYPT) {
data = mCipher.doFinal(data);
}
}
}
mByteCache.put(mBucketId, data);
final byte[] finalizedData = data;
return new Map.Entry<Integer, byte[]>() {
@Override
public byte[] setValue(byte[] value) {
throw new UnsupportedOperationException();
}
@Override
public byte[] getValue() {
return finalizedData;
}
@Override
public Integer getKey() {
return mBucketId;
}
};
*/
return null;
}
}
/**
* Single task to write data to the cloud.
*
* @author Sebastian Graf, University of Konstanz
*
*/
class WriteTask implements Callable<Integer> {
/**
* The bytes to buffer.
*/
final byte[] mData;
final int mBucketIndex;
final Cipher mCipher;
WriteTask(byte[] pData, int pBucketIndex) {
if (ENCRYPT) {
try {
mCipher = Cipher.getInstance(ALGO);
mCipher.init(Cipher.ENCRYPT_MODE, KEY);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException e) {
throw new RuntimeException(e);
}
} else {
mCipher = null;
}
this.mData = pData;
this.mBucketIndex = pBucketIndex;
}
@Override
public Integer call() throws Exception {
/*
boolean finished = false;
while (!finished) {
try {
// long time = System.currentTimeMillis();
byte[] data = mData;
if (ENCRYPT) {
data = mCipher.doFinal(mData);
}
Blob blob = mStore.blobBuilder(Integer.toString(mBucketIndex)).build();
blob.setPayload(data);
mStore.putBlob(mContainerName, blob);
// // DEBUG CODE
// upload.write(Integer.toString(mBucketIndex) + ", " +
// (System.currentTimeMillis() -
// time)
// + "\n");
// upload.flush();
} catch (Exception exc) {
}
finished = true;
}
*/
return mBucketIndex;
}
}
class ReadFutureCleaner extends Thread {
public void run() {
while (true) {
try {
Future<Map.Entry<Integer, byte[]>> element = mReaderService.take();
if (!element.isCancelled()) {
mRunningReadTasks.remove(element.get().getKey());
}
} catch (Exception exc) {
throw new RuntimeException(exc);
}
}
}
}
class WriteFutureCleaner extends Thread {
public void run() {
while (true) {
try {
Future<Integer> element = mWriterService.take();
if (!element.isCancelled()) {
mRunningWriteTasks.remove(element.get());
}
} catch (Exception exc) {
throw new RuntimeException(exc);
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public boolean isWriteProtected() {
return false;
}
}