package the8472.mldht;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.LinkedTransferQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TransferQueue;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import lbms.plugins.mldht.DHTConfiguration;
import lbms.plugins.mldht.kad.DHT;
import lbms.plugins.mldht.kad.DHT.LogLevel;
import lbms.plugins.mldht.kad.DHTLogger;
import the8472.utils.ConfigReader;
import the8472.utils.FilesystemNotifications;
import the8472.utils.XMLUtils;
import the8472.utils.concurrent.NonblockingScheduledExecutor;
import the8472.utils.io.NetMask;
public class Launcher {
Supplier<InputStream> configSchema = () -> Launcher.class.getResourceAsStream("config.xsd");
Supplier<InputStream> configDefaults = () -> Launcher.class.getResourceAsStream("config-defaults.xml");
List<Component> components = new ArrayList<>();
private ConfigReader configReader;
DHTConfiguration config = new DHTConfiguration() {
@Override
public boolean noRouterBootstrap() {
return !configReader.getBoolean("//core/useBootstrapServers").orElse(true);
}
@Override
public boolean isPersistingID() {
return configReader.getBoolean("//core/persistID").orElse(true);
}
@Override
public Path getStoragePath() {
return Paths.get(".");
}
@Override
public int getListeningPort() {
return configReader.getLong("//core/port").orElse(49001L).intValue();
}
@Override
public boolean allowMultiHoming() {
return configReader.getBoolean("//core/multihoming").orElse(true);
}
};
List<DHT> dhts = new ArrayList<>();
volatile boolean running = true;
Thread shutdownHook = new Thread(this::onVmShutdown, "shutdownHook");
ScheduledExecutorService scheduler;
DHTLogger logger;
public Launcher() {
configReader = new ConfigReader(Paths.get(".", "config.xml"), configDefaults, configSchema);
configReader.read();
scheduler = new NonblockingScheduledExecutor("mlDHT", Math.max(Runtime.getRuntime().availableProcessors(), 4), (t, ex) -> {
logger.log(ex, LogLevel.Fatal);
});
}
private void onVmShutdown() {
initiateShutdown();
shutdownCleanup();
}
FilesystemNotifications notifications = new FilesystemNotifications();
protected void start() throws Exception {
Arrays.asList(DHT.DHTtype.values()).stream().filter(t -> !this.isIPVersionDisabled(t.PREFERRED_ADDRESS_TYPE)).forEach(type -> {
dhts.add(new DHT(type));
});
dhts.forEach(d -> {
d.addSiblings(dhts);
d.setScheduler(scheduler);
});
Path logDir = Paths.get("./logs/");
Files.createDirectories(logDir);
final Path log = logDir.resolve("dht.log");
Path exLog = logDir.resolve("exceptions.log");
//final PrintWriter logWriter = ;
final PrintWriter exWriter = new PrintWriter(Files.newBufferedWriter(exLog, StandardCharsets.UTF_8, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE, StandardOpenOption.WRITE), true);
configReader.getAll(XMLUtils.buildXPath("//component/className",null)).forEach(className -> {
try {
Class<Component> clazz = (Class<Component>) Class.forName(className);
components.add(clazz.newInstance());
} catch (Exception e1) {
throw new RuntimeException(e1);
}
});
logger = new DHTLogger() {
private String timeFormat(LogLevel level) {
return "[" + Instant.now().toString() + "][" + level.toString() + "] ";
}
TransferQueue<String> toLog = new LinkedTransferQueue<>();
Thread writer = new Thread() {
@Override
public void run() {
try {
FileChannel.open(log, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING).close();
while (true) {
String toWrite = toLog.take();
// log rotate at 1GB
if(Files.size(log) > 1024*1024*1024)
Files.move(log, log.resolveSibling("dht.log.1"), StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
try(PrintWriter logWriter = new PrintWriter(Files.newBufferedWriter(log, StandardCharsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.APPEND))) {
do {
logWriter.println(toWrite);
} while((toWrite = toLog.poll()) != null);
logWriter.flush();
}
}
} catch (InterruptedException | IOException e) {
e.printStackTrace();
}
}
};
{
writer.setDaemon(true);
writer.setName("LogWriter");
writer.start();
}
public void log(String message, LogLevel l) {
try {
toLog.put(timeFormat(l) + message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void log(Throwable e, LogLevel l) {
exWriter.append(timeFormat(l));
e.printStackTrace(exWriter);
exWriter.flush();
}
};
DHT.setLogger(logger);
new Diagnostics().init(dhts, logDir);
setLogLevel();
configReader.registerFsNotifications(notifications);
configReader.addChangeCallback(this::setLogLevel);
for (DHT dht : dhts) {
if(isIPVersionDisabled(dht.getType().PREFERRED_ADDRESS_TYPE))
continue;
dht.start(config);
dht.bootstrap();
// dht.addIndexingListener(dumper);
}
// need to run this after startup, Node doesn't exist before then
setTrustedMasks();
configReader.addChangeCallback(this::setTrustedMasks);
components.forEach(c -> c.start(dhts, configReader));
Runtime.getRuntime().addShutdownHook(shutdownHook);
Path shutdown = Paths.get("./shutdown");
if(!Files.exists(shutdown))
Files.createFile(shutdown);
notifications.addRegistration(shutdown, (path, kind) -> {
if(path.equals(shutdown)) {
initiateShutdown();
}
});
// need 1 non-daemon-thread to keep VM alive
while(running) {
synchronized (this) {
this.wait();
}
}
shutdownCleanup();
}
private void setLogLevel() {
String rawLevel = configReader.get(XMLUtils.buildXPath("//core/logLevel")).orElse("Info");
LogLevel level = LogLevel.valueOf(rawLevel);
DHT.setLogLevel(level);
}
private void setTrustedMasks() {
Collection<NetMask> masks = configReader.getAll(XMLUtils.buildXPath("//core/clusterNodes/networkPrefix")).map(NetMask::fromString).collect(Collectors.toList());
dhts.forEach((d) -> {
if(d.isRunning())
d.getNode().setTrustedNetMasks(masks);
});
}
private boolean isIPVersionDisabled(Class<? extends InetAddress> type) {
long disabled = configReader.getLong("//core/disableIPVersion").orElse(-1L);
if(disabled == 6 && type.isAssignableFrom(Inet6Address.class))
return true;
if(disabled == 4 && type.isAssignableFrom(Inet4Address.class))
return true;
return false;
}
public void initiateShutdown() {
if(running) {
running = false;
synchronized (this) {
this.notifyAll();
}
}
}
boolean cleanupDone = false;
void shutdownCleanup() {
synchronized (this) {
if(cleanupDone)
return;
cleanupDone = true;
components.forEach(Component::stop);
dhts.forEach(DHT::stop);
}
}
/**
* @param args
*/
public static void main(String[] args) throws Exception {
new Launcher().start();
}
}