From 9641f9dd96392bd0df62ac8b671d43ea8607224a Mon Sep 17 00:00:00 2001 From: Gurkengewuerz Date: Sun, 22 Oct 2017 16:18:05 +0200 Subject: [PATCH] init Repo --- .gitignore | 4 + pom.xml | 89 ++++++ .../java/de/gurkengewuerz/monitoring/GUI.java | 254 ++++++++++++++++++ .../de/gurkengewuerz/monitoring/Poller.java | 109 ++++++++ .../de/gurkengewuerz/monitoring/StartUp.java | 107 ++++++++ .../gurkengewuerz/monitoring/Variables.java | 10 + .../monitoring/object/Database.java | 128 +++++++++ .../monitoring/object/MassCommand.java | 128 +++++++++ .../monitoring/object/SSHManager.java | 140 ++++++++++ .../monitoring/object/Server.java | 90 +++++++ .../monitoring/object/ServerStatus.java | 101 +++++++ .../monitoring/object/State.java | 38 +++ .../monitoring/object/StatusReply.java | 15 ++ 13 files changed, 1213 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/main/java/de/gurkengewuerz/monitoring/GUI.java create mode 100644 src/main/java/de/gurkengewuerz/monitoring/Poller.java create mode 100644 src/main/java/de/gurkengewuerz/monitoring/StartUp.java create mode 100644 src/main/java/de/gurkengewuerz/monitoring/Variables.java create mode 100644 src/main/java/de/gurkengewuerz/monitoring/object/Database.java create mode 100644 src/main/java/de/gurkengewuerz/monitoring/object/MassCommand.java create mode 100644 src/main/java/de/gurkengewuerz/monitoring/object/SSHManager.java create mode 100644 src/main/java/de/gurkengewuerz/monitoring/object/Server.java create mode 100644 src/main/java/de/gurkengewuerz/monitoring/object/ServerStatus.java create mode 100644 src/main/java/de/gurkengewuerz/monitoring/object/State.java create mode 100644 src/main/java/de/gurkengewuerz/monitoring/object/StatusReply.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9fec254 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.idea/ +target/ +*.sqlite3 +*.iml diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..ff5953c --- /dev/null +++ b/pom.xml @@ -0,0 +1,89 @@ + + + 4.0.0 + + de.gurkengewuerz.monitoring + monitoring + 1.0-SNAPSHOT + + + + com.jcraft + jsch + 0.1.54 + + + org.xerial + sqlite-jdbc + 3.15.1 + + + + org.json + json + 20170516 + + + org.tinylog + slf4j-binding + 1.2 + + + commons-cli + commons-cli + 1.3.1 + + + commons-validator + commons-validator + 1.6 + + + com.googlecode.lanterna + lanterna + 3.0.0 + + + + + + + maven-compiler-plugin + 3.5.1 + + 1.8 + 1.8 + + + + maven-assembly-plugin + 2.6 + + false + ${project.artifactId} + + + jar-with-dependencies + + + + + ${project.groupId}.StartUp + + + + + + assamble + + single + + package + + + + + + \ No newline at end of file diff --git a/src/main/java/de/gurkengewuerz/monitoring/GUI.java b/src/main/java/de/gurkengewuerz/monitoring/GUI.java new file mode 100644 index 0000000..8689aef --- /dev/null +++ b/src/main/java/de/gurkengewuerz/monitoring/GUI.java @@ -0,0 +1,254 @@ +package de.gurkengewuerz.monitoring; + +import com.googlecode.lanterna.TerminalSize; +import com.googlecode.lanterna.TextColor; +import com.googlecode.lanterna.gui2.*; +import com.googlecode.lanterna.gui2.dialogs.ActionListDialogBuilder; +import com.googlecode.lanterna.gui2.dialogs.MessageDialog; +import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton; +import com.googlecode.lanterna.gui2.dialogs.TextInputDialog; +import com.googlecode.lanterna.gui2.table.Table; +import com.googlecode.lanterna.screen.Screen; +import com.googlecode.lanterna.screen.TerminalScreen; +import com.googlecode.lanterna.terminal.DefaultTerminalFactory; +import com.googlecode.lanterna.terminal.Terminal; +import de.gurkengewuerz.monitoring.object.MassCommand; +import de.gurkengewuerz.monitoring.object.Server; +import de.gurkengewuerz.monitoring.object.ServerStatus; +import org.apache.commons.validator.routines.InetAddressValidator; +import org.pmw.tinylog.Logger; +import org.sqlite.date.DateFormatUtils; + +import java.io.IOException; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.net.URI; +import java.net.URISyntaxException; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; + +/** + * Created by gurkengewuerz.de on 21.10.2017. + */ +public class GUI { + public GUI() { + HashMap serverlist = Server.getServerList(StartUp.getDb()); + Poller poller = new Poller(); + try { + Terminal terminal = new DefaultTerminalFactory() + .setTerminalEmulatorTitle("Servermanagment").createTerminal(); + //TODO: Set GUI Icon + Screen screen = new TerminalScreen(terminal); + screen.startScreen(); + + Panel mainPanel = new Panel(); + mainPanel.setLayoutManager(new LinearLayout(Direction.HORIZONTAL)); + mainPanel.setPreferredSize(new TerminalSize(40, 20)); + + Panel leftPanel = new Panel(); + mainPanel.addComponent(leftPanel.withBorder(Borders.singleLine("Serverliste"))); + + Panel rightPanel = new Panel(); + mainPanel.addComponent(rightPanel.withBorder(Borders.singleLine("Aktionen"))); + + BasicWindow window = new BasicWindow(); + window.setComponent(mainPanel.withBorder(Borders.singleLine("Servermanagment"))); + window.setHints(Arrays.asList(Window.Hint.CENTERED)); + + MultiWindowTextGUI gui = new MultiWindowTextGUI(screen, new DefaultWindowManager(), new EmptySpace(TextColor.ANSI.BLUE)); + + CheckBoxList checkBoxList = new CheckBoxList().addTo(leftPanel); + updateList(checkBoxList, serverlist); + + new Button("Server hinzufügen", () -> { + String servername = TextInputDialog.showDialog(gui, "Servername", "Wie soll der Identifiziert werden (Servername)", "Server_Cloud"); + if (servername == null) return; + + if (serverlist.containsKey(servername)) { + MessageDialog.showMessageDialog(gui, "ERROR!", "Server bereits vorhanden", MessageDialogButton.Cancel); + return; + } + + String stringIPPort = TextInputDialog.showDialog(gui, "username@IP:Port", "Wie lautet der Hostname und Port des SSH Servers", "root@10.10.2.156:22"); + if (stringIPPort == null) return; + + String ss = "ssh://" + stringIPPort; + + URI uri; + try { + uri = new URI(ss); + } catch (URISyntaxException e) { + MessageDialog.showMessageDialog(gui, "ERROR!", "Fehler beim lesen der URL", MessageDialogButton.Cancel); + return; + } + + String ip = uri.getHost(); + int port = uri.getPort(); + String username = uri.getUserInfo() == null ? "root" : uri.getUserInfo(); + + if (port <= 0 || port > 65535) { + MessageDialog.showMessageDialog(gui, "ERROR!", "Port muss größer NUll und kleiner 65535 sein", MessageDialogButton.Cancel); + return; + } + + InetAddressValidator inetAddressValidator = new InetAddressValidator(); + if (!inetAddressValidator.isValid(ip)) { + MessageDialog.showMessageDialog(gui, "ERROR!", "Inkorrekte IP Adresse", MessageDialogButton.Cancel); + return; + } + + try { + PreparedStatement ps = StartUp.getDb().getPreparedStatement("INSERT INTO server (servername, username, ip, port, adddate) VALUES (?, ?, ?, ?, ?);"); + ps.setString(1, servername); + ps.setString(2, username); + ps.setString(3, ip); + ps.setInt(4, port); + ps.setLong(5, System.currentTimeMillis() / 1000); + ps.execute(); + + ResultSet rs = ps.getGeneratedKeys(); + rs.next(); + int serverid = rs.getInt(1); + Server server = new Server(serverid, servername, username, ip, port); + serverlist.put(servername, server); + checkBoxList.addItem(servername); + Logger.info("Added: " + ip + " " + port); + MessageDialog.showMessageDialog(gui, "INFO", "Server hinzugefügt.\n" + + "Gehe sicher das der Public Key von " + StartUp.getPrivate_key() + " auf dem Server abgelegt wurde.", MessageDialogButton.Continue); + } catch (SQLException e) { + Logger.error(e); + } + }).addTo(rightPanel).takeFocus(); + + new Button("Alle auswählen", () -> checkBoxList.getItems().forEach(s -> checkBoxList.setChecked(s, true))).addTo(rightPanel); + + new Button("Aktion ausführen", () -> { + List checkedItems = checkBoxList.getCheckedItems(); + if (checkedItems.size() <= 0) { + MessageDialog.showMessageDialog(gui, "ERROR!", "Bitte wähle zuerst Server aus!"); + return; + } + + List serverarray = new ArrayList<>(); + checkedItems.forEach(s -> serverarray.add(serverlist.get(s))); + + new ActionListDialogBuilder() + .setTitle("Aktion ausführen") + .setDescription("Was möchtest du unternhmen") + .addAction("Server löschen", () -> { + try { + PreparedStatement ps = StartUp.getDb().getPreparedStatement("DELETE FROM server WHERE id = ?"); + serverarray.forEach(server -> { + try { + ps.setInt(1, server.getId()); + ps.addBatch(); + serverlist.remove(server.getName()); + } catch (SQLException e) { + Logger.error(e); + } + }); + ps.executeBatch(); + } catch (SQLException e) { + Logger.error(e); + } + updateList(checkBoxList, serverlist); + }) + .addAction("Server Status", () -> { + BasicWindow windowStatus = getStatusWindow(checkBoxList, serverlist); + gui.addWindow(windowStatus); + gui.setActiveWindow(windowStatus); + }) + .addAction("Server updaten", () -> MassCommand.run("apt-get update && apt-get upgrade -y", gui, serverarray, StartUp.getPrivate_key())) + .addAction("Herunterfahren", () -> MassCommand.run("shutdown -h now", gui, serverarray, StartUp.getPrivate_key())) + .addAction("Neustart", () -> MassCommand.run("reboot", gui, serverarray, StartUp.getPrivate_key())) + .build() + .showDialog(gui); + }).addTo(rightPanel); + + new Button("Aktualisieren", () -> { + // TODO: serverlist = Server.getServerList(db); + updateList(checkBoxList, serverlist); + }).addTo(rightPanel); + + new Button("Beenden", () -> { + poller.stop(); + StartUp.getDb().closeConnection(); + System.exit(0); + }).addTo(rightPanel); + + screen.setCursorPosition(null); + + gui.addWindowAndWait(window); + } catch (IOException e) { + Logger.error(e); + } + } + + private void updateList(CheckBoxList checkBoxList, HashMap serverlist) { + checkBoxList.clearItems(); + serverlist.forEach((s, server) -> checkBoxList.addItem(s)); + } + + private BasicWindow getStatusWindow(CheckBoxList checkBoxList, HashMap serverlist) { + Panel statusPanel = new Panel(); + statusPanel.setLayoutManager(new LinearLayout(Direction.VERTICAL)); + + BasicWindow windowStatus = new BasicWindow(); + windowStatus.setComponent(statusPanel.withBorder(Borders.singleLine("Server Status"))); + windowStatus.setHints(Arrays.asList(Window.Hint.CENTERED)); + + Table table = new Table("Name", "OS", "CPU", "RAM", "Uptime", "Last", "Status").addTo(statusPanel); + + updateTable(table, checkBoxList, serverlist); + + Button back = new Button("Zurück", windowStatus::close).addTo(statusPanel); + + back.takeFocus(); + return windowStatus; + } + + private void updateTable(Table table, CheckBoxList checkBoxList, HashMap serverlist) { + for (String s : checkBoxList.getCheckedItems()) { + Server server = serverlist.get(s); + ServerStatus status = server.getStatus(); + if (status.getLastpoll() == 0) { + return; + } + double totalMB = round(status.getMemTotal() / 1024.0, 2); + double freeMB = round(status.getMemFree() / 1024.0, 2); + + table.getTableModel().addRow(server.getName(), + status.getOS(), status.getCPUcount() + "x " + status.getCPUModel(), + freeMB + "MB/" + totalMB + "MB", DateFormatUtils.format(status.getUptime() * 1000, "HH:mm:ss"), + String.valueOf(status.getLoad()), status.getState().toString()); + } + // "Name", "OS", "CPU", "RAM", "Uptime", "Last", "Status" + } + + public static BasicWindow getCallbackWindow(Label label, Button abort) { + Panel statusPanel = new Panel(); + statusPanel.setLayoutManager(new LinearLayout(Direction.VERTICAL)); + + BasicWindow windowStatus = new BasicWindow(); + windowStatus.setComponent(statusPanel.withBorder(Borders.singleLine("Update Status"))); + windowStatus.setHints(Arrays.asList(Window.Hint.CENTERED)); + + label.addTo(statusPanel); + + abort.addTo(statusPanel); + return windowStatus; + } + + private double round(double value, int places) { + if (places < 0) throw new IllegalArgumentException(); + + BigDecimal bd = new BigDecimal(value); + bd = bd.setScale(places, RoundingMode.HALF_UP); + return bd.doubleValue(); + } +} diff --git a/src/main/java/de/gurkengewuerz/monitoring/Poller.java b/src/main/java/de/gurkengewuerz/monitoring/Poller.java new file mode 100644 index 0000000..254861e --- /dev/null +++ b/src/main/java/de/gurkengewuerz/monitoring/Poller.java @@ -0,0 +1,109 @@ +package de.gurkengewuerz.monitoring; + +import de.gurkengewuerz.monitoring.object.SSHManager; +import de.gurkengewuerz.monitoring.object.Server; +import de.gurkengewuerz.monitoring.object.ServerStatus; +import de.gurkengewuerz.monitoring.object.State; +import org.pmw.tinylog.Logger; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.HashMap; +import java.util.Locale; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Created by gurkengewuerz.de on 21.10.2017. + */ +public class Poller { + final ScheduledExecutorService scheduler; + + public Poller() { + scheduler = Executors.newScheduledThreadPool(1); + scheduler.scheduleAtFixedRate(() -> { + HashMap serverlist = Server.getServerList(StartUp.getDb()); + + serverlist.forEach((s, server) -> { + Logger.info("Poling " + s); + ServerStatus status = server.getStatus(); + if (status.getState() == State.PAUSED) { + Logger.info("Paused!"); + return; + } + SSHManager manager = server.getSSHSession(StartUp.getPrivate_key()); + if (manager.hasError()) { + status.setState(State.OFFLINE); + Logger.error("Offline!"); + + try { + if (status.getLastpoll() == 0) return; + PreparedStatement ps = StartUp.getDb().getPreparedStatement("UPDATE status SET state = ?, lastpoll = ? WHERE server_id = ?;"); + ps.setString(1, status.getState().toString()); + ps.setLong(2, System.currentTimeMillis() / 1000); + ps.setInt(3, server.getId()); + ps.execute(); + } catch (SQLException e) { + Logger.error(e); + } + status.setLastpoll(System.currentTimeMillis() / 1000); + return; + } + + try { + status.setState(State.ONLINE); + Logger.info("Online!"); + + NumberFormat format = NumberFormat.getInstance(Locale.US); + String os = manager.sendCommand(". /etc/os-release; echo ${PRETTY_NAME/*, /}").replace("\n", ""); + status.setOS(os); + String cpumodel = manager.sendCommand("grep \"model name\" /proc/cpuinfo | cut -c14- | head -n 1").replace("\n", ""); + status.setCPU(cpumodel); + String cpucount = manager.sendCommand("cat /proc/cpuinfo | grep processor | wc -l").replace("\n", ""); + status.setCPUcount(format.parse(cpucount).intValue()); + String memtotal = manager.sendCommand("awk '/MemTotal/ { print $2 }' /proc/meminfo").replace("\n", ""); + status.setMemTotal(format.parse(memtotal).intValue()); + String memfree = manager.sendCommand("awk '/MemFree/ { print $2 }' /proc/meminfo").replace("\n", ""); + status.setMemFree(format.parse(memfree).intValue()); + String load = manager.sendCommand("if [[ -z `uptime | awk '{print $12}' | cut -d \",\" -f 1` ]]; then uptime | awk '{print $10}' | cut -d \",\" -f 1; else uptime | awk '{print $12}' | cut -d \",\" -f 1; fi;").replace("\n", ""); + status.setLoad(format.parse(load).floatValue()); + String uptimeseconds = manager.sendCommand("awk '{print $1}' /proc/uptime").replace("\n", ""); + status.setUptime(format.parse(uptimeseconds).longValue()); + + try { + PreparedStatement ps = StartUp.getDb().getPreparedStatement("UPDATE status SET os = ?, cpu = ?, cpucount = ?, max_ram = ?, current_ram = ?, load = ?, uptime = ?, state = ?, lastpoll = ? WHERE server_id = ?;"); + if (status.getLastpoll() == 0) { + ps = StartUp.getDb().getPreparedStatement("INSERT INTO status (os, cpu, cpucount, max_ram, current_ram, load, uptime, state, lastpoll, server_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?);"); + + } + ps.setString(1, os); + ps.setString(2, cpumodel); + ps.setInt(3, format.parse(cpucount).intValue()); + ps.setInt(4, format.parse(memtotal).intValue()); + ps.setInt(5, format.parse(memfree).intValue()); + ps.setFloat(6, format.parse(load).floatValue()); + ps.setLong(7, format.parse(uptimeseconds).longValue()); + ps.setString(8, status.getState().toString()); + ps.setLong(9, System.currentTimeMillis() / 1000); + ps.setInt(10, server.getId()); + ps.execute(); + } catch (SQLException e) { + Logger.error(e); + } + + status.setLastpoll(System.currentTimeMillis() / 1000); + } catch (ParseException e) { + Logger.error(e); + } + manager.close(); + }); + }, 0, 90, TimeUnit.SECONDS); + } + + public void stop() { + scheduler.shutdown(); + } +} diff --git a/src/main/java/de/gurkengewuerz/monitoring/StartUp.java b/src/main/java/de/gurkengewuerz/monitoring/StartUp.java new file mode 100644 index 0000000..b3ddf11 --- /dev/null +++ b/src/main/java/de/gurkengewuerz/monitoring/StartUp.java @@ -0,0 +1,107 @@ +package de.gurkengewuerz.monitoring; + +import de.gurkengewuerz.monitoring.object.Database; +import org.apache.commons.cli.*; +import org.pmw.tinylog.Logger; + +import java.io.File; +import java.sql.SQLException; + +/** + * Created by gurkengewuerz.de on 21.10.2017. + */ +public class StartUp { + private static Database db; + private static String private_key; + + public static void main(String... args) { + Options options = new Options(); + + Option database = new Option("d", "database", true, "Database Path"); + database.setRequired(false); + options.addOption(database); + + Option poller = new Option("p", "poller", false, "Start only the poller"); + poller.setRequired(false); + options.addOption(poller); + + Option sshkey = new Option("s", "key", true, "Set ssh private key"); + sshkey.setRequired(false); + options.addOption(sshkey); + + CommandLineParser parser = new DefaultParser(); + HelpFormatter formatter = new HelpFormatter(); + CommandLine cmd; + + try { + cmd = parser.parse(options, args); + } catch (ParseException e) { + Logger.error(e); + formatter.printHelp("utility-name", options); + + System.exit(1); + return; + } + + try { + db = new Database(cmd.getOptionValue("d", Variables.DATABASE_NAME)); + } catch (Exception e) { + Logger.error(e); + } + + if (db == null) System.exit(1); + + try { + createDatabase(); + } catch (SQLException e) { + Logger.error(e); + System.exit(1); + } + + private_key = cmd.getOptionValue("s", Variables.PRIVATE_KEY); + if (!new File(private_key).exists()) { + Logger.error(private_key + " wurde nicht gefunden!"); + System.exit(1); + } + if (cmd.hasOption("p")) { + new Poller(); + } else { + new GUI(); + } + } + + public static Database getDb() { + return db; + } + + public static String getPrivate_key() { + return private_key; + } + + private static void createDatabase() throws SQLException { + db.executeUpdate( + "CREATE TABLE IF NOT EXISTS server (" + + " id INTEGER PRIMARY KEY AUTOINCREMENT," + + " servername text NULL," + + " username text NULL," + + " ip text NULL," + + " port int NULL," + + " adddate float NOT NULL" + + ");" + ); + + db.executeUpdate("CREATE TABLE IF NOT EXISTS status (" + + "server_id INTEGER," + + "os TEXT," + + "cpu TEXT," + + "cpucount INTEGER," + + "max_ram INTEGER," + + "current_ram INTEGER," + + "load float," + + "uptime INTEGER," + + "state text," + + "lastpoll INTEGER" + + ");" + ); + } +} diff --git a/src/main/java/de/gurkengewuerz/monitoring/Variables.java b/src/main/java/de/gurkengewuerz/monitoring/Variables.java new file mode 100644 index 0000000..925f78a --- /dev/null +++ b/src/main/java/de/gurkengewuerz/monitoring/Variables.java @@ -0,0 +1,10 @@ +package de.gurkengewuerz.monitoring; + +/** + * Created by gurkengewuerz.de on 21.10.2017. + */ +public class Variables { + + public static final String DATABASE_NAME = "data.sqlite3"; + public static final String PRIVATE_KEY = System.getProperty("user.home") + "/.ssh/id_rsa"; +} diff --git a/src/main/java/de/gurkengewuerz/monitoring/object/Database.java b/src/main/java/de/gurkengewuerz/monitoring/object/Database.java new file mode 100644 index 0000000..907ee80 --- /dev/null +++ b/src/main/java/de/gurkengewuerz/monitoring/object/Database.java @@ -0,0 +1,128 @@ +package de.gurkengewuerz.monitoring.object; + +import java.sql.*; + +/** + * Created by gurkengewuerz.de on 08.04.2017. + */ +public class Database { + + private String sUrl = null; + private int iTimeout = 30; + private Connection conn = null; + private Statement statement = null; + + + public Database(String sUrlToLoad) throws Exception { + setUrl(sUrlToLoad); + setConnection(); + setStatement(); + } + + private void setUrl(String sUrlVar) { + sUrl = sUrlVar; + } + + private void setConnection() throws Exception { + conn = DriverManager.getConnection("jdbc:sqlite:" + sUrl); + } + + + public Connection getConnection() { + return conn; + } + + private void setStatement() throws Exception { + if (conn == null) { + setConnection(); + } + statement = conn.createStatement(); + statement.setQueryTimeout(iTimeout); // set timeout to 30 sec. + } + + public PreparedStatement getPreparedStatement(String sql) throws SQLException { + return conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); + } + + public void executeUpdate(String instruction) throws SQLException { + statement.executeUpdate(instruction); + } + + public ResultSet executeQuery(String instruction) throws SQLException { + return statement.executeQuery(instruction); + } + + public void closeConnection() { + try { + conn.close(); + } catch (Exception ignore) { + } + } + + public static String escapeString(String x, boolean escapeDoubleQuotes) { + StringBuilder sBuilder = new StringBuilder(x.length() * 11 / 10); + + int stringLength = x.length(); + + for (int i = 0; i < stringLength; ++i) { + char c = x.charAt(i); + + switch (c) { + case 0: /* Must be escaped for 'mysql' */ + sBuilder.append('\\'); + sBuilder.append('0'); + + break; + + case '\n': /* Must be escaped for logs */ + sBuilder.append('\\'); + sBuilder.append('n'); + + break; + + case '\r': + sBuilder.append('\\'); + sBuilder.append('r'); + + break; + + case '\\': + sBuilder.append('\\'); + sBuilder.append('\\'); + + break; + + case '\'': + sBuilder.append('\\'); + sBuilder.append('\''); + + break; + + case '"': /* Better safe than sorry */ + if (escapeDoubleQuotes) { + sBuilder.append('\\'); + } + + sBuilder.append('"'); + + break; + + case '\032': /* This gives problems on Win32 */ + sBuilder.append('\\'); + sBuilder.append('Z'); + + break; + + case '\u00a5': + case '\u20a9': + // escape characters interpreted as backslash by mysql + // fall through + + default: + sBuilder.append(c); + } + } + + return sBuilder.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/de/gurkengewuerz/monitoring/object/MassCommand.java b/src/main/java/de/gurkengewuerz/monitoring/object/MassCommand.java new file mode 100644 index 0000000..3c78157 --- /dev/null +++ b/src/main/java/de/gurkengewuerz/monitoring/object/MassCommand.java @@ -0,0 +1,128 @@ +package de.gurkengewuerz.monitoring.object; + +import com.googlecode.lanterna.gui2.BasicWindow; +import com.googlecode.lanterna.gui2.Button; +import com.googlecode.lanterna.gui2.Label; +import com.googlecode.lanterna.gui2.MultiWindowTextGUI; +import com.googlecode.lanterna.gui2.dialogs.MessageDialog; +import com.googlecode.lanterna.gui2.dialogs.MessageDialogButton; +import de.gurkengewuerz.monitoring.GUI; +import org.pmw.tinylog.Logger; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by gurkengewuerz.de on 22.10.2017. + */ +public class MassCommand { + + private String command; + private String private_key; + private List serverList = new ArrayList<>(); + private StatusReply answer; + private boolean stop = false; + private Thread t; + + public MassCommand(String command, String private_key, List serverList, StatusReply answer) { + this.command = command; + this.private_key = private_key; + this.serverList = serverList; + this.answer = answer; + } + + public MassCommand(String command, List serverList, StatusReply answer) { + this(command, "", serverList, answer); + } + + public MassCommand setPrivateKey(String private_key) { + this.private_key = private_key; + return this; + } + + public void start() { + t = new Thread(() -> { + final boolean[] failed = {false}; + final int[] counter = {0}; + answer.onStart(); + serverList.forEach(server -> { + if (stop) return; + if (failed[0]) return; + SSHManager manager = server.getSSHSession(private_key); + if (manager.hasError()) { + failed[0] = true; + answer.onFail(manager.getErrorMessage()); + return; + } + if (failed[0]) return; + + String result = manager.sendCommand(command); + Logger.info(result); + + manager.close(); + answer.onUpdate(counter[0] + 1, serverList.size()); + counter[0]++; + }); + + if (!failed[0]) answer.onFinish(); + }); + + t.start(); + } + + public void stop() { + stop = true; + t.interrupt(); + } + + public static void run(String command, MultiWindowTextGUI gui, List serverarray, String ssh_private) { + Label label = new Label("[ ] 00%"); + Button button = new Button("Abbruch"); + BasicWindow windowStatus = GUI.getCallbackWindow(label, button); + gui.addWindow(windowStatus); + gui.setActiveWindow(windowStatus); + + MassCommand mc = new MassCommand(command, serverarray, new StatusReply() { + @Override + public void onFinish() { + windowStatus.close(); + MessageDialog.showMessageDialog(gui, "Erfolgreich", "Befehle erfolgreich auf allen Servern ausgeführt", MessageDialogButton.OK); + } + + @Override + public void onStart() { + + } + + @Override + public void onUpdate(int current, int to_go) { + double perc = (double) current / (double) to_go; + double d = Math.round(perc * 20) / 20.0; // round up to multiple of 0.05 + int percInt = (int) (d * 100); + int count = percInt / 5; + + StringBuilder stringtext = new StringBuilder("["); + for (int i = 0; i < 20; i++) { + if (i + 1 <= count) { + stringtext.append("#"); + } else { + stringtext.append(" "); + } + } + stringtext.append("] ").append(percInt).append("%"); + + label.setText(stringtext.toString()); + } + + @Override + public void onFail(String message) { + windowStatus.close(); + MessageDialog.showMessageDialog(gui, "ERROR!", "Verbindung zum Server konnte nicht hergestellt werden!\n" + message, MessageDialogButton.Close); + } + }).setPrivateKey(ssh_private); + mc.start(); + + button.addListener(button1 -> mc.stop()); + button.takeFocus(); + } +} diff --git a/src/main/java/de/gurkengewuerz/monitoring/object/SSHManager.java b/src/main/java/de/gurkengewuerz/monitoring/object/SSHManager.java new file mode 100644 index 0000000..81121c6 --- /dev/null +++ b/src/main/java/de/gurkengewuerz/monitoring/object/SSHManager.java @@ -0,0 +1,140 @@ +package de.gurkengewuerz.monitoring.object; + +import com.jcraft.jsch.*; + +import java.io.IOException; +import java.io.InputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + + +/* +* SSHManager +* +* @author cabbott +* @version 1.0 +*/ +public class SSHManager { + private static final Logger LOGGER = + Logger.getLogger(SSHManager.class.getName()); + private JSch jschSSHChannel; + private String strUserName; + private String strConnectionIP; + private int intConnectionPort; + private String strPassword; + private Session sesConnection; + private String errorMessage = null; + private int intTimeOut; + + private void doCommonConstructorActions(String userName, + String password, String connectionIP, String knownHostsFileName) { + jschSSHChannel = new JSch(); + + try { + jschSSHChannel.addIdentity(password); + jschSSHChannel.setKnownHosts(knownHostsFileName); + } catch (JSchException jschX) { + logError(jschX.getMessage()); + } + + strUserName = userName; + strPassword = password; + strConnectionIP = connectionIP; + } + + public SSHManager(String userName, String password, + String connectionIP, String knownHostsFileName) { + doCommonConstructorActions(userName, password, + connectionIP, knownHostsFileName); + intConnectionPort = 22; + intTimeOut = 60000; + } + + public SSHManager(String userName, String password, String connectionIP, + String knownHostsFileName, int connectionPort) { + doCommonConstructorActions(userName, password, connectionIP, + knownHostsFileName); + intConnectionPort = connectionPort; + intTimeOut = 60000; + } + + public SSHManager(String userName, String password, String connectionIP, + String knownHostsFileName, int connectionPort, int timeOutMilliseconds) { + doCommonConstructorActions(userName, password, connectionIP, + knownHostsFileName); + intConnectionPort = connectionPort; + intTimeOut = timeOutMilliseconds; + } + + public String connect() { + errorMessage = null; + + try { + sesConnection = jschSSHChannel.getSession(strUserName, + strConnectionIP, intConnectionPort); + //sesConnection.setPassword(strPassword); + // UNCOMMENT THIS FOR TESTING PURPOSES, BUT DO NOT USE IN PRODUCTION + sesConnection.setConfig("StrictHostKeyChecking", "no"); + sesConnection.connect(intTimeOut); + } catch (JSchException jschX) { + errorMessage = jschX.getMessage(); + } + + return errorMessage; + } + + public boolean hasError() { + return errorMessage != null; + } + + public String getErrorMessage() { + return errorMessage; + } + + private String logError(String errorMessage) { + if (errorMessage != null) { + LOGGER.log(Level.SEVERE, "{0}:{1} - {2}", + new Object[]{strConnectionIP, intConnectionPort, errorMessage}); + } + + return errorMessage; + } + + private String logWarning(String warnMessage) { + if (warnMessage != null) { + LOGGER.log(Level.WARNING, "{0}:{1} - {2}", + new Object[]{strConnectionIP, intConnectionPort, warnMessage}); + } + + return warnMessage; + } + + public String sendCommand(String command) { + StringBuilder outputBuffer = new StringBuilder(); + + try { + Channel channel = sesConnection.openChannel("exec"); + ((ChannelExec) channel).setCommand(command); + InputStream commandOutput = channel.getInputStream(); + channel.connect(); + int readByte = commandOutput.read(); + + while (readByte != 0xffffffff) { + outputBuffer.append((char) readByte); + readByte = commandOutput.read(); + } + + channel.disconnect(); + } catch (IOException | JSchException ioX) { + logWarning(ioX.getMessage()); + return null; + } + + return outputBuffer.toString(); + } + + public void close() { + sesConnection.disconnect(); + } + +} \ No newline at end of file diff --git a/src/main/java/de/gurkengewuerz/monitoring/object/Server.java b/src/main/java/de/gurkengewuerz/monitoring/object/Server.java new file mode 100644 index 0000000..5bd472d --- /dev/null +++ b/src/main/java/de/gurkengewuerz/monitoring/object/Server.java @@ -0,0 +1,90 @@ +package de.gurkengewuerz.monitoring.object; + +import org.pmw.tinylog.Logger; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.HashMap; + +/** + * Created by gurkengewuerz.de on 21.10.2017. + */ +public class Server { + + private int id; + private String name; + private String username; + private String ip; + private int port; + private long timeAdded; + private ServerStatus status; + + public Server(int id, String name, String username, String ip, int port, long timeAdded, ServerStatus status) { + this.id = id; + this.name = name; + this.username = username; + this.ip = ip; + this.port = port; + this.timeAdded = timeAdded; + this.status = status; + } + + public Server(int id, String name, String username, String ip, int port) { + this(id, name, username, ip, port, System.currentTimeMillis() / 1000, + new ServerStatus(null, null, 0, 0, 0, 0, 0, State.OFFLINE, 0)); + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public String getUsername() { + return username; + } + + public long getTimeAdded() { + return timeAdded; + } + + public ServerStatus getStatus() { + return status; + } + + public void setStatus(ServerStatus status) { + this.status = status; + } + + public SSHManager getSSHSession(String ssh_private) { + SSHManager instance = new SSHManager(getUsername(), ssh_private, ip, "", port); + String errorMessage = instance.connect(); + + if (errorMessage != null) { + Logger.error(errorMessage); + } + return instance; + } + + public static HashMap getServerList(Database db) { + HashMap serverlist = new HashMap<>(); + + try { + ResultSet rs = db.executeQuery("SELECT * FROM server LEFT JOIN status ON server.id = status.server_id;"); + while (rs.next()) { + ServerStatus status = new ServerStatus(rs.getString("os"), + rs.getString("cpu"), rs.getInt("cpucount"), rs.getInt("max_ram"), + rs.getInt("current_ram"), rs.getFloat("load"), + rs.getLong("uptime"), State.getByName(rs.getString("state")), rs.getLong("lastpoll")); + Server s = new Server(rs.getInt("id"), rs.getString("servername"), rs.getString("username"), + rs.getString("ip"), rs.getInt("port"), rs.getLong("adddate"), status); + serverlist.put(rs.getString("servername"), s); + } + } catch (SQLException e) { + Logger.error(e); + } + return serverlist; + } +} diff --git a/src/main/java/de/gurkengewuerz/monitoring/object/ServerStatus.java b/src/main/java/de/gurkengewuerz/monitoring/object/ServerStatus.java new file mode 100644 index 0000000..e59b4cf --- /dev/null +++ b/src/main/java/de/gurkengewuerz/monitoring/object/ServerStatus.java @@ -0,0 +1,101 @@ +package de.gurkengewuerz.monitoring.object; + +/** + * Created by gurkengewuerz.de on 21.10.2017. + */ +public class ServerStatus { + + private String os; + private String cpu; + private int cpucount; + private int memTotal; + private int memFree; + private float load; + private long uptime; + private State state; + private long lastpoll; + + public ServerStatus(String os, String cpu, int cpucount, int memTotal, int memFree, float load, long uptime, State state, long lastpoll) { + this.os = os == null ? "Unknown" : os; + this.cpu = cpu == null ? "Unknown" : cpu; + this.cpucount = cpucount; + this.memTotal = memTotal; + this.memFree = memFree; + this.load = load; + this.uptime = uptime; + this.state = state; + this.lastpoll = lastpoll; + } + + public String getOS() { + return os; + } + + public String getCPUModel() { + return cpu; + } + + public int getCPUcount() { + return cpucount; + } + + public int getMemTotal() { + return memTotal; + } + + public int getMemFree() { + return memFree; + } + + public float getLoad() { + return load; + } + + public long getUptime() { + return uptime; + } + + public State getState() { + return state; + } + + public long getLastpoll() { + return lastpoll; + } + + public void setOS(String os) { + this.os = os; + } + + public void setCPU(String cpu) { + this.cpu = cpu; + } + + public void setCPUcount(int cpucount) { + this.cpucount = cpucount; + } + + public void setMemTotal(int memTotal) { + this.memTotal = memTotal; + } + + public void setMemFree(int memFree) { + this.memFree = memFree; + } + + public void setLoad(float load) { + this.load = load; + } + + public void setUptime(long uptime) { + this.uptime = uptime; + } + + public void setState(State state) { + this.state = state; + } + + public void setLastpoll(long lastpoll) { + this.lastpoll = lastpoll; + } +} diff --git a/src/main/java/de/gurkengewuerz/monitoring/object/State.java b/src/main/java/de/gurkengewuerz/monitoring/object/State.java new file mode 100644 index 0000000..7a7fb83 --- /dev/null +++ b/src/main/java/de/gurkengewuerz/monitoring/object/State.java @@ -0,0 +1,38 @@ +package de.gurkengewuerz.monitoring.object; + +/** + * Created by gurkengewuerz.de on 21.10.2017. + */ +public enum State { + ONLINE { + @Override + public String toString() { + return "online"; + } + }, + OFFLINE { + @Override + public String toString() { + return "offline"; + } + }, + PAUSED { + @Override + public String toString() { + return "paused"; + } + }; + + public static State getByName(String state) { + if (state == null) return null; + switch (state.toLowerCase()) { + case "offline": + return OFFLINE; + case "online": + return ONLINE; + case "paused": + return PAUSED; + } + return null; + } +} diff --git a/src/main/java/de/gurkengewuerz/monitoring/object/StatusReply.java b/src/main/java/de/gurkengewuerz/monitoring/object/StatusReply.java new file mode 100644 index 0000000..5bce543 --- /dev/null +++ b/src/main/java/de/gurkengewuerz/monitoring/object/StatusReply.java @@ -0,0 +1,15 @@ +package de.gurkengewuerz.monitoring.object; + +/** + * Created by gurkengewuerz.de on 22.10.2017. + */ +public interface StatusReply { + + void onFinish(); + + void onStart(); + + void onUpdate(int current, int to_go); + + void onFail(String message); +}