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

io.zeebe.gateway.grpc.GrpcErrorMapper Maven / Gradle / Ivy

There is a newer version: 1.0.0-alpha7
Show newest version
/*
 * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH under
 * one or more contributor license agreements. See the NOTICE file distributed
 * with this work for additional information regarding copyright ownership.
 * Licensed under the Zeebe Community License 1.0. You may not use this file
 * except in compliance with the Zeebe Community License 1.0.
 */
package io.zeebe.gateway.grpc;

import com.google.protobuf.Any;
import com.google.rpc.Code;
import com.google.rpc.Status;
import com.google.rpc.Status.Builder;
import io.grpc.StatusRuntimeException;
import io.grpc.protobuf.StatusProto;
import io.netty.channel.ConnectTimeoutException;
import io.zeebe.gateway.Loggers;
import io.zeebe.gateway.cmd.BrokerErrorException;
import io.zeebe.gateway.cmd.BrokerRejectionException;
import io.zeebe.gateway.cmd.InvalidBrokerRequestArgumentException;
import io.zeebe.gateway.cmd.NoTopologyAvailableException;
import io.zeebe.gateway.cmd.PartitionNotFoundException;
import io.zeebe.gateway.impl.broker.RequestRetriesExhaustedException;
import io.zeebe.gateway.impl.broker.response.BrokerError;
import io.zeebe.gateway.impl.broker.response.BrokerRejection;
import io.zeebe.msgpack.MsgpackPropertyException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import org.slf4j.Logger;

/** Maps arbitrary {@link Throwable} to {@link StatusRuntimeException} and logs the exception. */
public final class GrpcErrorMapper {
  public StatusRuntimeException mapError(final Throwable error) {
    return mapError(error, Loggers.GATEWAY_LOGGER);
  }

  public StatusRuntimeException mapError(final Throwable error, final Logger logger) {
    return StatusProto.toStatusRuntimeException(mapErrorToStatus(error, logger));
  }

  private Status mapErrorToStatus(final Throwable error, final Logger logger) {
    final Builder builder = Status.newBuilder();

    if (error instanceof ExecutionException) {
      return mapErrorToStatus(error.getCause(), logger);
    } else if (error instanceof BrokerErrorException) {
      final Status status =
          mapBrokerErrorToStatus(((BrokerErrorException) error).getError(), logger);
      builder.mergeFrom(status);
    } else if (error instanceof BrokerRejectionException) {
      final Status status = mapRejectionToStatus(((BrokerRejectionException) error).getRejection());
      builder.mergeFrom(status);
      logger.trace("Expected to handle gRPC request, but the broker rejected it", error);
    } else if (error instanceof TimeoutException) { // can be thrown by transport
      builder
          .setCode(Code.DEADLINE_EXCEEDED_VALUE)
          .setMessage("Time out between gateway and broker: " + error.getMessage());
      logger.debug(
          "Expected to handle gRPC request, but request timed out between gateway and broker",
          error);
    } else if (error instanceof InvalidBrokerRequestArgumentException) {
      builder.setCode(Code.INVALID_ARGUMENT_VALUE).setMessage(error.getMessage());
      logger.debug("Expected to handle gRPC request, but broker argument was invalid", error);
    } else if (error instanceof MsgpackPropertyException) {
      builder.setCode(Code.INVALID_ARGUMENT_VALUE).setMessage(error.getMessage());
      logger.debug("Expected to handle gRPC request, but messagepack property was invalid", error);
    } else if (error instanceof PartitionNotFoundException) {
      builder.setCode(Code.UNAVAILABLE_VALUE).setMessage(error.getMessage());
      logger.debug("Expected to handle gRPC request, but request could not be delivered", error);
    } else if (error instanceof RequestRetriesExhaustedException) {
      builder.setCode(Code.RESOURCE_EXHAUSTED_VALUE).setMessage(error.getMessage());

      // RequestRetriesExhaustedException will sometimes carry suppressed exceptions which can be
      // added/mapped as error details to give more information to the user
      for (final Throwable suppressed : error.getSuppressed()) {
        builder.addDetails(Any.pack(mapErrorToStatus(suppressed, logger)));
      }

      // this error occurs when all partitions have exhausted for requests which have no fixed
      // partitions - it will then also occur when back pressure kicks in, leading to a large burst
      // of error logs that is, in fact, expected
      logger.trace("Expected to handle gRPC request, but all retries have been exhausted", error);
    } else if (error instanceof NoTopologyAvailableException) {
      builder.setCode(Code.UNAVAILABLE_VALUE).setMessage(error.getMessage());
      logger.trace(
          "Expected to handle gRPC request, but the gateway does not know any partitions yet",
          error);
    } else if (error instanceof ConnectTimeoutException) {
      builder.setCode(Code.UNAVAILABLE_VALUE).setMessage(error.getMessage());
      logger.warn(
          "Expected to handle gRPC request, but a connection timeout exception occurred", error);
    } else {
      builder
          .setCode(Code.INTERNAL_VALUE)
          .setMessage(
              "Unexpected error occurred during the request processing: " + error.getMessage());
      logger.error("Expected to handle gRPC request, but an unexpected error occurred", error);
    }

    return builder.build();
  }

  private Status mapBrokerErrorToStatus(final BrokerError error, final Logger logger) {
    final Builder builder = Status.newBuilder();
    String message = error.getMessage();

    switch (error.getCode()) {
      case WORKFLOW_NOT_FOUND:
        builder.setCode(Code.NOT_FOUND_VALUE);
        break;
      case RESOURCE_EXHAUSTED:
        builder.setCode(Code.RESOURCE_EXHAUSTED_VALUE);
        logger.trace("Target broker is currently overloaded: {}", error);
        break;
      case PARTITION_LEADER_MISMATCH:
        // return UNAVAILABLE to indicate to the user that retrying might solve the issue, as this
        // is usually a transient issue
        logger.trace("Target broker was not the leader of the partition: {}", error);
        builder.setCode(Code.UNAVAILABLE_VALUE);
        break;
      default:
        // all the following are for cases where retrying (with the same gateway) is not expected
        // to solve anything
        logger.error(
            "Expected to handle gRPC request, but received an internal error from broker: {}",
            error);
        builder.setCode(Code.INTERNAL_VALUE);
        message =
            String.format(
                "Unexpected error occurred between gateway and broker (code: %s)", error.getCode());
        break;
    }

    return builder.setMessage(message).build();
  }

  private Status mapRejectionToStatus(final BrokerRejection rejection) {
    final String message =
        String.format(
            "Command '%s' rejected with code '%s': %s",
            rejection.getIntent(), rejection.getType(), rejection.getReason());
    final Builder builder = Status.newBuilder().setMessage(message);

    switch (rejection.getType()) {
      case INVALID_ARGUMENT:
        builder.setCode(Code.INVALID_ARGUMENT_VALUE);
        break;
      case NOT_FOUND:
        builder.setCode(Code.NOT_FOUND_VALUE);
        break;
      case ALREADY_EXISTS:
        builder.setCode(Code.ALREADY_EXISTS_VALUE);
        break;
      case INVALID_STATE:
        builder.setCode(Code.FAILED_PRECONDITION_VALUE);
        break;
      case PROCESSING_ERROR:
        builder.setCode(Code.INTERNAL_VALUE);
        break;
      case SBE_UNKNOWN:
      case NULL_VAL:
      default:
        builder.setCode(Code.UNKNOWN_VALUE);
        break;
    }

    return builder.build();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy