You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by tp...@apache.org on 2021/08/19 16:13:33 UTC
[nifi] branch main updated: NIFI-8752: Automatic diagnostic at NiFi
restart/stop
This is an automated email from the ASF dual-hosted git repository.
tpalfy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 9bcbf83 NIFI-8752: Automatic diagnostic at NiFi restart/stop
9bcbf83 is described below
commit 9bcbf83e5a73ec471f2dcf081e7ca4ed00b45eb2
Author: Lehel Boér <Le...@hotmail.com>
AuthorDate: Tue Jun 29 17:03:59 2021 +0200
NIFI-8752: Automatic diagnostic at NiFi restart/stop
This closes #5195.
Signed-off-by: Tamas Palfy <ta...@gmail.com>
---
.../java/org/apache/nifi/util/NiFiProperties.java | 68 +++++++--
.../src/main/asciidoc/administration-guide.adoc | 27 +++-
.../src/main/resources/conf/nifi.properties | 20 +++
.../src/main/java/org/apache/nifi/NiFi.java | 165 ++++++++++++---------
.../java/org/apache/nifi/util/DiagnosticUtils.java | 98 ++++++++++++
5 files changed, 293 insertions(+), 85 deletions(-)
diff --git a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
index a7bda62..c68bb5b 100644
--- a/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
+++ b/nifi-commons/nifi-properties/src/main/java/org/apache/nifi/util/NiFiProperties.java
@@ -303,6 +303,18 @@ public class NiFiProperties extends ApplicationProperties {
public static final String MONITOR_LONG_RUNNING_TASK_SCHEDULE = "nifi.monitor.long.running.task.schedule";
public static final String MONITOR_LONG_RUNNING_TASK_THRESHOLD = "nifi.monitor.long.running.task.threshold";
+ // automatic diagnostic properties
+ public static final String DIAGNOSTICS_ON_SHUTDOWN_ENABLED = "nifi.diagnostics.on.shutdown.enabled";
+ public static final String DIAGNOSTICS_ON_SHUTDOWN_VERBOSE = "nifi.diagnostics.on.shutdown.verbose";
+ public static final String DIAGNOSTICS_ON_SHUTDOWN_DIRECTORY = "nifi.diagnostics.on.shutdown.directory";
+ public static final String DIAGNOSTICS_ON_SHUTDOWN_MAX_FILE_COUNT = "nifi.diagnostics.on.shutdown.max.filecount";
+ public static final String DIAGNOSTICS_ON_SHUTDOWN_MAX_DIRECTORY_SIZE = "nifi.diagnostics.on.shutdown.max.directory.size";
+
+ // automatic diagnostic defaults
+ public static final String DEFAULT_DIAGNOSTICS_ON_SHUTDOWN_DIRECTORY = "./diagnostics";
+ public static final int DEFAULT_DIAGNOSTICS_ON_SHUTDOWN_MAX_FILE_COUNT = 10;
+ public static final String DEFAULT_DIAGNOSTICS_ON_SHUTDOWN_MAX_DIRECTORY_SIZE = "10 MB";
+
// defaults
public static final Boolean DEFAULT_AUTO_RESUME_STATE = true;
public static final String DEFAULT_AUTHORIZER_CONFIGURATION_FILE = "conf/authorizers.xml";
@@ -770,6 +782,7 @@ public class NiFiProperties extends ApplicationProperties {
/**
* Returns true if auto reload of the keystore and truststore is enabled.
+ *
* @return true if auto reload of the keystore and truststore is enabled.
*/
public boolean isSecurityAutoReloadEnabled() {
@@ -778,6 +791,7 @@ public class NiFiProperties extends ApplicationProperties {
/**
* Returns the auto reload interval of the keystore and truststore.
+ *
* @return The interval over which the keystore and truststore should auto-reload.
*/
public String getSecurityAutoReloadInterval() {
@@ -1076,7 +1090,7 @@ public class NiFiProperties extends ApplicationProperties {
return Collections.emptyList();
} else {
List<String> fallbackClaims = Arrays.asList(rawProperty.split(","));
- return fallbackClaims.stream().map(String::trim).filter(s->!s.isEmpty()).collect(Collectors.toList());
+ return fallbackClaims.stream().map(String::trim).filter(s -> !s.isEmpty()).collect(Collectors.toList());
}
}
@@ -1084,6 +1098,32 @@ public class NiFiProperties extends ApplicationProperties {
return Boolean.parseBoolean(getProperty(WEB_SHOULD_SEND_SERVER_VERSION, DEFAULT_WEB_SHOULD_SEND_SERVER_VERSION));
}
+ // Automatic diagnostic getters
+
+ public boolean isDiagnosticsOnShutdownEnabled() {
+ return Boolean.parseBoolean(getProperty(DIAGNOSTICS_ON_SHUTDOWN_ENABLED));
+ }
+
+ public boolean isDiagnosticsOnShutdownVerbose() {
+ return Boolean.parseBoolean(getProperty(DIAGNOSTICS_ON_SHUTDOWN_VERBOSE));
+ }
+
+ public String getDiagnosticsOnShutdownDirectory() {
+ return getProperty(DIAGNOSTICS_ON_SHUTDOWN_DIRECTORY, DEFAULT_DIAGNOSTICS_ON_SHUTDOWN_DIRECTORY);
+ }
+
+ public int getDiagnosticsOnShutdownMaxFileCount() {
+ try {
+ return Integer.parseInt(getProperty(DIAGNOSTICS_ON_SHUTDOWN_MAX_FILE_COUNT));
+ } catch (NumberFormatException e) {
+ return DEFAULT_DIAGNOSTICS_ON_SHUTDOWN_MAX_FILE_COUNT;
+ }
+ }
+
+ public String getDiagnosticsOnShutdownDirectoryMaxSize() {
+ return getProperty(DIAGNOSTICS_ON_SHUTDOWN_MAX_DIRECTORY_SIZE, DEFAULT_DIAGNOSTICS_ON_SHUTDOWN_MAX_DIRECTORY_SIZE);
+ }
+
/**
* Returns whether Knox SSO is enabled.
*
@@ -1165,7 +1205,7 @@ public class NiFiProperties extends ApplicationProperties {
/**
* The name of an attribute in the SAML assertions that contains the user identity.
- *
+ * <p>
* If not specified, or missing, the NameID of the Subject will be used.
*
* @return the attribute name containing the user identity
@@ -1587,17 +1627,17 @@ public class NiFiProperties extends ApplicationProperties {
public boolean isZooKeeperTlsConfigurationPresent() {
return StringUtils.isNotBlank(getProperty(NiFiProperties.ZOOKEEPER_CLIENT_SECURE))
- && StringUtils.isNotBlank(getProperty(NiFiProperties.ZOOKEEPER_SECURITY_KEYSTORE))
- && getProperty(NiFiProperties.ZOOKEEPER_SECURITY_KEYSTORE_PASSWD) != null
- && StringUtils.isNotBlank(getProperty(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE))
- && getProperty(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE_PASSWD) != null;
+ && StringUtils.isNotBlank(getProperty(NiFiProperties.ZOOKEEPER_SECURITY_KEYSTORE))
+ && getProperty(NiFiProperties.ZOOKEEPER_SECURITY_KEYSTORE_PASSWD) != null
+ && StringUtils.isNotBlank(getProperty(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE))
+ && getProperty(NiFiProperties.ZOOKEEPER_SECURITY_TRUSTSTORE_PASSWD) != null;
}
public boolean isTlsConfigurationPresent() {
return StringUtils.isNotBlank(getProperty(SECURITY_KEYSTORE))
- && getProperty(SECURITY_KEYSTORE_PASSWD) != null
- && StringUtils.isNotBlank(getProperty(SECURITY_TRUSTSTORE))
- && getProperty(SECURITY_TRUSTSTORE_PASSWD) != null;
+ && getProperty(SECURITY_KEYSTORE_PASSWD) != null
+ && StringUtils.isNotBlank(getProperty(SECURITY_TRUSTSTORE))
+ && getProperty(SECURITY_TRUSTSTORE_PASSWD) != null;
}
public String getFlowFileRepoEncryptionKeyId() {
@@ -1915,7 +1955,6 @@ public class NiFiProperties extends ApplicationProperties {
*
* @param prefix The exact string the returned properties should start with. Dots are considered, thus prefix "item" will return both
* properties starting with "item." and "items". Properties with empty value will be included as well.
- *
* @return A map of properties starting with the prefix.
*/
public Map<String, String> getPropertiesWithPrefix(final String prefix) {
@@ -1924,13 +1963,12 @@ public class NiFiProperties extends ApplicationProperties {
/**
* Returns with all the possible next "tokens" after the given prefix. An alphanumeric string between dots is considered as a "token".
- *
+ * <p>
* For example if there are "parent.sub1" and a "parent.sub2" properties are set, and the prefix is "parent", the method will return
* with a set, consisting of "sub1" and "sub2. Only directly subsequent tokens are considered, so in case of "parent.sub1.subsub1", the
* result will contain "sub1" as well.
*
* @param prefix The prefix of the request.
- *
* @return A set of direct subsequent tokens.
*/
public Set<String> getDirectSubsequentTokens(final String prefix) {
@@ -1951,9 +1989,9 @@ public class NiFiProperties extends ApplicationProperties {
* file specified cannot be found/read a runtime exception will be thrown.
* If one is not specified an empty object will be returned.
*
- * @param propertiesFilePath if provided properties will be loaded from
- * given file; else will be loaded from System property.
- * Can be null. Passing {@code ""} skips any attempt to load from the file system.
+ * @param propertiesFilePath if provided properties will be loaded from
+ * given file; else will be loaded from System property.
+ * Can be null. Passing {@code ""} skips any attempt to load from the file system.
* @return NiFiProperties
*/
public static NiFiProperties createBasicNiFiProperties(final String propertiesFilePath) {
diff --git a/nifi-docs/src/main/asciidoc/administration-guide.adoc b/nifi-docs/src/main/asciidoc/administration-guide.adoc
index 95fcaa3..8c7897c 100644
--- a/nifi-docs/src/main/asciidoc/administration-guide.adoc
+++ b/nifi-docs/src/main/asciidoc/administration-guide.adoc
@@ -2640,8 +2640,13 @@ configured recipients whenever NiFi is started.
defined in the `notification.services.file` property. The services with the specified identifiers will be used to notify their
configured recipients whenever NiFi is stopped.
|`nifi.died.notification.services`|This property is a comma-separated list of Notification Service identifiers that correspond to the Notification Services
-defined in the `notification.services.file` property. The services with the specified identifiers will be used to notify their
-configured recipients if the bootstrap determines that NiFi has unexpectedly died.
+ defined in the `notification.services.file` property. The services with the specified identifiers will be used to notify their
+ configured recipients if the bootstrap determines that NiFi has unexpectedly died.
+|`nifi.diagnostics.on.shutdown.enabled`|(true or false) This property decides whether to run NiFi diagnostics before shutting down.
+|`nifi.diagnostics.on.shutdown.verbose`|(true or false) This property decides whether to run NiFi diagnostics in verbose mode.
+|`nifi.diagnostics.on.shutdown.directory`|This property specifies the location of the NiFi diagnostics directory.
+|`nifi.diagnostics.on.shutdown.max.filecount`|This property specifies the maximum permitted number of diagnostic files. If the limit is exceeded, the oldest files are deleted.
+|`nifi.diagnostics.on.shutdown.max.directory.size`|This property specifies the maximum permitted size of the diagnostics directory. If the limit is exceeded, the oldest files are deleted.
|====
[[notification_services]]
@@ -4262,3 +4267,21 @@ Example configuration:
nifi.nar.library.provider.hdfs2.implementation=org.apache.nifi.nar.hadoop.HDFSNarProvider
nifi.nar.library.provider.hdfs2.resources=/etc/hadoop/core-site.xml
nifi.nar.library.provider.hdfs2.source.directory=/other/dir/for/customNars
+
+== NiFi diagnostics
+
+It is possible to run diagnostics on NiFi with
+
+```
+$ ./bin/nifi.sh --diagnostics --verbose <dumpfilePath>
+```
+
+During the diagnostic, NiFi sends a request to an already running NiFi instance, which collects information about clusters,
+components, part of the configuration, memory usage, etc., and writes it to the specified file or, failing that, to the logs.
+
+The verbose switch is optional and can be used to control the level of diagnostic detail. In case of a missing dump file path, NiFi writes the diagnostics information to the bootstrap.log file.
+
+=== Automatic diagnostics on restart and shutdown
+
+NiFi supports automatic diagnostics in the event of a shutdown. The feature is disabled by default. The settings can be found in the nifi.properties file and the feature can be enabled there also.
+In the case of a lengthy diagnostic, NiFi may terminate before the diagnostics are completed. In this case, the graceful.shutdown.seconds property should be set to a higher value in the bootstrap.conf.
\ No newline at end of file
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
index b5d9a6a..272d15f 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-resources/src/main/resources/conf/nifi.properties
@@ -327,3 +327,23 @@ nifi.analytics.connection.model.score.threshold=${nifi.analytics.connection.mode
# runtime monitoring properties
nifi.monitor.long.running.task.schedule=
nifi.monitor.long.running.task.threshold=
+
+# Create automatic diagnostics when stopping/restarting NiFi.
+
+# Enable automatic diagnostic at shutdown.
+nifi.diagnostics.on.shutdown.enabled=false
+
+# Include verbose diagnostic information.
+nifi.diagnostics.on.shutdown.verbose=false
+
+# The location of the diagnostics folder.
+nifi.diagnostics.on.shutdown.directory=./diagnostics
+
+# The maximum number of files permitted in the directory. If the limit is exceeded, the oldest files are deleted.
+nifi.diagnostics.on.shutdown.max.filecount=10
+
+# The diagnostics folder's maximum permitted size in bytes. If the limit is exceeded, the oldest files are deleted.
+nifi.diagnostics.on.shutdown.max.directory.size=10 MB
+
+
+
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-runtime/src/main/java/org/apache/nifi/NiFi.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-runtime/src/main/java/org/apache/nifi/NiFi.java
index 445217d..01e1c71 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-runtime/src/main/java/org/apache/nifi/NiFi.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-runtime/src/main/java/org/apache/nifi/NiFi.java
@@ -17,11 +17,14 @@
package org.apache.nifi;
import org.apache.nifi.bundle.Bundle;
+import org.apache.nifi.diagnostics.DiagnosticsDump;
import org.apache.nifi.nar.ExtensionMapping;
import org.apache.nifi.nar.NarClassLoaders;
import org.apache.nifi.nar.NarClassLoadersHolder;
import org.apache.nifi.nar.NarUnpacker;
import org.apache.nifi.nar.SystemBundle;
+import org.apache.nifi.processor.DataUnit;
+import org.apache.nifi.util.DiagnosticUtils;
import org.apache.nifi.util.FileUtils;
import org.apache.nifi.util.NiFiProperties;
import org.slf4j.Logger;
@@ -29,9 +32,10 @@ import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
import java.io.File;
+import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
-import java.lang.Thread.UncaughtExceptionHandler;
+import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
@@ -39,7 +43,10 @@ import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -54,33 +61,38 @@ import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Stream;
public class NiFi implements NiFiEntryPoint {
+ public static final String BOOTSTRAP_PORT_PROPERTY = "nifi.bootstrap.listen.port";
+ public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss");
+
private static final Logger LOGGER = LoggerFactory.getLogger(NiFi.class);
private static final String KEY_FILE_FLAG = "-K";
+
private final NiFiServer nifiServer;
private final BootstrapListener bootstrapListener;
+ private final NiFiProperties properties;
- public static final String BOOTSTRAP_PORT_PROPERTY = "nifi.bootstrap.listen.port";
private volatile boolean shutdown = false;
public NiFi(final NiFiProperties properties)
- throws ClassNotFoundException, IOException, NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
-
+ throws ClassNotFoundException, IOException, IllegalArgumentException {
this(properties, ClassLoader.getSystemClassLoader());
-
}
public NiFi(final NiFiProperties properties, ClassLoader rootClassLoader)
- throws ClassNotFoundException, IOException, NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
+ throws ClassNotFoundException, IOException, IllegalArgumentException {
+
+ this.properties = properties;
// There can only be one krb5.conf for the overall Java process so set this globally during
// start up so that processors and our Kerberos authentication code don't have to set this
final File kerberosConfigFile = properties.getKerberosConfigurationFile();
if (kerberosConfigFile != null) {
final String kerberosConfigFilePath = kerberosConfigFile.getAbsolutePath();
- LOGGER.info("Setting java.security.krb5.conf to {}", new Object[]{kerberosConfigFilePath});
+ LOGGER.info("Setting java.security.krb5.conf to {}", kerberosConfigFilePath);
System.setProperty("java.security.krb5.conf", kerberosConfigFilePath);
}
@@ -164,8 +176,8 @@ public class NiFi implements NiFiEntryPoint {
}
final long duration = System.nanoTime() - startTime;
- LOGGER.info("Controller initialization took " + duration + " nanoseconds "
- + "(" + (int) TimeUnit.SECONDS.convert(duration, TimeUnit.NANOSECONDS) + " seconds).");
+ LOGGER.info("Controller initialization took {} nanoseconds ( {} seconds).",
+ duration, (int) TimeUnit.SECONDS.convert(duration, TimeUnit.NANOSECONDS));
}
}
@@ -174,23 +186,17 @@ public class NiFi implements NiFiEntryPoint {
}
protected void setDefaultUncaughtExceptionHandler() {
- Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
- @Override
- public void uncaughtException(final Thread t, final Throwable e) {
- LOGGER.error("An Unknown Error Occurred in Thread {}: {}", t, e.toString());
- LOGGER.error("", e);
- }
+ Thread.setDefaultUncaughtExceptionHandler((thread, exception) -> {
+ LOGGER.error("An Unknown Error Occurred in Thread {}: {}", thread, exception.toString());
+ LOGGER.error("", exception);
});
}
protected void addShutdownHook() {
- Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
- @Override
- public void run() {
+ Runtime.getRuntime().addShutdownHook(new Thread(() ->
// shutdown the jetty server
- shutdownHook(false);
- }
- }));
+ shutdownHook(false)
+ ));
}
protected void initLogging() {
@@ -201,8 +207,8 @@ public class NiFi implements NiFiEntryPoint {
private static ClassLoader createBootstrapClassLoader() {
//Get list of files in bootstrap folder
final List<URL> urls = new ArrayList<>();
- try {
- Files.list(Paths.get("lib/bootstrap")).forEach(p -> {
+ try (final Stream<Path> files = Files.list(Paths.get("lib/bootstrap"))) {
+ files.forEach(p -> {
try {
urls.add(p.toUri().toURL());
} catch (final MalformedURLException mef) {
@@ -216,14 +222,40 @@ public class NiFi implements NiFiEntryPoint {
return new URLClassLoader(urls.toArray(new URL[0]), Thread.currentThread().getContextClassLoader());
}
- public void shutdownHook(boolean isReload) {
+ public void shutdownHook(final boolean isReload) {
try {
+ runDiagnosticsOnShutdown();
shutdown();
} catch (final Throwable t) {
- LOGGER.warn("Problem occurred ensuring Jetty web server was properly terminated due to " + t);
+ LOGGER.warn("Problem occurred ensuring Jetty web server was properly terminated due to ", t);
}
}
+ private void runDiagnosticsOnShutdown() throws IOException {
+ if (properties.isDiagnosticsOnShutdownEnabled()) {
+ final String diagnosticDirectoryPath = properties.getDiagnosticsOnShutdownDirectory();
+ final boolean isCreated = DiagnosticUtils.createDiagnosticDirectory(diagnosticDirectoryPath);
+ if (isCreated) {
+ LOGGER.debug("Diagnostic directory has successfully been created.");
+ }
+ while (DiagnosticUtils.isFileCountExceeded(diagnosticDirectoryPath, properties.getDiagnosticsOnShutdownMaxFileCount())
+ || DiagnosticUtils.isSizeExceeded(diagnosticDirectoryPath, DataUnit.parseDataSize(properties.getDiagnosticsOnShutdownDirectoryMaxSize(), DataUnit.B).longValue())) {
+ final Path oldestFile = DiagnosticUtils.getOldestFile(diagnosticDirectoryPath);
+ Files.delete(oldestFile);
+ }
+ final String fileName = String.format("%s/diagnostic-%s.log", diagnosticDirectoryPath, DATE_TIME_FORMATTER.format(LocalDateTime.now()));
+ diagnose(new File(fileName), properties.isDiagnosticsOnShutdownVerbose());
+ }
+ }
+
+ private void diagnose(final File file, final boolean verbose) throws IOException {
+ final DiagnosticsDump diagnosticsDump = getServer().getDiagnosticsFactory().create(verbose);
+ try (final OutputStream fileOutputStream = new FileOutputStream(file)) {
+ diagnosticsDump.writeTo(fileOutputStream);
+ }
+ }
+
+
protected void shutdown() {
this.shutdown = true;
@@ -249,8 +281,8 @@ public class NiFi implements NiFiEntryPoint {
private final ThreadFactory defaultFactory = Executors.defaultThreadFactory();
@Override
- public Thread newThread(final Runnable r) {
- final Thread t = defaultFactory.newThread(r);
+ public Thread newThread(final Runnable runnable) {
+ final Thread t = defaultFactory.newThread(runnable);
t.setDaemon(true);
t.setName("Detect Timing Issues");
return t;
@@ -259,18 +291,15 @@ public class NiFi implements NiFiEntryPoint {
final AtomicInteger occurrencesOutOfRange = new AtomicInteger(0);
final AtomicInteger occurrences = new AtomicInteger(0);
- final Runnable command = new Runnable() {
- @Override
- public void run() {
- final long curMillis = System.currentTimeMillis();
- final long difference = curMillis - lastTriggerMillis.get();
- final long millisOff = Math.abs(difference - 2000L);
- occurrences.incrementAndGet();
- if (millisOff > 500L) {
- occurrencesOutOfRange.incrementAndGet();
- }
- lastTriggerMillis.set(curMillis);
+ final Runnable command = () -> {
+ final long curMillis = System.currentTimeMillis();
+ final long difference = curMillis - lastTriggerMillis.get();
+ final long millisOff = Math.abs(difference - 2000L);
+ occurrences.incrementAndGet();
+ if (millisOff > 500L) {
+ occurrencesOutOfRange.incrementAndGet();
}
+ lastTriggerMillis.set(curMillis);
};
final ScheduledFuture<?> future = service.scheduleWithFixedDelay(command, 2000L, 2000L, TimeUnit.MILLISECONDS);
@@ -384,38 +413,38 @@ public class NiFi implements NiFiEntryPoint {
throw new IllegalArgumentException("The bootstrap process provided the " + KEY_FILE_FLAG + " flag but no key");
}
try {
- String passwordfile_path = parsedArgs.get(i + 1);
- // Slurp in the contents of the file:
- byte[] encoded = Files.readAllBytes(Paths.get(passwordfile_path));
- key = new String(encoded,StandardCharsets.UTF_8);
- if (0 == key.length())
- throw new IllegalArgumentException("Key in keyfile " + passwordfile_path + " yielded an empty key");
-
- LOGGER.info("Now overwriting file in "+passwordfile_path);
-
- // Overwrite the contents of the file (to avoid littering file system
- // unlinked with key material):
- File password_file = new File(passwordfile_path);
- FileWriter overwriter = new FileWriter(password_file,false);
-
- // Construe a random pad:
- Random r = new Random();
- StringBuffer sb = new StringBuffer();
- // Note on correctness: this pad is longer, but equally sufficient.
- while(sb.length() < encoded.length){
- sb.append(Integer.toHexString(r.nextInt()));
- }
- String pad = sb.toString();
- LOGGER.info("Overwriting key material with pad: "+pad);
- overwriter.write(pad);
- overwriter.close();
-
- LOGGER.info("Removing/unlinking file: "+passwordfile_path);
- password_file.delete();
+ String passwordfilePath = parsedArgs.get(i + 1);
+ // Slurp in the contents of the file:
+ byte[] encoded = Files.readAllBytes(Paths.get(passwordfilePath));
+ key = new String(encoded, StandardCharsets.UTF_8);
+ if (0 == key.length())
+ throw new IllegalArgumentException("Key in keyfile " + passwordfilePath + " yielded an empty key");
+
+ LOGGER.info("Now overwriting file in {}", passwordfilePath);
+
+ // Overwrite the contents of the file (to avoid littering file system
+ // unlinked with key material):
+ File passwordFile = new File(passwordfilePath);
+ FileWriter overwriter = new FileWriter(passwordFile, false);
+
+ // Construe a random pad:
+ Random random = new Random();
+ StringBuffer sb = new StringBuffer();
+ // Note on correctness: this pad is longer, but equally sufficient.
+ while (sb.length() < encoded.length) {
+ sb.append(Integer.toHexString(random.nextInt()));
+ }
+ String pad = sb.toString();
+ LOGGER.info("Overwriting key material with pad: {}", pad);
+ overwriter.write(pad);
+ overwriter.close();
+
+ LOGGER.info("Removing/unlinking file: {}", passwordfilePath);
+ passwordFile.delete();
} catch (IOException e) {
- LOGGER.error("Caught IOException while retrieving the "+KEY_FILE_FLAG+"-passed keyfile; aborting: "+e.toString());
- System.exit(1);
+ LOGGER.error("Caught IOException while retrieving the {} -passed keyfile; aborting: {}", KEY_FILE_FLAG, e.toString());
+ System.exit(1);
}
LOGGER.info("Read property protection key from key file provided by bootstrap process");
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-runtime/src/main/java/org/apache/nifi/util/DiagnosticUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-runtime/src/main/java/org/apache/nifi/util/DiagnosticUtils.java
new file mode 100644
index 0000000..9cc47ae
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-runtime/src/main/java/org/apache/nifi/util/DiagnosticUtils.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.nifi.util;
+
+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.Comparator;
+import java.util.Optional;
+import java.util.function.ToLongFunction;
+import java.util.stream.Stream;
+
+public final class DiagnosticUtils {
+
+ private static final Logger logger = LoggerFactory.getLogger(DiagnosticUtils.class);
+
+ private DiagnosticUtils() {
+ // utility class, not meant to be instantiated
+ }
+
+ public static Path getOldestFile(final String diagnosticDirectoryPath) throws IOException {
+ Comparator<? super Path> lastModifiedComparator = Comparator.comparingLong(p -> p.toFile().lastModified());
+
+ final Optional<Path> oldestFile;
+
+ try (Stream<Path> paths = Files.walk(Paths.get(diagnosticDirectoryPath))) {
+ oldestFile = paths
+ .filter(Files::isRegularFile)
+ .min(lastModifiedComparator);
+ }
+
+ return oldestFile.orElseThrow(
+ () -> new RuntimeException(String.format("Could not find oldest file in diagnostic directory: %s", diagnosticDirectoryPath)));
+ }
+
+ public static boolean isFileCountExceeded(final String diagnosticDirectoryPath, final int maxFileCount) {
+ final String[] fileNames = new File(diagnosticDirectoryPath).list();
+ if (fileNames == null) {
+ logger.error("The diagnostic directory path provided is either invalid or not permitted to be listed.");
+ return false;
+ }
+ return fileNames.length >= maxFileCount;
+ }
+
+ public static boolean isSizeExceeded(final String diagnosticDirectoryPath, final long maxSizeInBytes) {
+ return getDirectorySize(Paths.get(diagnosticDirectoryPath)) >= maxSizeInBytes;
+ }
+
+
+ public static boolean createDiagnosticDirectory(final String diagnosticDirectoryPath) {
+ File file = new File(diagnosticDirectoryPath);
+ return file.mkdir();
+ }
+
+ private static long getDirectorySize(Path path) {
+ long size = 0;
+ try (Stream<Path> walk = Files.walk(path)) {
+ size = walk
+ .filter(Files::isRegularFile)
+ .mapToLong(getFileSizeByPathFunction())
+ .sum();
+
+ } catch (IOException e) {
+ logger.error("Directory [{}] size calculation failed", path, e);
+ }
+ return size;
+ }
+
+ private static ToLongFunction<Path> getFileSizeByPathFunction() {
+ return path -> {
+ try {
+ return Files.size(path);
+ } catch (IOException e) {
+ logger.error("Failed to get size of file {}", path, e);
+ return 0L;
+ }
+ };
+ }
+}