org.apache.flink.runtime.rest.handler.async.AbstractAsynchronousOperationHandlers Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.flink.runtime.rest.handler.async;
import org.apache.flink.api.common.time.Time;
import org.apache.flink.runtime.concurrent.FutureUtils;
import org.apache.flink.runtime.rest.NotFoundException;
import org.apache.flink.runtime.rest.handler.AbstractRestHandler;
import org.apache.flink.runtime.rest.handler.HandlerRequest;
import org.apache.flink.runtime.rest.handler.RestHandlerException;
import org.apache.flink.runtime.rest.messages.EmptyRequestBody;
import org.apache.flink.runtime.rest.messages.MessageHeaders;
import org.apache.flink.runtime.rest.messages.MessageParameters;
import org.apache.flink.runtime.rest.messages.RequestBody;
import org.apache.flink.runtime.webmonitor.RestfulGateway;
import org.apache.flink.runtime.webmonitor.retriever.GatewayRetriever;
import org.apache.flink.types.Either;
import javax.annotation.Nonnull;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
/**
* HTTP handlers for asynchronous operations.
*
* Some operations are long-running. To avoid blocking HTTP
* connections, these operations are executed in two steps. First, an HTTP request is issued to trigger
* the operation asynchronously. The request will be assigned a trigger id,
* which is returned in the response body. Next, the returned id should be used to poll the status
* of the operation until it is finished.
*
*
An operation is triggered by sending an HTTP {@code POST} request to a registered {@code url}. The HTTP
* request may contain a JSON body to specify additional parameters, e.g.,
*
* { "target-directory": "/tmp" }
*
* As written above, the response will contain a request id, e.g.,
*
* { "request-id": "7d273f5a62eb4730b9dea8e833733c1e" }
*
*
* To poll for the status of an ongoing operation, an HTTP {@code GET} request is issued to
* {@code url/:triggerid}. If the specified savepoint is still ongoing,
* the response will be
*
* {
* "status": {
* "id": "IN_PROGRESS"
* }
* }
*
* If the specified operation has completed, the status id will transition to {@code COMPLETED}, and
* the response will additionally contain information about the operation result:
*
* {
* "status": {
* "id": "COMPLETED"
* },
* "operation": {
* "result": "/tmp/savepoint-d9813b-8a68e674325b"
* }
* }
*
*
* @param type of the operation key under which the result future is stored
* @param type of the operation result
*/
public abstract class AbstractAsynchronousOperationHandlers {
private final CompletedOperationCache completedOperationCache = new CompletedOperationCache<>();
/**
* Handler which is responsible for triggering an asynchronous operation. After the operation has
* been triggered, it stores the result future in the {@link #completedOperationCache}.
*
* @param type of the gateway
* @param type of the request
* @param type of the message parameters
*/
protected abstract class TriggerHandler extends AbstractRestHandler {
protected TriggerHandler(
CompletableFuture localRestAddress,
GatewayRetriever leaderRetriever,
Time timeout,
Map responseHeaders,
MessageHeaders messageHeaders) {
super(localRestAddress, leaderRetriever, timeout, responseHeaders, messageHeaders);
}
@Override
public CompletableFuture handleRequest(@Nonnull HandlerRequest request, @Nonnull T gateway) throws RestHandlerException {
final CompletableFuture resultFuture = triggerOperation(request, gateway);
final K operationKey = createOperationKey(request);
completedOperationCache.registerOngoingOperation(operationKey, resultFuture);
return CompletableFuture.completedFuture(new TriggerResponse(operationKey.getTriggerId()));
}
/**
* Trigger the asynchronous operation and return its future result.
*
* @param request with which the trigger handler has been called
* @param gateway to the leader
* @return Future result of the asynchronous operation
* @throws RestHandlerException if something went wrong
*/
protected abstract CompletableFuture triggerOperation(HandlerRequest request, T gateway) throws RestHandlerException;
/**
* Create the operation key under which the result future of the asynchronous operation will
* be stored.
*
* @param request with which the trigger handler has been called.
* @return Operation key under which the result future will be stored
*/
protected abstract K createOperationKey(HandlerRequest request);
}
/**
* Handler which will be polled to retrieve the asynchronous operation's result. The handler returns a
* {@link AsynchronousOperationResult} which indicates whether the operation is still in progress or
* has completed. In case that the operation has been completed, the {@link AsynchronousOperationResult}
* contains the operation result.
*
* @param type of the leader gateway
* @param type of the operation result
* @param type of the message headers
*/
protected abstract class StatusHandler extends AbstractRestHandler, M> {
protected StatusHandler(
CompletableFuture localRestAddress,
GatewayRetriever leaderRetriever,
Time timeout,
Map responseHeaders,
MessageHeaders, M> messageHeaders) {
super(localRestAddress, leaderRetriever, timeout, responseHeaders, messageHeaders);
}
@Override
public CompletableFuture> handleRequest(@Nonnull HandlerRequest request, @Nonnull T gateway) throws RestHandlerException {
final K key = getOperationKey(request);
final Either operationResultOrError;
try {
operationResultOrError = completedOperationCache.get(key);
} catch (UnknownOperationKeyException e) {
return FutureUtils.completedExceptionally(
new NotFoundException("Operation not found under key: " + key, e));
}
if (operationResultOrError != null) {
if (operationResultOrError.isLeft()) {
return CompletableFuture.completedFuture(
AsynchronousOperationResult.completed(exceptionalOperationResultResponse(operationResultOrError.left())));
} else {
return CompletableFuture.completedFuture(
AsynchronousOperationResult.completed(operationResultResponse(operationResultOrError.right())));
}
} else {
return CompletableFuture.completedFuture(AsynchronousOperationResult.inProgress());
}
}
@Override
public CompletableFuture closeHandlerAsync() {
return completedOperationCache.closeAsync();
}
/**
* Extract the operation key under which the operation result future is stored.
*
* @param request with which the status handler has been called
* @return Operation key under which the operation result future is stored
*/
protected abstract K getOperationKey(HandlerRequest request);
/**
* Create an exceptional operation result from the given {@link Throwable}. This
* method is called if the asynchronous operation failed.
*
* @param throwable failure of the asynchronous operation
* @return Exceptional operation result
*/
protected abstract V exceptionalOperationResultResponse(Throwable throwable);
/**
* Create the operation result from the given value.
*
* @param operationResult of the asynchronous operation
* @return Operation result
*/
protected abstract V operationResultResponse(R operationResult);
}
}