/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.addthis.hydra.minion;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import com.addthis.basis.kv.KVPairs;
import com.addthis.basis.util.SimpleExec;
import com.addthis.hydra.job.mq.JobKey;
import com.addthis.maljson.JSONObject;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
class MinionHandler extends AbstractHandler {
private static final Logger log = LoggerFactory.getLogger(MinionHandler.class);
private final Minion minion;
public MinionHandler(Minion minion) {
this.minion = minion;
}
@Override
public void doStop() {
}
@Override
public void doStart() {
}
@Override
public void handle(String target,
Request request,
HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse) throws IOException, ServletException {
try {
doHandle(target, request, httpServletRequest, httpServletResponse);
} catch (IOException | ServletException io) {
throw io;
} catch (Exception ex) {
throw new IOException(ex);
}
}
public void doHandle(String target,
Request baseRequest,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
response.setBufferSize(65535);
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Headers", "accept, username");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT");
KVPairs kv = new KVPairs();
boolean handled = true;
for (Enumeration<String> e = request.getParameterNames(); e.hasMoreElements(); ) {
String k = e.nextElement();
String v = request.getParameter(k);
kv.add(k, v);
}
if (target.equals("/ping")) {
response.getWriter().write("ACK");
} else if (target.startsWith("/metrics")) {
minion.metricsHandler.handle(target, baseRequest, request, response);
} else if (target.equals("/job.port")) {
String job = kv.getValue("id");
int taskID = kv.getIntValue("node", -1);
JobKey key = new JobKey(job, taskID);
JobTask task = minion.tasks.get(key.toString());
Integer jobPort = null;
if (task != null) {
jobPort = task.getPort();
}
response.getWriter().write("{port:" + (jobPort != null ? jobPort : 0) + "}");
} else if (target.equals("/job.profile")) {
String jobName = kv.getValue("id", "") + "/" + kv.getIntValue("node", 0);
JobTask job = minion.tasks.get(jobName);
if (job != null) {
response.getWriter().write(job.profile());
} else {
response.sendError(400, "No Job");
}
} else if (target.equals("/job.head")) {
String jobName = kv.getValue("id", "") + "/" + kv.getIntValue("node", 0);
int lines = kv.getIntValue("lines", 10);
boolean out = !kv.getValue("out", "0").equals("0");
boolean err = !kv.getValue("err", "0").equals("0");
String html = kv.getValue("html");
JobTask job = minion.tasks.get(jobName);
if (job != null) {
String outB = out ? LogUtils.head(job.logOut, lines) : "";
String errB = err ? LogUtils.head(job.logErr, lines) : "";
if (html != null) {
html = html.replace("{{out}}", outB);
html = html.replace("{{err}}", errB);
response.setContentType("text/html");
response.getWriter().write(html);
} else {
response.getWriter().write(new JSONObject().put("out", outB).put("err", errB).toString());
}
} else {
response.sendError(400, "No Job");
}
} else if (target.equals("/job.tail")) {
String jobName = kv.getValue("id", "") + "/" + kv.getIntValue("node", 0);
int lines = kv.getIntValue("lines", 10);
boolean out = !kv.getValue("out", "0").equals("0");
boolean err = !kv.getValue("err", "0").equals("0");
String html = kv.getValue("html");
JobTask job = minion.tasks.get(jobName);
if (job != null) {
String outB = out ? LogUtils.tail(job.logOut, lines) : "";
String errB = err ? LogUtils.tail(job.logErr, lines) : "";
if (html != null) {
html = html.replace("{{out}}", outB);
html = html.replace("{{err}}", errB);
response.setContentType("text/html");
response.getWriter().write(html);
} else {
response.getWriter().write(new JSONObject().put("out", outB).put("err", errB).toString());
}
} else {
response.sendError(400, "No Job");
}
} else if (target.equals("/job.log")) {
String jobName = kv.getValue("id", "") + "/" + kv.getIntValue("node", 0);
int offset = kv.getIntValue("offset", -1);
int lines = kv.getIntValue("lines", 10);
int runsAgo = kv.getIntValue("runsAgo", 0);
boolean out = kv.getValue("out", "1").equals("1");
JobTask job = minion.tasks.get(jobName);
if (job != null) {
String logSuffix = out ? "out" : "err";
// treat negative runs as searches for archived task-error logs rather than bothering with extra flag
if (runsAgo < 0) {
runsAgo *= -1;
runsAgo -= 1;
logSuffix += ".bad";
}
response.getWriter().write(LogUtils.readLogLines(job, offset, lines, runsAgo, logSuffix).toString());
} else {
response.sendError(400, "No Job");
}
} else if (target.equals("/jobs.import")) {
int count = minion.updateJobsMeta(new File(kv.getValue("dir", ".")));
response.getWriter().write("imported " + count + " jobs");
} else if (target.equals("/xdebug/findnextport")) {
response.getWriter().write("port: " + 0);
} else if (target.equals("/active.tasks")) {
Minion.capacityLock.lock();
try {
response.getWriter().write("tasks: " + minion.activeTaskKeys.toString());
} finally {
Minion.capacityLock.unlock();
}
} else if (target.equals("/task.size")) {
String jobId = kv.getValue("id");
int taskId = kv.getIntValue("node", -1);
if (jobId != null && taskId >= 0) {
String duOutput = new SimpleExec(MacUtils.ducmd + " -s --block-size=1 " + ProcessUtils.getTaskBaseDir(
minion.rootDir.getAbsolutePath(), jobId, taskId)).join().stdoutString();
response.getWriter().write(duOutput.split("\t")[0]);
}
} else {
response.sendError(404);
}
((Request) request).setHandled(handled);
}
}