diff options
author | Haakon Dybdahl <dybdahl@yahoo-inc.com> | 2017-03-06 09:22:16 +0100 |
---|---|---|
committer | Haakon Dybdahl <dybdahl@yahoo-inc.com> | 2017-03-06 09:22:16 +0100 |
commit | 3fcafa4cc9a2f29c417cfbda9bed0c8390ba2ab7 (patch) | |
tree | 8a297abd0b7deb4f52098033535a12f11f290cd8 | |
parent | 351903b75e5cf8feaa6606c139ee56e1caeccd23 (diff) |
Extend APIs with condition-not-met.
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; |