azkaban.storage.StorageManager Maven / Gradle / Ivy
/*
* Copyright 2017 LinkedIn Corp.
*
* 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 azkaban.storage;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static java.util.Objects.requireNonNull;
import azkaban.project.Project;
import azkaban.project.ProjectFileHandler;
import azkaban.project.ProjectLoader;
import azkaban.spi.Storage;
import azkaban.spi.StorageException;
import azkaban.spi.StorageMetadata;
import azkaban.user.User;
import azkaban.utils.Md5Hasher;
import azkaban.utils.Props;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
/**
* StorageManager manages and coordinates all interactions with the Storage layer. This also
* includes bookkeeping like updating DB with the new versionm, etc
*/
@Singleton
public class StorageManager {
private static final Logger log = Logger.getLogger(StorageManager.class);
private final StorageCleaner storageCleaner;
private final Storage storage;
private final ProjectLoader projectLoader;
private final File tempDir;
@Inject
public StorageManager(final Props props, final Storage storage,
final ProjectLoader projectLoader,
final StorageCleaner storageCleaner) {
this.tempDir = new File(props.getString("project.temp.dir", "temp"));
this.storage = requireNonNull(storage, "storage is null");
this.projectLoader = requireNonNull(projectLoader, "projectLoader is null");
this.storageCleaner = requireNonNull(storageCleaner, "storageCleanUp is null");
prepareTempDir();
}
private void prepareTempDir() {
if (!this.tempDir.exists()) {
this.tempDir.mkdirs();
}
checkArgument(this.tempDir.isDirectory());
}
/**
* API to a project file into Azkaban Storage
*
* TODO clean up interface
*
* @param project project
* @param version The new version to be uploaded
* @param localFile local file
* @param uploader the user who uploaded
*/
public void uploadProject(
final Project project,
final int version,
final File localFile,
final User uploader) {
byte[] md5 = null;
if (!(this.storage instanceof DatabaseStorage)) {
md5 = computeHash(localFile);
}
final StorageMetadata metadata = new StorageMetadata(
project.getId(),
version,
uploader.getUserId(),
md5);
log.info(String.format("Adding archive to storage. Meta:%s File: %s[%d bytes]",
metadata, localFile.getName(), localFile.length()));
/* upload to storage */
final String resourceId = this.storage.put(metadata, localFile);
/* Add metadata to db */
// TODO spyne: remove hack. Database storage should go through the same flow
if (!(this.storage instanceof DatabaseStorage)) {
this.projectLoader.addProjectVersion(
project.getId(),
version,
localFile,
uploader.getUserId(),
requireNonNull(md5),
requireNonNull(resourceId)
);
log.info(String.format("Added project metadata to DB. Meta:%s File: %s[%d bytes] URI: %s",
metadata, localFile.getName(), localFile.length(), resourceId));
}
}
/**
* Clean up project artifacts based on project ID. See {@link StorageCleaner#cleanupProjectArtifacts(int)}
*/
public void cleanupProjectArtifacts(final int projectId) {
try {
this.storageCleaner.cleanupProjectArtifacts(projectId);
} catch (final Exception e) {
log.error("Error occured during cleanup. Ignoring and continuing...", e);
}
}
private byte[] computeHash(final File localFile) {
final byte[] md5;
try {
md5 = Md5Hasher.md5Hash(localFile);
} catch (final IOException e) {
throw new StorageException(e);
}
return md5;
}
/**
* Fetch project file from storage.
*
* @param projectId required project ID
* @param version version to be fetched
* @return Handler object containing hooks to fetched project file
*/
public ProjectFileHandler getProjectFile(final int projectId, final int version) {
log.info(
String.format("Fetching project file. project ID: %d version: %d", projectId, version));
// TODO spyne: remove huge hack ! There should not be any special handling for Database Storage.
if (this.storage instanceof DatabaseStorage) {
return ((DatabaseStorage) this.storage).get(projectId, version);
}
/* Fetch meta data from db */
final ProjectFileHandler pfh = this.projectLoader.fetchProjectMetaData(projectId, version);
/* Fetch project file from storage and copy to local file */
final String resourceId = requireNonNull(pfh.getResourceId(),
String.format("URI is null. project ID: %d version: %d",
pfh.getProjectId(), pfh.getVersion()));
try (InputStream is = this.storage.get(resourceId)) {
final File file = createTempOutputFile(pfh);
/* Copy from storage to output stream */
try (FileOutputStream fos = new FileOutputStream(file)) {
IOUtils.copy(is, fos);
}
/* Validate checksum */
validateChecksum(file, pfh);
/* Attach file to handler */
pfh.setLocalFile(file);
return pfh;
} catch (final IOException e) {
throw new StorageException(e);
}
}
private void validateChecksum(final File file, final ProjectFileHandler pfh) throws IOException {
final byte[] hash = Md5Hasher.md5Hash(file);
checkState(Arrays.equals(pfh.getMd5Hash(), hash),
String.format("MD5 HASH Failed. project ID: %d version: %d Expected: %s Actual: %s",
pfh.getProjectId(), pfh.getVersion(),
new String(pfh.getMd5Hash(), StandardCharsets.UTF_8),
new String(hash, StandardCharsets.UTF_8))
);
}
private File createTempOutputFile(final ProjectFileHandler projectFileHandler)
throws IOException {
return File.createTempFile(
projectFileHandler.getFileName(),
String.valueOf(projectFileHandler.getVersion()), this.tempDir);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy