/**
* Copyright 2014 Yahoo! Inc. 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. See accompanying
* LICENSE file.
*/
package com.yahoo.sql4d.sql4ddriver;
import com.yahoo.sql4d.CrudStatementMeta;
import java.io.IOException;
import static java.lang.String.format;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Submit tasks to druid. Both Synchronous(with timeout) and asynchronous are supported.
* @author srikalyan
*/
public class OverlordAccessor extends DruidNodeAccessor {
private static final Logger log = LoggerFactory.getLogger(OverlordAccessor.class);
private final String overlordUrl = "http://%s:%d/druid/indexer/v1/task";
private final String overlordHost;
private int overlordPort = 8087;
private static final int TWO_HOURS_IN_MILLIS = 7200000;// 7200 secs
public OverlordAccessor(String host, int port, int maxConns) {
super(host, port, maxConns);
this.overlordHost = host;
this.overlordPort = port;
}
/**
* Task means an indexer task(goes straight to overlord).
* @param meta
* @param reqHeaders
* @param wait
* @return
*/
public String fireTask(CrudStatementMeta meta, Map<String, String> reqHeaders, boolean wait) {
CloseableHttpResponse resp = null;
String url = format(overlordUrl, overlordHost, overlordPort);
try {
resp = postJson(url, meta.toString(), reqHeaders);
if (resp.getStatusLine().getStatusCode() == 500) {
return "Task failed with server error, " + IOUtils.toString(resp.getEntity().getContent());
}
//TODO: Check for nulls in the following.
String strResp = IOUtils.toString(resp.getEntity().getContent());
JSONObject respJson = new JSONObject(strResp);
if (wait) {
if (waitForTask(respJson.getString("task"), reqHeaders, TWO_HOURS_IN_MILLIS)) {
return "Task completed successfully , task Id " + respJson;
}
return "Task failed/still running, task Id " + respJson;
} else {
String taskId = respJson.optString("task");
if (StringUtils.isBlank(taskId)) {
log.error("Response has no taskId, Response body : {}", respJson);
return null;
}
return respJson.getString("task");
}
} catch (IOException | IllegalStateException | JSONException ex) {
log.error("Error when firing task to overlord {}", ExceptionUtils.getStackTrace(ex));
return format("Http %s \n", ex);
} finally {
returnClient(resp);
}
}
/**
*
* @param taskId
* @param reqHeaders
* @param maxWaitTime
* @return
*/
public boolean waitForTask(String taskId, Map<String, String> reqHeaders, int maxWaitTime) {
long startT = System.currentTimeMillis();
long endT = System.currentTimeMillis();
boolean finalStatus = false;
String statusStr = "Task failed ..";
OUTER:
while (endT - startT < maxWaitTime) {
CloseableHttpResponse resp = null;
String url = format("%s/%s/status", format(overlordUrl, overlordHost, overlordPort), taskId);
try {
resp = get(url, reqHeaders);
//TODO: Check for nulls in the following.
JSONObject respJson = new JSONObject(IOUtils.toString(resp.getEntity().getContent()));
JSONObject status = respJson.getJSONObject("status");
endT = System.currentTimeMillis();
if (null != status.getString("status")) {
switch (status.getString("status")) {
case "SUCCESS":
statusStr = "Task succeeded ..";
finalStatus = true;
break OUTER;
case "RUNNING":
continue;
default:
break OUTER;
}
}
} catch (IOException ex) {
log.error("Error waiting for task {}", ExceptionUtils.getStackTrace(ex));
} finally {
returnClient(resp);
}
}
log.info("Status : {}", statusStr);
return finalStatus;
}
/**
* Poll for task's status for tasks fired with 0 wait time.
* @param taskId
* @param reqHeaders
* @return
*/
public TaskStatus pollTaskStatus(String taskId, Map<String, String> reqHeaders) {
CloseableHttpResponse resp = null;
String url = format("%s/%s/status", format(overlordUrl, overlordHost, overlordPort), taskId);
try {
resp = get(url, reqHeaders);
//TODO: Check for nulls in the following.
JSONObject respJson = new JSONObject(IOUtils.toString(resp.getEntity().getContent()));
JSONObject status = respJson.optJSONObject("status");
if (status != null && null != status.getString("status")) {
switch (status.getString("status")) {
case "SUCCESS":
return TaskStatus.SUCCESS;
case "RUNNING":
return TaskStatus.RUNNING;
default:
return TaskStatus.FAILED;
}
}
} catch (IOException ex) {
log.error("Error polling for task status {}", ExceptionUtils.getStackTrace(ex));
} finally {
returnClient(resp);
}// Happens when result looks like {"task":"null"} which has no status(because such a task itself does not exist)
return TaskStatus.UNKNOWN;
}
}