package nagini.client;
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.util.List;
import java.util.Set;
import nagini.config.NaginiConfig;
import nagini.protocol.RequestType;
import nagini.protocol.ResponseType;
import nagini.protocol.SocketAndStreams;
import nagini.server.NaginiServerStatus;
import nagini.utils.NaginiFileUtils;
import nagini.utils.NaginiZipUtils;
import nagini.utils.process.NaginiProcessUtils;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
public class NaginiClient {
public NaginiConfig config;
public ControlOperations controlOps;
public FileOperations fileOps;
public ServiceOperations serviceOps;
public NaginiClient(String configPath) throws IOException {
config = new NaginiConfig(configPath);
controlOps = new ControlOperations();
fileOps = new FileOperations();
serviceOps = new ServiceOperations();
}
public String getClientTempPath() {
return config.client.tempPath + File.separator + "Neko_" + System.nanoTime();
}
public void loadConfig(String configPath) throws IOException {
config.loadConfig(configPath);
}
public Integer receiveResponseMessage(SocketAndStreams sands) throws IOException {
DataInputStream dis = sands.getInputStream();
Integer responseType = dis.readInt();
String responseMessage = null;
switch(responseType) {
case ResponseType.RESPONSE_SUCCESS:
responseMessage = dis.readUTF();
System.out.println(responseMessage);
responseMessage = dis.readUTF();
System.out.println(responseMessage);
break;
case ResponseType.RESPONSE_FAIL:
responseMessage = dis.readUTF();
System.out.println(responseMessage);
responseMessage = dis.readUTF();
System.out.println(responseMessage);
break;
case ResponseType.RESPONSE_FILE:
break;
case ResponseType.RESPONSE_NOOP:
break;
default:
throw new RuntimeException("Client: unexpected server response type.");
}
return responseType;
}
public void receiveAndCheckResponseMessage(SocketAndStreams sands) throws IOException {
Integer responseType = receiveResponseMessage(sands);
switch(responseType) {
case ResponseType.RESPONSE_SUCCESS:
break;
case ResponseType.RESPONSE_FAIL:
throw new RuntimeException("Client: server operation failed");
default:
sands.close();
throw new RuntimeException("Client: invalid server response type.");
}
}
public class ControlOperations {
/**
* Inner function that pings one remote server.
*
* @param hostName
* @throws IOException
*/
private void pingInner(String hostName) throws IOException {
SocketAndStreams sands = new SocketAndStreams(hostName, config.server.portId);
DataOutputStream dos = sands.getOutputStream();
System.out.println("Client: ping " + hostName + " ...");
// send request type
dos.writeInt(RequestType.REQUEST_PING);
// flush request header
dos.flush();
// receive server response
DataInputStream dis = sands.getInputStream();
int responseType = dis.readInt();
if(responseType == ResponseType.RESPONSE_SUCCESS) {
String responseMessage = dis.readUTF();
System.out.println(responseMessage);
responseMessage = dis.readUTF();
NaginiServerStatus status = new Gson().fromJson(responseMessage,
NaginiServerStatus.class);
status.print(System.out);
sands.close();
} else {
sands.close();
throw new RuntimeException("server operation failed");
}
}
/**
* Pings one remote server.
*
* @param hostName
* @throws IOException
*/
public void pingOneHost(String hostName) throws IOException {
pingInner(hostName);
}
/**
* Pings all remote servers.
*
* @throws IOException
*/
public void pingAllHosts() throws IOException {
for(String hostName: config.server.mapHostToNodes.keySet()) {
try {
pingInner(hostName);
} catch(IOException e) {
e.printStackTrace();
}
}
}
/**
* Inner function that stops one remote server.
*
* @param hostName
* @throws IOException
*/
private void stopInner(String hostName) throws IOException {
SocketAndStreams sands = new SocketAndStreams(hostName, config.server.portId);
DataOutputStream dos = sands.getOutputStream();
System.out.println("Client: stopping " + hostName + " ...");
// send request type
dos.writeInt(RequestType.REQUEST_STOP);
// flush request header
dos.flush();
receiveAndCheckResponseMessage(sands);
sands.close();
}
/**
* Stops one remote server.
*
* @param hostName
* @throws IOException
*/
public void stopOneHost(String hostName) throws IOException {
stopInner(hostName);
}
/**
* Stops all remote servers.
*
* @throws IOException
*/
public void stopAllHosts() throws IOException {
for(String hostName: config.server.mapHostToNodes.keySet()) {
try {
stopInner(hostName);
} catch(IOException e) {
e.printStackTrace();
}
}
}
/**
* Inner function that starts one remote server.
*
* @param hostName
* @throws Exception
*/
private void startInner(String hostName) throws Exception {
List<String> args = Lists.newArrayList();
System.out.println("Client: starting " + hostName + " ...");
args.add("ssh");
args.add(hostName);
args.add("\"\"sudo -u " + config.server.userName + " -sn bash "
+ config.server.basePath + "/nagini/bin/nagini-server.sh "
+ config.server.getConfigPath() + " " + hostName + "\"\"");
NaginiProcessUtils.command(args, new File(config.client.basePath), System.out);
}
/**
* Starts one remote server.
*
* @param hostName
* @throws Exception
*/
public void startOneHost(String hostName) throws Exception {
startInner(hostName);
}
/**
* Starts all remote servers.
*
* @throws Exception
*/
public void startAllHosts() throws Exception {
for(String hostName: config.server.mapHostToNodes.keySet()) {
try {
startInner(hostName);
} catch(IOException e) {
e.printStackTrace();
}
}
}
/**
* Inner function that reloads config on one remote server.
*
* @param hostName
* @param configPath
* @throws IOException
*/
private void reconfigInner(String hostName, String configPath) throws IOException {
SocketAndStreams sands = new SocketAndStreams(hostName, config.server.portId);
DataOutputStream dos = sands.getOutputStream();
System.out.println("Client: reloading config on " + hostName + " ...");
// send request type
dos.writeInt(RequestType.REQUEST_RECONFIG);
// send config path
dos.writeUTF(configPath);
// flush request header
dos.flush();
receiveAndCheckResponseMessage(sands);
sands.close();
}
/**
* Reloads config on one remote server.
*
* @param hostName
* @param configPath
* @throws IOException
*/
public void reconfig(String hostName, String configPath) throws IOException {
reconfigInner(hostName, configPath);
}
/**
* Reloads config on all remote servers.
*
* @param configPath
* @throws IOException
*/
public void reconfig(String configPath) throws IOException {
for(String hostName: config.server.mapHostToNodes.keySet()) {
reconfigInner(hostName, configPath);
}
}
}
public class FileOperations {
/**
* Inner function that puts file to one remote server.
*
* @param hostName, remote server host name
* @param localPath, absolute local path
* @param remotePath, absolute remote path
* @throws IOException
*/
private void putInner(String hostName, String localPath, String remotePath)
throws IOException {
SocketAndStreams sands = new SocketAndStreams(hostName, config.server.portId);
DataOutputStream dos = sands.getOutputStream();
File localFile = new File(localPath);
FileInputStream fis = new FileInputStream(localFile);
System.out.println("Client: putting " + localPath + " to " + hostName + " ... ("
+ localFile.length() + " bytes)");
// send request type
dos.writeInt(RequestType.REQUEST_FILE_PUT);
// send remote file path
dos.writeUTF(remotePath);
// send file length
dos.writeLong(localFile.length());
// flush request header
dos.flush();
int bufferSize = 65536;
byte[] buffer = new byte[bufferSize];
int read;
while((read = fis.read(buffer)) != -1) {
dos.write(buffer, 0, read);
}
dos.flush();
fis.close();
receiveAndCheckResponseMessage(sands);
sands.close();
}
/**
* Puts file to one remote server.
*
* @param hostName
* @param localPath
* @param remotePath
* @throws IOException
*/
public void putOneHost(String hostName, String localPath, String remotePath)
throws IOException {
String tempZipPath = getClientTempPath() + ".zip";
localPath = localPath.replace("~", System.getProperty("user.home"));
if(!new File(localPath).exists()) {
throw new RuntimeException("Client: cannot find " + localPath);
}
System.out.println("Client: zipping " + localPath + " ...");
NaginiZipUtils.zip(localPath, tempZipPath, null);
putInner(hostName, tempZipPath, remotePath);
NaginiFileUtils.delete(new File(tempZipPath));
}
/**
* Puts file to all remote servers.
*
* @param localPath
* @param remotePath
* @throws IOException
*/
public void putAllHosts(String localPath, String remotePath) throws IOException {
String tempZipPath = getClientTempPath() + ".zip";
localPath = localPath.replace("~", System.getProperty("user.home"));
if(!new File(localPath).exists()) {
throw new RuntimeException("Client: cannot find " + localPath);
}
System.out.println("Client: zipping " + localPath + " ...");
NaginiZipUtils.zip(localPath, tempZipPath, null);
for(String hostName: config.server.mapHostToNodes.keySet()) {
try {
putInner(hostName, tempZipPath, remotePath);
} catch (Exception e) {
System.err.println("Failed to put file on host: " + hostName);
}
}
NaginiFileUtils.delete(new File(tempZipPath));
}
/**
* Inner function that gets file from one remote server.
*
* @param hostName, remote server host name
* @param remotePath, absolute remote path
* @param localPath, absolute local path
* @throws IOException
*/
private void getInner(String hostName, String remotePath, String localPath)
throws IOException {
SocketAndStreams sands = new SocketAndStreams(hostName, config.server.portId);
DataOutputStream dos = sands.getOutputStream();
DataInputStream dis = sands.getInputStream();
System.out.println("Client: getting " + localPath + " from " + hostName + " ...");
// send request type
dos.writeInt(RequestType.REQUEST_FILE_GET);
// send remote file path
dos.writeUTF(remotePath);
// flush request header
dos.flush();
// receive file get response
int responseType = dis.readInt();
if(responseType != ResponseType.RESPONSE_FILE) {
sands.close();
throw new RuntimeException("Client: failed to get file from " + hostName
+ ". (unknown)");
}
Long fileLength = dis.readLong();
File localFile = new File(localPath);
FileOutputStream fos = new FileOutputStream(localFile);
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();
sands.close();
if(localFile.exists() && done == fileLength) {
System.out.println("Client: successfully received file. (" + done + " bytes)");
} else {
throw new RuntimeException("Client: failed to receive file. (" + done + " bytes)");
}
}
/**
* Gets file from one remote server.
*
* @param hostName
* @param remotePath
* @param localPath
* @throws IOException
*/
public void getOneHost(String hostName, String remotePath, String localPath)
throws IOException {
localPath = localPath.replace("~", System.getProperty("user.home"));
String tempZipPath = getClientTempPath() + ".zip";
File tempZipFile = new File(tempZipPath);
String destinationPath = localPath;
getInner(hostName, remotePath, tempZipPath);
System.out.println("Client: unzipping received file ...");
NaginiZipUtils.unzip(tempZipPath, destinationPath, null);
NaginiFileUtils.delete(tempZipFile);
}
/**
* Gets file from all remote servers (automatically creates separate
* folders for files from each server).
*
* @param remotePath
* @param localPath
* @throws IOException
*/
public void getAllHosts(String remotePath, String localPath) throws IOException {
localPath = localPath.replace("~", System.getProperty("user.home"));
for(String hostName: config.server.mapHostToNodes.keySet()) {
String tempZipPath = getClientTempPath() + ".zip";
File tempZipFile = new File(tempZipPath);
String destinationPath = localPath + File.separator + hostName;
getInner(hostName, remotePath, tempZipPath);
System.out.println("Client: unzipping received file ...");
NaginiZipUtils.unzip(tempZipPath, destinationPath, null);
NaginiFileUtils.delete(tempZipFile);
}
}
/**
* Inner function that deletes file from one remote server.
*
* @param hostName, remote server host name
* @param remotePath, absolute remote path
* @throws IOException
*/
private void deleteInner(String hostName, String remotePath) throws IOException {
SocketAndStreams sands = new SocketAndStreams(hostName, config.server.portId);
DataOutputStream dos = sands.getOutputStream();
System.out.println("Client: deleting file " + remotePath + " on " + hostName + " ...");
// send request type
dos.writeInt(RequestType.REQUEST_FILE_DELETE);
// send remote file path
dos.writeUTF(remotePath);
// flush request header
dos.flush();
receiveAndCheckResponseMessage(sands);
sands.close();
}
/**
* Deletes file on one remote server.
*
* @param remotePath
* @throws IOException
*/
public void deleteOneHost(String hostName, String remotePath) throws IOException {
deleteInner(hostName, remotePath);
}
/**
* Deletes file on all remote servers.
*
* @param remotePath
* @throws IOException
*/
public void deleteAllHosts(String remotePath) throws IOException {
for(String hostName: config.server.mapHostToNodes.keySet()) {
try {
deleteInner(hostName, remotePath);
} catch (Exception e) {
System.err.println("Failed to delete file on host: " + hostName);
}
}
}
}
public class ServiceOperations {
/**
* Inner function that starts one application node.
*
* @param nodeId
* @throws IOException
*/
private void startApplicationInner(Integer nodeId) throws IOException {
String hostName = config.server.mapNodeToHost.get(nodeId);
SocketAndStreams sands = new SocketAndStreams(hostName, config.server.portId);
DataOutputStream dos = sands.getOutputStream();
System.out.println("Client: starting application node " + nodeId + " on " + hostName
+ " ...");
// send request type
dos.writeInt(RequestType.REQUEST_SERVICE_START_APPLICATION);
// send node id
dos.writeInt(nodeId);
// flush request header
dos.flush();
receiveAndCheckResponseMessage(sands);
sands.close();
}
/**
* Starts one application node.
*
* @param nodeId
* @throws IOException
*/
public void startApplicationOneNode(Integer nodeId) throws IOException {
startApplicationInner(nodeId);
}
/**
* Starts all application nodes.
*
* @throws IOException
*/
public void startApplicationAllNodes() throws IOException {
for(String hostName: config.server.mapHostToNodes.keySet()) {
for(Integer nodeId: config.server.mapHostToNodes.get(hostName)) {
try {
startApplicationOneNode(nodeId);
} catch (Exception e) {
System.err.println("Failed to start node '" + nodeId + "' on host: " + hostName);
}
}
}
}
/**
* Inner function that stops one application node.
*
* @param nodeId
* @throws IOException
*/
private void stopApplicationInner(Integer nodeId) throws IOException {
String hostName = config.server.mapNodeToHost.get(nodeId);
SocketAndStreams sands = new SocketAndStreams(hostName, config.server.portId);
DataOutputStream dos = sands.getOutputStream();
System.out.println("Client: stopping application node " + nodeId + " on " + hostName
+ " ...");
// send request type
dos.writeInt(RequestType.REQUEST_SERVICE_STOP_APPLICATION);
// send node id
dos.writeInt(nodeId);
// flush request header
dos.flush();
receiveAndCheckResponseMessage(sands);
sands.close();
}
/**
* Stops one application node.
*
* @param nodeId
* @throws IOException
*/
public void stopApplicationOneNode(Integer nodeId) throws IOException {
stopApplicationInner(nodeId);
}
/**
* Stops all application nodes.
*
* @throws IOException
*/
public void stopApplicationAllNodes() throws IOException {
for(String hostName: config.server.mapHostToNodes.keySet()) {
for(Integer nodeId: config.server.mapHostToNodes.get(hostName)) {
try {
stopApplicationOneNode(nodeId);
} catch (Exception e) {
System.err.println("Failed to stop node '" + nodeId + "' on host: " + hostName);
}
}
}
}
/**
* Inner function that watches one application node.
*
* @param nodeId
* @param tail
* @return true if application is running on this node
* @throws IOException
*/
private Boolean watchApplicationInner(Integer nodeId, Integer tail) throws IOException {
String hostName = config.server.mapNodeToHost.get(nodeId);
SocketAndStreams sands = new SocketAndStreams(hostName, config.server.portId);
DataOutputStream dos = sands.getOutputStream();
// send request type
dos.writeInt(RequestType.REQUEST_SERVICE_WATCH_APPLICATION);
// send node id
dos.writeInt(nodeId);
// send tail number
dos.writeInt(tail);
// flush request header
dos.flush();
Integer responseType = receiveResponseMessage(sands);
sands.close();
return responseType == ResponseType.RESPONSE_SUCCESS;
}
/**
* Watches one application node.
*
* @param nodeId
* @param interval
* @param tail
* @throws IOException
* @throws
*/
public void watchApplicationOneNode(Integer nodeId, Integer interval, Integer tail)
throws IOException {
Set<Integer> nodeIds = Sets.newHashSet();
nodeIds.add(nodeId);
watchApplicationMultipleNodes(nodeIds, interval, tail);
}
/**
* Watches all application nodes.
*
* @param nodeIds
* @param interval
* @param tail
* @throws IOException
*/
public void watchApplicationMultipleNodes(Set<Integer> nodeIds,
Integer interval,
Integer tail) throws IOException {
while(nodeIds.size() > 0) {
Set<Integer> removedNodeIds = Sets.newHashSet();
for(Integer nodeId: nodeIds) {
if(!watchApplicationInner(nodeId, tail)) {
System.out.println("Node "
+ nodeId
+ " is not running application. Remove from watch node list.");
removedNodeIds.add(nodeId);
}
}
nodeIds.removeAll(removedNodeIds);
if(nodeIds.size() > 0) {
try {
Thread.sleep(interval * 1000);
} catch(Exception e) {
throw new IOException(e);
}
}
}
}
/**
* Watches all application nodes.
*
* @param interval
* @param tail
* @throws IOException
*/
public void watchApplicationAllNodes(Integer interval, Integer tail) throws IOException {
Set<Integer> nodeIds = Sets.newHashSet();
for(String hostName: config.server.mapHostToNodes.keySet()) {
for(Integer nodeId: config.server.mapHostToNodes.get(hostName)) {
nodeIds.add(nodeId);
}
}
watchApplicationMultipleNodes(nodeIds, interval, tail);
}
}
}