package com.zillabyte.motherbrain.container.local;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.ServerSocket;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.log4j.Logger;
import org.apache.tools.ant.types.Commandline;
import com.google.common.collect.Maps;
import com.zillabyte.motherbrain.container.Container;
import com.zillabyte.motherbrain.container.ContainerException;
import com.zillabyte.motherbrain.container.ContainerExecuteBuilder;
import com.zillabyte.motherbrain.container.TcpSocketHelper;
import com.zillabyte.motherbrain.container.UnixSocketHelper;
import com.zillabyte.motherbrain.container.UnixSocketHelper.AFUNIXServerSocketWrapper;
import com.zillabyte.motherbrain.flow.Flow;
import com.zillabyte.motherbrain.flow.config.FlowConfig;
import com.zillabyte.motherbrain.flow.operations.multilang.MultiLangProcess;
import com.zillabyte.motherbrain.utils.Utils;
/**
* Local containers write to your local filesystem
* Allows development on OSX, treat this as the mock implementation
* @author sjarvie
*
*/
public class InplaceContainer implements Container, Serializable {
private static final long serialVersionUID = 1921652389278262665L;
private boolean _started = false;
private File _flowRoot;
private FlowConfig _flowConfig;
private static Logger _log = Logger.getLogger(InplaceContainer.class);
private Map<String,String> _containerEnv = Maps.newHashMap();
/**
* Init local container, uses FlowConfig to match to a flow
* @param fc
*/
public InplaceContainer(File flowRoot, FlowConfig fc) {
_flowConfig = fc;
// _flowRoot = new File(ContainerPathHelper.externalPathForFlowRoot(_flowConfig.getFlowId(), "flow"));
_flowRoot = flowRoot;
}
@Override
public void start() throws ContainerException {
_log.info("starting container " + _flowConfig.getFlowId());
_started = true;
}
/**
* Executes a CLI command witin the internal container
* @param socket
* @param flowId
* @param command
* @return
* @throws ContainerException
*/
private MultiLangProcess executeCli(ServerSocket socket, String dir, String... command) throws ContainerException {
try {
// Sanity
if (!_started) throw new IllegalStateException("start() not called");
String host = "127.0.0.1";
// Execute the command in a wrapped /bin/bash, so we get the expected environment.
// TODO: remove this wrapper at some point
StringBuilder sb = new StringBuilder();
// Set env
for(Entry<String,String> e : _containerEnv.entrySet()) {
sb.append(Commandline.quoteArgument(e.getKey()));
sb.append("=");
sb.append(Commandline.quoteArgument(e.getValue()));
sb.append(" ");
}
// command
sb.append("zillabyte ");
for(String s : command) {
sb.append(Commandline.quoteArgument(s));
sb.append(" ");
}
// sockets
if (socket != null) {
if (socket instanceof AFUNIXServerSocketWrapper) {
// Unix socket...
String file = ((AFUNIXServerSocketWrapper)socket).getSocketFile();
sb.append(" --unix_socket " + Commandline.quoteArgument(file));
} else {
// TCP socket..
sb.append(" --host " + host);
sb.append(" --port " + socket.getLocalPort());
}
}
StringBuilder bootstrap = new StringBuilder();
String[] realCommand = {
"/bin/bash", "-l", "-c",
bootstrap.toString() + "cd \"" + getFile(dir) + "\"; " +
sb.toString()};
// Execute the command
MultiLangProcess process = MultiLangProcess.create(
getEnvironment(),
realCommand,
socket
);
// Done
return process;
} catch(Exception ex) {
throw (ContainerException) new ContainerException(ex).setUserMessage("Failed to execute \"" + command.toString() + "\".").adviseRetry();
}
}
/**
* Create a new base environment for execution
* @return
*/
private Map<String, String> getEnvironment() {
Map<String, String> m = Maps.newHashMap();
return m;
}
/**
* Get the file at the internalPath within the container filesystem
* @param internalPath
* @return
* @throws ContainerException
*/
@Override
public File getFile(String internalPath) throws ContainerException {
return new File(FilenameUtils.concat(_flowRoot.getAbsolutePath(), internalPath));
}
/**
* For local containers, simply delete the container directory
*/
@Override
public void cleanup() throws ContainerException {
_log.info("cleaning up container " + this._flowConfig.getFlowId() + " " + this._flowRoot);
_started = false;
//removeDirectory();
}
@Override
public void writeFile(String internalPath, byte[] contents) throws ContainerException {
throw new IllegalStateException("Files should not be written to InPlace containers");
// try {
// File f = new File(_flowRoot, internalPath);
// Files.write(contents, f);
// } catch (IOException e) {
// throw new ContainerException(e);
// }
}
@Override
public ContainerExecuteBuilder buildCommand() {
return new ContainerExecuteBuilder() {
@Override
public MultiLangProcess createProcess() throws ContainerException {
// Port?
ServerSocket socket = null;
if (super._withTcpSockets) {
socket = TcpSocketHelper.getNextAvailableTcpSocket();
} else if (super._withUnixSockets) {
// Unix Sockets have a path name limit of 114 chars --> don't use UUIDs
String name = Integer.toHexString((int) (Math.random() * 10000));
File sockFile = getFile("/tmp/" + name + ".sock");
sockFile.deleteOnExit();
socket = UnixSocketHelper.createUnixSocket(sockFile);
// Wait for it to exist...
while(sockFile.exists() == false) {
_log.info("waiting for unix socket...");
Utils.sleep(100);
}
}
// Execute
return executeCli(socket, _flowRoot.getAbsolutePath(), _command);
}
};
}
@Override
public byte[] readFileAsBytes(String file) throws ContainerException {
try {
File f = new File(_flowRoot, file);
return FileUtils.readFileToByteArray(f);
} catch (IOException e) {
throw (ContainerException) new ContainerException(e).setUserMessage("Failed to read \""+file+"\".").adviseRetry();
}
}
@Override
public void createDirectory(String path) throws ContainerException {
throw new IllegalStateException("Files should not be written to InPlace containers");
// File f = new File(_flowRoot, path);
// f.mkdirs();
}
public FlowConfig getFlowConfig() {
return _flowConfig;
}
@Override
public File getRoot() {
return _flowRoot;
}
public void setStarted(boolean state) {
_started = state;
}
@Override
public void cacheFlow(Flow flow) {
// Do nothing, no caching in local mode
return;
}
@Override
public Flow maybeGetCachedFlow(String id, Integer version) {
// Do nothing, no caching in local mode
return null;
}
}