All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opensearch.migrations.replay.http.retries.OpenSearchDefaultRetry Maven / Gradle / Ivy

package org.opensearch.migrations.replay.http.retries;

import java.io.IOException;
import java.util.Optional;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.async.ByteBufferFeeder;

import org.opensearch.migrations.replay.AggregatedRawResponse;
import org.opensearch.migrations.replay.HttpByteBufFormatter;
import org.opensearch.migrations.replay.IRequestResponsePacketPair;
import org.opensearch.migrations.replay.RequestSenderOrchestrator;
import org.opensearch.migrations.replay.util.TextTrackedFuture;
import org.opensearch.migrations.replay.util.TrackedFuture;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.LastHttpContent;
import lombok.NonNull;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

@Slf4j
public class OpenSearchDefaultRetry extends DefaultRetry {

    private static final Pattern bulkPathMatcher = Pattern.compile("^(/[^/]*)?/_bulk(/.*)?$");

    private static class BulkErrorFindingHandler extends ChannelInboundHandlerAdapter {
        private final JsonParser parser;
        private final ByteBufferFeeder feeder;
        private Boolean errorField = null;

        @SneakyThrows
        public BulkErrorFindingHandler() {
            JsonFactory jsonFactory = new JsonFactory();
            parser = jsonFactory.createNonBlockingByteBufferParser();
            feeder = (ByteBufferFeeder) parser.getNonBlockingInputFeeder();
        }

        /**
         * @return true iff the "errors" field was present at the root and its value was false.  false otherwise.
         */
        boolean hadNoErrors() {
            return errorField != null && !errorField;
        }

        @Override
        public void channelRead(@NonNull ChannelHandlerContext ctx, @NonNull Object msg) throws Exception {
            if (msg instanceof HttpContent && errorField == null) {
                log.atDebug().setMessage(() -> "body contents: " + ((HttpContent) msg).content().duplicate().toString()).log();
                feeder.feedInput(((HttpContent) msg).content().nioBuffer());
                consumeInput();
                if (msg instanceof LastHttpContent) {
                    feeder.endOfInput();
                    consumeInput();
                }
            }
            ctx.fireChannelRead(msg); // Pass other messages down the pipeline
        }

        private void consumeInput() throws IOException {
            if (errorField != null) {
                return;
            }
            JsonToken token;
            while (!parser.isClosed() &&
                ((token = parser.nextToken()) != null) &&
                token != JsonToken.NOT_AVAILABLE)
            {
                JsonToken finalToken = token;
                log.atTrace().setMessage(() -> "Got token: " + finalToken).log();
                if (token == JsonToken.FIELD_NAME && "errors".equals(parser.getCurrentName())) {
                    parser.nextToken();
                    errorField = parser.getValueAsBoolean();
                    break;
                } else if (parser.getParsingContext().inRoot() && token == JsonToken.END_OBJECT) {
                    break;
                } else if (token != JsonToken.START_OBJECT &&
                    token != JsonToken.END_OBJECT &&
                    !parser.getParsingContext().inRoot())
                {
                    // Skip non-root level content
                    parser.skipChildren();
                }
            }
        }
    }

    boolean bulkResponseHadNoErrors(ByteBuf responseByteBuf) {
        var errorFieldFinderHandler = new BulkErrorFindingHandler();
        HttpByteBufFormatter.processHttpMessageFromBufs(HttpByteBufFormatter.HttpMessageType.RESPONSE,
            Stream.of(responseByteBuf), errorFieldFinderHandler);
        return errorFieldFinderHandler.hadNoErrors();
    }


    @Override
    public TrackedFuture
    shouldRetry(@NonNull ByteBuf targetRequestBytes,
                @NonNull AggregatedRawResponse currentResponse,
                @NonNull TrackedFuture reconstructedSourceTransactionFuture) {

        var targetRequestByteBuf = Unpooled.wrappedBuffer(targetRequestBytes);
        var parsedRequest = HttpByteBufFormatter.parseHttpRequestFromBufs(Stream.of(targetRequestByteBuf), 0);
        if (parsedRequest != null &&
            bulkPathMatcher.matcher(parsedRequest.uri()).matches() &&
            // do a more granular check.  If the raw response wasn't present, then just push it to the superclass
            // since it isn't going to be any kind of response, let alone a bulk one
            Optional.ofNullable(currentResponse.getRawResponse())
                .map(r->r.status().code() == 200)
                .orElse(false))
        {
            if (bulkResponseHadNoErrors(currentResponse.getResponseAsByteBuf())) {
                return TextTrackedFuture.completedFuture(RequestSenderOrchestrator.RetryDirective.DONE,
                    () -> "no errors found in the target response, so not retrying");
            } else {
                return reconstructedSourceTransactionFuture.thenCompose(rrp ->
                        TextTrackedFuture.completedFuture(
                            bulkResponseHadNoErrors(rrp.getResponseData().asByteBuf()) ?
                                RequestSenderOrchestrator.RetryDirective.RETRY :
                                RequestSenderOrchestrator.RetryDirective.DONE,
                            () -> "evaluating retry status dependent upon source error field"),
                    () -> "checking the accumulated source response value");
            }
        }

        return super.shouldRetry(targetRequestBytes, currentResponse, reconstructedSourceTransactionFuture);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy