/* * BackendClient.java * * Copyright (C) 2015 Pixelgaffer * * This work is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the * Free Software Foundation; either version 2 of the License, or any later * version. * * This work 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 version 2 and version 3 of the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.pixelgaffer.turnierserver.worker.backendclient; import static org.pixelgaffer.turnierserver.PropertyUtils.RECON_IVAL; import static org.pixelgaffer.turnierserver.networking.bwprotocol.ProtocolLine.AICONNECTED; import static org.pixelgaffer.turnierserver.networking.bwprotocol.ProtocolLine.ANSWER; import static org.pixelgaffer.turnierserver.networking.bwprotocol.ProtocolLine.INFO; import static org.pixelgaffer.turnierserver.networking.bwprotocol.ProtocolLine.SANDBOX_MESSAGE; import static org.pixelgaffer.turnierserver.networking.messages.SandboxCommand.KILL_AI; import static org.pixelgaffer.turnierserver.networking.messages.SandboxCommand.RUN_AI; import static org.pixelgaffer.turnierserver.networking.messages.SandboxCommand.TERM_AI; import static org.pixelgaffer.turnierserver.networking.messages.SandboxMessage.TERMINATED_AI; import static org.pixelgaffer.turnierserver.networking.messages.WorkerCommand.COMPILE; import static org.pixelgaffer.turnierserver.networking.messages.WorkerCommand.KILLAI; import static org.pixelgaffer.turnierserver.networking.messages.WorkerCommand.STARTAI; import static org.pixelgaffer.turnierserver.networking.messages.WorkerCommand.TERMAI; import java.io.IOException; import lombok.Getter; import naga.NIOSocket; import naga.SocketObserver; import org.pixelgaffer.turnierserver.Airbrake; import org.pixelgaffer.turnierserver.Parsers; import org.pixelgaffer.turnierserver.PropertyUtils; import org.pixelgaffer.turnierserver.compile.Backend; import org.pixelgaffer.turnierserver.networking.NetworkService; import org.pixelgaffer.turnierserver.networking.bwprotocol.AiConnected; import org.pixelgaffer.turnierserver.networking.bwprotocol.ProtocolLine; import org.pixelgaffer.turnierserver.networking.bwprotocol.WorkerCommandAnswer; import org.pixelgaffer.turnierserver.networking.messages.SandboxCommand; import org.pixelgaffer.turnierserver.networking.messages.SandboxMessage; import org.pixelgaffer.turnierserver.networking.messages.WorkerCommand; import org.pixelgaffer.turnierserver.networking.messages.WorkerInfo; import org.pixelgaffer.turnierserver.networking.util.DataBuffer; import org.pixelgaffer.turnierserver.worker.Sandbox; import org.pixelgaffer.turnierserver.worker.Sandboxes; import org.pixelgaffer.turnierserver.worker.WorkerMain; import org.pixelgaffer.turnierserver.worker.compile.CompileQueue; /** * Diese Klasse ist der Client zum Backend. */ public class BackendClient implements SocketObserver, Backend { private NIOSocket client; @Getter private String ip; @Getter private int port; /** Speichert, ob der Client verbunden ist. */ @Getter private boolean connected = false; private DataBuffer buf = new DataBuffer(); public BackendClient (String ip, int port) throws IOException { this.ip = ip; this.port = port; client = NetworkService.getService().openSocket(ip, port); client.listen(this); } public void sendAnswer (WorkerCommandAnswer answer) throws IOException { client.write(new ProtocolLine(ANSWER, answer).serialize(true)); } public void sendInfo (WorkerInfo info) throws IOException { client.write(new ProtocolLine(INFO, info).serialize(true)); } public void sendAiConnected (AiConnected aiConnected) throws IOException { client.write(new ProtocolLine(AICONNECTED, aiConnected).serialize(true)); } public void sendSandboxMessage (SandboxMessage msg) throws IOException { client.write(new ProtocolLine(SANDBOX_MESSAGE, msg).serialize(true)); } @Override public void connectionOpened (NIOSocket socket) { WorkerMain.getLogger().info("Verbunden mit " + socket.getIp()); connected = true; try { socket.write(Parsers.getWorker().parse(WorkerMain.workerInfo, true)); } catch (IOException e) { Airbrake.log(e).printStackTrace(); } } private final Runnable reconnector = () -> { int interval = PropertyUtils.getInt(RECON_IVAL, 3000); while (!connected) { try { client = NetworkService.getService().openSocket(ip, port); client.listen(this); } catch (IOException e) { Airbrake.log(e).printStackTrace(); } try { Thread.sleep(interval); } catch (InterruptedException e) { e.printStackTrace(); } } reconnectorRunning = false; }; private boolean reconnectorRunning = false; @Override public void connectionBroken (NIOSocket socket, Exception exception) { WorkerMain.getLogger().critical("Das Backend hat die Verbindung getrennt" + exception != null ? ": " + exception : ""); connected = false; synchronized (this) { if (!reconnectorRunning) new Thread(reconnector, "Reconnector").start(); reconnectorRunning = true; } } @Override public void packetReceived (NIOSocket socket, byte[] packet) { buf.add(packet); byte line[]; while ((line = buf.readLine()) != null) { try { WorkerCommand cmd = Parsers.getWorker().parse(line, WorkerCommand.class); WorkerMain.getLogger().info("Empfangen: " + cmd); if (cmd.getAction() == COMPILE) CompileQueue.addJob(cmd); else if (cmd.getAction() == STARTAI) { try { SandboxCommand scmd = new SandboxCommand(RUN_AI, cmd.getAiId(), cmd.getVersion(), Sandboxes.nextIsolateBoxid(cmd.getUuid()), cmd.getLang(), cmd.getUuid(), cmd.getMaxRuntime()); Sandbox s = Sandboxes.send(scmd); if (s == null) { WorkerMain.getLogger().todo("hier sollte evtl kein T-result geschickt werden"); sendSandboxMessage(new SandboxMessage(TERMINATED_AI, cmd.getUuid())); } else Sandboxes.sandboxJobs.put(cmd.getUuid(), s); } catch (Exception e) { WorkerMain.getLogger().critical("Fehler beim Senden des StartKI-Befehls: " + e); Airbrake.log(e).printStackTrace(); } } else if (cmd.getAction() == TERMAI || cmd.getAction() == KILLAI) { try { SandboxCommand scmd = new SandboxCommand(cmd.getAction() == TERMAI ? TERM_AI : KILL_AI, cmd.getAiId(), cmd.getVersion(), -1, cmd.getLang(), cmd.getUuid(), cmd.getMaxRuntime()); Sandbox s = Sandboxes.sandboxJobs.get(cmd.getUuid()); if (s == null) WorkerMain.getLogger().critical("Das Backend hat mich beauftragt die unbekannte KI " + cmd.getUuid() + " zu beenden."); else s.sendJob(scmd); } catch (IOException ioe) { WorkerMain.getLogger().critical("Fehler beim Senden des StopKI-Befehls: " + ioe); Airbrake.log(ioe).printStackTrace(); } } else WorkerMain.getLogger().critical("Unbekannter Auftrag: " + cmd.getAction()); } catch (Exception e) { WorkerMain.getLogger().critical("Failed to parse Command: " + e); } } } @Override public void packetSent (NIOSocket socket, Object tag) { } }