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

io.stargate.grpc.service.ExceptionHandler Maven / Gradle / Ivy

There is a newer version: 2.1.0-BETA-19
Show newest version
package io.stargate.grpc.service;

import io.grpc.Metadata;
import io.grpc.Status;
import io.grpc.StatusException;
import io.grpc.StatusRuntimeException;
import io.grpc.protobuf.ProtoUtils;
import io.grpc.stub.StreamObserver;
import io.stargate.proto.QueryOuterClass;
import java.util.concurrent.CompletionException;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.apache.cassandra.stargate.exceptions.AlreadyExistsException;
import org.apache.cassandra.stargate.exceptions.CasWriteUnknownResultException;
import org.apache.cassandra.stargate.exceptions.FunctionExecutionException;
import org.apache.cassandra.stargate.exceptions.PersistenceException;
import org.apache.cassandra.stargate.exceptions.ReadFailureException;
import org.apache.cassandra.stargate.exceptions.ReadTimeoutException;
import org.apache.cassandra.stargate.exceptions.UnavailableException;
import org.apache.cassandra.stargate.exceptions.UnhandledClientException;
import org.apache.cassandra.stargate.exceptions.WriteFailureException;
import org.apache.cassandra.stargate.exceptions.WriteTimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public abstract class ExceptionHandler {
  private static final Logger LOG = LoggerFactory.getLogger(ExceptionHandler.class);

  static final Metadata.Key UNAVAILABLE_KEY =
      ProtoUtils.keyForProto(QueryOuterClass.Unavailable.getDefaultInstance());
  static final Metadata.Key WRITE_TIMEOUT_KEY =
      ProtoUtils.keyForProto(QueryOuterClass.WriteTimeout.getDefaultInstance());
  static final Metadata.Key READ_TIMEOUT_KEY =
      ProtoUtils.keyForProto(QueryOuterClass.ReadTimeout.getDefaultInstance());
  static final Metadata.Key READ_FAILURE_KEY =
      ProtoUtils.keyForProto(QueryOuterClass.ReadFailure.getDefaultInstance());
  static final Metadata.Key FUNCTION_FAILURE_KEY =
      ProtoUtils.keyForProto(QueryOuterClass.FunctionFailure.getDefaultInstance());
  static final Metadata.Key WRITE_FAILURE_KEY =
      ProtoUtils.keyForProto(QueryOuterClass.WriteFailure.getDefaultInstance());
  static final Metadata.Key ALREADY_EXISTS_KEY =
      ProtoUtils.keyForProto(QueryOuterClass.AlreadyExists.getDefaultInstance());
  static final Metadata.Key CAS_WRITE_UNKNOWN_KEY =
      ProtoUtils.keyForProto(QueryOuterClass.CasWriteUnknown.getDefaultInstance());

  protected abstract void onError(
      @Nullable Status status, @Nonnull Throwable throwable, @Nullable Metadata trailer);

  /**
   * It handles the throwable that can be gRPC {@link StatusRuntimeException}, persistence related
   * {@link PersistenceException} or unknown. It recursively unwraps the underlying exception if it
   * is {@link CompletionException} or {@link MessageHandler.ExceptionWithIdempotencyInfo}. Finally,
   * it converts the exception to a gRPC specific response using the {@link
   * StreamObserver#onError(Throwable)} method.
   *
   * @param throwable
   */
  public void handleException(Throwable throwable) {
    if (throwable instanceof CompletionException
        || throwable instanceof MessageHandler.ExceptionWithIdempotencyInfo) {
      handleException(throwable.getCause());
    } else if (throwable instanceof StatusException
        || throwable instanceof StatusRuntimeException) {
      LOG.error(getErrorMessage(null, throwable), throwable);
      onError(throwable);
    } else if (throwable instanceof UnhandledClientException) {
      LOG.error(getErrorMessage(Status.UNAVAILABLE, throwable), throwable);
      onError(Status.UNAVAILABLE, throwable);
    } else if (throwable instanceof PersistenceException) {
      handlePersistenceException((PersistenceException) throwable);
    } else {
      LOG.error("Unhandled error returning UNKNOWN to the client", throwable);
      onError(Status.UNKNOWN.withDescription(throwable.getMessage()).withCause(throwable));
    }
  }

  /**
   * @param status
   * @param throwable
   * @return generic error message for gRPC exceptions using gRPC Status and Exception thrown. If
   *     the status is null and throwable is a {@link StatusException} or {@link
   *     StatusRuntimeException} it extracts its status. If the status is null and throwable is
   *     another instance type, it will have the status code {@code Status.UNKNOWN}.
   */
  protected String getErrorMessage(@Nullable Status status, @Nonnull Throwable throwable) {
    if (status == null) {
      if (throwable instanceof StatusException) {
        status = ((StatusException) throwable).getStatus();
      } else if (throwable instanceof StatusRuntimeException) {
        status = ((StatusRuntimeException) throwable).getStatus();
      } else {
        status = Status.UNKNOWN;
      }
    }

    return status.withDescription(throwable.getMessage()).toString();
  }

  private void handlePersistenceException(PersistenceException pe) {
    switch (pe.code()) {
      case SERVER_ERROR:
      case PROTOCOL_ERROR: // Fallthrough
      case UNPREPARED: // Fallthrough
        LOG.error(getErrorMessage(Status.INTERNAL, pe), pe);
        onError(Status.INTERNAL, pe);
        break;
      case INVALID:
      case SYNTAX_ERROR: // Fallthrough
        onError(Status.INVALID_ARGUMENT, pe);
        break;
      case TRUNCATE_ERROR:
      case CDC_WRITE_FAILURE: // Fallthrough
        LOG.error(getErrorMessage(Status.ABORTED, pe), pe);
        onError(Status.ABORTED, pe);
        break;
      case BAD_CREDENTIALS:
        onError(Status.UNAUTHENTICATED, pe);
        break;
      case UNAVAILABLE:
        handleUnavailable((UnavailableException) pe);
        break;
      case OVERLOADED:
        onError(Status.RESOURCE_EXHAUSTED, pe);
        break;
      case IS_BOOTSTRAPPING:
        LOG.error(getErrorMessage(Status.UNAVAILABLE, pe), pe);
        onError(Status.UNAVAILABLE, pe);
        break;
      case WRITE_TIMEOUT:
        handleWriteTimeout((WriteTimeoutException) pe);
        break;
      case READ_TIMEOUT:
        handleReadTimeout((ReadTimeoutException) pe);
        break;
      case READ_FAILURE:
        handleReadFailure((ReadFailureException) pe);
        break;
      case FUNCTION_FAILURE:
        handleFunctionExecutionException((FunctionExecutionException) pe);
        break;
      case WRITE_FAILURE:
        handleWriteFailure((WriteFailureException) pe);
        break;
      case CAS_WRITE_UNKNOWN:
        handleCasWriteUnknown((CasWriteUnknownResultException) pe);
        break;
      case UNAUTHORIZED:
        onError(Status.PERMISSION_DENIED, pe);
        break;
      case CONFIG_ERROR:
        LOG.error(getErrorMessage(Status.FAILED_PRECONDITION, pe), pe);
        onError(Status.FAILED_PRECONDITION, pe);
        break;
      case ALREADY_EXISTS:
        handleAlreadyExists((AlreadyExistsException) pe);
        break;
      default:
        LOG.error("Unhandled persistence exception returning UNKNOWN to the client", pe);
        onError(Status.UNKNOWN, pe);
        break;
    }
  }

  private void handleUnavailable(UnavailableException ue) {
    LOG.error(getErrorMessage(Status.UNAVAILABLE, ue), ue);
    onError(
        Status.UNAVAILABLE,
        ue,
        makeTrailer(
            UNAVAILABLE_KEY,
            QueryOuterClass.Unavailable.newBuilder()
                .setConsistencyValue(ue.consistency.code)
                .setAlive(ue.alive)
                .setRequired(ue.required)
                .build()));
  }

  private void handleWriteTimeout(WriteTimeoutException wte) {
    LOG.error(getErrorMessage(Status.DEADLINE_EXCEEDED, wte));
    onError(
        Status.DEADLINE_EXCEEDED,
        wte,
        makeTrailer(
            WRITE_TIMEOUT_KEY,
            QueryOuterClass.WriteTimeout.newBuilder()
                .setConsistencyValue(wte.consistency.code)
                .setBlockFor(wte.blockFor)
                .setReceived(wte.received)
                .setWriteType(wte.writeType.name())
                .build()));
  }

  private void handleReadTimeout(ReadTimeoutException rte) {
    LOG.error(getErrorMessage(Status.DEADLINE_EXCEEDED, rte));
    onError(
        Status.DEADLINE_EXCEEDED,
        rte,
        makeTrailer(
            READ_TIMEOUT_KEY,
            QueryOuterClass.ReadTimeout.newBuilder()
                .setConsistencyValue(rte.consistency.code)
                .setBlockFor(rte.blockFor)
                .setReceived(rte.received)
                .setDataPresent(rte.dataPresent)
                .build()));
  }

  private void handleReadFailure(ReadFailureException rfe) {
    LOG.error(getErrorMessage(Status.ABORTED, rfe), rfe);
    onError(
        Status.ABORTED,
        rfe,
        makeTrailer(
            READ_FAILURE_KEY,
            QueryOuterClass.ReadFailure.newBuilder()
                .setConsistencyValue(rfe.consistency.code)
                .setNumFailures(rfe.failureReasonByEndpoint.size())
                .setBlockFor(rfe.blockFor)
                .setReceived(rfe.received)
                .setDataPresent(rfe.dataPresent)
                .build()));
  }

  private void handleFunctionExecutionException(FunctionExecutionException fee) {
    LOG.error(getErrorMessage(Status.FAILED_PRECONDITION, fee), fee);
    onError(
        Status.FAILED_PRECONDITION,
        fee,
        makeTrailer(
            FUNCTION_FAILURE_KEY,
            QueryOuterClass.FunctionFailure.newBuilder()
                .setKeyspace(fee.functionName.keyspace)
                .setFunction(fee.functionName.name)
                .addAllArgTypes(fee.argTypes)
                .build()));
  }

  private void handleWriteFailure(WriteFailureException wfe) {
    LOG.error(getErrorMessage(Status.ABORTED, wfe), wfe);
    onError(
        Status.ABORTED,
        wfe,
        makeTrailer(
            WRITE_FAILURE_KEY,
            QueryOuterClass.WriteFailure.newBuilder()
                .setConsistencyValue(wfe.consistency.code)
                .setNumFailures(wfe.failureReasonByEndpoint.size())
                .setBlockFor(wfe.blockFor)
                .setReceived(wfe.received)
                .setWriteType(wfe.writeType.name())
                .build()));
  }

  private void handleCasWriteUnknown(CasWriteUnknownResultException cwe) {
    LOG.error(getErrorMessage(Status.ABORTED, cwe), cwe);
    onError(
        Status.ABORTED,
        cwe,
        makeTrailer(
            CAS_WRITE_UNKNOWN_KEY,
            QueryOuterClass.CasWriteUnknown.newBuilder()
                .setConsistencyValue(cwe.consistency.code)
                .setBlockFor(cwe.blockFor)
                .setReceived(cwe.received)
                .build()));
  }

  private void handleAlreadyExists(AlreadyExistsException aee) {
    onError(
        Status.ALREADY_EXISTS,
        aee,
        makeTrailer(
            ALREADY_EXISTS_KEY,
            QueryOuterClass.AlreadyExists.newBuilder()
                .setKeyspace(aee.ksName)
                .setTable(aee.cfName)
                .build()));
  }

  private void onError(Status status, Throwable throwable) {
    onError(status, throwable, null);
  }

  private void onError(Status status) {
    onError(status, status.asRuntimeException());
  }

  private void onError(Throwable throwable) {
    onError(null, throwable, null);
  }

  private  Metadata makeTrailer(Metadata.Key key, T value) {
    Metadata trailer = new Metadata();
    trailer.put(key, value);
    return trailer;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy