package com.zillabyte.motherbrain.flow.operations.multilang.builder;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.TimeoutException;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;
import org.apache.log4j.Logger;
import com.google.common.io.Files;
import com.zillabyte.motherbrain.api.APIException;
import com.zillabyte.motherbrain.api.APIService;
import com.zillabyte.motherbrain.container.ContainerEnvironmentHelper;
import com.zillabyte.motherbrain.container.ContainerException;
import com.zillabyte.motherbrain.container.ContainerPathHelper;
import com.zillabyte.motherbrain.container.ContainerWrapper;
import com.zillabyte.motherbrain.flow.Flow;
import com.zillabyte.motherbrain.flow.FlowCompilationException;
import com.zillabyte.motherbrain.flow.components.builtin.BuiltinComponents;
import com.zillabyte.motherbrain.flow.config.FlowConfig;
import com.zillabyte.motherbrain.flow.operations.OperationLogger;
import com.zillabyte.motherbrain.flow.operations.multilang.MultiLangProcessException;
import com.zillabyte.motherbrain.universe.S3Exception;
import com.zillabyte.motherbrain.universe.Universe;
import com.zillabyte.motherbrain.utils.Utils;
import com.zillabyte.motherbrain.utils.dfs.DFSServiceWrapper;
public class APIFlowBuilder implements FlowFetcher {
/**
*
*/
private static final long serialVersionUID = 5830540114377373350L;
private static final Logger _log = Utils.getLogger(APIFlowBuilder.class);
private static final Long CLI_PULL_TIMEOUT = 1000L * 60 * 2;
private static final long EXPLODED_FILE_COPY_THRESHOLD = 100_000L; // don't copy files larger than this to the exploded view...
private MultilangFlowCompiler _flowBuilder;
private OperationLogger _logger;
private APIService _api;
private ContainerWrapper _container;
private FlowConfig _flowConfig;
/***
*
* @param authToken
*/
public APIFlowBuilder(FlowConfig flowConfig, ContainerWrapper destContainer, OperationLogger logger) {
_flowBuilder = new MultilangFlowCompiler(this, flowConfig, destContainer, logger);
_flowConfig = flowConfig;
_logger = logger;
_api = Universe.instance().api();
_container = destContainer;
}
/**
*
* @param id
* @param flowLogger
* @param destDir
* @throws InterruptedException
* @throws LXCException
* @throws FlowCompilationException
*/
@Override
public Flow buildFlow(String flowName, JSONObject overrideConfig) throws FlowCompilationException {
try {
// Builtin?
if (BuiltinComponents.exists(flowName)) {
return BuiltinComponents.create(flowName, overrideConfig);
}
// Step 1: ask the API to resolve the ID to something concrete...
JSONObject settings = _api.getFlowSettings(flowName, _flowConfig.getAuthToken());
String concreteId = settings.getString("id");
Integer version = Integer.valueOf( settings.getString("version") );
// Do we have a cached version of this flow?
Flow cachedFlow = null;
try {
cachedFlow = _container.maybeGetCachedFlow(concreteId, version);
} catch(Exception e) {
_log.warn("unable to deserialize cached flow: " + e.getMessage());
cachedFlow = null;
}
if (cachedFlow != null) {
return cachedFlow;
} else {
// Step 1b: write a file, make sure it's writable.
_container.createDirectory(ContainerPathHelper.internalPathForFlow(concreteId));
// Step 2: use the CLI to pull into the container...
_container.buildCommand()
.withEnvironment(ContainerEnvironmentHelper.getCLIEnvironment(this._flowConfig))
.withCLICommand("pull", concreteId, ".")
.inFlowDirectory(concreteId)
.withoutSockets()
// Run it
.createProcess()
.addLogListener(_logger)
.start()
.waitForExit(CLI_PULL_TIMEOUT);
// Step 3: Actually build the flow
Flow flow = _flowBuilder.compileFlow(concreteId, overrideConfig);
// Step 4: Capture all the files are report them back to the API for UI stuff
flow.addMeta("files", buildMetaFiles(concreteId, version));
// Step 5: Cache it for later use...
_container.cacheFlow(flow);
return flow;
}
} catch(InterruptedException | MultiLangProcessException | ContainerException | TimeoutException | APIException | IOException | S3Exception e) {
throw (FlowCompilationException) new FlowCompilationException(e).setUserMessage("An error occurred while pulling flow "+flowName+" from our servers.").adviseRetry();
}
}
public Flow buildFlow(String flowName) throws FlowCompilationException {
return buildFlow(flowName, new JSONObject());
}
private JSONArray buildMetaFiles(String concreteId, Integer version) throws ContainerException, IOException, S3Exception {
// INIT
DFSServiceWrapper dfs = Universe.instance().dfsService();
File dir = _container.getFlowRoot(concreteId);
Path rootPath = Paths.get(dir.getAbsolutePath());
// Traverse each file...
JSONArray fileList = new JSONArray();
for(File file : Files.fileTreeTraverser().preOrderTraversal(dir)) {
// File?
if (!file.isFile() || file.getAbsolutePath().contains("/vEnv/")) continue; // Ignore python vEnv
// Init
Path path = Paths.get(file.getAbsolutePath());
Path rel = rootPath.relativize(path);
String dfsPath = "/flow_files/" + concreteId + "/cycle_" + version + "/" + rel.toString();
JSONObject info = new JSONObject();
// Meta hash...
info.put("file", rel.toString());
info.put("size", file.length());
info.put("modified", file.lastModified());
// Copy it over
if (file.length() == 0) {
_log.info("skipping 0-length file: " + dfsPath);
} else if (file.length() < EXPLODED_FILE_COPY_THRESHOLD) {
_log.info("copying file to DFS: " + dfsPath);
dfs.copyFile(file, dfsPath);
info.put("dfs_uri", dfs.getUriFor(dfsPath));
} else {
_log.info("skilling file because it is too large: " + dfsPath);
}
fileList.add(info);
}
// Done
return fileList;
}
}