forked from Qortal/qortal
Rework restart node and bootstrap node
This commit is contained in:
parent
2d070f343b
commit
677fd7a64f
227
src/main/java/org/qortal/ApplyBootstrap.java
Normal file
227
src/main/java/org/qortal/ApplyBootstrap.java
Normal file
@ -0,0 +1,227 @@
|
||||
package org.qortal;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
||||
import org.qortal.api.ApiKey;
|
||||
import org.qortal.api.ApiRequest;
|
||||
import org.qortal.controller.BootstrapNode;
|
||||
import org.qortal.settings.Settings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.security.Security;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.qortal.controller.BootstrapNode.AGENTLIB_JVM_HOLDER_ARG;
|
||||
|
||||
public class ApplyBootstrap {
|
||||
|
||||
static {
|
||||
// This static block will be called before others if using ApplyBootstrap.main()
|
||||
|
||||
// Log into different files for bootstrap - this has to be before LogManger.getLogger() calls
|
||||
System.setProperty("log4j2.filenameTemplate", "log-apply-bootstrap.txt");
|
||||
|
||||
// This must go before any calls to LogManager/Logger
|
||||
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(ApplyBootstrap.class);
|
||||
private static final String JAR_FILENAME = BootstrapNode.JAR_FILENAME;
|
||||
private static final String WINDOWS_EXE_LAUNCHER = "qortal.exe";
|
||||
private static final String JAVA_TOOL_OPTIONS_NAME = "JAVA_TOOL_OPTIONS";
|
||||
private static final String JAVA_TOOL_OPTIONS_VALUE = "";
|
||||
|
||||
private static final long CHECK_INTERVAL = 15 * 1000L; // ms
|
||||
private static final int MAX_ATTEMPTS = 20;
|
||||
|
||||
public static void main(String[] args) {
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
||||
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
|
||||
|
||||
// Load/check settings, which potentially sets up blockchain config, etc.
|
||||
if (args.length > 0)
|
||||
Settings.fileInstance(args[0]);
|
||||
else
|
||||
Settings.getInstance();
|
||||
|
||||
LOGGER.info("Applying bootstrap...");
|
||||
|
||||
// Shutdown node using API
|
||||
if (!shutdownNode())
|
||||
return;
|
||||
|
||||
// Delete db
|
||||
deleteDB();
|
||||
|
||||
// Restart node
|
||||
restartNode(args);
|
||||
|
||||
LOGGER.info("Bootstrapping...");
|
||||
}
|
||||
|
||||
private static boolean shutdownNode() {
|
||||
String baseUri = "http://localhost:" + Settings.getInstance().getApiPort() + "/";
|
||||
LOGGER.info(() -> String.format("Shutting down node using API via %s", baseUri));
|
||||
|
||||
// The /admin/stop endpoint requires an API key, which may or may not be already generated
|
||||
boolean apiKeyNewlyGenerated = false;
|
||||
ApiKey apiKey = null;
|
||||
try {
|
||||
apiKey = new ApiKey();
|
||||
if (!apiKey.generated()) {
|
||||
apiKey.generate();
|
||||
apiKeyNewlyGenerated = true;
|
||||
LOGGER.info("Generated API key");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.info("Error loading API key: {}", e.getMessage());
|
||||
}
|
||||
|
||||
// Create GET params
|
||||
Map<String, String> params = new HashMap<>();
|
||||
if (apiKey != null) {
|
||||
params.put("apiKey", apiKey.toString());
|
||||
}
|
||||
|
||||
// Attempt to stop the node
|
||||
int attempt;
|
||||
for (attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
|
||||
final int attemptForLogging = attempt;
|
||||
LOGGER.info(() -> String.format("Attempt #%d out of %d to shutdown node", attemptForLogging + 1, MAX_ATTEMPTS));
|
||||
String response = ApiRequest.perform(baseUri + "admin/stop", params);
|
||||
if (response == null) {
|
||||
// No response - consider node shut down
|
||||
if (apiKeyNewlyGenerated) {
|
||||
// API key was newly generated for bootstrapping node, so we need to remove it
|
||||
ApplyBootstrap.removeGeneratedApiKey();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGGER.info(() -> String.format("Response from API: %s", response));
|
||||
|
||||
try {
|
||||
Thread.sleep(CHECK_INTERVAL);
|
||||
} catch (InterruptedException e) {
|
||||
// We still need to check...
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (apiKeyNewlyGenerated) {
|
||||
// API key was newly generated for bootstrapping node, so we need to remove it
|
||||
ApplyBootstrap.removeGeneratedApiKey();
|
||||
}
|
||||
|
||||
if (attempt == MAX_ATTEMPTS) {
|
||||
LOGGER.error("Failed to shutdown node - giving up");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void removeGeneratedApiKey() {
|
||||
try {
|
||||
LOGGER.info("Removing newly generated API key...");
|
||||
|
||||
// Delete the API key since it was only generated for bootstrapping node
|
||||
ApiKey apiKey = new ApiKey();
|
||||
apiKey.delete();
|
||||
|
||||
} catch (IOException e) {
|
||||
LOGGER.info("Error loading or deleting API key: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void deleteDB() {
|
||||
// Get the repository path from settings
|
||||
String repositoryPath = Settings.getInstance().getRepositoryPath();
|
||||
LOGGER.info(String.format("Repository path: %s", repositoryPath));
|
||||
|
||||
try {
|
||||
Path directory = Paths.get(repositoryPath);
|
||||
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
Files.delete(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
Files.delete(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
} catch (IOException e) {
|
||||
LOGGER.info("Error deleting DB: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void restartNode(String[] args) {
|
||||
String javaHome = System.getProperty("java.home");
|
||||
LOGGER.info(() -> String.format("Java home: %s", javaHome));
|
||||
|
||||
Path javaBinary = Paths.get(javaHome, "bin", "java");
|
||||
LOGGER.info(() -> String.format("Java binary: %s", javaBinary));
|
||||
|
||||
Path exeLauncher = Paths.get(WINDOWS_EXE_LAUNCHER);
|
||||
LOGGER.info(() -> String.format("Windows EXE launcher: %s", exeLauncher));
|
||||
|
||||
List<String> javaCmd;
|
||||
if (Files.exists(exeLauncher)) {
|
||||
javaCmd = Arrays.asList(exeLauncher.toString());
|
||||
} else {
|
||||
javaCmd = new ArrayList<>();
|
||||
// Java runtime binary itself
|
||||
javaCmd.add(javaBinary.toString());
|
||||
|
||||
// JVM arguments
|
||||
javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
|
||||
|
||||
// Reapply any retained, but disabled, -agentlib JVM arg
|
||||
javaCmd = javaCmd.stream()
|
||||
.map(arg -> arg.replace(AGENTLIB_JVM_HOLDER_ARG, "-agentlib"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Call mainClass in JAR
|
||||
javaCmd.addAll(Arrays.asList("-jar", JAR_FILENAME));
|
||||
|
||||
// Add saved command-line args
|
||||
javaCmd.addAll(Arrays.asList(args));
|
||||
}
|
||||
|
||||
try {
|
||||
LOGGER.info(String.format("Restarting node with: %s", String.join(" ", javaCmd)));
|
||||
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd);
|
||||
|
||||
if (Files.exists(exeLauncher)) {
|
||||
LOGGER.info(() -> String.format("Setting env %s to %s", JAVA_TOOL_OPTIONS_NAME, JAVA_TOOL_OPTIONS_VALUE));
|
||||
processBuilder.environment().put(JAVA_TOOL_OPTIONS_NAME, JAVA_TOOL_OPTIONS_VALUE);
|
||||
}
|
||||
|
||||
// New process will inherit our stdout and stderr
|
||||
processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||
processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
|
||||
Process process = processBuilder.start();
|
||||
|
||||
// Nothing to pipe to new process, so close output stream (process's stdin)
|
||||
process.getOutputStream().close();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(String.format("Failed to restart node (BAD): %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
196
src/main/java/org/qortal/ApplyRestart.java
Normal file
196
src/main/java/org/qortal/ApplyRestart.java
Normal file
@ -0,0 +1,196 @@
|
||||
package org.qortal;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider;
|
||||
import org.qortal.api.ApiKey;
|
||||
import org.qortal.api.ApiRequest;
|
||||
import org.qortal.controller.RestartNode;
|
||||
import org.qortal.settings.Settings;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.Security;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.qortal.controller.RestartNode.AGENTLIB_JVM_HOLDER_ARG;
|
||||
|
||||
public class ApplyRestart {
|
||||
|
||||
static {
|
||||
// This static block will be called before others if using ApplyRestart.main()
|
||||
|
||||
// Log into different files for restart node - this has to be before LogManger.getLogger() calls
|
||||
System.setProperty("log4j2.filenameTemplate", "log-apply-restart.txt");
|
||||
|
||||
// This must go before any calls to LogManager/Logger
|
||||
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
|
||||
}
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(ApplyRestart.class);
|
||||
private static final String JAR_FILENAME = RestartNode.JAR_FILENAME;
|
||||
private static final String WINDOWS_EXE_LAUNCHER = "qortal.exe";
|
||||
private static final String JAVA_TOOL_OPTIONS_NAME = "JAVA_TOOL_OPTIONS";
|
||||
private static final String JAVA_TOOL_OPTIONS_VALUE = "";
|
||||
|
||||
private static final long CHECK_INTERVAL = 10 * 1000L; // ms
|
||||
private static final int MAX_ATTEMPTS = 12;
|
||||
|
||||
public static void main(String[] args) {
|
||||
Security.insertProviderAt(new BouncyCastleProvider(), 0);
|
||||
Security.insertProviderAt(new BouncyCastleJsseProvider(), 1);
|
||||
|
||||
// Load/check settings, which potentially sets up blockchain config, etc.
|
||||
if (args.length > 0)
|
||||
Settings.fileInstance(args[0]);
|
||||
else
|
||||
Settings.getInstance();
|
||||
|
||||
LOGGER.info("Applying restart...");
|
||||
|
||||
// Shutdown node using API
|
||||
if (!shutdownNode())
|
||||
return;
|
||||
|
||||
// Restart node
|
||||
restartNode(args);
|
||||
|
||||
LOGGER.info("Restarting...");
|
||||
}
|
||||
|
||||
private static boolean shutdownNode() {
|
||||
String baseUri = "http://localhost:" + Settings.getInstance().getApiPort() + "/";
|
||||
LOGGER.info(() -> String.format("Shutting down node using API via %s", baseUri));
|
||||
|
||||
// The /admin/stop endpoint requires an API key, which may or may not be already generated
|
||||
boolean apiKeyNewlyGenerated = false;
|
||||
ApiKey apiKey = null;
|
||||
try {
|
||||
apiKey = new ApiKey();
|
||||
if (!apiKey.generated()) {
|
||||
apiKey.generate();
|
||||
apiKeyNewlyGenerated = true;
|
||||
LOGGER.info("Generated API key");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOGGER.info("Error loading API key: {}", e.getMessage());
|
||||
}
|
||||
|
||||
// Create GET params
|
||||
Map<String, String> params = new HashMap<>();
|
||||
if (apiKey != null) {
|
||||
params.put("apiKey", apiKey.toString());
|
||||
}
|
||||
|
||||
// Attempt to stop the node
|
||||
int attempt;
|
||||
for (attempt = 0; attempt < MAX_ATTEMPTS; ++attempt) {
|
||||
final int attemptForLogging = attempt;
|
||||
LOGGER.info(() -> String.format("Attempt #%d out of %d to shutdown node", attemptForLogging + 1, MAX_ATTEMPTS));
|
||||
String response = ApiRequest.perform(baseUri + "admin/stop", params);
|
||||
if (response == null) {
|
||||
// No response - consider node shut down
|
||||
if (apiKeyNewlyGenerated) {
|
||||
// API key was newly generated for restarting node, so we need to remove it
|
||||
ApplyRestart.removeGeneratedApiKey();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
LOGGER.info(() -> String.format("Response from API: %s", response));
|
||||
|
||||
try {
|
||||
Thread.sleep(CHECK_INTERVAL);
|
||||
} catch (InterruptedException e) {
|
||||
// We still need to check...
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (apiKeyNewlyGenerated) {
|
||||
// API key was newly generated for restarting node, so we need to remove it
|
||||
ApplyRestart.removeGeneratedApiKey();
|
||||
}
|
||||
|
||||
if (attempt == MAX_ATTEMPTS) {
|
||||
LOGGER.error("Failed to shutdown node - giving up");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void removeGeneratedApiKey() {
|
||||
try {
|
||||
LOGGER.info("Removing newly generated API key...");
|
||||
|
||||
// Delete the API key since it was only generated for restarting node
|
||||
ApiKey apiKey = new ApiKey();
|
||||
apiKey.delete();
|
||||
|
||||
} catch (IOException e) {
|
||||
LOGGER.info("Error loading or deleting API key: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static void restartNode(String[] args) {
|
||||
String javaHome = System.getProperty("java.home");
|
||||
LOGGER.info(() -> String.format("Java home: %s", javaHome));
|
||||
|
||||
Path javaBinary = Paths.get(javaHome, "bin", "java");
|
||||
LOGGER.info(() -> String.format("Java binary: %s", javaBinary));
|
||||
|
||||
Path exeLauncher = Paths.get(WINDOWS_EXE_LAUNCHER);
|
||||
LOGGER.info(() -> String.format("Windows EXE launcher: %s", exeLauncher));
|
||||
|
||||
List<String> javaCmd;
|
||||
if (Files.exists(exeLauncher)) {
|
||||
javaCmd = Arrays.asList(exeLauncher.toString());
|
||||
} else {
|
||||
javaCmd = new ArrayList<>();
|
||||
// Java runtime binary itself
|
||||
javaCmd.add(javaBinary.toString());
|
||||
|
||||
// JVM arguments
|
||||
javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
|
||||
|
||||
// Reapply any retained, but disabled, -agentlib JVM arg
|
||||
javaCmd = javaCmd.stream()
|
||||
.map(arg -> arg.replace(AGENTLIB_JVM_HOLDER_ARG, "-agentlib"))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Call mainClass in JAR
|
||||
javaCmd.addAll(Arrays.asList("-jar", JAR_FILENAME));
|
||||
|
||||
// Add saved command-line args
|
||||
javaCmd.addAll(Arrays.asList(args));
|
||||
}
|
||||
|
||||
try {
|
||||
LOGGER.info(String.format("Restarting node with: %s", String.join(" ", javaCmd)));
|
||||
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd);
|
||||
|
||||
if (Files.exists(exeLauncher)) {
|
||||
LOGGER.info(() -> String.format("Setting env %s to %s", JAVA_TOOL_OPTIONS_NAME, JAVA_TOOL_OPTIONS_VALUE));
|
||||
processBuilder.environment().put(JAVA_TOOL_OPTIONS_NAME, JAVA_TOOL_OPTIONS_VALUE);
|
||||
}
|
||||
|
||||
// New process will inherit our stdout and stderr
|
||||
processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||
processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
|
||||
Process process = processBuilder.start();
|
||||
|
||||
// Nothing to pipe to new process, so close output stream (process's stdin)
|
||||
process.getOutputStream().close();
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(String.format("Failed to restart node (BAD): %s", e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
@ -38,7 +38,7 @@ public class ApplyUpdate {
|
||||
private static final String NEW_JAR_FILENAME = AutoUpdate.NEW_JAR_FILENAME;
|
||||
private static final String WINDOWS_EXE_LAUNCHER = "qortal.exe";
|
||||
private static final String JAVA_TOOL_OPTIONS_NAME = "JAVA_TOOL_OPTIONS";
|
||||
private static final String JAVA_TOOL_OPTIONS_VALUE = "-XX:MaxRAMFraction=4";
|
||||
private static final String JAVA_TOOL_OPTIONS_VALUE = "";
|
||||
|
||||
private static final long CHECK_INTERVAL = 30 * 1000L; // ms
|
||||
private static final int MAX_ATTEMPTS = 12;
|
||||
|
@ -25,7 +25,9 @@ import org.qortal.api.model.NodeInfo;
|
||||
import org.qortal.api.model.NodeStatus;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.controller.AutoUpdate;
|
||||
import org.qortal.controller.BootstrapNode;
|
||||
import org.qortal.controller.Controller;
|
||||
import org.qortal.controller.RestartNode;
|
||||
import org.qortal.controller.Synchronizer;
|
||||
import org.qortal.controller.Synchronizer.SynchronizationResult;
|
||||
import org.qortal.controller.repository.BlockArchiveRebuilder;
|
||||
@ -250,7 +252,7 @@ public class AdminResource {
|
||||
// Not important
|
||||
}
|
||||
|
||||
AutoUpdate.attemptRestart();
|
||||
RestartNode.attemptToRestart();
|
||||
|
||||
}).start();
|
||||
|
||||
@ -281,7 +283,7 @@ public class AdminResource {
|
||||
// Not important
|
||||
}
|
||||
|
||||
AutoUpdate.attemptBootstrap();
|
||||
BootstrapNode.attemptToBootstrap();
|
||||
|
||||
}).start();
|
||||
|
||||
|
@ -23,12 +23,9 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.FileVisitResult;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.SimpleFileVisitor;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.ArrayList;
|
||||
@ -294,171 +291,4 @@ public class AutoUpdate extends Thread {
|
||||
return true; // repo was okay, even if applying update failed
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean attemptRestart() {
|
||||
LOGGER.info(String.format("Restarting node..."));
|
||||
|
||||
// Give repository a chance to backup in case things go badly wrong (if enabled)
|
||||
if (Settings.getInstance().getRepositoryBackupInterval() > 0) {
|
||||
try {
|
||||
// Timeout if the database isn't ready for backing up after 60 seconds
|
||||
long timeout = 60 * 1000L;
|
||||
RepositoryManager.backup(true, "backup", timeout);
|
||||
|
||||
} catch (TimeoutException e) {
|
||||
LOGGER.info("Attempt to backup repository failed due to timeout: {}", e.getMessage());
|
||||
// Continue with the node restart anyway...
|
||||
}
|
||||
}
|
||||
|
||||
// Call ApplyUpdate to end this process (unlocking current JAR so it can be replaced)
|
||||
String javaHome = System.getProperty("java.home");
|
||||
LOGGER.debug(String.format("Java home: %s", javaHome));
|
||||
|
||||
Path javaBinary = Paths.get(javaHome, "bin", "java");
|
||||
LOGGER.debug(String.format("Java binary: %s", javaBinary));
|
||||
|
||||
try {
|
||||
List<String> javaCmd = new ArrayList<>();
|
||||
// Java runtime binary itself
|
||||
javaCmd.add(javaBinary.toString());
|
||||
|
||||
// JVM arguments
|
||||
javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
|
||||
|
||||
// Disable, but retain, any -agentlib JVM arg as sub-process might fail if it tries to reuse same port
|
||||
javaCmd = javaCmd.stream()
|
||||
.map(arg -> arg.replace("-agentlib", AGENTLIB_JVM_HOLDER_ARG))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Remove JNI options as they won't be supported by command-line 'java'
|
||||
// These are typically added by the AdvancedInstaller Java launcher EXE
|
||||
javaCmd.removeAll(Arrays.asList("abort", "exit", "vfprintf"));
|
||||
|
||||
// Call ApplyUpdate using JAR
|
||||
javaCmd.addAll(Arrays.asList("-cp", JAR_FILENAME, ApplyUpdate.class.getCanonicalName()));
|
||||
|
||||
// Add command-line args saved from start-up
|
||||
String[] savedArgs = Controller.getInstance().getSavedArgs();
|
||||
if (savedArgs != null)
|
||||
javaCmd.addAll(Arrays.asList(savedArgs));
|
||||
|
||||
LOGGER.info(String.format("Restarting node with: %s", String.join(" ", javaCmd)));
|
||||
|
||||
SysTray.getInstance().showMessage(Translator.INSTANCE.translate("SysTray", "AUTO_UPDATE"), //TODO
|
||||
Translator.INSTANCE.translate("SysTray", "APPLYING_UPDATE_AND_RESTARTING"), //TODO
|
||||
MessageType.INFO);
|
||||
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd);
|
||||
|
||||
// New process will inherit our stdout and stderr
|
||||
processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||
processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
|
||||
Process process = processBuilder.start();
|
||||
|
||||
// Nothing to pipe to new process, so close output stream (process's stdin)
|
||||
process.getOutputStream().close();
|
||||
|
||||
return true; // restarting node OK
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(String.format("Failed to restart node: %s", e.getMessage()));
|
||||
|
||||
return true; // repo was okay, even if applying update failed
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean attemptBootstrap() {
|
||||
LOGGER.info(String.format("Bootstrapping node..."));
|
||||
|
||||
// Give repository a chance to backup in case things go badly wrong (if enabled)
|
||||
if (Settings.getInstance().getRepositoryBackupInterval() > 0) {
|
||||
try {
|
||||
// Timeout if the database isn't ready for backing up after 60 seconds
|
||||
long timeout = 60 * 1000L;
|
||||
RepositoryManager.backup(true, "backup", timeout);
|
||||
|
||||
} catch (TimeoutException e) {
|
||||
LOGGER.info("Attempt to backup repository failed due to timeout: {}", e.getMessage());
|
||||
// Continue with the bootstrap anyway...
|
||||
}
|
||||
}
|
||||
|
||||
// Get the repository path from settings
|
||||
String repositoryPath = Settings.getInstance().getRepositoryPath();
|
||||
LOGGER.debug(String.format("Repository path: %s", repositoryPath));
|
||||
|
||||
// Call ApplyUpdate to end this process (unlocking current JAR so it can be replaced)
|
||||
String javaHome = System.getProperty("java.home");
|
||||
LOGGER.debug(String.format("Java home: %s", javaHome));
|
||||
|
||||
Path javaBinary = Paths.get(javaHome, "bin", "java");
|
||||
LOGGER.debug(String.format("Java binary: %s", javaBinary));
|
||||
|
||||
try {
|
||||
Path directory = Paths.get(repositoryPath);
|
||||
Files.walkFileTree(directory, new SimpleFileVisitor<Path>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
Files.delete(file);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
||||
Files.delete(dir);
|
||||
return FileVisitResult.CONTINUE;
|
||||
}
|
||||
});
|
||||
|
||||
List<String> javaCmd = new ArrayList<>();
|
||||
|
||||
// Java runtime binary itself
|
||||
javaCmd.add(javaBinary.toString());
|
||||
|
||||
// JVM arguments
|
||||
javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
|
||||
|
||||
// Disable, but retain, any -agentlib JVM arg as sub-process might fail if it tries to reuse same port
|
||||
javaCmd = javaCmd.stream()
|
||||
.map(arg -> arg.replace("-agentlib", AGENTLIB_JVM_HOLDER_ARG))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Remove JNI options as they won't be supported by command-line 'java'
|
||||
// These are typically added by the AdvancedInstaller Java launcher EXE
|
||||
javaCmd.removeAll(Arrays.asList("abort", "exit", "vfprintf"));
|
||||
|
||||
// Call ApplyUpdate using JAR
|
||||
javaCmd.addAll(Arrays.asList("-cp", JAR_FILENAME, ApplyUpdate.class.getCanonicalName()));
|
||||
|
||||
// Add command-line args saved from start-up
|
||||
String[] savedArgs = Controller.getInstance().getSavedArgs();
|
||||
if (savedArgs != null)
|
||||
javaCmd.addAll(Arrays.asList(savedArgs));
|
||||
|
||||
LOGGER.info(String.format("Restarting node with: %s", String.join(" ", javaCmd)));
|
||||
|
||||
SysTray.getInstance().showMessage(Translator.INSTANCE.translate("SysTray", "AUTO_UPDATE"), //TODO
|
||||
Translator.INSTANCE.translate("SysTray", "APPLYING_UPDATE_AND_RESTARTING"), //TODO
|
||||
MessageType.INFO);
|
||||
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd);
|
||||
|
||||
// New process will inherit our stdout and stderr
|
||||
processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||
processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
|
||||
Process process = processBuilder.start();
|
||||
|
||||
// Nothing to pipe to new process, so close output stream (process's stdin)
|
||||
process.getOutputStream().close();
|
||||
|
||||
return true; // restarting node OK
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(String.format("Failed to restart node: %s", e.getMessage()));
|
||||
|
||||
return true; // repo was okay, even if applying update failed
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
103
src/main/java/org/qortal/controller/BootstrapNode.java
Normal file
103
src/main/java/org/qortal/controller/BootstrapNode.java
Normal file
@ -0,0 +1,103 @@
|
||||
package org.qortal.controller;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.ApplyBootstrap;
|
||||
import org.qortal.globalization.Translator;
|
||||
import org.qortal.gui.SysTray;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.settings.Settings;
|
||||
|
||||
import java.awt.TrayIcon.MessageType;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/* NOTE: It is CRITICAL that we use OpenJDK and not Java SE because our uber jar repacks BouncyCastle which, in turn, unsigns BC causing it to be rejected as a security provider by Java SE. */
|
||||
|
||||
public class BootstrapNode {
|
||||
|
||||
public static final String JAR_FILENAME = "qortal.jar";
|
||||
public static final String AGENTLIB_JVM_HOLDER_ARG = "-DQORTAL_agentlib=";
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(BootstrapNode.class);
|
||||
|
||||
public static boolean attemptToBootstrap() {
|
||||
LOGGER.info(String.format("Bootstrapping node..."));
|
||||
|
||||
// Give repository a chance to backup in case things go badly wrong (if enabled)
|
||||
if (Settings.getInstance().getRepositoryBackupInterval() > 0) {
|
||||
try {
|
||||
// Timeout if the database isn't ready for backing up after 60 seconds
|
||||
long timeout = 60 * 1000L;
|
||||
RepositoryManager.backup(true, "backup", timeout);
|
||||
|
||||
} catch (TimeoutException e) {
|
||||
LOGGER.info("Attempt to backup repository failed due to timeout: {}", e.getMessage());
|
||||
// Continue with the bootstrap anyway...
|
||||
}
|
||||
}
|
||||
|
||||
// Call ApplyBootstrap to end this process
|
||||
String javaHome = System.getProperty("java.home");
|
||||
LOGGER.debug(String.format("Java home: %s", javaHome));
|
||||
|
||||
Path javaBinary = Paths.get(javaHome, "bin", "java");
|
||||
LOGGER.debug(String.format("Java binary: %s", javaBinary));
|
||||
|
||||
try {
|
||||
List<String> javaCmd = new ArrayList<>();
|
||||
|
||||
// Java runtime binary itself
|
||||
javaCmd.add(javaBinary.toString());
|
||||
|
||||
// JVM arguments
|
||||
javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
|
||||
|
||||
// Disable, but retain, any -agentlib JVM arg as sub-process might fail if it tries to reuse same port
|
||||
javaCmd = javaCmd.stream()
|
||||
.map(arg -> arg.replace("-agentlib", AGENTLIB_JVM_HOLDER_ARG))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Remove JNI options as they won't be supported by command-line 'java'
|
||||
// These are typically added by the AdvancedInstaller Java launcher EXE
|
||||
javaCmd.removeAll(Arrays.asList("abort", "exit", "vfprintf"));
|
||||
|
||||
// Call ApplyBootstrap using JAR
|
||||
javaCmd.addAll(Arrays.asList("-cp", JAR_FILENAME, ApplyBootstrap.class.getCanonicalName()));
|
||||
|
||||
// Add command-line args saved from start-up
|
||||
String[] savedArgs = Controller.getInstance().getSavedArgs();
|
||||
if (savedArgs != null)
|
||||
javaCmd.addAll(Arrays.asList(savedArgs));
|
||||
|
||||
LOGGER.info(String.format("Restarting node with: %s", String.join(" ", javaCmd)));
|
||||
|
||||
SysTray.getInstance().showMessage(Translator.INSTANCE.translate("SysTray", "BOOTSTRAP_NODE"),
|
||||
Translator.INSTANCE.translate("SysTray", "APPLYING_BOOTSTRAP_AND_RESTARTING"),
|
||||
MessageType.INFO);
|
||||
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd);
|
||||
|
||||
// New process will inherit our stdout and stderr
|
||||
processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||
processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
|
||||
Process process = processBuilder.start();
|
||||
|
||||
// Nothing to pipe to new process, so close output stream (process's stdin)
|
||||
process.getOutputStream().close();
|
||||
|
||||
return true; // restarting node OK
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(String.format("Failed to restart node: %s", e.getMessage()));
|
||||
|
||||
return true; // repo was okay, even if applying bootstrap failed
|
||||
}
|
||||
}
|
||||
}
|
102
src/main/java/org/qortal/controller/RestartNode.java
Normal file
102
src/main/java/org/qortal/controller/RestartNode.java
Normal file
@ -0,0 +1,102 @@
|
||||
package org.qortal.controller;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.qortal.ApplyRestart;;
|
||||
import org.qortal.globalization.Translator;
|
||||
import org.qortal.gui.SysTray;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.settings.Settings;
|
||||
|
||||
import java.awt.TrayIcon.MessageType;
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/* NOTE: It is CRITICAL that we use OpenJDK and not Java SE because our uber jar repacks BouncyCastle which, in turn, unsigns BC causing it to be rejected as a security provider by Java SE. */
|
||||
|
||||
public class RestartNode {
|
||||
|
||||
public static final String JAR_FILENAME = "qortal.jar";
|
||||
public static final String AGENTLIB_JVM_HOLDER_ARG = "-DQORTAL_agentlib=";
|
||||
|
||||
private static final Logger LOGGER = LogManager.getLogger(RestartNode.class);
|
||||
|
||||
public static boolean attemptToRestart() {
|
||||
LOGGER.info(String.format("Restarting node..."));
|
||||
|
||||
// Give repository a chance to backup in case things go badly wrong (if enabled)
|
||||
if (Settings.getInstance().getRepositoryBackupInterval() > 0) {
|
||||
try {
|
||||
// Timeout if the database isn't ready for backing up after 60 seconds
|
||||
long timeout = 60 * 1000L;
|
||||
RepositoryManager.backup(true, "backup", timeout);
|
||||
|
||||
} catch (TimeoutException e) {
|
||||
LOGGER.info("Attempt to backup repository failed due to timeout: {}", e.getMessage());
|
||||
// Continue with the node restart anyway...
|
||||
}
|
||||
}
|
||||
|
||||
// Call ApplyRestart to end this process
|
||||
String javaHome = System.getProperty("java.home");
|
||||
LOGGER.debug(String.format("Java home: %s", javaHome));
|
||||
|
||||
Path javaBinary = Paths.get(javaHome, "bin", "java");
|
||||
LOGGER.debug(String.format("Java binary: %s", javaBinary));
|
||||
|
||||
try {
|
||||
List<String> javaCmd = new ArrayList<>();
|
||||
// Java runtime binary itself
|
||||
javaCmd.add(javaBinary.toString());
|
||||
|
||||
// JVM arguments
|
||||
javaCmd.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments());
|
||||
|
||||
// Disable, but retain, any -agentlib JVM arg as sub-process might fail if it tries to reuse same port
|
||||
javaCmd = javaCmd.stream()
|
||||
.map(arg -> arg.replace("-agentlib", AGENTLIB_JVM_HOLDER_ARG))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// Remove JNI options as they won't be supported by command-line 'java'
|
||||
// These are typically added by the AdvancedInstaller Java launcher EXE
|
||||
javaCmd.removeAll(Arrays.asList("abort", "exit", "vfprintf"));
|
||||
|
||||
// Call ApplyRestart using JAR
|
||||
javaCmd.addAll(Arrays.asList("-cp", JAR_FILENAME, ApplyRestart.class.getCanonicalName()));
|
||||
|
||||
// Add command-line args saved from start-up
|
||||
String[] savedArgs = Controller.getInstance().getSavedArgs();
|
||||
if (savedArgs != null)
|
||||
javaCmd.addAll(Arrays.asList(savedArgs));
|
||||
|
||||
LOGGER.info(String.format("Restarting node with: %s", String.join(" ", javaCmd)));
|
||||
|
||||
SysTray.getInstance().showMessage(Translator.INSTANCE.translate("SysTray", "RESTARTING_NODE"),
|
||||
Translator.INSTANCE.translate("SysTray", "APPLYING_RESTARTING_NODE"),
|
||||
MessageType.INFO);
|
||||
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(javaCmd);
|
||||
|
||||
// New process will inherit our stdout and stderr
|
||||
processBuilder.redirectOutput(ProcessBuilder.Redirect.INHERIT);
|
||||
processBuilder.redirectError(ProcessBuilder.Redirect.INHERIT);
|
||||
|
||||
Process process = processBuilder.start();
|
||||
|
||||
// Nothing to pipe to new process, so close output stream (process's stdin)
|
||||
process.getOutputStream().close();
|
||||
|
||||
return true; // restarting node OK
|
||||
} catch (Exception e) {
|
||||
LOGGER.error(String.format("Failed to restart node: %s", e.getMessage()));
|
||||
|
||||
return true; // repo was okay, even if applying restart failed
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user