io.cdap.cdap.internal.app.worker.sidecar.ArtifactLocalizerCleaner Maven / Gradle / Ivy
/*
* Copyright © 2021 Cask Data, Inc.
* 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 io.cdap.cdap.internal.app.worker.sidecar;
import io.cdap.cdap.common.utils.DirUtils;
import io.cdap.cdap.common.utils.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import javax.validation.constraints.NotNull;
/**
* Artifact cleaner that will deleted out-of-date cache entries that were localized by {@link ArtifactLocalizer}
*/
public class ArtifactLocalizerCleaner implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(ArtifactLocalizerCleaner.class);
private final Path cacheDir;
private final int cacheCleanupInterval;
public ArtifactLocalizerCleaner(Path cacheDir, int cacheCleanupInterval) {
this.cacheDir = cacheDir;
this.cacheCleanupInterval = cacheCleanupInterval;
}
@Override
public void run() {
try {
cleanupArtifactCache(cacheDir.toFile());
} catch (Exception e) {
LOG.warn("ArtifactLocalizerService failed to clean up cache. Will retry again in {} minutes: {}",
cacheCleanupInterval, e);
}
}
/**
* Recursively looks for out of date artifact jar files in cacheDir. Once a jar file is found, we will first attempt
* to delete the unpacked directory for this jar (if it exists) and then delete the jar if and only if that was
* successful.
*
* @param cacheDir The directory that contains the cached jar files
*/
private void cleanupArtifactCache(@NotNull File cacheDir) throws IOException {
List timestamps = new ArrayList<>();
// Scan all files in the current directory, if its a directory recurse, otherwise add the timestamp value to a list
List files = DirUtils.listFiles(cacheDir);
for (File file : files) {
if (file.isDirectory()) {
cleanupArtifactCache(file);
continue;
}
// We're only interested in jar files
String fileName = file.getName();
if (!fileName.endsWith(".jar")) {
continue;
}
try {
timestamps.add(Long.parseLong(FileUtils.getNameWithoutExtension(fileName)));
} catch (NumberFormatException e) {
// If we encounter a file that doesn't have a timestamp as the filename
LOG.warn("Encountered unexpected file during artifact cache cleanup: {}. This file will be deleted.", file);
Files.deleteIfExists(file.toPath());
}
}
// If there are less than two timestamp files then we don't need to clean anything
if (timestamps.size() < 2) {
return;
}
// Only keep the file that has the highest timestamp (the newest file)
String maxTimestamp = timestamps.stream().max(Long::compare).get().toString();
List filesToDelete = DirUtils.listFiles(cacheDir, (dir, name) -> !name.startsWith(maxTimestamp));
for (File file : filesToDelete) {
// Only delete the jar file if we successfully deleted the unpacked directory for this artifact
if (deleteUnpackedCacheDir(file)) {
Files.deleteIfExists(file.toPath());
LOG.debug("Deleted JAR file {}", file);
}
}
}
/**
* Deletes the unpacked directory that corresponds to the given jar file
*
* @param jarPath the artifact jar file which will be used to construct the unpacked directory path
* @return true if the delete was successful or the unpacked directory does not exist, false otherwise
*/
private boolean deleteUnpackedCacheDir(@NotNull File jarPath) {
String unpackedPath = jarPath.getParentFile().getPath().replaceFirst("artifacts", "unpacked");
String unpackedDirName = FileUtils.getNameWithoutExtension(jarPath.getName());
// If this directory doesn't exist then this artifact was never unpacked and we can return true
File dirPath = Paths.get(unpackedPath, unpackedDirName).toFile();
if (!dirPath.exists()) {
LOG.debug("Unpacked directory does not exist, no need to delete: {}", dirPath.getPath());
return true;
}
try {
DirUtils.deleteDirectoryContents(dirPath);
LOG.debug("Successfully deleted unpacked directory {}", dirPath.getPath());
return true;
} catch (IOException e) {
LOG.warn("Encountered error when attempting to delete unpacked dir {}: {}", dirPath.getPath(), e);
}
return false;
}
}