package com.intuit.tank.storage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import com.amazonaws.ClientConfiguration; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.AccessControlList; import com.amazonaws.services.s3.model.AmazonS3Exception; import com.amazonaws.services.s3.model.Bucket; import com.amazonaws.services.s3.model.ListObjectsRequest; import com.amazonaws.services.s3.model.ObjectListing; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.PutObjectRequest; import com.amazonaws.services.s3.model.S3Object; import com.amazonaws.services.s3.model.S3ObjectInputStream; import com.amazonaws.services.s3.model.S3ObjectSummary; import com.intuit.tank.vm.settings.CloudCredentials; import com.intuit.tank.vm.settings.CloudProvider; import com.intuit.tank.vm.settings.TankConfig; /** * FileStorage that writes to the file system. * * @author denisa * */ public class S3FileStorage implements FileStorage, Serializable { private static final long serialVersionUID = 1L; private static final Logger LOG = LogManager.getLogger(S3FileStorage.class); private String bucketName; private String extraPath; private boolean compress = true; private boolean encrypt = true; private AmazonS3Client s3Client; /** * @param basePath */ public S3FileStorage(String bucketName, boolean compress) { super(); parseBucketName(bucketName); this.compress = compress; try { TankConfig tankConfig = new TankConfig(); encrypt = tankConfig.isS3EncryptionEnabled(); CloudCredentials creds = tankConfig.getVmManagerConfig().getCloudCredentials(CloudProvider.amazon); ClientConfiguration config = new ClientConfiguration(); if (StringUtils.isNotBlank(System.getProperty("http.proxyHost"))) { try { config.setProxyHost(System.getProperty("http.proxyHost")); if (StringUtils.isNotBlank(System.getProperty("http.proxyPort"))) { config.setProxyPort(Integer.valueOf(System.getProperty("http.proxyPort"))); } } catch (NumberFormatException e) { LOG.error("invalid proxy setup."); } } if (StringUtils.isNotBlank(creds.getKeyId()) && StringUtils.isNotBlank(creds.getKey())) { BasicAWSCredentials credentials = new BasicAWSCredentials(creds.getKeyId(), creds.getKey()); this.s3Client = new AmazonS3Client(credentials, config); } else { this.s3Client = new AmazonS3Client(config); } createBucket(bucketName); } catch (Exception ex) { LOG.error(ex.getMessage(), ex); throw new RuntimeException(ex); } } private void parseBucketName(String name) { if (name.indexOf('/') == -1) { bucketName = name; extraPath = ""; } else { bucketName = name.substring(0, name.indexOf('/')); extraPath = name.substring(name.indexOf('/')); } extraPath = FilenameUtils.separatorsToUnix(FilenameUtils.normalize(extraPath + "/")); } @Override public void storeFileData(FileData fileData, InputStream in) { String path = FilenameUtils.separatorsToUnix(FilenameUtils.normalize(extraPath + fileData.getPath() + "/" + fileData.getFileName())); path = StringUtils.stripStart(path, "/"); OutputStream out = null; try { ObjectMetadata metaData = new ObjectMetadata(); if (encrypt) { metaData.setSSEAlgorithm(ObjectMetadata.AES_256_SERVER_SIDE_ENCRYPTION); } if (fileData.getAttributes() != null) { for (Entry<String, String> entry : fileData.getAttributes().entrySet()) { metaData.addUserMetadata(entry.getKey(), entry.getValue()); } } if (compress) { File tmp = File.createTempFile("tmp-", ".gz"); out = new GZIPOutputStream(new FileOutputStream(tmp)); IOUtils.copy(in, out); IOUtils.closeQuietly(out); in = new FileInputStream(tmp); } s3Client.putObject(bucketName, path, in, metaData); } catch (Exception e) { LOG.error("Error storing file: " + e, e); throw new RuntimeException(e); } finally { if (out != null) { IOUtils.closeQuietly(out); IOUtils.closeQuietly(in); } } } @Override public InputStream readFileData(FileData fileData) { String path = FilenameUtils.separatorsToUnix(FilenameUtils.normalize(extraPath + fileData.getPath() + "/" + fileData.getFileName())); path = StringUtils.stripStart(path, "/"); InputStream ret = null; S3ObjectInputStream objectContent = null; try { S3Object object = s3Client.getObject(bucketName, path); if (object != null) { ByteArrayOutputStream temp = new ByteArrayOutputStream(); objectContent = object.getObjectContent(); IOUtils.copy(objectContent, temp); ret = new ByteArrayInputStream(temp.toByteArray()); if (compress) { ret = new GZIPInputStream(ret); } } } catch (Exception e) { LOG.error("Error getting File: " + e, e); throw new RuntimeException(e); } finally { IOUtils.closeQuietly(objectContent); } return ret; } private void createBucket(String bucketName) { AccessControlList configuration = null; try { configuration = s3Client.getBucketAcl(bucketName); } catch (Exception e) { LOG.info("Bucket " + bucketName + " does not exist."); } if (configuration == null) { Bucket bucket = s3Client.createBucket(bucketName); LOG.info("Created bucket " + bucket.getName() + " at " + bucket.getCreationDate()); } } @Override public boolean exists(FileData fileData) { boolean ret = true; String path = FilenameUtils.separatorsToUnix(FilenameUtils.normalize(extraPath + fileData.getPath() + "/" + fileData.getFileName())); path = StringUtils.stripStart(path, "/"); try { s3Client.getObjectMetadata(bucketName, path); } catch (AmazonS3Exception e) { ret = false; } return ret; } @Override public List<FileData> listFileData(String path) { List<FileData> ret = new ArrayList<FileData>(); try { String prefix = extraPath + path; if (!prefix.endsWith("/")) { prefix = prefix + "/"; } prefix = StringUtils.removeStart(prefix, "/"); ListObjectsRequest listObjectsRequest = new ListObjectsRequest().withBucketName(bucketName).withPrefix(prefix); listObjectsRequest.setDelimiter("/"); ObjectListing objectListing; do { objectListing = s3Client.listObjects(listObjectsRequest); for (S3ObjectSummary objectSummary : objectListing.getObjectSummaries()) { String fileName = FilenameUtils.getName(FilenameUtils.normalize(objectSummary.getKey())); if (StringUtils.isNotBlank(fileName)) { ret.add(new FileData(path, fileName)); } } listObjectsRequest.setMarker(objectListing.getNextMarker()); } while (objectListing.isTruncated()); } catch (AmazonS3Exception e) { LOG.error("Error Listing Files: " + e, e); throw new RuntimeException(e); } return ret; } @Override public boolean delete(FileData fileData) { boolean ret = true; String path = FilenameUtils.separatorsToUnix(FilenameUtils.normalize(fileData.getPath() + "/" + fileData.getFileName())); path = StringUtils.stripStart(path, "/"); try { s3Client.deleteObject(bucketName, path); } catch (AmazonS3Exception e) { ret = false; } return ret; } }