mirror of https://github.com/apache/druid.git
More useful query errors. (#3335)
Follow-up to #1773, which meant to add more useful query errors but did not actually do so. Since that patch, any error other than interrupt/cancel/timeout was reported as `{"error":"Unknown exception"}`. With this patch, the error fields are: - error, one of the specific strings "Query interrupted", "Query timeout", "Query cancelled", or "Unknown exception" (same behavior as before). - errorMessage, the message of the topmost non-QueryInterruptedException in the causality chain. - errorClass, the class of the topmost non-QueryInterruptedException in the causality chain. - host, the host that failed the query.
This commit is contained in:
parent
2613e68477
commit
21bce96c4c
|
@ -66,3 +66,35 @@ For example, if the query ID is `abc123`, the query can be cancelled as follows:
|
||||||
```sh
|
```sh
|
||||||
curl -X DELETE "http://host:port/druid/v2/abc123"
|
curl -X DELETE "http://host:port/druid/v2/abc123"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Query Errors
|
||||||
|
------------
|
||||||
|
|
||||||
|
If a query fails, you will get an HTTP 500 response containing a JSON object with the following structure:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error" : "Query timeout",
|
||||||
|
"errorMessage" : "Timeout waiting for task.",
|
||||||
|
"errorClass" : "java.util.concurrent.TimeoutException",
|
||||||
|
"host" : "druid1.example.com:8083"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The fields in the response are:
|
||||||
|
|
||||||
|
|field|description|
|
||||||
|
|-----|-----------|
|
||||||
|
|error|A well-defined error code (see below).|
|
||||||
|
|errorMessage|A free-form message with more information about the error. May be null.|
|
||||||
|
|errorClass|The class of the exception that caused this error. May be null.|
|
||||||
|
|host|The host on which this error occurred. May be null.|
|
||||||
|
|
||||||
|
Possible codes for the *error* field include:
|
||||||
|
|
||||||
|
|code|description|
|
||||||
|
|----|-----------|
|
||||||
|
|`Query timeout`|The query timed out.|
|
||||||
|
|`Query interrupted`|The query was interrupted, possibly due to JVM shutdown.|
|
||||||
|
|`Query cancelled`|The query was cancelled through the query cancellation API.|
|
||||||
|
|`Unknown exception`|Some other exception occurred. Check errorMessage and errorClass for details, although keep in mind that the contents of those fields are free-form and may change from release to release.|
|
||||||
|
|
|
@ -21,12 +21,24 @@ package io.druid.query;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.CancellationException;
|
import java.util.concurrent.CancellationException;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception representing a failed query. The name "QueryInterruptedException" is a misnomer; this is actually
|
||||||
|
* used on the client side for *all* kinds of failed queries.
|
||||||
|
*
|
||||||
|
* Fields:
|
||||||
|
* - "errorCode" is a well-defined errorCode code taken from a specific list (see the static constants). "Unknown exception"
|
||||||
|
* represents all wrapped exceptions other than interrupt/timeout/cancellation.
|
||||||
|
* - "errorMessage" is the toString of the wrapped exception
|
||||||
|
* - "errorClass" is the class of the wrapped exception
|
||||||
|
* - "host" is the host that the errorCode occurred on
|
||||||
|
*
|
||||||
|
* The QueryResource is expected to emit the JSON form of this object when errors happen, and the DirectDruidClient
|
||||||
|
* deserializes and wraps them.
|
||||||
|
*/
|
||||||
public class QueryInterruptedException extends RuntimeException
|
public class QueryInterruptedException extends RuntimeException
|
||||||
{
|
{
|
||||||
public static final String QUERY_INTERRUPTED = "Query interrupted";
|
public static final String QUERY_INTERRUPTED = "Query interrupted";
|
||||||
|
@ -34,75 +46,100 @@ public class QueryInterruptedException extends RuntimeException
|
||||||
public static final String QUERY_CANCELLED = "Query cancelled";
|
public static final String QUERY_CANCELLED = "Query cancelled";
|
||||||
public static final String UNKNOWN_EXCEPTION = "Unknown exception";
|
public static final String UNKNOWN_EXCEPTION = "Unknown exception";
|
||||||
|
|
||||||
private static final Set<String> listKnownException = ImmutableSet.of(
|
private final String errorCode;
|
||||||
QUERY_CANCELLED,
|
private final String errorClass;
|
||||||
QUERY_INTERRUPTED,
|
|
||||||
QUERY_TIMEOUT,
|
|
||||||
UNKNOWN_EXCEPTION
|
|
||||||
);
|
|
||||||
|
|
||||||
@JsonProperty
|
|
||||||
private final String causeMessage;
|
|
||||||
@JsonProperty
|
|
||||||
private final String host;
|
private final String host;
|
||||||
|
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
public QueryInterruptedException(
|
public QueryInterruptedException(
|
||||||
@JsonProperty("error") String message,
|
@JsonProperty("error") String errorCode,
|
||||||
@JsonProperty("causeMessage") String causeMessage,
|
@JsonProperty("errorMessage") String errorMessage,
|
||||||
|
@JsonProperty("errorClass") String errorClass,
|
||||||
@JsonProperty("host") String host
|
@JsonProperty("host") String host
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
super(message);
|
super(errorMessage);
|
||||||
this.causeMessage = causeMessage;
|
this.errorCode = errorCode;
|
||||||
|
this.errorClass = errorClass;
|
||||||
this.host = host;
|
this.host = host;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new QueryInterruptedException wrapping an underlying exception. The errorMessage and errorClass
|
||||||
|
* of this exception will be based on the highest non-QueryInterruptedException in the causality chain.
|
||||||
|
*
|
||||||
|
* @param cause wrapped exception
|
||||||
|
*/
|
||||||
public QueryInterruptedException(Throwable cause)
|
public QueryInterruptedException(Throwable cause)
|
||||||
{
|
{
|
||||||
this(cause, null);
|
this(cause, getHostFromThrowable(cause));
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryInterruptedException(Throwable e, String host)
|
public QueryInterruptedException(Throwable cause, String host)
|
||||||
{
|
{
|
||||||
super(e);
|
super(cause == null ? null : cause.getMessage(), cause);
|
||||||
|
this.errorCode = getErrorCodeFromThrowable(cause);
|
||||||
|
this.errorClass = getErrorClassFromThrowable(cause);
|
||||||
this.host = host;
|
this.host = host;
|
||||||
causeMessage = e.getMessage();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonProperty("error")
|
@JsonProperty("error")
|
||||||
|
public String getErrorCode()
|
||||||
|
{
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty("errorMessage")
|
||||||
@Override
|
@Override
|
||||||
public String getMessage()
|
public String getMessage()
|
||||||
{
|
{
|
||||||
if (this.getCause() == null) {
|
return super.getMessage();
|
||||||
return super.getMessage();
|
}
|
||||||
} else if (this.getCause() instanceof QueryInterruptedException) {
|
|
||||||
return getCause().getMessage();
|
@JsonProperty
|
||||||
} else if (this.getCause() instanceof InterruptedException) {
|
public String getErrorClass()
|
||||||
|
{
|
||||||
|
return errorClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
public String getHost()
|
||||||
|
{
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getErrorCodeFromThrowable(Throwable e)
|
||||||
|
{
|
||||||
|
if (e instanceof QueryInterruptedException) {
|
||||||
|
return ((QueryInterruptedException) e).getErrorCode();
|
||||||
|
} else if (e instanceof InterruptedException) {
|
||||||
return QUERY_INTERRUPTED;
|
return QUERY_INTERRUPTED;
|
||||||
} else if (this.getCause() instanceof CancellationException) {
|
} else if (e instanceof CancellationException) {
|
||||||
return QUERY_CANCELLED;
|
return QUERY_CANCELLED;
|
||||||
} else if (this.getCause() instanceof TimeoutException) {
|
} else if (e instanceof TimeoutException) {
|
||||||
return QUERY_TIMEOUT;
|
return QUERY_TIMEOUT;
|
||||||
} else {
|
} else {
|
||||||
return UNKNOWN_EXCEPTION;
|
return UNKNOWN_EXCEPTION;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonProperty("causeMessage")
|
private static String getErrorClassFromThrowable(Throwable e)
|
||||||
public String getCauseMessage()
|
|
||||||
{
|
{
|
||||||
return causeMessage;
|
if (e instanceof QueryInterruptedException) {
|
||||||
|
return ((QueryInterruptedException) e).getErrorClass();
|
||||||
|
} else if (e != null) {
|
||||||
|
return e.getClass().getName();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonProperty("host")
|
private static String getHostFromThrowable(Throwable e)
|
||||||
public String getHost()
|
|
||||||
{
|
{
|
||||||
return host;
|
if (e instanceof QueryInterruptedException) {
|
||||||
}
|
return ((QueryInterruptedException) e).getHost();
|
||||||
|
} else {
|
||||||
public boolean isNotKnown()
|
return null;
|
||||||
{
|
}
|
||||||
return !listKnownException.contains(getMessage());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -281,7 +281,7 @@ public class ChainedExecutionQueryRunnerTest
|
||||||
}
|
}
|
||||||
catch (ExecutionException e) {
|
catch (ExecutionException e) {
|
||||||
Assert.assertTrue(e.getCause() instanceof QueryInterruptedException);
|
Assert.assertTrue(e.getCause() instanceof QueryInterruptedException);
|
||||||
Assert.assertEquals("Query timeout", e.getCause().getMessage());
|
Assert.assertEquals("Query timeout", ((QueryInterruptedException) e.getCause()).getErrorCode());
|
||||||
cause = (QueryInterruptedException) e.getCause();
|
cause = (QueryInterruptedException) e.getCause();
|
||||||
}
|
}
|
||||||
queriesInterrupted.await();
|
queriesInterrupted.await();
|
||||||
|
|
|
@ -0,0 +1,195 @@
|
||||||
|
/*
|
||||||
|
* Licensed to Metamarkets Group Inc. (Metamarkets) under one
|
||||||
|
* or more contributor license agreements. See the NOTICE file
|
||||||
|
* distributed with this work for additional information
|
||||||
|
* regarding copyright ownership. Metamarkets licenses this file
|
||||||
|
* to you 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 io.druid.query;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
|
import com.metamx.common.ISE;
|
||||||
|
import io.druid.jackson.DefaultObjectMapper;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.concurrent.CancellationException;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
|
public class QueryInterruptedExceptionTest
|
||||||
|
{
|
||||||
|
private static final ObjectMapper MAPPER = new DefaultObjectMapper();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testErrorCode()
|
||||||
|
{
|
||||||
|
Assert.assertEquals(
|
||||||
|
"Query cancelled",
|
||||||
|
new QueryInterruptedException(new QueryInterruptedException(new CancellationException())).getErrorCode()
|
||||||
|
);
|
||||||
|
Assert.assertEquals("Query cancelled", new QueryInterruptedException(new CancellationException()).getErrorCode());
|
||||||
|
Assert.assertEquals("Query interrupted", new QueryInterruptedException(new InterruptedException()).getErrorCode());
|
||||||
|
Assert.assertEquals("Query timeout", new QueryInterruptedException(new TimeoutException()).getErrorCode());
|
||||||
|
Assert.assertEquals("Unknown exception", new QueryInterruptedException(null).getErrorCode());
|
||||||
|
Assert.assertEquals("Unknown exception", new QueryInterruptedException(new ISE("Something bad!")).getErrorCode());
|
||||||
|
Assert.assertEquals(
|
||||||
|
"Unknown exception",
|
||||||
|
new QueryInterruptedException(new QueryInterruptedException(new ISE("Something bad!"))).getErrorCode()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testErrorMessage()
|
||||||
|
{
|
||||||
|
Assert.assertEquals(
|
||||||
|
null,
|
||||||
|
new QueryInterruptedException(new QueryInterruptedException(new CancellationException())).getMessage()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
null,
|
||||||
|
new QueryInterruptedException(new CancellationException()).getMessage()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
null,
|
||||||
|
new QueryInterruptedException(new InterruptedException()).getMessage()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
null,
|
||||||
|
new QueryInterruptedException(new TimeoutException()).getMessage()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
null,
|
||||||
|
new QueryInterruptedException(null).getMessage()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"Something bad!",
|
||||||
|
new QueryInterruptedException(new ISE("Something bad!")).getMessage()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"Something bad!",
|
||||||
|
new QueryInterruptedException(new QueryInterruptedException(new ISE("Something bad!"))).getMessage()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testErrorClass()
|
||||||
|
{
|
||||||
|
Assert.assertEquals(
|
||||||
|
"java.util.concurrent.CancellationException",
|
||||||
|
new QueryInterruptedException(new QueryInterruptedException(new CancellationException())).getErrorClass()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"java.util.concurrent.CancellationException",
|
||||||
|
new QueryInterruptedException(new CancellationException()).getErrorClass()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"java.lang.InterruptedException",
|
||||||
|
new QueryInterruptedException(new InterruptedException()).getErrorClass()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"java.util.concurrent.TimeoutException",
|
||||||
|
new QueryInterruptedException(new TimeoutException()).getErrorClass()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
null,
|
||||||
|
new QueryInterruptedException(null).getErrorClass()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"com.metamx.common.ISE",
|
||||||
|
new QueryInterruptedException(new ISE("Something bad!")).getErrorClass()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"com.metamx.common.ISE",
|
||||||
|
new QueryInterruptedException(new QueryInterruptedException(new ISE("Something bad!"))).getErrorClass()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testHost()
|
||||||
|
{
|
||||||
|
Assert.assertEquals(
|
||||||
|
"myhost",
|
||||||
|
new QueryInterruptedException(new QueryInterruptedException(new CancellationException(), "myhost")).getHost()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSerde()
|
||||||
|
{
|
||||||
|
Assert.assertEquals(
|
||||||
|
"Query cancelled",
|
||||||
|
roundTrip(new QueryInterruptedException(new QueryInterruptedException(new CancellationException()))).getErrorCode()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"java.util.concurrent.CancellationException",
|
||||||
|
roundTrip(new QueryInterruptedException(new QueryInterruptedException(new CancellationException()))).getErrorClass()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
null,
|
||||||
|
roundTrip(new QueryInterruptedException(new QueryInterruptedException(new CancellationException()))).getMessage()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"java.util.concurrent.CancellationException",
|
||||||
|
roundTrip(new QueryInterruptedException(new CancellationException())).getErrorClass()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"java.lang.InterruptedException",
|
||||||
|
roundTrip(new QueryInterruptedException(new InterruptedException())).getErrorClass()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"java.util.concurrent.TimeoutException",
|
||||||
|
roundTrip(new QueryInterruptedException(new TimeoutException())).getErrorClass()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
null,
|
||||||
|
roundTrip(new QueryInterruptedException(null)).getErrorClass()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"com.metamx.common.ISE",
|
||||||
|
roundTrip(new QueryInterruptedException(new ISE("Something bad!"))).getErrorClass()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"com.metamx.common.ISE",
|
||||||
|
roundTrip(new QueryInterruptedException(new QueryInterruptedException(new ISE("Something bad!")))).getErrorClass()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"Something bad!",
|
||||||
|
roundTrip(new QueryInterruptedException(new ISE("Something bad!"))).getMessage()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"Something bad!",
|
||||||
|
roundTrip(new QueryInterruptedException(new QueryInterruptedException(new ISE("Something bad!")))).getMessage()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"Unknown exception",
|
||||||
|
roundTrip(new QueryInterruptedException(new ISE("Something bad!"))).getErrorCode()
|
||||||
|
);
|
||||||
|
Assert.assertEquals(
|
||||||
|
"Unknown exception",
|
||||||
|
roundTrip(new QueryInterruptedException(new QueryInterruptedException(new ISE("Something bad!")))).getErrorCode()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static QueryInterruptedException roundTrip(final QueryInterruptedException e)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return MAPPER.readValue(MAPPER.writeValueAsBytes(e), QueryInterruptedException.class);
|
||||||
|
}
|
||||||
|
catch (Exception e2) {
|
||||||
|
throw Throwables.propagate(e2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -478,13 +478,7 @@ public class DirectDruidClient<T> implements QueryRunner<T>
|
||||||
final JsonToken nextToken = jp.nextToken();
|
final JsonToken nextToken = jp.nextToken();
|
||||||
if (nextToken == JsonToken.START_OBJECT) {
|
if (nextToken == JsonToken.START_OBJECT) {
|
||||||
QueryInterruptedException cause = jp.getCodec().readValue(jp, QueryInterruptedException.class);
|
QueryInterruptedException cause = jp.getCodec().readValue(jp, QueryInterruptedException.class);
|
||||||
//case we get an exception with an unknown message.
|
throw new QueryInterruptedException(cause, host);
|
||||||
if (cause.isNotKnown()) {
|
|
||||||
throw new QueryInterruptedException(QueryInterruptedException.UNKNOWN_EXCEPTION, cause.getMessage(), host);
|
|
||||||
} else {
|
|
||||||
throw new QueryInterruptedException(cause, host);
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (nextToken != JsonToken.START_ARRAY) {
|
} else if (nextToken != JsonToken.START_ARRAY) {
|
||||||
throw new IAE("Next token wasn't a START_ARRAY, was[%s] from url [%s]", jp.getCurrentToken(), url);
|
throw new IAE("Next token wasn't a START_ARRAY, was[%s] from url [%s]", jp.getCurrentToken(), url);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -83,7 +83,7 @@ public class QueryResource
|
||||||
@Deprecated // use SmileMediaTypes.APPLICATION_JACKSON_SMILE
|
@Deprecated // use SmileMediaTypes.APPLICATION_JACKSON_SMILE
|
||||||
private static final String APPLICATION_SMILE = "application/smile";
|
private static final String APPLICATION_SMILE = "application/smile";
|
||||||
|
|
||||||
private static final int RESPONSE_CTX_HEADER_LEN_LIMIT = 7*1024;
|
private static final int RESPONSE_CTX_HEADER_LEN_LIMIT = 7 * 1024;
|
||||||
|
|
||||||
private final QueryToolChestWarehouse warehouse;
|
private final QueryToolChestWarehouse warehouse;
|
||||||
private final ServerConfig config;
|
private final ServerConfig config;
|
||||||
|
@ -345,11 +345,7 @@ public class QueryResource
|
||||||
log.error(e2, "Unable to log query [%s]!", query);
|
log.error(e2, "Unable to log query [%s]!", query);
|
||||||
}
|
}
|
||||||
return Response.serverError().type(contentType).entity(
|
return Response.serverError().type(contentType).entity(
|
||||||
jsonWriter.writeValueAsBytes(
|
jsonWriter.writeValueAsBytes(new QueryInterruptedException(e))
|
||||||
ImmutableMap.of(
|
|
||||||
"error", e.getMessage() == null ? "null exception" : e.getMessage()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
).build();
|
).build();
|
||||||
}
|
}
|
||||||
catch (Exception e) {
|
catch (Exception e) {
|
||||||
|
@ -395,11 +391,7 @@ public class QueryResource
|
||||||
.emit();
|
.emit();
|
||||||
|
|
||||||
return Response.serverError().type(contentType).entity(
|
return Response.serverError().type(contentType).entity(
|
||||||
jsonWriter.writeValueAsBytes(
|
jsonWriter.writeValueAsBytes(new QueryInterruptedException(e))
|
||||||
ImmutableMap.of(
|
|
||||||
"error", e.getMessage() == null ? "null exception" : e.getMessage()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
).build();
|
).build();
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
|
|
|
@ -20,7 +20,6 @@
|
||||||
package io.druid.client;
|
package io.druid.client;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
@ -314,7 +313,7 @@ public class DirectDruidClientTest
|
||||||
|
|
||||||
TimeBoundaryQuery query = Druids.newTimeBoundaryQueryBuilder().dataSource("test").build();
|
TimeBoundaryQuery query = Druids.newTimeBoundaryQueryBuilder().dataSource("test").build();
|
||||||
HashMap<String, List> context = Maps.newHashMap();
|
HashMap<String, List> context = Maps.newHashMap();
|
||||||
interruptionFuture.set(new ByteArrayInputStream("{\"error\":\"testing\"}".getBytes()));
|
interruptionFuture.set(new ByteArrayInputStream("{\"error\":\"testing1\",\"errorMessage\":\"testing2\"}".getBytes()));
|
||||||
Sequence results = client1.run(query, context);
|
Sequence results = client1.run(query, context);
|
||||||
|
|
||||||
QueryInterruptedException actualException = null;
|
QueryInterruptedException actualException = null;
|
||||||
|
@ -325,9 +324,9 @@ public class DirectDruidClientTest
|
||||||
actualException = e;
|
actualException = e;
|
||||||
}
|
}
|
||||||
Assert.assertNotNull(actualException);
|
Assert.assertNotNull(actualException);
|
||||||
Assert.assertEquals(actualException.getMessage(), QueryInterruptedException.UNKNOWN_EXCEPTION);
|
Assert.assertEquals("testing1", actualException.getErrorCode());
|
||||||
Assert.assertEquals(actualException.getCauseMessage(), "testing");
|
Assert.assertEquals("testing2", actualException.getMessage());
|
||||||
Assert.assertEquals(actualException.getHost(), hostName);
|
Assert.assertEquals(hostName, actualException.getHost());
|
||||||
EasyMock.verify(httpClient);
|
EasyMock.verify(httpClient);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue