summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHaakon Dybdahl <dybdahl@yahoo-inc.com>2017-03-06 09:22:16 +0100
committerHaakon Dybdahl <dybdahl@yahoo-inc.com>2017-03-06 09:22:16 +0100
commit3fcafa4cc9a2f29c417cfbda9bed0c8390ba2ab7 (patch)
tree8a297abd0b7deb4f52098033535a12f11f290cd8
parent351903b75e5cf8feaa6606c139ee56e1caeccd23 (diff)
Extend APIs with condition-not-met.
-rw-r--r--documentapi/src/main/java/com/yahoo/documentapi/DocumentAccessException.java6
-rw-r--r--documentapi/src/main/java/com/yahoo/documentapi/Result.java22
-rw-r--r--documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusAsyncSession.java21
-rwxr-xr-xdocumentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusSyncSession.java2
-rw-r--r--vespa-http-client/src/main/java/com/yahoo/vespa/http/client/Result.java20
-rw-r--r--vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/OperationStatus.java15
-rw-r--r--vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/api/ResultImpl.java8
-rw-r--r--vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/DryRunGatewayConnection.java2
-rw-r--r--vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/EndpointResultQueue.java2
-rw-r--r--vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/IOThread.java2
-rw-r--r--vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/operationProcessor/EndPointResultFactory.java7
-rw-r--r--vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/operationProcessor/OperationProcessor.java1
-rw-r--r--vespa-http-client/src/test/java/com/yahoo/vespa/http/client/V3HttpAPITest.java27
-rw-r--r--vespa-http-client/src/test/java/com/yahoo/vespa/http/client/handlers/V3MockParsingRequestHandler.java36
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/OperationHandlerImpl.java48
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/Response.java16
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/RestUri.java33
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/RestApi.java11
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/ClientFeederV3.java15
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeedReplyReader.java11
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/Feeder.java13
-rw-r--r--vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/OperationHandlerImplTest.java5
-rw-r--r--vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/MockedOperationHandler.java2
-rw-r--r--vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/RestApiTest.java3
-rw-r--r--vespaclient-container-plugin/src/test/java/com/yahoo/feedhandler/v3/FeedTesterV3.java2
25 files changed, 238 insertions, 92 deletions
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/DocumentAccessException.java b/documentapi/src/main/java/com/yahoo/documentapi/DocumentAccessException.java
index 4c9c3b0e817..27f5a89a9b2 100644
--- a/documentapi/src/main/java/com/yahoo/documentapi/DocumentAccessException.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/DocumentAccessException.java
@@ -1,6 +1,8 @@
// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.documentapi;
+import com.yahoo.documentapi.messagebus.protocol.DocumentProtocol;
+
import java.util.Set;
import java.util.HashSet;
@@ -30,6 +32,10 @@ public class DocumentAccessException extends RuntimeException {
this.errorCodes = errorCodes;
}
+ public boolean hasConditionNotMetError(){
+ return errorCodes.contains(DocumentProtocol.ERROR_TEST_AND_SET_CONDITION_FAILED);
+ }
+
public DocumentAccessException(String message, Throwable cause) {
super(message, cause);
}
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/Result.java b/documentapi/src/main/java/com/yahoo/documentapi/Result.java
index 9a24da9a676..4d94da71a55 100644
--- a/documentapi/src/main/java/com/yahoo/documentapi/Result.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/Result.java
@@ -66,12 +66,26 @@ public class Result {
public long getRequestId() { return requestId; }
/**
+ * Deprecated: Use getResultType() instead.
* Returns the type of result.
*
- * @return the type of result, typically if this is an error or a success, and what kind of error
+ * @return the type of result, typically if this is an error or a success, and what kind of error.
+ * Does not return CONDITION_NOT_MET_ERROR for backward compatibility.
* @see com.yahoo.documentapi.Result.ResultType
*/
- public ResultType getType() { return type; }
+ @Deprecated
+ public ResultType getType() { return
+ type == ResultType.CONDITION_NOT_MET_ERROR
+ ? ResultType.FATAL_ERROR
+ : type;}
+
+ /**
+ * Returns the type of result.
+ *
+ * @return the type of result, typically if this is an error or a success, and what kind of error.
+ * @see com.yahoo.documentapi.Result.ResultType
+ */
+ public ResultType getResultType() { return type;}
/** The types that a Result can have. */
public enum ResultType {
@@ -80,6 +94,8 @@ public class Result {
/** The request failed, but may be successful if retried at a later time. */
TRANSIENT_ERROR,
/** The request failed, and retrying is pointless. */
- FATAL_ERROR
+ FATAL_ERROR,
+ /** Condition specified in operation not met error */
+ CONDITION_NOT_MET_ERROR
}
}
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusAsyncSession.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusAsyncSession.java
index 5795ea771b5..d7af471cc20 100644
--- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusAsyncSession.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusAsyncSession.java
@@ -4,7 +4,6 @@ package com.yahoo.documentapi.messagebus;
import com.yahoo.document.Document;
import com.yahoo.document.DocumentId;
import com.yahoo.document.DocumentPut;
-import com.yahoo.document.DocumentType;
import com.yahoo.document.DocumentUpdate;
import com.yahoo.documentapi.*;
import com.yahoo.documentapi.Result;
@@ -211,13 +210,21 @@ public class MessageBusAsyncSession implements MessageBusSession, AsyncSession {
private static Result toResult(long reqId, com.yahoo.messagebus.Result mbusResult) {
if (mbusResult.isAccepted()) {
return new Result(reqId);
- } else if (mbusResult.getError().getCode() == ErrorCode.SEND_QUEUE_FULL) {
- return new Result(Result.ResultType.TRANSIENT_ERROR,
- new Error(mbusResult.getError().getMessage() + " (" + mbusResult.getError().getCode() + ")"));
- } else {
- return new Result(Result.ResultType.FATAL_ERROR,
- new Error(mbusResult.getError().getMessage() + " (" + mbusResult.getError().getCode() + ")"));
}
+ final Error error = new Error(mbusResult.getError().getMessage() + " (" + mbusResult.getError().getCode() + ")");
+ final Result.ResultType resultType;
+ switch (mbusResult.getError().getCode()) {
+ case ErrorCode.SEND_QUEUE_FULL:
+ resultType = Result.ResultType.TRANSIENT_ERROR;
+ break;
+ case DocumentProtocol.ERROR_TEST_AND_SET_CONDITION_FAILED:
+ resultType = Result.ResultType.CONDITION_NOT_MET_ERROR;
+ break;
+ default:
+ resultType = Result.ResultType.FATAL_ERROR;
+ break;
+ }
+ return new Result(resultType, error);
}
private static Response toResponse(Reply reply) {
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusSyncSession.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusSyncSession.java
index 095d0c14a49..19116d69f83 100755
--- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusSyncSession.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/MessageBusSyncSession.java
@@ -77,7 +77,7 @@ public class MessageBusSyncSession implements MessageBusSession, SyncSession, Re
msg.setContext(monitor);
msg.pushHandler(this); // store monitor
Result result = null;
- while (result == null || result.getType() == Result.ResultType.TRANSIENT_ERROR) {
+ while (result == null || result.getResultType() == Result.ResultType.TRANSIENT_ERROR) {
result = session.send(msg);
if (result != null && result.isSuccess()) {
break;
diff --git a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/Result.java b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/Result.java
index 3e809cfe0ad..4488f8603c2 100644
--- a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/Result.java
+++ b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/Result.java
@@ -52,6 +52,12 @@ public abstract class Result {
*/
abstract public boolean isTransient();
+ /**
+ * Returns true if a error was caused by condition-not-met in the document operation.
+ * @return true if condition is not met.
+ */
+ abstract public boolean isConditionNotMet();
+
abstract public List<Detail> getDetails();
/**
@@ -71,14 +77,16 @@ public abstract class Result {
private final Endpoint endpoint;
private final boolean success;
private final boolean _transient;
+ private final boolean isConditionNotMet;
private final Exception exception;
private final String traceMessage;
private final long timeStampMillis = System.currentTimeMillis();
- public Detail(Endpoint endpoint, boolean success, boolean _transient, String traceMessage, Exception e) {
+ public Detail(Endpoint endpoint, boolean success, boolean _transient, boolean isConditionNotMet, String traceMessage, Exception e) {
this.endpoint = endpoint;
this.success = success;
this._transient = _transient;
+ this.isConditionNotMet = isConditionNotMet;
this.exception = e;
this.traceMessage = traceMessage;
}
@@ -87,6 +95,7 @@ public abstract class Result {
this.endpoint = endpoint;
this.success = true;
this._transient = true;
+ this.isConditionNotMet = false;
this.exception = null;
this.traceMessage = null;
}
@@ -120,6 +129,14 @@ public abstract class Result {
}
/**
+ * Returns true if a condition in the document operation was not met.
+ * @return if condition not met in operation.
+ */
+ public boolean isConditionNotMet() {
+ return isConditionNotMet;
+ }
+
+ /**
* Returns any exception related to this Detail, if unsuccessful. Might be null.
*
* @return any exception related to this Detail, if unsuccessful. Might be null.
@@ -143,6 +160,7 @@ public abstract class Result {
b.append("success=").append(success).append(" ");
if (!success) {
b.append("transient=").append(_transient).append(" ");
+ b.append("conditionNotMet=").append(isConditionNotMet).append(" ");
}
if (exception != null) {
b.append("exception='").append(Exceptions.toMessageString(exception)).append("' ");
diff --git a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/OperationStatus.java b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/OperationStatus.java
index 16315a1ca95..d0355b0e269 100644
--- a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/OperationStatus.java
+++ b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/OperationStatus.java
@@ -15,16 +15,19 @@ import java.util.Iterator;
*/
@Beta
public final class OperationStatus {
+ public static final String IS_CONDITION_NOT_MET = "IS-CONDITION-NOT-MET";
public final String message;
public final String operationId;
public final ErrorCode errorCode;
public final String traceMessage;
+ public final boolean isConditionNotMet;
private static final char EOL = '\n';
private static final char SEPARATOR = ' ';
private static final Splitter spaceSep = Splitter.on(SEPARATOR);
- public OperationStatus(String message, String operationId, ErrorCode errorCode, String traceMessage) {
+ public OperationStatus(String message, String operationId, ErrorCode errorCode, boolean isConditionNotMet, String traceMessage) {
+ this.isConditionNotMet = isConditionNotMet;
this.message = message;
this.operationId = operationId;
this.errorCode = errorCode;
@@ -56,19 +59,25 @@ public final class OperationStatus {
.toString();
errorCode = ErrorCode.valueOf(Encoder.decode(input.next(),
new StringBuilder()).toString());
+
message = Encoder.decode(input.next(), new StringBuilder()).toString();
// We are backwards compatible, meaning it is ok not to supply the last argument.
+ boolean isConditionNotMet = false;
+ if (message.startsWith(IS_CONDITION_NOT_MET)) {
+ message = message.replaceFirst(IS_CONDITION_NOT_MET, "");
+ isConditionNotMet = true;
+ }
if (input.hasNext()) {
traceMessage = Encoder.decode(input.next(), new StringBuilder()).toString();
}
- return new OperationStatus(message, operationId, errorCode, traceMessage);
+ return new OperationStatus(message, operationId, errorCode, isConditionNotMet, traceMessage);
}
public String render() {
StringBuilder s = new StringBuilder();
Encoder.encode(operationId, s).append(SEPARATOR);
Encoder.encode(errorCode.toString(), s).append(SEPARATOR);
- Encoder.encode(message, s).append(SEPARATOR);
+ Encoder.encode(isConditionNotMet ? IS_CONDITION_NOT_MET + message : message, s).append(SEPARATOR);
Encoder.encode(traceMessage, s).append(EOL);
return s.toString();
}
diff --git a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/api/ResultImpl.java b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/api/ResultImpl.java
index 00196f793c5..3e3f6897ba8 100644
--- a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/api/ResultImpl.java
+++ b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/api/ResultImpl.java
@@ -24,6 +24,7 @@ final public class ResultImpl extends Result {
private final Document document;
private final boolean success;
private final boolean _transient;
+ private final boolean isConditionNotMet;
private final List<Detail> details;
private final String localTrace;
@@ -32,12 +33,15 @@ final public class ResultImpl extends Result {
this.details = Collections.unmodifiableList(new ArrayList<>(values));
boolean totalSuccess = true;
boolean totalTransient = true;
+ boolean isConditionNotMet = true;
for (Detail d : details) {
if (!d.isSuccess()) {totalSuccess = false; }
if (!d.isTransient()) {totalTransient = false; }
+ if (!d.isConditionNotMet()) { isConditionNotMet = false; }
}
this.success = totalSuccess;
this._transient = totalTransient;
+ this.isConditionNotMet = isConditionNotMet;
this.localTrace = localTrace == null ? null : localTrace.toString();
}
@@ -67,6 +71,10 @@ final public class ResultImpl extends Result {
}
@Override
+ public boolean isConditionNotMet() { return isConditionNotMet; }
+
+
+ @Override
public List<Detail> getDetails() { return details; }
@Override
diff --git a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/DryRunGatewayConnection.java b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/DryRunGatewayConnection.java
index f634a2e22f7..53336b6899c 100644
--- a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/DryRunGatewayConnection.java
+++ b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/DryRunGatewayConnection.java
@@ -31,7 +31,7 @@ public class DryRunGatewayConnection implements GatewayConnection {
public InputStream writeOperations(List<Document> docs) throws ServerResponseException, IOException {
StringBuilder result = new StringBuilder();
for (Document doc : docs) {
- OperationStatus operationStatus = new OperationStatus("ok", doc.getOperationId(), ErrorCode.OK, "");
+ OperationStatus operationStatus = new OperationStatus("ok", doc.getOperationId(), ErrorCode.OK, false, "");
result.append(operationStatus.render());
}
return new ByteArrayInputStream(result.toString().getBytes(StandardCharsets.UTF_8));
diff --git a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/EndpointResultQueue.java b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/EndpointResultQueue.java
index 37395f87fd8..948d2e7f865 100644
--- a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/EndpointResultQueue.java
+++ b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/EndpointResultQueue.java
@@ -104,7 +104,7 @@ class EndpointResultQueue {
}
private synchronized void failedOperationId(String operationId, Exception exception) {
- EndpointResult endpointResult = EndPointResultFactory.createError(endpoint, operationId, exception);
+ EndpointResult endpointResult = EndPointResultFactory.createError(endpoint, operationId, false, exception);
operationProcessor.resultReceived(endpointResult, clusterId);
}
diff --git a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/IOThread.java b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/IOThread.java
index d769c5bdf0c..9697256fbde 100644
--- a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/IOThread.java
+++ b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/communication/IOThread.java
@@ -404,7 +404,7 @@ class IOThread implements Runnable, AutoCloseable {
for (Document document : documentQueue.removeAllDocuments()) {
EndpointResult endpointResult=
- EndPointResultFactory.createError(endpoint, document.getOperationId(), exception);
+ EndPointResultFactory.createError(endpoint, document.getOperationId(), false, exception);
resultQueue.failOperation(endpointResult, clusterId);
}
}
diff --git a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/operationProcessor/EndPointResultFactory.java b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/operationProcessor/EndPointResultFactory.java
index a91e170fbd5..b357efc82d4 100644
--- a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/operationProcessor/EndPointResultFactory.java
+++ b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/operationProcessor/EndPointResultFactory.java
@@ -41,13 +41,13 @@ public final class EndPointResultFactory {
}
public static EndpointResult createError(
- Endpoint endpoint, String operationId, Exception exception) {
- return new EndpointResult(operationId, new Result.Detail(endpoint, false, false, null, exception));
+ Endpoint endpoint, String operationId, boolean isConditionNotMetError, Exception exception) {
+ return new EndpointResult(operationId, new Result.Detail(endpoint, false, false, isConditionNotMetError, null, exception));
}
public static EndpointResult createTransientError(
Endpoint endpoint, String operationId, Exception exception) {
- return new EndpointResult(operationId, new Result.Detail(endpoint, false, true, null, exception));
+ return new EndpointResult(operationId, new Result.Detail(endpoint, false, true, false, null, exception));
}
private static EndpointResult parseResult(String line, Endpoint endpoint) {
@@ -71,6 +71,7 @@ public final class EndPointResultFactory {
new Result.Detail(endpoint,
reply.errorCode.isSuccess(),
reply.errorCode.isTransient(),
+ reply.isConditionNotMet,
reply.traceMessage,
exception));
} catch (Throwable t) {
diff --git a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/operationProcessor/OperationProcessor.java b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/operationProcessor/OperationProcessor.java
index b736a727c8f..199c5992e8b 100644
--- a/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/operationProcessor/OperationProcessor.java
+++ b/vespa-http-client/src/main/java/com/yahoo/vespa/http/client/core/operationProcessor/OperationProcessor.java
@@ -247,6 +247,7 @@ public class OperationProcessor {
EndPointResultFactory.createError(
eio.getEndpoint(),
document.getOperationId(),
+ false,
eio),
clusterConnection.getClusterId());
}
diff --git a/vespa-http-client/src/test/java/com/yahoo/vespa/http/client/V3HttpAPITest.java b/vespa-http-client/src/test/java/com/yahoo/vespa/http/client/V3HttpAPITest.java
index a095cb184a0..68e86c86929 100644
--- a/vespa-http-client/src/test/java/com/yahoo/vespa/http/client/V3HttpAPITest.java
+++ b/vespa-http-client/src/test/java/com/yahoo/vespa/http/client/V3HttpAPITest.java
@@ -57,7 +57,7 @@ public class V3HttpAPITest extends TestOnCiBuildingSystemOnly {
TestUtils.writeDocuments(session, Collections.<TestDocument>singletonList(documents.get(0)));
}
- private void testServerWithMock(V3MockParsingRequestHandler serverMock, boolean failFast) throws Exception {
+ private void testServerWithMock(V3MockParsingRequestHandler serverMock, boolean failFast, boolean conditionNotMet) throws Exception {
try (Server server = new Server(serverMock, 0);
Session session = SessionFactory.create(
new SessionParams.Builder()
@@ -84,7 +84,9 @@ public class V3HttpAPITest extends TestOnCiBuildingSystemOnly {
TestDocument document = documents.get(0);
Result r = results.remove(document.getDocumentId());
assertThat(r, not(nullValue()));
+ assertThat(r.isConditionNotMet(), is(conditionNotMet));
assertThat(r.getDetails().toString(), r.isSuccess(), is(false));
+ assertThat(r.getDetails().toString(), r.isConditionNotMet(), is(conditionNotMet));
assertThat(results.isEmpty(), is(true));
}
}
@@ -109,33 +111,33 @@ public class V3HttpAPITest extends TestOnCiBuildingSystemOnly {
@Test
public void requireThatBadResponseCodeFails() throws Exception {
- testServerWithMock(new V3MockParsingRequestHandler(401/*Unauthorized*/), true);
- testServerWithMock(new V3MockParsingRequestHandler(403/*Forbidden*/), true);
- testServerWithMock(new V3MockParsingRequestHandler(407/*Proxy Authentication Required*/), true);
+ testServerWithMock(new V3MockParsingRequestHandler(401/*Unauthorized*/), true, false);
+ testServerWithMock(new V3MockParsingRequestHandler(403/*Forbidden*/), true, false);
+ testServerWithMock(new V3MockParsingRequestHandler(407/*Proxy Authentication Required*/), true, false);
}
@Test
public void requireThatUnexpectedVersionIsHandledProperly() throws Exception {
testServerWithMock(new V3MockParsingRequestHandler(
- 200, V3MockParsingRequestHandler.Scenario.RETURN_UNEXPECTED_VERSION), true);
+ 200, V3MockParsingRequestHandler.Scenario.RETURN_UNEXPECTED_VERSION), true, false);
}
@Test
public void requireThatNonAcceptedVersionIsHandledProperly() throws Exception {
testServerWithMock(new V3MockParsingRequestHandler(
- 200, V3MockParsingRequestHandler.Scenario.DONT_ACCEPT_VERSION), true);
+ 200, V3MockParsingRequestHandler.Scenario.DONT_ACCEPT_VERSION), true, false);
}
@Test
public void requireThatNon200OkIsHandledProperly() throws Exception {
testServerWithMock(new V3MockParsingRequestHandler(
- 200, V3MockParsingRequestHandler.Scenario.INTERNAL_SERVER_ERROR), true);
+ 200, V3MockParsingRequestHandler.Scenario.INTERNAL_SERVER_ERROR), true, false);
}
@Test
public void requireThatMbusErrorIsHandledProperly() throws Exception {
testServerWithMock(new V3MockParsingRequestHandler(
- 200, V3MockParsingRequestHandler.Scenario.MBUS_RETURNED_ERROR), false);
+ 200, V3MockParsingRequestHandler.Scenario.MBUS_RETURNED_ERROR), false, false);
}
@Test
@@ -181,12 +183,17 @@ public class V3HttpAPITest extends TestOnCiBuildingSystemOnly {
@Test
public void requireThatCouldNotFeedErrorIsHandledProperly() throws Exception {
testServerWithMock(new V3MockParsingRequestHandler(
- 200, V3MockParsingRequestHandler.Scenario.COULD_NOT_FEED), false);
+ 200, V3MockParsingRequestHandler.Scenario.COULD_NOT_FEED), false, false);
}
@Test
public void requireThatImmediateDisconnectIsHandledProperly() throws Exception {
testServerWithMock(new V3MockParsingRequestHandler(
- 200, V3MockParsingRequestHandler.Scenario.DISCONNECT_IMMEDIATELY), true);
+ 200, V3MockParsingRequestHandler.Scenario.DISCONNECT_IMMEDIATELY), true, false);
+ }
+ @Test
+ public void testConditionNotMet() throws Exception {
+ testServerWithMock(new V3MockParsingRequestHandler(
+ 200, V3MockParsingRequestHandler.Scenario.CONDITON_NOT_MET), false, true);
}
}
diff --git a/vespa-http-client/src/test/java/com/yahoo/vespa/http/client/handlers/V3MockParsingRequestHandler.java b/vespa-http-client/src/test/java/com/yahoo/vespa/http/client/handlers/V3MockParsingRequestHandler.java
index c43feef088b..2b5b5ff10e9 100644
--- a/vespa-http-client/src/test/java/com/yahoo/vespa/http/client/handlers/V3MockParsingRequestHandler.java
+++ b/vespa-http-client/src/test/java/com/yahoo/vespa/http/client/handlers/V3MockParsingRequestHandler.java
@@ -42,7 +42,7 @@ public class V3MockParsingRequestHandler extends AbstractHandler {
DISCONNECT_IMMEDIATELY, DONT_ACCEPT_VERSION, RETURN_UNEXPECTED_VERSION,
INTERNAL_SERVER_ERROR, COULD_NOT_FEED, MBUS_RETURNED_ERROR,
NEVER_RETURN_ANY_RESULTS, DELAYED_RESPONSE, BAD_REQUEST, SERVER_ERROR_TWICE_THEN_OK,
- EXPECT_HIGHEST_PRIORITY_AND_TRACELEVEL_123
+ EXPECT_HIGHEST_PRIORITY_AND_TRACELEVEL_123, CONDITON_NOT_MET
}
public V3MockParsingRequestHandler() {
@@ -120,6 +120,9 @@ public class V3MockParsingRequestHandler extends AbstractHandler {
checkIfSessionThenHighPriorityAndTraceLevel123(request);
allOk(baseRequest, request, response);
break;
+ case CONDITON_NOT_MET:
+ conditionNotMetRequest(baseRequest, request, response);
+ break;
default:
throw new IllegalArgumentException("Test scenario " + scenario + " not supported.");
}
@@ -132,6 +135,24 @@ public class V3MockParsingRequestHandler extends AbstractHandler {
}
}
+ private void conditionNotMetRequest(Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException {
+ String sessionId = getSessionId(request);
+ setHeaders(response, sessionId);
+ response.setStatus(responseCode);
+ baseRequest.setHandled(true);
+ PrintWriter responseWriter = response.getWriter();
+ String operationId;
+ while ((operationId = readOperationId(request.getInputStream())) != null) {
+ long lengthToSkip = readByteLength(request.getInputStream());
+ while (lengthToSkip > 0) {
+ long skipped = request.getInputStream().skip(lengthToSkip);
+ lengthToSkip -= skipped;
+ }
+ respondConditionNotMet(responseWriter, operationId);
+ }
+ closeChannel(responseWriter);
+
+ }
private void badRequest(Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException {
if (badRequestScenarioShouldReturnBadRequest.get()) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
@@ -353,24 +374,29 @@ public class V3MockParsingRequestHandler extends AbstractHandler {
private void respondFailed(PrintWriter responseWriter, String docId) {
final OperationStatus operationStatus =
- new OperationStatus("mbus returned boom", docId, ErrorCode.ERROR, "trace");
+ new OperationStatus("mbus returned boom", docId, ErrorCode.ERROR, false, "trace");
writeResponse(responseWriter, operationStatus);
}
private void respondTransientFailed(PrintWriter responseWriter, String docId) {
final OperationStatus operationStatus = new OperationStatus(
- "Could not put", docId, ErrorCode.TRANSIENT_ERROR, "");
+ "Could not put", docId, ErrorCode.TRANSIENT_ERROR, false, "");
writeResponse(responseWriter, operationStatus);
}
private void respondFailedWithTransitiveErrorSeenFromClient(PrintWriter responseWriter, String docId) {
final OperationStatus operationStatus =
- new OperationStatus("NETWORK_ERROR", docId, ErrorCode.ERROR, "trace");
+ new OperationStatus("NETWORK_ERROR", docId, ErrorCode.ERROR, false, "trace");
writeResponse(responseWriter, operationStatus);
}
+ private void respondConditionNotMet(PrintWriter responseWriter, String docId) {
+ final OperationStatus operationStatus =
+ new OperationStatus("this is a test", docId, ErrorCode.ERROR, true, "trace");
+ writeResponse(responseWriter, operationStatus);
+ }
private void respondOK(PrintWriter responseWriter, String docId) {
- final OperationStatus operationStatus = new OperationStatus("Doc fed", docId, ErrorCode.OK, "Trace message");
+ final OperationStatus operationStatus = new OperationStatus("Doc fed", docId, ErrorCode.OK, false, "Trace message");
writeResponse(responseWriter, operationStatus);
}
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/OperationHandlerImpl.java b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/OperationHandlerImpl.java
index 4387f975d01..3d664d87622 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/OperationHandlerImpl.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/OperationHandlerImpl.java
@@ -70,16 +70,25 @@ public class OperationHandlerImpl implements OperationHandler {
private static final int HTTP_STATUS_BAD_REQUEST = 400;
private static final int HTTP_STATUS_INSUFFICIENT_STORAGE = 507;
+ private static final int HTTP_PRE_CONDIDTION_FAILED = 412;
- private static int getHTTPStatusCode(Set<Integer> errorCodes) {
- if (errorCodes.size() == 1 && errorCodes.contains(DocumentProtocol.ERROR_NO_SPACE)) {
+ private static int getHTTPStatusCode(DocumentAccessException documentException) {
+ if (documentException.getErrorCodes().size() == 1 && documentException.getErrorCodes().contains(DocumentProtocol.ERROR_NO_SPACE)) {
return HTTP_STATUS_INSUFFICIENT_STORAGE;
}
+ if (documentException.hasConditionNotMetError()) {
+ return HTTP_PRE_CONDIDTION_FAILED;
+ }
return HTTP_STATUS_BAD_REQUEST;
}
private static Response createErrorResponse(DocumentAccessException documentException, RestUri restUri) {
- return Response.createErrorResponse(getHTTPStatusCode(documentException.getErrorCodes()), documentException.getMessage(), restUri);
+ if (documentException.hasConditionNotMetError()) {
+ return Response.createErrorResponse(getHTTPStatusCode(documentException), "Condition did not match document.",
+ restUri, RestUri.apiErrorCodes.DOCUMENT_CONDITION_NOT_MET);
+ }
+ return Response.createErrorResponse(getHTTPStatusCode(documentException), documentException.getMessage(), restUri,
+ RestUri.apiErrorCodes.DOCUMENT_EXCPETION);
}
@Override
@@ -105,7 +114,8 @@ public class OperationHandlerImpl implements OperationHandler {
throw new RestApiException(Response.createErrorResponse(
500,
"Failed during parsing of arguments for visiting: " + ExceptionUtils.getStackTrace(e),
- restUri));
+ restUri,
+ RestUri.apiErrorCodes.VISITOR_ERROR));
}
try {
return doVisit(visitorControlHandler, localDataVisitorHandler, restUri);
@@ -120,13 +130,13 @@ public class OperationHandlerImpl implements OperationHandler {
RestUri restUri) throws RestApiException {
try {
if (! visitorControlHandler.waitUntilDone(VISIT_TIMEOUT_MS)) {
- throw new RestApiException(Response.createErrorResponse(500, "Timed out", restUri));
+ throw new RestApiException(Response.createErrorResponse(500, "Timed out", restUri, RestUri.apiErrorCodes.TIME_OUT));
}
if (visitorControlHandler.getResult().code != VisitorControlHandler.CompletionCode.SUCCESS) {
- throw new RestApiException(Response.createErrorResponse(400, visitorControlHandler.getResult().toString()));
+ throw new RestApiException(Response.createErrorResponse(400, visitorControlHandler.getResult().toString(), RestUri.apiErrorCodes.VISITOR_ERROR));
}
} catch (InterruptedException e) {
- throw new RestApiException(Response.createErrorResponse(500, ExceptionUtils.getStackTrace(e), restUri));
+ throw new RestApiException(Response.createErrorResponse(500, ExceptionUtils.getStackTrace(e), restUri, RestUri.apiErrorCodes.INTERRUPTED));
}
if (localDataVisitorHandler.getErrors().isEmpty()) {
final Optional<String> continuationToken;
@@ -138,14 +148,14 @@ public class OperationHandlerImpl implements OperationHandler {
}
return new VisitResult(continuationToken, localDataVisitorHandler.getCommaSeparatedJsonDocuments());
}
- throw new RestApiException(Response.createErrorResponse(500, localDataVisitorHandler.getErrors(), restUri));
+ throw new RestApiException(Response.createErrorResponse(500, localDataVisitorHandler.getErrors(), restUri, RestUri.apiErrorCodes.UNSPECIFIED));
}
private void setRoute(SyncSession session, Optional<String> route) throws RestApiException {
if (! (session instanceof MessageBusSyncSession)) {
// Not sure if this ever could happen but better be safe.
throw new RestApiException(Response.createErrorResponse(
- 400, "Can not set route since the API is not using message bus."));
+ 400, "Can not set route since the API is not using message bus.", RestUri.apiErrorCodes.NO_ROUTE_WHEN_NOT_PART_OF_MESSAGEBUS));
}
((MessageBusSyncSession) session).setRoute(route.orElse("default"));
}
@@ -161,7 +171,7 @@ public class OperationHandlerImpl implements OperationHandler {
} catch (DocumentAccessException documentException) {
throw new RestApiException(createErrorResponse(documentException, restUri));
} catch (Exception e) {
- throw new RestApiException(Response.createErrorResponse(500, ExceptionUtils.getStackTrace(e), restUri));
+ throw new RestApiException(Response.createErrorResponse(500, ExceptionUtils.getStackTrace(e), restUri, RestUri.apiErrorCodes.INTERNAL_EXCEPTION));
} finally {
syncSessions.free(syncSession);
}
@@ -176,7 +186,7 @@ public class OperationHandlerImpl implements OperationHandler {
} catch (DocumentAccessException documentException) {
throw new RestApiException(createErrorResponse(documentException, restUri));
} catch (Exception e) {
- throw new RestApiException(Response.createErrorResponse(500, ExceptionUtils.getStackTrace(e), restUri));
+ throw new RestApiException(Response.createErrorResponse(500, ExceptionUtils.getStackTrace(e), restUri, RestUri.apiErrorCodes.INTERNAL_EXCEPTION));
} finally {
syncSessions.free(syncSession);
}
@@ -194,9 +204,13 @@ public class OperationHandlerImpl implements OperationHandler {
}
syncSession.remove(documentRemove);
} catch (DocumentAccessException documentException) {
- throw new RestApiException(Response.createErrorResponse(400, documentException.getMessage(), restUri));
+ if (documentException.hasConditionNotMetError()) {
+ throw new RestApiException(Response.createErrorResponse(412, "Condition not met: " + documentException.getMessage(),
+ restUri, RestUri.apiErrorCodes.DOCUMENT_CONDITION_NOT_MET));
+ }
+ throw new RestApiException(Response.createErrorResponse(400, documentException.getMessage(), restUri, RestUri.apiErrorCodes.DOCUMENT_EXCPETION));
} catch (Exception e) {
- throw new RestApiException(Response.createErrorResponse(500, ExceptionUtils.getStackTrace(e), restUri));
+ throw new RestApiException(Response.createErrorResponse(500, ExceptionUtils.getStackTrace(e), restUri, RestUri.apiErrorCodes.UNSPECIFIED));
} finally {
syncSessions.free(syncSession);
}
@@ -218,7 +232,7 @@ public class OperationHandlerImpl implements OperationHandler {
return Optional.of(outputStream.toString(StandardCharsets.UTF_8.name()));
} catch (Exception e) {
- throw new RestApiException(Response.createErrorResponse(500, ExceptionUtils.getStackTrace(e), restUri));
+ throw new RestApiException(Response.createErrorResponse(500, ExceptionUtils.getStackTrace(e), restUri, RestUri.apiErrorCodes.UNSPECIFIED));
} finally {
syncSessions.free(syncSession);
}
@@ -238,7 +252,7 @@ public class OperationHandlerImpl implements OperationHandler {
if (! wantedCluster.isPresent()) {
if (clusters.size() != 1) {
new RestApiException(Response.createErrorResponse(400, "Several clusters exist: " +
- clusterListToString(clusters) + " you must specify one.. "));
+ clusterListToString(clusters) + " you must specify one.. ", RestUri.apiErrorCodes.SEVERAL_CLUSTERS));
}
return clusterDefToRoute(clusters.get(0));
}
@@ -249,7 +263,7 @@ public class OperationHandlerImpl implements OperationHandler {
}
}
throw new RestApiException(Response.createErrorResponse(400, "Your vespa cluster contains the content clusters " +
- clusterListToString(clusters) + " not " + wantedCluster.get() + ". Please select a valid vespa cluster."));
+ clusterListToString(clusters) + " not " + wantedCluster.get() + ". Please select a valid vespa cluster.", RestUri.apiErrorCodes.MISSING_CLUSTER));
}
@@ -304,7 +318,7 @@ public class OperationHandlerImpl implements OperationHandler {
try {
params.setResumeToken(ContinuationHit.getToken(continuation.get()));
} catch (Exception e) {
- throw new RestApiException(Response.createErrorResponse(500, ExceptionUtils.getStackTrace(e), restUri));
+ throw new RestApiException(Response.createErrorResponse(500, ExceptionUtils.getStackTrace(e), restUri, RestUri.apiErrorCodes.UNSPECIFIED));
}
}
return params;
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/Response.java b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/Response.java
index 9c846e9ce38..cbc816b4e09 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/Response.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/Response.java
@@ -25,16 +25,18 @@ public class Response extends HttpResponse {
jsonMessage = objectNode.toString();
}
- public static Response createErrorResponse(int code, String errorMessage) {
- ObjectNode objectNode = objectMapper.createObjectNode();
- objectNode.putArray("errors").add(errorMessage);
- return new Response(code, Optional.of(objectNode), Optional.<RestUri>empty());
+ public static Response createErrorResponse(int code, String errorMessage, RestUri.apiErrorCodes errorID) {
+ return createErrorResponse(code, errorMessage, null, errorID);
}
- public static Response createErrorResponse(int code, String errorMessage, RestUri restUri) {
+ public static Response createErrorResponse(int code, String errorMessage, RestUri restUri, RestUri.apiErrorCodes errorID) {
+ ObjectNode errorNode = objectMapper.createObjectNode();
+ errorNode.put("description", errorID.name() + " " + errorMessage);
+ errorNode.put("id", errorID.value);
+
ObjectNode objectNode = objectMapper.createObjectNode();
- objectNode.putArray("errors").add(errorMessage);
- return new Response(code, Optional.of(objectNode), Optional.of(restUri));
+ objectNode.putArray("errors").add(errorNode);
+ return new Response(code, Optional.of(objectNode), Optional.ofNullable(restUri));
}
@Override
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/RestUri.java b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/RestUri.java
index f50e7c247b1..7b6bc4e87c5 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/RestUri.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/RestUri.java
@@ -25,6 +25,30 @@ public class RestUri {
public static final String V_1 = "v1";
public static final String ID = "id:";
+ public enum apiErrorCodes {
+ ERROR_ID_BASIC_USAGE(-1),
+ ERROR_ID_DECODING_PATH(-2),
+ VISITOR_ERROR(-3),
+ NO_ROUTE_WHEN_NOT_PART_OF_MESSAGEBUS(-4),
+ SEVERAL_CLUSTERS(-5),
+ URL_PARSING(-6),
+ INVALID_CREATE_VALUE(-7),
+ TOO_MANY_PARALLEL_REQUESTS(-8),
+ MISSING_CLUSTER(-9), INTERNAL_EXCEPTION(-9),
+ DOCUMENT_CONDITION_NOT_MET(-10),
+ DOCUMENT_EXCPETION(-11),
+ PARSER_ERROR(-11),
+ GROUP_AND_EXPRESSION_ERROR(-12),
+ TIME_OUT(-13),
+ INTERRUPTED(-14),
+ UNSPECIFIED(-15);
+
+ public final long value;
+ apiErrorCodes(long value) {
+ this.value = value;
+ }
+ }
+
/**
* Represents the "grouping" part of document id which can be used with streaming model.
*/
@@ -69,6 +93,7 @@ public class RestUri {
}
static class PathParser {
+ public static final long ERROR_ID_DECODING_PATH = -10L;
final List<String> rawParts;
final String originalPath;
int readPos = 0;
@@ -88,7 +113,7 @@ public class RestUri {
try {
return URLDecoder.decode(rawId, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
- throw new RestApiException(Response.createErrorResponse(BAD_REQUEST,"Problems decoding the URI: " + e.getMessage()));
+ throw new RestApiException(Response.createErrorResponse(BAD_REQUEST,"Problems decoding the URI: " + e.getMessage(), apiErrorCodes.ERROR_ID_DECODING_PATH));
}
}
}
@@ -119,9 +144,9 @@ public class RestUri {
private static void throwUsage(String inputPath) throws RestApiException {
throw new RestApiException(Response.createErrorResponse(BAD_REQUEST,
- "Expected:\n" +
- ".../{namespace}/{document-type}/group/{name}/[{user-specified}]\n" +
- ".../{namespace}/{document-type}/docid/[{user-specified}]\n: but got " + inputPath));
+ "Expected: " +
+ ".../{namespace}/{document-type}/group/{name}/[{user-specified}] " +
+ ".../{namespace}/{document-type}/docid/[{user-specified}] : but got " + inputPath, apiErrorCodes.ERROR_ID_BASIC_USAGE));
}
}
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/RestApi.java b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/RestApi.java
index fba32f5c1fd..66e8c6475aa 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/RestApi.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/RestApi.java
@@ -114,7 +114,7 @@ public class RestApi extends LoggingRequestHandler {
if (threadsAvailableForApi.decrementAndGet() < 1) {
return Response.createErrorResponse(
429 /* Too Many Requests */,
- "Too many parallel requests, consider using http-vespa-java-client. Please try again later.");
+ "Too many parallel requests, consider using http-vespa-java-client. Please try again later.", RestUri.apiErrorCodes.TOO_MANY_PARALLEL_REQUESTS);
}
return handleInternal(request);
} finally {
@@ -130,13 +130,13 @@ public class RestApi extends LoggingRequestHandler {
} catch (RestApiException e) {
return e.getResponse();
} catch (Exception e2) {
- return Response.createErrorResponse(500, "Exception while parsing URI: " + e2.getMessage());
+ return Response.createErrorResponse(500, "Exception while parsing URI: " + e2.getMessage(), RestUri.apiErrorCodes.URL_PARSING);
}
Optional<Boolean> create = parseBoolean(CREATE_PARAMETER_NAME, request);
if (create == null) {
return Response.createErrorResponse(403, "Non valid value for 'create' parameter, must be empty, true, or " +
- "false: " + request.getProperty(CREATE_PARAMETER_NAME));
+ "false: " + request.getProperty(CREATE_PARAMETER_NAME), RestUri.apiErrorCodes.INVALID_CREATE_VALUE);
}
String condition = request.getProperty(CONDITION_PARAMETER_NAME);
Optional<String> route = Optional.ofNullable(request.getProperty(ROUTE_PARAMETER_NAME));
@@ -163,7 +163,7 @@ public class RestApi extends LoggingRequestHandler {
} catch (Exception e2) {
// We always blame the user. This might be a bit nasty, but the parser throws various kind of exception
// types, but with nice descriptions.
- return Response.createErrorResponse(400, e2.getMessage(), restUri);
+ return Response.createErrorResponse(400, e2.getMessage(), restUri, RestUri.apiErrorCodes.PARSER_ERROR);
}
return new Response(200, resultJson, Optional.of(restUri));
}
@@ -221,7 +221,8 @@ public class RestApi extends LoggingRequestHandler {
return Response.createErrorResponse(
400,
"Visiting does not support setting value for group/value in combination with expression, try using only expression parameter instead.",
- restUri);
+ restUri,
+ RestUri.apiErrorCodes.GROUP_AND_EXPRESSION_ERROR);
}
RestUri.Group group = restUri.getGroup().get();
if (group.name == 'n') {
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/ClientFeederV3.java b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/ClientFeederV3.java
index 301b81f040c..f91038a0a79 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/ClientFeederV3.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/ClientFeederV3.java
@@ -154,7 +154,7 @@ class ClientFeederV3 {
+ Exceptions.toMessageString(e), e);
} finally {
try {
- replies.add(createOperationStatus("-", "-", ErrorCode.END_OF_FEED, null));
+ replies.add(createOperationStatus("-", "-", ErrorCode.END_OF_FEED, false, null));
} catch (InterruptedException e) {
// NOP, we are already exiting the thread
}
@@ -196,7 +196,7 @@ class ClientFeederV3 {
log.log(LogLevel.DEBUG, Exceptions.toMessageString(e), e);
}
repliesFromOldMessages.add(new OperationStatus(
- Exceptions.toMessageString(e), operationId.get(), ErrorCode.ERROR, ""));
+ Exceptions.toMessageString(e), operationId.get(), ErrorCode.ERROR, false, ""));
continue;
}
@@ -246,7 +246,7 @@ class ClientFeederV3 {
} catch (RuntimeException e) {
repliesFromOldMessages.add(createOperationStatus(msg.get().getOperationId(), Exceptions.toMessageString(e),
- ErrorCode.ERROR, msg.get().getMessage()));
+ ErrorCode.ERROR, false, msg.get().getMessage()));
continue;
}
@@ -256,24 +256,25 @@ class ClientFeederV3 {
log(LogLevel.DEBUG, "Sent message successfully, document id: ", msg.get().getOperationId());
} else if (!result.getError().isFatal()) {
repliesFromOldMessages.add(createOperationStatus(msg.get().getOperationId(), result.getError().getMessage(),
- ErrorCode.TRANSIENT_ERROR, msg.get().getMessage()));
+ ErrorCode.TRANSIENT_ERROR, false, msg.get().getMessage()));
continue;
} else {
// should probably not happen, but everybody knows stuff that
// shouldn't happen, happens all the time
+ boolean isConditionNotMet = result.getError().getCode() == DocumentProtocol.ERROR_TEST_AND_SET_CONDITION_FAILED;
repliesFromOldMessages.add(createOperationStatus(msg.get().getOperationId(), result.getError().getMessage(),
- ErrorCode.ERROR, msg.get().getMessage()));
+ ErrorCode.ERROR, isConditionNotMet, msg.get().getMessage()));
continue;
}
}
}
- private OperationStatus createOperationStatus(String id, String message, ErrorCode code, Message msg)
+ private OperationStatus createOperationStatus(String id, String message, ErrorCode code, boolean isConditionNotMet, Message msg)
throws InterruptedException {
String traceMessage = msg != null && msg.getTrace() != null && msg.getTrace().getLevel() > 0
? msg.getTrace().toString()
: "";
- return new OperationStatus(message, id, code, traceMessage);
+ return new OperationStatus(message, id, code, isConditionNotMet, traceMessage);
}
// protected for mocking
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeedReplyReader.java b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeedReplyReader.java
index f22e25dd577..26dc97c5c65 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeedReplyReader.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/FeedReplyReader.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.http.server;
import java.util.logging.Logger;
+import com.yahoo.documentapi.messagebus.protocol.DocumentProtocol;
import com.yahoo.jdisc.Metric;
import com.yahoo.log.LogLevel;
import com.yahoo.messagebus.Reply;
@@ -38,17 +39,19 @@ public class FeedReplyReader implements ReplyHandler {
null);
if (reply.hasErrors()) {
metric.add(MetricNames.FAILED, 1, null);
- enqueue(context, reply.getError(0).getMessage(), ErrorCode.ERROR, reply.getTrace());
+ enqueue(context, reply.getError(0).getMessage(), ErrorCode.ERROR,
+ reply.getError(0).getCode() == DocumentProtocol.ERROR_TEST_AND_SET_CONDITION_FAILED, reply.getTrace());
} else {
metric.add(MetricNames.SUCCEEDED, 1, null);
- enqueue(context, "Document processed.", ErrorCode.OK, reply.getTrace());
+ enqueue(context, "Document processed.", ErrorCode.OK, false, reply.getTrace());
}
}
- private void enqueue(ReplyContext context, String message, ErrorCode status, Trace trace) {
+ private void enqueue(ReplyContext context, String message, ErrorCode status, boolean isConditionNotMet, Trace trace) {
try {
String traceMessage = (trace != null && trace.getLevel() > 0) ? trace.toString() : "";
- context.feedReplies.put(new OperationStatus(message, context.docId, status, traceMessage));
+
+ context.feedReplies.put(new OperationStatus(message, context.docId, status, isConditionNotMet, traceMessage));
} catch (InterruptedException e) {
log.log(LogLevel.WARNING,
"Interrupted while enqueueing result from putting document with id: " + context.docId);
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/Feeder.java b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/Feeder.java
index b361dc22d95..ad773519458 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/Feeder.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/Feeder.java
@@ -184,7 +184,7 @@ public class Feeder implements Runnable {
requestReceived.countDown();
putClient();
try {
- enqueue("-", "-", ErrorCode.END_OF_FEED, null);
+ enqueue("-", "-", ErrorCode.END_OF_FEED, false, null);
} catch (InterruptedException e) {
// NOP, we are already exiting the thread
}
@@ -252,7 +252,7 @@ public class Feeder implements Runnable {
}
} catch (RuntimeException e) {
enqueue(msg.first, Exceptions.toMessageString(e),
- ErrorCode.ERROR, msg.second);
+ ErrorCode.ERROR, false, msg.second);
return;
}
if (result.isAccepted() || result.getError().getCode() != SEND_QUEUE_FULL) {
@@ -272,12 +272,13 @@ public class Feeder implements Runnable {
updateOpsPerSec();
log(LogLevel.DEBUG, "Sent message successfully, document id: ", msg.first);
} else if (!result.getError().isFatal()) {
- enqueue(msg.first, result.getError().getMessage(), ErrorCode.TRANSIENT_ERROR, msg.second);
+ enqueue(msg.first, result.getError().getMessage(), ErrorCode.TRANSIENT_ERROR, false, msg.second);
break;
} else {
// should probably not happen, but everybody knows stuff that
// shouldn't happen, happens all the time
- enqueue(msg.first, result.getError().getMessage(), ErrorCode.ERROR, msg.second);
+ boolean isConditionNotMet = result.getError().getCode() == DocumentProtocol.ERROR_TEST_AND_SET_CONDITION_FAILED;
+ enqueue(msg.first, result.getError().getMessage(), ErrorCode.ERROR, isConditionNotMet, msg.second);
break;
}
}
@@ -487,12 +488,12 @@ public class Feeder implements Runnable {
--numPending;
}
- private void enqueue(String id, String message, ErrorCode code, Message msg)
+ private void enqueue(String id, String message, ErrorCode code, boolean isConditionalNotMet, Message msg)
throws InterruptedException {
String traceMessage = msg != null && msg.getTrace() != null && msg.getTrace().getLevel() > 0
? msg.getTrace().toString()
: "";
- operations.put(new OperationStatus(message, id, code, traceMessage));
+ operations.put(new OperationStatus(message, id, code, isConditionalNotMet, traceMessage));
}
public void waitForRequestReceived() throws InterruptedException {
diff --git a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/OperationHandlerImplTest.java b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/OperationHandlerImplTest.java
index 87360fcf998..8b7276441f2 100644
--- a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/OperationHandlerImplTest.java
+++ b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/OperationHandlerImplTest.java
@@ -66,8 +66,9 @@ public class OperationHandlerImplTest {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
e.getResponse().render(stream);
String errorMsg = new String( stream.toByteArray());
- assertThat(errorMsg, is("{\"errors\":[\"Your vespa cluster contains the content clusters foo2 " +
- "(configId2), foo (configId), foo3 (configId2), not wrong. Please select a valid vespa cluster.\"]}"));
+ assertThat(errorMsg, is("{\"errors\":[{\"description\":" +
+ "\"MISSING_CLUSTER Your vespa cluster contains the content clusters foo2 (configId2), foo (configId)," +
+ " foo3 (configId2), not wrong. Please select a valid vespa cluster.\",\"id\":-9}]}"));
return;
}
fail("Expected exception");
diff --git a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/MockedOperationHandler.java b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/MockedOperationHandler.java
index 2d98b72e579..f4f745fbcee 100644
--- a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/MockedOperationHandler.java
+++ b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/MockedOperationHandler.java
@@ -45,7 +45,7 @@ public class MockedOperationHandler implements OperationHandler {
String theLog = log.toString();
log = new StringBuilder();
deleteCount = 0;
- throw new RestApiException(Response.createErrorResponse(666, theLog));
+ throw new RestApiException(Response.createErrorResponse(666, theLog, RestUri.apiErrorCodes.ERROR_ID_BASIC_USAGE));
}
log.append("DELETE: " + restUri.generateFullId());
}
diff --git a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/RestApiTest.java b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/RestApiTest.java
index 3971b4320b5..ecaaba77435 100644
--- a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/RestApiTest.java
+++ b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/RestApiTest.java
@@ -96,7 +96,6 @@ public class RestApiTest {
assertThat(x, is(post_test_response_cond));
}
- String post_test_empty_response = "{\"errors\":[\"Could not read document, no document?\"]";
@Test
public void testEmptyPost() throws Exception {
Request request = new Request("http://localhost:" + getFirstListenPort() + post_test_uri);
@@ -104,7 +103,7 @@ public class RestApiTest {
StringEntity entity = new StringEntity("", ContentType.create("application/json"));
httpPost.setEntity(entity);
String x = doRest(httpPost);
- assertThat(x, startsWith(post_test_empty_response));
+ assertThat(x, containsString("Could not read document, no document?"));
}
String update_test_uri = "/document/v1/namespace/testdocument/docid/c";
diff --git a/vespaclient-container-plugin/src/test/java/com/yahoo/feedhandler/v3/FeedTesterV3.java b/vespaclient-container-plugin/src/test/java/com/yahoo/feedhandler/v3/FeedTesterV3.java
index 708a9ed7a6b..7e5ac4dff3a 100644
--- a/vespaclient-container-plugin/src/test/java/com/yahoo/feedhandler/v3/FeedTesterV3.java
+++ b/vespaclient-container-plugin/src/test/java/com/yahoo/feedhandler/v3/FeedTesterV3.java
@@ -111,7 +111,7 @@ public class FeedTesterV3 {
Object[] args = invocation.getArguments();
PutDocumentMessage putDocumentMessage = (PutDocumentMessage) args[0];
ReplyContext replyContext = (ReplyContext)putDocumentMessage.getContext();
- replyContext.feedReplies.add(new OperationStatus("message", replyContext.docId, ErrorCode.OK, "trace"));
+ replyContext.feedReplies.add(new OperationStatus("message", replyContext.docId, ErrorCode.OK, false, "trace"));
Result result = mock(Result.class);
when(result.isAccepted()).thenReturn(true);
return result;