package nagini.server; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.ServerSocket; import java.util.Arrays; import java.util.List; import java.util.Map; import nagini.config.NaginiConfig; import nagini.protocol.RequestType; import nagini.protocol.ResponseType; import nagini.protocol.SocketAndStreams; import nagini.utils.JavaCommandBuilder; import nagini.utils.NaginiFileUtils; import nagini.utils.NaginiZipUtils; import nagini.utils.process.ProcessThread; import nagini.utils.process.Service; import com.google.common.base.Joiner; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.gson.Gson; public class NaginiServer { public String hostName; public NaginiConfig config; public ServerSocket serverSocket; public List<Integer> nodeIds; public Map<Integer, Service> mapNodeIdToApplicationStarterService; public NaginiServer(String configPath, String hostName) throws IOException { this.hostName = hostName; this.config = null; this.serverSocket = null; this.nodeIds = null; this.mapNodeIdToApplicationStarterService = null; loadConfig(configPath); } private static boolean isInteger(String s) { if(s.isEmpty()) { return false; } for(int i = 0; i < s.length(); i++) { if(!Character.isDigit(s.charAt(i))) { return false; } } return true; } private static void setupNodeConfig(File parent, Integer nodeId) { if(!parent.exists() || !parent.isDirectory()) { return; } for(File file: parent.listFiles()) { String path = parent.getAbsolutePath(); String name = file.getName(); String[] parsedName = name.split("\\."); String suffix = parsedName[parsedName.length - 1]; if(parsedName.length > 1 && isInteger(suffix)) { if(!suffix.equals(nodeId.toString())) { NaginiFileUtils.delete(file); } else { String newName = new String(); for(int i = 0; i < parsedName.length - 1; ++i) { newName = newName + parsedName[i]; if(i < parsedName.length - 2) { newName = newName + "."; } } NaginiFileUtils.move(path + File.separator + name, path + File.separator + newName); } } } } private void generateNodeConfig(Integer nodeId) throws IOException { // check and create node/config folder NaginiFileUtils.delete(config.server.getNodeConfigPath(nodeId)); NaginiFileUtils.copy(config.server.getConfigApplicationPath(), config.server.getNodeConfigPath(nodeId)); setupNodeConfig(new File(config.server.getNodeConfigPath(nodeId)), nodeId); } private String getServerTempPath() { return config.server.tempPath + File.separator + "Neko_" + System.nanoTime(); } public void loadConfig(String configPath) throws IOException { // load config from files Integer oldPortId = null; if(config != null) { oldPortId = config.server.portId; } config = new NaginiConfig(configPath); // load server socket if(serverSocket != null && oldPortId != config.server.portId) { serverSocket.close(); serverSocket = null; } if(serverSocket == null) { serverSocket = new ServerSocket(config.server.portId); } // load node id list nodeIds = config.server.mapHostToNodes.get(hostName); if(nodeIds == null) { nodeIds = Lists.newArrayList(); } // create map node to serviec mapNodeIdToApplicationStarterService = Maps.newHashMap(); // generate application node config and create node services for(Integer nodeId: nodeIds) { generateNodeConfig(nodeId); mapNodeIdToApplicationStarterService.put(nodeId, new Service("application-starter-" + nodeId, config.server.getNodeApplicationLogFilePath(nodeId), 1, config.server.watchEnabled)); } } private void startServices() { Service service = null; for(Integer nodeId: nodeIds) { service = mapNodeIdToApplicationStarterService.get(nodeId); if(service != null) { service.start(); } } } private void stopServices() throws InterruptedException { Service service = null; for(Integer nodeId: nodeIds) { service = mapNodeIdToApplicationStarterService.get(nodeId); if(service != null) { service.terminate(); service.join(); } } } public void start() throws IOException { startServices(); handleRequests(); } private void handleRequests() throws IOException { while(true) { SocketAndStreams sands = null; try { sands = new SocketAndStreams(serverSocket.accept()); int requestType = sands.getInputStream().readInt(); switch(requestType) { case RequestType.REQUEST_PING: handleControlPing(sands); break; case RequestType.REQUEST_STOP: handleControlStop(sands); break; case RequestType.REQUEST_RECONFIG: handleControlReconfig(sands); break; case RequestType.REQUEST_FILE_PUT: handleFilePutRequest(sands); break; case RequestType.REQUEST_FILE_GET: handleFileGetRequest(sands); break; case RequestType.REQUEST_FILE_DELETE: handleFileDeleteRequest(sands); break; case RequestType.REQUEST_SERVICE_START_APPLICATION: handleStartApplicationRequest(sands); break; case RequestType.REQUEST_SERVICE_STOP_APPLICATION: handleStopApplicationRequest(sands); break; case RequestType.REQUEST_SERVICE_WATCH_APPLICATION: handleWatchApplicationRequest(sands); break; default: sendFailResponse(sands, "invalid request. (0x" + Integer.toHexString(requestType) + ")"); break; } } catch(Exception e) { System.out.println("NaginiServer Exception: " + e.getMessage()); e.printStackTrace(); } finally { if(sands != null) { sands.close(); } } } } private void sendSuccessResponse(SocketAndStreams sands, String message) throws IOException { DataOutputStream dos = sands.getOutputStream(); dos.writeInt(ResponseType.RESPONSE_SUCCESS); dos.writeUTF("Server: [host=" + hostName + "]: "); dos.writeUTF(message); dos.flush(); } private void sendFailResponse(SocketAndStreams sands, String message) throws IOException { DataOutputStream dos = sands.getOutputStream(); dos.writeInt(ResponseType.RESPONSE_FAIL); dos.writeUTF("Server: [host=" + hostName + "]: "); dos.writeUTF(message); dos.flush(); } private void sendWatchResponse(SocketAndStreams sands, Service service, Integer nodeId, Integer tail) throws IOException { if(!service.isAlive()) { sendFailResponse(sands, "service " + service.getName() + " is corrupted."); } else if(service.getJobCount() == 0) { sendFailResponse(sands, "service " + service.getName() + " does not have any job to run."); } else if(!service.isRunningJob()) { sendSuccessResponse(sands, "service " + service.getName() + " is going to run the next job."); } else { List<String> output = Lists.newArrayList(); output.add(" [node = " + nodeId + "]"); output.addAll(service.readOutput()); if(tail > 0) { while(output.size() > tail) { output.remove(0); } } sendSuccessResponse(sands, Joiner.on("\n").join(output)); } } private JobStatus getJobStatus(ProcessThread thread) { JobStatus status = new JobStatus(); status.job_name = thread.getName(); status.is_active = thread.isAlive(); return status; } private ServiceStatus getServiceStatus(Service service) { ServiceStatus status = new ServiceStatus(); status.service_name = service.getName(); status.is_alive = service.isAlive(); status.job_list = Lists.newArrayList(); for(ProcessThread job: service.getAllJobs()) { status.job_list.add(getJobStatus(job)); } return status; } private NodeStatus getNodeStatus(Integer nodeId) { NodeStatus status = new NodeStatus(); status.node_id = nodeId; status.service_list = Lists.newArrayList(); status.service_list.add(getServiceStatus(mapNodeIdToApplicationStarterService.get(nodeId))); return status; } private NaginiServerStatus getServerStatus() { NaginiServerStatus status = new NaginiServerStatus(); status.host_name = hostName; status.node_list = Lists.newArrayList(); for(Integer nodeId: nodeIds) { status.node_list.add(getNodeStatus(nodeId)); } return status; } private void handleControlPing(SocketAndStreams sands) throws IOException { NaginiServerStatus status = getServerStatus(); Gson gson = new Gson(); String responseMessage = gson.toJson(status, NaginiServerStatus.class); sendSuccessResponse(sands, responseMessage); } private void handleControlStop(SocketAndStreams sands) throws IOException, InterruptedException { sendSuccessResponse(sands, "stopping Nagini server ..."); stopServices(); System.exit(0); } private void handleControlReconfig(SocketAndStreams sands) throws IOException, InterruptedException { DataInputStream dis = sands.getInputStream(); String configPath = dis.readUTF(); sendSuccessResponse(sands, "started reloading config file from " + configPath); stopServices(); loadConfig(configPath); startServices(); } private void handleFilePutRequest(SocketAndStreams sands) throws IOException { String tempZipPath = getServerTempPath() + ".zip"; File tempZipFile = new File(tempZipPath); DataInputStream dis = sands.getInputStream(); String destPath = dis.readUTF(); Long fileLength = dis.readLong(); FileOutputStream fos = new FileOutputStream(tempZipFile); int bufferSize = 65536; byte[] buffer = new byte[bufferSize]; int done = 0; while(done < fileLength) { int read = dis.read(buffer); fos.write(buffer, 0, read); if(read == -1) { break; } done += read; } fos.close(); if(tempZipFile.exists() && done == fileLength) { sendSuccessResponse(sands, "successfully received file + " + tempZipPath + ". (" + done + " bytes)"); System.out.println("unzipping file " + tempZipPath + "..."); NaginiZipUtils.unzip(tempZipPath, destPath, null); System.out.println("unzipped file" + tempZipPath + "."); } else { sendFailResponse(sands, "failed to receive file " + tempZipPath + ". (" + done + " out of " + fileLength + " bytes)"); } NaginiFileUtils.delete(tempZipFile); } private void handleFileGetRequest(SocketAndStreams sands) throws IOException { String tempZipPath = getServerTempPath() + ".zip"; File tempZipFile = new File(tempZipPath); DataOutputStream dos = sands.getOutputStream(); String filePath = sands.getInputStream().readUTF(); if(!new File(filePath).exists()) { sendFailResponse(sands, "failed to send " + filePath + ". (file does not exist)"); return; } System.out.println("zipping " + filePath + " ..."); NaginiZipUtils.zip(filePath, tempZipPath, null); FileInputStream fis = new FileInputStream(tempZipFile); Long fileLength = tempZipFile.length(); int bufferSize = 65536; byte[] buffer = new byte[bufferSize]; int read; System.out.println("sending " + tempZipPath + " ..."); dos.writeInt(ResponseType.RESPONSE_FILE); dos.writeLong(fileLength); while((read = fis.read(buffer)) != -1) { dos.write(buffer, 0, read); } dos.flush(); fis.close(); NaginiFileUtils.delete(tempZipFile); System.out.println("" + filePath + " sent."); } private void handleFileDeleteRequest(SocketAndStreams sands) throws IOException { String filePath = sands.getInputStream().readUTF(); Boolean succeed = false; try { succeed = NaginiFileUtils.delete(new File(filePath)); } catch(Exception e) { System.out.println("Exception during file deletion: " + e.getMessage()); e.printStackTrace(); } if(succeed) { sendSuccessResponse(sands, "successfully deleted " + filePath + "."); } else { sendFailResponse(sands, "failed to delete " + filePath + "."); } } private void handleStartApplicationRequest(SocketAndStreams sands) throws Exception { Integer nodeId = sands.getInputStream().readInt(); Service service = mapNodeIdToApplicationStarterService.get(nodeId); if (service == null || !service.isAlive()) { sendFailResponse(sands, "application starter service is corrupted."); return; } if(service.isRunningJob()) { sendSuccessResponse(sands, "application is already running."); } else { try { List<String> args = null; if(config.server.appStartCommand == null) { JavaCommandBuilder jcb = new JavaCommandBuilder(); jcb.setClassName(config.server.appJavaMainClass) .setJavaExec(config.server.expandNodePath(config.server.javaExec, nodeId)) .setJvmOption(config.server.expandNodePath(config.server.appJvmOpts, nodeId)) .addClassOption(config.server.expandNodePath(config.server.appJavaClassOpts, nodeId)); for(String subPath: config.server.appJavaClassSubPaths) { jcb.addClassPathByFolder(config.server.getApplicationPath() + File.separator + subPath); } args = jcb.getJavaCommand(); } else { args = Arrays.asList(config.server.expandNodePath(config.server.appStartCommand, nodeId).split(" ")); } sendSuccessResponse(sands, "starting application ..."); service.addJob("application-" + nodeId, args, config.server.getNodePath(nodeId)); System.out.println("starting application: (node = " + nodeId + ")"); System.out.println(Joiner.on(" ").join(args)); } catch (Exception e) { String failureMessage = "Failed to start application because of: " + e.getMessage(); sendFailResponse(sands, failureMessage); System.out.println(failureMessage); throw e; } } } private void handleStopApplicationRequest(SocketAndStreams sands) throws IOException { Integer nodeId = sands.getInputStream().readInt(); Service service = mapNodeIdToApplicationStarterService.get(nodeId); if (service == null || !service.isAlive()) { sendFailResponse(sands, "application starter service is corrupted."); return; } if(!service.isRunningJob()) { sendSuccessResponse(sands, "application is not running."); } else { service.removeAllJobs(); sendSuccessResponse(sands, "stopping application ..."); System.out.println("stopping application. (node = " + nodeId + ")"); } } private void handleWatchApplicationRequest(SocketAndStreams sands) throws IOException { DataInputStream dis = sands.getInputStream(); Integer nodeId = dis.readInt(); Integer tail = dis.readInt(); Service service = mapNodeIdToApplicationStarterService.get(nodeId); sendWatchResponse(sands, service, nodeId, tail); } }