com.sap.hana.datalake.files.utils.filewatcher.CertificateWatcher Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of sap-hdlfs Show documentation
Show all versions of sap-hdlfs Show documentation
An implementation of org.apache.hadoop.fs.FileSystem targeting SAP HANA Data Lake Files.
// © 2023-2024 SAP SE or an SAP affiliate company. All rights reserved.
package com.sap.hana.datalake.files.utils.filewatcher;
import com.sap.hana.datalake.files.classification.InterfaceAudience;
import com.sap.hana.datalake.files.utils.threads.ThreadUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.hadoop.conf.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStoreException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicLong;
@InterfaceAudience.Private
public class CertificateWatcher {
@FunctionalInterface
public interface CertificateRotationHandler {
void handleCertificateUpdate() throws CertificateException, IOException, KeyStoreException;
}
private static final Logger LOG = LoggerFactory.getLogger(CertificateWatcher.class);
private static final AtomicLong INSTANCE_GLOBAL_INDEX = new AtomicLong(0);
private static final String THREAD_NAME_PREFIX_FORMAT = "CertificateWatcher-%d-thread";
protected final List certificateRotationHandlers;
private final Map watchedCertificates;
private final ExecutorService watcherThreadExecutor;
public CertificateWatcher(final List certificatePaths,
final CertificateWatcher.CertificateRotationHandler certificateRotationHandler,
final Configuration conf) {
this(certificatePaths, conf);
this.registerCertificateHandler(certificateRotationHandler);
}
public CertificateWatcher(final List certificatePaths, final Configuration conf) {
final String threadNamePrefix = String.format(THREAD_NAME_PREFIX_FORMAT, INSTANCE_GLOBAL_INDEX.getAndIncrement());
final ExecutorService watcherThreadExecutor = ThreadUtils.newSingleDaemonThreadExecutor(threadNamePrefix, conf);
this.certificateRotationHandlers = new ArrayList<>();
this.watchedCertificates = new HashMap<>();
this.watcherThreadExecutor = watcherThreadExecutor;
try {
this.setupWatchedCertificates(certificatePaths);
} catch (final IOException ex) {
final String errMsg = String.format("Could not initialize CertificateWatcher(certificatePaths=%s)", certificatePaths);
LOG.error(errMsg, ex);
throw new RuntimeException(errMsg);
}
}
public void start() {
final Set watchedDirectories = new HashSet<>();
for (final WatchedCertificate watchedCertificate : this.watchedCertificates.values()) {
final String certificateFilePath = watchedCertificate.getPath();
final String fileDir = Paths.get(certificateFilePath).toAbsolutePath().getParent().toString();
watchedDirectories.add(fileDir);
}
try {
final DirectoryWatcher directoryWatcher = new DirectoryWatcher(new ArrayList<>(watchedDirectories), LOG, this::handleModifyEvent);
this.watcherThreadExecutor.submit(directoryWatcher);
} catch (final FileWatcherException ex) {
final String errMsg = "Error occurred while setting up the Certificate Watcher";
LOG.error(errMsg, ex);
throw new RuntimeException(errMsg);
}
}
public void shutdown() {
this.watcherThreadExecutor.shutdownNow();
}
public void registerCertificateHandler(final CertificateRotationHandler certificateRotationHandler) {
this.certificateRotationHandlers.add(certificateRotationHandler);
}
protected String calculateChecksum(final String filePath) throws IOException {
try (final InputStream inputStream = Files.newInputStream(Paths.get(filePath))) {
return DigestUtils.sha256Hex(inputStream);
}
}
private void setupWatchedCertificates(final List certificatePaths) throws IOException {
for (final String certPath : certificatePaths) {
final String fileName = Paths.get(certPath).getFileName().toString();
this.watchedCertificates.put(fileName, new WatchedCertificate(certPath, this.calculateChecksum(certPath)));
}
}
private void handleModifyEvent(final String fileName) throws FileWatcherException {
final WatchedCertificate modifiedCertificate = this.watchedCertificates.get(fileName);
if (modifiedCertificate == null) {
LOG.debug("File [{}] has been modified but it is not a watched certificate; ignore", fileName);
return;
}
final String previousChecksum = modifiedCertificate.getChecksum();
try {
final String currentChecksum = this.calculateChecksum(modifiedCertificate.getPath());
if (currentChecksum.equals(previousChecksum)) {
LOG.debug("Certificate file [{}] has been modified, but its checksum has not changed; ignore", fileName);
return;
}
LOG.info("Certificate file [{}] has been modified; handling certificate rotation", fileName);
for (final CertificateRotationHandler certificateRotationHandler : this.certificateRotationHandlers) {
certificateRotationHandler.handleCertificateUpdate();
}
modifiedCertificate.setChecksum(currentChecksum);
} catch (final Exception ex) {
final String errMsg = String.format("Error handling certificate [%s] rotation", fileName);
LOG.error(errMsg, ex);
throw new FileWatcherException(errMsg, ex);
}
}
private static class WatchedCertificate {
private final String path;
private String checksum;
private WatchedCertificate(final String path, final String checksum) {
this.path = path;
this.checksum = checksum;
}
public String getPath() {
return this.path;
}
public String getChecksum() {
return this.checksum;
}
public void setChecksum(final String checksum) {
this.checksum = checksum;
}
}
}
// © 2023-2024 SAP SE or an SAP affiliate company. All rights reserved.
© 2015 - 2025 Weber Informatics LLC | Privacy Policy