diff --git a/src/de/steamwar/bungeecore/BungeeCore.java b/src/de/steamwar/bungeecore/BungeeCore.java index 7dee2ca..0b71cb5 100644 --- a/src/de/steamwar/bungeecore/BungeeCore.java +++ b/src/de/steamwar/bungeecore/BungeeCore.java @@ -89,9 +89,10 @@ public class BungeeCore extends Plugin { new WorldDownloader(); new BrandListener(); - commands.put("/b", null); - commands.put("/gs", null); - commands.put("/bau", null); + new Node.LocalNode(); + new Node.RemoteNode("lx"); + new Node.RemoteNode("az"); + commands.put("/tp", null); commands.put("/bc", null); commands.put("/bauchat", null); @@ -158,14 +159,13 @@ public class BungeeCore extends Plugin { @Override public void onDisable(){ - ErrorLogger.stop(); - Statement.close(); try { SteamwarDiscordBot.instance().getJda().shutdownNow(); SteamwarDiscordBot.instance().getJda().awaitStatus(JDA.Status.SHUTDOWN); - } catch (Exception e) { - // Ignored + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } + Statement.close(); } public static BungeeCore get() { diff --git a/src/de/steamwar/bungeecore/ErrorLogger.java b/src/de/steamwar/bungeecore/ErrorLogger.java index 5f4ab53..4bfe84c 100644 --- a/src/de/steamwar/bungeecore/ErrorLogger.java +++ b/src/de/steamwar/bungeecore/ErrorLogger.java @@ -20,83 +20,51 @@ package de.steamwar.bungeecore; import de.steamwar.bungeecore.sql.SWException; -import net.md_5.bungee.api.ProxyServer; +import de.steamwar.bungeecore.sql.Statement; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; import java.text.MessageFormat; -import java.util.logging.Filter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.LogRecord; +import java.util.logging.Logger; -public class ErrorLogger extends Handler implements Filter { - private static ErrorLogger instance; +public class ErrorLogger extends Handler { private int ddosRate = 0; ErrorLogger(){ - ProxyServer.getInstance().getLogger().addHandler(this); - ProxyServer.getInstance().getLogger().setFilter(this); - instance = this; - } - - static void stop(){ - ProxyServer.getInstance().getLogger().removeHandler(instance); + Logger.getLogger("").addHandler(this); } @Override - public boolean isLoggable(LogRecord record) { - if(record.getLevel() != Level.SEVERE) - return true; + public void publish(LogRecord logRecord) { + if(logRecord.getLevel().intValue() < Level.WARNING.intValue()) + return; - StringBuilder stacktrace = new StringBuilder(record.getSourceClassName() + "\n" + record.getSourceMethodName()); - Throwable thrown = record.getThrown(); - while(thrown != null){ - stacktrace.append("\nCaused by ").append(thrown.getMessage()); + String message = MessageFormat.format(logRecord.getMessage(), logRecord.getParameters()); + for(String reason : ignoreContains) + if(message.contains(reason)) + return; - for(StackTraceElement ste : thrown.getStackTrace()) - stacktrace.append("\n").append(ste.toString()); - - thrown = thrown.getCause(); - } - - String stacktraceString = stacktrace.toString(); - if(stacktraceString.contains("Cannot request protocol")){ - ddosRate++; - if(ddosRate % 1000 == 0){ + ByteArrayOutputStream stacktraceOutput = new ByteArrayOutputStream(); + if(logRecord.getThrown() != null) + logRecord.getThrown().printStackTrace(new PrintStream(stacktraceOutput)); + String stacktrace = stacktraceOutput.toString(); + if(stacktrace.contains("Cannot request protocol")) { + if(++ddosRate % 1000 == 0) { SWException.log("Bungee", "DDOS", ddosRate + ""); } - return false; - } - return true; - } - - @Override - public void publish(LogRecord record) { - if(record.getLevel() != Level.SEVERE && record.getLevel() != Level.WARNING) return; - - StringBuilder stacktrace = new StringBuilder(record.getSourceClassName() + "\n" + record.getSourceMethodName()); - Throwable thrown = record.getThrown(); - while(thrown != null){ - stacktrace.append("\nCaused by ").append(thrown.getMessage()); - - for(StackTraceElement ste : thrown.getStackTrace()) - stacktrace.append("\n").append(ste.toString()); - - thrown = thrown.getCause(); + } else if (!Statement.connectionStable()) { + return; } - String stacktraceString = stacktrace.toString(); - String message = MessageFormat.format(record.getMessage(), record.getParameters()); - if(message.contains("ServerConnector") - || message.contains("InitialHandler") - || message.contains("UpstreamBridge") - || message.contains("DownstreamBridge") - || message.contains(" took ") - || message.contains("No client connected for pending server!")) - return; - - SWException.log("Bungee", message, stacktraceString); + SWException.log("Bungee", message, stacktrace); } @Override @@ -108,4 +76,17 @@ public class ErrorLogger extends Handler implements Filter { public void close() { //ignored } + + private static final List ignoreContains; + + static { + List contains = new ArrayList<>(); + contains.add("ServerConnector"); + contains.add("InitialHandler"); + contains.add("UpstreamBridge"); + contains.add("DownstreamBridge"); + contains.add(" took "); + contains.add("No client connected for pending server!"); + ignoreContains = Collections.unmodifiableList(contains); + } } diff --git a/src/de/steamwar/bungeecore/LoadEvaluation.java b/src/de/steamwar/bungeecore/LoadEvaluation.java deleted file mode 100644 index 59fda6c..0000000 --- a/src/de/steamwar/bungeecore/LoadEvaluation.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2021 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package de.steamwar.bungeecore; - -import java.io.*; - -public class LoadEvaluation { - private LoadEvaluation(){} - - private static final File meminfo = new File("/proc/meminfo"); - - public static double getRamPercentage() { - try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(meminfo)))) { - String memTotal = bufferedReader.readLine().replaceAll(" +", " "); - bufferedReader.readLine(); - String memAvailable = bufferedReader.readLine().replaceAll(" +", " "); - - long memTotalLong = getNumber(memTotal); - long memAvailableLong = getNumber(memAvailable); - return (memTotalLong - memAvailableLong) / (double) memTotalLong; - } catch (IOException e) { - return 1D; - } - } - - public static double getRemoteRamPercentage(String remote) { - try { - // Attention: - // memInfo.sh needs to contain: cat /proc/meminfo - Process process = new ProcessBuilder("ssh", remote, "\"./memInfo.sh\"").start(); - process.waitFor(); - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); - String memTotal = bufferedReader.readLine().replaceAll(" +", " "); - bufferedReader.readLine(); - String memAvailable = bufferedReader.readLine().replaceAll(" +", " "); - - long memTotalLong = getNumber(memTotal); - long memAvailableLong = getNumber(memAvailable); - return (memTotalLong - memAvailableLong) / (double) memTotalLong; - } catch (IOException e) { - return 1D; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return 1D; - } - } - - public static double getCPULoad() { - try { - Process process = new ProcessBuilder("bash", "-c", "cat <(grep 'cpu ' /proc/stat) <(sleep 1 && grep 'cpu ' /proc/stat) | awk -v RS=\"\" '{printf \"%.2f\\n\", ($13-$2+$15-$4)*100/($13-$2+$15-$4+$16-$5)}'").start(); - process.waitFor(); - return Double.parseDouble(new BufferedReader(new InputStreamReader(process.getInputStream())).readLine()) / 100.0; - } catch (IOException e) { - return 1D; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return 1D; - } - } - - public static double getRemoteCPULoad(String remote) { - try { - // Attention: - // cpuLoad.sh needs to contain: cat <(grep 'cpu ' /proc/stat) <(sleep 1 && grep 'cpu ' /proc/stat) | awk -v RS="" '{printf "%.2f\n", ($13-$2+$15-$4)*100/($13-$2+$15-$4+$16-$5)}' - Process process = new ProcessBuilder("ssh", remote, "\"./cpuLoad.sh\"").start(); - process.waitFor(); - return Double.parseDouble(new BufferedReader(new InputStreamReader(process.getInputStream())).readLine()) / 100.0; - } catch (IOException e) { - return 1D; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return 1D; - } - } - - private static long getNumber(String s) { - return Long.parseLong(s.split(" ")[1]); - } -} diff --git a/src/de/steamwar/bungeecore/Node.java b/src/de/steamwar/bungeecore/Node.java new file mode 100644 index 0000000..ea0a2ed --- /dev/null +++ b/src/de/steamwar/bungeecore/Node.java @@ -0,0 +1,261 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2020 SteamWar.de-Serverteam + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +package de.steamwar.bungeecore; + +import net.md_5.bungee.api.ProxyServer; + +import java.io.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.logging.Level; + +public abstract class Node { + + private static final List JVM_ARGS = Arrays.asList("-Dlog4j.configurationFile=log4j2.xml", "-server", "-Xms128M", "-XX:+UseCompressedOops", "-XX:+TieredCompilation", "-XX:TargetSurvivorRatio=90", "-XX:SurvivorRatio=8", "-XX:MaxTenuringThreshold=15", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseBiasedLocking", "-XX:UseSSE=3", "-XX:+UseCodeCacheFlushing", "-XX:+UseThreadPriorities", "-XX:+AggressiveOpts", "-XX:+ReduceSignalUsage", "-XX:+UseInterpreter", "-XX:+UseSharedSpaces", "-XX:AllocatePrefetchStyle=1", "-XX:+AlwaysCompileLoopMethods", "-XX:+UseConcMarkSweepGC", "-XX:+RewriteFrequentPairs", "-XX:+OptimizeStringConcat", "-XX:+CMSCleanOnEnter", "-XX:+UseInlineCaches"); + private static final List JVM8_ARGS = Arrays.asList("-XX:ThreadPriorityPolicy=42", "-XX:SharedReadOnlySize=30m", "-XX:+UseFastEmptyMethods", "-XX:+UseFastAccessorMethods"); + private static final double MIN_FREE_MEM = 4.0 * 1024 * 1024; // 4 GiB + + private static final List nodes = new ArrayList<>(); + public static Node local = null; + + public static Node getNode() { + Node node = local; + double minLoad = local.getLoad(); + if(minLoad < 0.5) + return local; + + synchronized (nodes) { + Iterator it = nodes.iterator(); + while(it.hasNext()) { + Node n = it.next(); + double load = n.getLoad(); + if (load < minLoad) { + minLoad = load; + node = n; + } else if (load == Double.POSITIVE_INFINITY) { + BungeeCore.get().getLogger().log(Level.SEVERE, "Removing " + n.getName() + " due to infinite load!"); + it.remove(); + } + } + } + return node; + } + + public static void forEach(Consumer consumer) { + consumer.accept(local); + synchronized (nodes) { + nodes.forEach(consumer); + } + } + + public abstract ProcessBuilder startServer(String serverJar, File directory, String worldDir, String levelName, int port, String xmx, String... dParams); + public abstract void execute(String... command); + public abstract String getName(); + public abstract double getLoad(); + + protected void constructServerstart(List cmd, String serverJar, String worldDir, String levelName, int port, String xmx, String... dParams) { + boolean jdk11 = serverJar.contains("1.15.2"); + + if(jdk11) + cmd.add("/usr/lib/jvm/java-11-openjdk-amd64/bin/java"); + else + cmd.add("java"); + + for(String param : dParams){ + cmd.add("-D" + param); + } + cmd.add("-Xmx" + xmx); + cmd.addAll(JVM_ARGS); + if(!jdk11) + cmd.addAll(JVM8_ARGS); + cmd.add("-jar"); + cmd.add("/binarys/" + serverJar); + cmd.add("--log-strip-color"); + cmd.add("--world-dir"); + cmd.add(worldDir); + cmd.add("--level-name"); + cmd.add(levelName); + cmd.add("--port"); + cmd.add(String.valueOf(port)); + cmd.add("nogui"); + } + + protected void execute(ProcessBuilder builder) { + try { + builder.start().waitFor(); + } catch (IOException e) { + throw new SecurityException("Could not execute command", e); + } catch (InterruptedException e) { + ProxyServer.getInstance().getLogger().log(Level.SEVERE, "Interrupted during execution", e); + Thread.currentThread().interrupt(); + } + } + + public static class LocalNode extends Node { + private static final File meminfo = new File("/proc/meminfo"); + private static final File loadavg = new File("/proc/loadavg"); + + private final int cores; + + public LocalNode() { + this.cores = Runtime.getRuntime().availableProcessors(); + local = this; + } + + @Override + public ProcessBuilder startServer(String serverJar, File directory, String worldDir, String levelName, int port, String xmx, String... dParams) { + List cmd = new ArrayList<>(); + constructServerstart(cmd, serverJar, worldDir, levelName, port, xmx, dParams); + ProcessBuilder builder = new ProcessBuilder(cmd); + builder.directory(directory); + return builder; + } + + @Override + public void execute(String... command) { + execute(new ProcessBuilder(command)); + } + + @Override + public String getName() { + return "local"; + } + + @Override + public double getLoad() { + double totalMem; + double freeMem; + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(meminfo)))) { + totalMem = Double.parseDouble(bufferedReader.readLine().replaceAll(" +", " ").split(" ")[1]); + bufferedReader.readLine(); + freeMem = Double.parseDouble(bufferedReader.readLine().replaceAll(" +", " ").split(" ")[1]); + } catch (IOException e) { + throw new SecurityException("Could not read local memory", e); + } + + double cpuLoad; + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(loadavg)))) { + cpuLoad = Double.parseDouble(bufferedReader.readLine().split(" ")[0]); + } catch (IOException e) { + throw new SecurityException("Could not read local cpu", e); + } + + return cpuLoad / cores + (freeMem > MIN_FREE_MEM ? 0 : ((totalMem - freeMem) / totalMem)); + } + } + + public static class RemoteNode extends Node { + private final int cores; + private final String remote; + + public RemoteNode(String remote) { + this.remote = remote; + + //Determin core count + Process process; + try { + process = new ProcessBuilder("ssh", remote, "nproc").start(); + if(!process.waitFor(5, TimeUnit.SECONDS)) + throw new IOException("Timeout of " + remote + " on init"); + } catch (IOException e) { + BungeeCore.get().getLogger().log(Level.SEVERE, "Could not initialize " + remote); + cores = 1; + return; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + cores = 1; + return; + } + + int c; + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + c = Integer.parseInt(bufferedReader.readLine()); + } catch (IOException e) { + BungeeCore.get().getLogger().log(Level.SEVERE, "Could not read cores of" + remote, e); + c = 1; + } + cores = c; + BungeeCore.get().getLogger().log(Level.INFO, "Adding node " + remote + " with " + cores + " cores."); + + synchronized (nodes) { + nodes.add(this); + } + } + + @Override + public ProcessBuilder startServer(String serverJar, File directory, String worldDir, String levelName, int port, String xmx, String... dParams) { + List cmd = new ArrayList<>(); + cmd.add("ssh"); + cmd.add("-L"); + cmd.add(port + ":localhost:" + port); + cmd.add(remote); + cmd.add("cd"); + cmd.add(directory.getPath()); + cmd.add(";"); + constructServerstart(cmd, serverJar, worldDir, levelName, port, xmx, dParams); + return new ProcessBuilder(cmd); + } + + @Override + public void execute(String... command) { + List cmd = new ArrayList<>(); + cmd.add("ssh"); + cmd.add(remote); + cmd.addAll(Arrays.asList(command)); + execute(new ProcessBuilder(cmd)); + } + + @Override + public String getName() { + return remote; + } + + @Override + public double getLoad() { + Process process; + try { + process = new ProcessBuilder("ssh", remote, "cat /proc/loadavg;cat /proc/meminfo").start(); + if(!process.waitFor(1, TimeUnit.SECONDS)) + return Double.POSITIVE_INFINITY; + } catch (IOException e) { + BungeeCore.get().getLogger().log(Level.SEVERE, "Could starting process to read load", e); + return Double.POSITIVE_INFINITY; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return Double.POSITIVE_INFINITY; + } + + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + double cpuLoad = Double.parseDouble(bufferedReader.readLine().split(" ")[0]); + double totalMem = Double.parseDouble(bufferedReader.readLine().replaceAll(" +", " ").split(" ")[1]); + bufferedReader.readLine(); + double freeMem = Double.parseDouble(bufferedReader.readLine().replaceAll(" +", " ").split(" ")[1]); + return cpuLoad / cores + (freeMem > MIN_FREE_MEM ? 0 : ((totalMem - freeMem) / totalMem)); + } catch (IOException e) { + BungeeCore.get().getLogger().log(Level.SEVERE, "Could read load", e); + return Double.POSITIVE_INFINITY; + } + } + } +} diff --git a/src/de/steamwar/bungeecore/SubserverSystem.java b/src/de/steamwar/bungeecore/SubserverSystem.java index d5e85b0..018c8a3 100644 --- a/src/de/steamwar/bungeecore/SubserverSystem.java +++ b/src/de/steamwar/bungeecore/SubserverSystem.java @@ -27,18 +27,14 @@ import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.connection.ProxiedPlayer; import java.io.File; -import java.io.IOException; import java.net.InetSocketAddress; import java.time.format.DateTimeFormatter; -import java.util.*; -import java.util.logging.Level; +import java.util.UUID; public class SubserverSystem { private SubserverSystem(){} - private static final String BACKBONE = "/home/minecraft/backbone/"; - private static final List JVM_ARGS = Arrays.asList("-Dlog4j.configurationFile=log4j2.xml", "-server", "-Xms128M", "-XX:+UseCompressedOops", "-XX:+TieredCompilation", "-XX:TargetSurvivorRatio=90", "-XX:SurvivorRatio=8", "-XX:MaxTenuringThreshold=15", "-XX:+UnlockExperimentalVMOptions", "-XX:+UseBiasedLocking", "-XX:UseSSE=3", "-XX:+UseCodeCacheFlushing", "-XX:+UseThreadPriorities", "-XX:+AggressiveOpts", "-XX:+ReduceSignalUsage", "-XX:+UseInterpreter", "-XX:+UseSharedSpaces", "-XX:AllocatePrefetchStyle=1", "-XX:+AlwaysCompileLoopMethods", "-XX:+UseConcMarkSweepGC", "-XX:+RewriteFrequentPairs", "-XX:+OptimizeStringConcat", "-XX:+CMSCleanOnEnter", "-XX:+UseInlineCaches"); - private static final List JVM8_ARGS = Arrays.asList("-XX:ThreadPriorityPolicy=42", "-XX:SharedReadOnlySize=30m", "-XX:+UseFastEmptyMethods", "-XX:+UseFastAccessorMethods"); + private static final String BACKBONE = "/home/minecraft/"; private static final String ARENA_PATH = BACKBONE + "arenaserver/"; private static final String SERVER_PATH = BACKBONE + "server/"; private static final String EVENT_PATH = BACKBONE + "event/"; @@ -91,6 +87,7 @@ public class SubserverSystem { */ public static synchronized Subserver startArena(ArenaMode modus, String map, int eventFightID, int checkSchemID, int prepareSchemID, String serverName, String mapName, UUID player1, UUID player2, boolean ranked){ //Generate missing parameters + Node node = eventFightID > 0 ? Node.local : Node.getNode(); int port = freePort(FIRST_ARENA_PORT); if(serverName == null){ @@ -109,56 +106,31 @@ public class SubserverSystem { worldDir = ARENA_PATH; //Copy world - try { - new ProcessBuilder("cp", "-r", SERVER_PATH + modus.getFolder() + "/arenas/" + map, worldDir + mapName).start().waitFor(); - } catch (IOException e) { - throw new SecurityException("Could not copy folder", e); - } catch (InterruptedException e) { - ProxyServer.getInstance().getLogger().log(Level.SEVERE, "Interrupted while copying folder", e); - Thread.currentThread().interrupt(); - } + node.execute("cp", "-r", SERVER_PATH + modus.getFolder() + "/arenas/" + map, worldDir + mapName); File directory = new File(SERVER_PATH, modus.getFolder()); - List cmd = serverStartCommand( - modus.serverJar(), - directory, - worldDir, - mapName, - port, - "2G", - "logPath=" + mapName, - "config="+modus.getConfig(), - "fightID=" + eventFightID, - "ranked=" + ranked, - "checkSchemID=" + checkSchemID, - "prepareSchemID=" + prepareSchemID, + ProcessBuilder builder = node.startServer( + modus.serverJar(), directory, worldDir, mapName, port, "2G", + "logPath=" + mapName, "config=" + modus.getConfig(), + "fightID=" + eventFightID, "ranked=" + ranked, + "checkSchemID=" + checkSchemID, "prepareSchemID=" + prepareSchemID, player1 != null && eventFightID != -1 ? "blueLeader=" + player1 : null, - player2 != null ? "redLeader=" + player2 : null); - - //Start server - ProcessBuilder process = new ProcessBuilder(cmd); - process.directory(directory); + player2 != null ? "redLeader=" + player2 : null + ); String finalMapName = mapName; if(eventFightID == -1) - return new Bauserver(serverName, player1, port, process, () -> deleteFolder(ARENA_PATH + finalMapName)); + return new Bauserver(serverName, player1, port, builder, () -> deleteFolder(node, ARENA_PATH + finalMapName)); else - return new Subserver(Servertype.ARENA, serverName, port, process, () -> { + return new Subserver(Servertype.ARENA, serverName, port, builder, () -> { if(eventFightID > 0) return; - deleteFolder(ARENA_PATH + finalMapName); + deleteFolder(node, ARENA_PATH + finalMapName); }); } - public static void deleteFolder(String worldName){ - try { - new ProcessBuilder("rm", "-r", worldName).start().waitFor(); - } catch (IOException e) { - throw new SecurityException("Could not clean up folder", e); - } catch (InterruptedException e) { - ProxyServer.getInstance().getLogger().log(Level.SEVERE, "Interrupted while deleting folder", e); - Thread.currentThread().interrupt(); - } + public static void deleteFolder(Node node, String worldName){ + node.execute("rm", "-r", worldName); } public static Subserver startEventArena(EventFight eventFight, String serverName){ @@ -183,25 +155,15 @@ public class SubserverSystem { if(bauRunning(p, owner)) return; + copyBauweltIfRequired(prototype, worldFolder + worldName); SteamwarUser user = SteamwarUser.get(owner); - copyBauweltIfRequired(p, prototype, worldFolder + worldName); + File directory = new File(SERVER_PATH, serverName); + Node node = Node.getNode(); int port = freePort(4000); - File directory = new File(SERVER_PATH, serverName); - List cmd = serverStartCommand( - serverJar, - directory, - worldDir, - worldName, - port, - xmx, - "logPath=" + worldName); - - //Start server - ProcessBuilder process = new ProcessBuilder(cmd); - process.directory(directory); - - new Bauserver(user.getUserName() + "s Bau", owner, port, process, () -> {}).sendPlayer(p); + new Bauserver(user.getUserName() + "s Bau", owner, port, node.startServer( + serverJar, directory, worldDir, worldName, port, xmx, "logPath=" + worldName + ), () -> {}).sendPlayer(p); } public static void sendToBauServer(ProxiedPlayer p, UUID owner){ @@ -235,63 +197,6 @@ public class SubserverSystem { new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/bau addmember " + p.getName())); } - private static List serverStartCommand(String serverJar, File directory, String worldDir, String levelName, int port, String xmx, String... dParams){ - List cmd = new ArrayList<>(); - boolean jdk11 = serverJar.contains("1.15.2"); - - boolean fallback = false; - if (!steamwarStartAvailable()) { - cmd.add("ssh"); - cmd.add("-L"); - cmd.add(port + ":localhost:" + port); - if (remoteStartAvailable("lx")) { - cmd.add("lx"); - } else if (remoteStartAvailable("az")) { - cmd.add("az"); - } else { - fallback = true; - } - cmd.add("cd"); - cmd.add(directory.getPath()); - cmd.add(";"); - } - if (fallback) { - cmd.clear(); - } - - if(jdk11) - cmd.add("/usr/lib/jvm/java-11-openjdk-amd64/bin/java"); - else - cmd.add("java"); - - for(String param : dParams){ - cmd.add("-D" + param); - } - cmd.add("-Xmx" + xmx); - cmd.addAll(JVM_ARGS); - if(!jdk11) - cmd.addAll(JVM8_ARGS); - cmd.add("-jar"); - cmd.add("/binarys/" + serverJar); - cmd.add("--log-strip-color"); - cmd.add("--world-dir"); - cmd.add(worldDir); - cmd.add("--level-name"); - cmd.add(levelName); - cmd.add("--port"); - cmd.add(String.valueOf(port)); - - return cmd; - } - - private static boolean steamwarStartAvailable(){ - return LoadEvaluation.getCPULoad() < 0.7 && LoadEvaluation.getRamPercentage() < 0.8; - } - - private static boolean remoteStartAvailable(String remote) { - return LoadEvaluation.getRemoteCPULoad(remote) < 0.7 && LoadEvaluation.getRemoteRamPercentage(remote) < 0.8; - } - private static boolean bauRunning(ProxiedPlayer p, UUID owner){ for(Subserver subserver : Subserver.getServerList()){ if(subserver.getType() == Servertype.BAUSERVER && ((Bauserver)subserver).getOwner().equals(owner)){ @@ -302,20 +207,10 @@ public class SubserverSystem { return false; } - private static void copyBauweltIfRequired(ProxiedPlayer p, String sourcePath, String targetPath){ + private static void copyBauweltIfRequired(String sourcePath, String targetPath){ File w = new File(targetPath); - if (!w.exists() || !w.isDirectory()){ - try { - new ProcessBuilder("cp", "-r", sourcePath, targetPath).start().waitFor(); - } catch (IOException e) { - Message.send("SERVER_WORLD_ERROR", p); - throw new SecurityException("Could not create Bauwelt", e); - } catch (InterruptedException e){ - BungeeCore.log("Could not create Bauwelt", e); - Message.send("SERVER_WORLD_ERROR", p); - Thread.currentThread().interrupt(); - } - } + if (!w.exists() || !w.isDirectory()) + Node.local.execute("cp", "-r", sourcePath, targetPath); } private static int freePort(int start){ diff --git a/src/de/steamwar/bungeecore/commands/BauCommand.java b/src/de/steamwar/bungeecore/commands/BauCommand.java index e827427..2965f08 100644 --- a/src/de/steamwar/bungeecore/commands/BauCommand.java +++ b/src/de/steamwar/bungeecore/commands/BauCommand.java @@ -257,7 +257,7 @@ public class BauCommand extends BasicCommand { break; } } - SubserverSystem.deleteFolder(world); + SubserverSystem.deleteFolder(Node.local, world); }); } diff --git a/src/de/steamwar/bungeecore/commands/StatCommand.java b/src/de/steamwar/bungeecore/commands/StatCommand.java index 1488dbf..b633fb5 100644 --- a/src/de/steamwar/bungeecore/commands/StatCommand.java +++ b/src/de/steamwar/bungeecore/commands/StatCommand.java @@ -19,8 +19,8 @@ package de.steamwar.bungeecore.commands; -import de.steamwar.bungeecore.LoadEvaluation; import de.steamwar.bungeecore.Message; +import de.steamwar.bungeecore.Node; import net.md_5.bungee.api.CommandSender; import java.io.BufferedReader; @@ -36,34 +36,22 @@ public class StatCommand extends BasicCommand { @Override public void execute(CommandSender sender, String[] args) { + Map serverCount = new HashMap<>(); try { Process process = new ProcessBuilder("ps", "x").start(); - process.waitFor(); - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream())); - Map serverCounts = new HashMap<>(); - bufferedReader.lines().forEach(s -> { - if (!s.contains("--port")) { + new BufferedReader(new InputStreamReader(process.getInputStream())).lines().forEach(s -> { + if (!s.contains("--port")) return; - } - String server = "SW"; - if (s.contains("ssh -L")) { - server = s.substring(s.indexOf("ssh -L") + 6).split(" ")[2]; - } - serverCounts.put(server, serverCounts.computeIfAbsent(server, s1 -> 0) + 1); + serverCount.compute( + s.contains("ssh -L") ? s.substring(s.indexOf("ssh -L") + 6).split(" ")[2] : "local", + (server, count) -> (count != null ? count : 0) + 1 + ); }); - serverCounts.forEach((s, integer) -> { - if (s.equals("SW")) { - Message.send("STAT_SERVER", sender, s, LoadEvaluation.getRamPercentage(), LoadEvaluation.getCPULoad(), integer); - } else { - Message.send("STAT_SERVER", sender, s.toUpperCase(), LoadEvaluation.getRemoteRamPercentage(s), LoadEvaluation.getRemoteCPULoad(s), integer); - } - }); - if (serverCounts.isEmpty()) { - Message.send("NO_STATS", sender); - } } catch (Exception e) { throw new SecurityException(e.getMessage(), e); } + + Node.forEach(node -> Message.send("STAT_SERVER", sender, node.getName(), node.getLoad(), serverCount.getOrDefault(node.getName(), 0))); } } diff --git a/src/de/steamwar/bungeecore/sql/Statement.java b/src/de/steamwar/bungeecore/sql/Statement.java index eac7650..c37538b 100644 --- a/src/de/steamwar/bungeecore/sql/Statement.java +++ b/src/de/steamwar/bungeecore/sql/Statement.java @@ -40,39 +40,48 @@ public class Statement { Statement.user = user; Statement.password = password; try { - con = DriverManager.getConnection(url + "?autoreconnect=true", user, password); + con = DriverManager.getConnection(url + "?autoreconnect=true&useServerPrepStmts=true", user, password); } catch (SQLException e) { ProxyServer.getInstance().stop(); throw new SecurityException("Could not start SQL-Connection", e); } } - private static void reset(SQLException e) { - BungeeCore.get().getLogger().log(Level.WARNING, "SQL Exception thrown", e); + private static void reset() { close(); connect(url, user, password); try { for (Statement statement : statements) { statement.init(); } - } catch (SQLException ex) { - throw new SecurityException("Could not reprepare SQL Statements", ex); + } catch (SQLException e) { + throw new SecurityException("Could not reprepare SQL statements", e); } } public static void close() { - for (Statement statement : statements) { + synchronized (statements) { + for (Statement statement : statements) { + try { + statement.st.close(); + } catch (SQLException e) { + BungeeCore.get().getLogger().log(Level.INFO, "Could not close statement", e); + } + } + try { - statement.st.close(); + con.close(); } catch (SQLException e) { - BungeeCore.get().getLogger().log(Level.INFO, "Could not close statement", e); + BungeeCore.get().getLogger().log(Level.INFO, "Could not close SQL connection", e); } } + } + public static boolean connectionStable() { try { - con.close(); + return !con.isClosed(); } catch (SQLException e) { - BungeeCore.log("Could not close SQL-Connection", e); + return false; } } @@ -85,39 +94,38 @@ public class Statement { try { init(); } catch (SQLException e) { - reset(e); + throw new SecurityException("Could not init SQL statement", e); } } - private synchronized void init() throws SQLException { + private void init() throws SQLException { st = con.prepareStatement(sql); } T select(ResultSetUser user, Object... objects) { - return prepare(() -> { - ResultSet rs = st.executeQuery(); - T result = user.use(rs); - rs.close(); - return result; - }, objects); + synchronized (statements) { + return prepare(() -> { + ResultSet rs = st.executeQuery(); + T result = user.use(rs); + rs.close(); + return result; + }, objects); + } } void update(Object... objects) { - prepare(st::executeUpdate, objects); + synchronized (statements) { + prepare(st::executeUpdate, objects); + } } - private synchronized T prepare(SQLRunnable runnable, Object... objects) { + private T prepare(SQLRunnable runnable, Object... objects) { try { setObjects(objects); return runnable.run(); } catch (SQLException e) { - reset(e); - try { - setObjects(objects); - return runnable.run(); - } catch (SQLException ex) { - throw new SecurityException("Could not execute SQL statement", ex); - } + reset(); + throw new SecurityException("Could not execute SQL statement", e); } } diff --git a/src/de/steamwar/messages/BungeeCore.properties b/src/de/steamwar/messages/BungeeCore.properties index 31cb6cc..5d32af1 100644 --- a/src/de/steamwar/messages/BungeeCore.properties +++ b/src/de/steamwar/messages/BungeeCore.properties @@ -107,8 +107,7 @@ MOD_YELLOW_PLUR=§7Deaktiviere die Mods\n§e{0}\n§7um weiter auf §eSteam§8War #Various commands ALERT=§f{0} -STAT_SERVER=§7Server §f{0} - §7Ram §f{1} §7CPU §f{2} §7Server Count §f{3} -NO_STATS=§7Kein Bau oder Fight Server gestartet +STAT_SERVER=§7Server §e{0}§8: §7Load §e{1} §7Serveranzahl §e{2} #Ban&Mute-Command BAN_TEAM_BANNED={0} §c{1} wurde von {2} {3} gebannt. §f§lGrund: §f{4} diff --git a/steamwarci.yml b/steamwarci.yml new file mode 100644 index 0000000..432a095 --- /dev/null +++ b/steamwarci.yml @@ -0,0 +1,6 @@ +build: + - "ln -s /home/gitea/lib" + - "mvn package -B" + +artifacts: + "/binarys/bungeecore.jar": "target/bungeecore.jar"