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

alluxio.hub.common.RpcClient Maven / Gradle / Ivy

/*
 * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
 * (the "License"). You may not use this work except in compliance with the License, which is
 * available at www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied, as more fully set forth in the License.
 *
 * See the NOTICE file distributed with this work for information regarding copyright ownership.
 */

package alluxio.hub.common;

import alluxio.conf.AlluxioConfiguration;
import alluxio.conf.InstancedConfiguration;
import alluxio.conf.PropertyKey;
import alluxio.conf.Source;
import alluxio.exception.status.AlluxioStatusException;
import alluxio.grpc.GrpcChannel;
import alluxio.grpc.GrpcChannelBuilder;
import alluxio.grpc.GrpcServerAddress;
import alluxio.retry.ExponentialTimeBoundedRetry;
import alluxio.retry.RetryPolicy;
import alluxio.security.authentication.AuthType;
import alluxio.security.user.ServerUserState;

import io.grpc.Channel;
import io.grpc.Status;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * A wrapper around a particular client to a gRPC service that will automatically create and
 * connect a channel to the desired endpoint. After creating this class, typical usage will be
 *
 * 
 *   RpcClient c = new RpcClient(conf, address, service::newBlockingStub)
 *   c.get().makeRpc(argument)
 * 
 *
 * The class tracks the underlying channel and if it becomes closed or unhealthy will
 * automatically create and connect with a new one.
 *
 * @param  the type of client to expose
 */
public class RpcClient> implements AutoCloseable {
  private static final Logger LOG = LoggerFactory.getLogger(RpcClient.class);

  private static final Supplier POLICY_SUPPLIER =
      () -> ExponentialTimeBoundedRetry.builder()
      .withInitialSleep(Duration.ofMillis(0))
      .withMaxSleep(Duration.ofMillis(2000))
      .withSkipInitialSleep()
      .withMaxDuration(Duration.ofMinutes(2))
      .build();

  /**
   * The currently open channel to the desired target.
   *
   * We use {@link Channel} here instead of {@link GrpcChannel} because it allows for building
   * unit tests around the gRPC services without needing to create an actual server. In this
   * class we'll perform {@code instanceof} checks in order to call methods for
   * {@link GrpcChannel} specific pieces of code.
   */
  private Channel mChannel;
  private T mClient;
  private final Function mClientFactory;
  private final ChannelSupplier mChannelFactory;
  private final InetSocketAddress mAddr;

  /**
   * Create a new instance of the {@link RpcClient}.
   *
   * @param conf the configuration used to make the connection
   * @param connectAddress the address to connect to
   * @param channelFactory the factory method used create a new gRPC client around the channel
   * @param policySupplier the a supplier of policies to use when attempting to create new channels
   */
  public RpcClient(AlluxioConfiguration conf, InetSocketAddress connectAddress,
      Function channelFactory, Supplier policySupplier) {
    this(connectAddress, channelFactory, (addr) -> {
      AlluxioStatusException last = null;
      RetryPolicy policy = policySupplier.get();
      while (policy.attempt()) {
        try {
          return createChannel(addr, conf);
        } catch (AlluxioStatusException e) {
          last = e;
        }
      }
      if (last != null) {
        throw last;
      } else {
        throw new AlluxioStatusException(Status.UNKNOWN
            .withDescription("Failed to create a channel connecting to " + connectAddress));
      }
    });
  }

  /**
   * Creates a gRPC channel that uses NOSASL for security authentication type.
   *
   * @param addr the address to connect to
   * @param conf the configuration used to make the connection
   * @return new instance of {@link GrpcChannel}
   * @throws AlluxioStatusException
   */
  public static GrpcChannel createChannel(InetSocketAddress addr, AlluxioConfiguration conf)
      throws AlluxioStatusException {
    InstancedConfiguration modifiedConfig = InstancedConfiguration.defaults();
    Map properties = new HashMap<>(conf.toMap());
    properties.put(PropertyKey.SECURITY_AUTHENTICATION_TYPE.getName(), AuthType.NOSASL);
    modifiedConfig.merge(properties, Source.RUNTIME);
    LOG.info("Auth type = {}", modifiedConfig.get(PropertyKey.SECURITY_AUTHENTICATION_TYPE));
    GrpcChannelBuilder builder = GrpcChannelBuilder
        .newBuilder(GrpcServerAddress.create(addr), modifiedConfig);
    return builder.setSubject(ServerUserState.global().getSubject()).build();
  }

  /**
   * Create a new RPC client with the desired channel factory. Use this method for testing.
   *
   * @param factory the client factory
   * @param channelFactory the channel factory
   */
  RpcClient(InetSocketAddress addr, Function factory, ChannelSupplier channelFactory) {
    mClientFactory = factory;
    mChannelFactory = channelFactory;
    mAddr = addr;
  }

  private synchronized void beforeRpc() throws AlluxioStatusException {
    if (mChannel == null
        || (mChannel instanceof GrpcChannel && (((GrpcChannel) mChannel).isShutdown()
        || !((GrpcChannel) mChannel).isHealthy()))) {
      // Perform this check because it could have been shutdown or unhealthy.
      // If it's not shut down, then we should shut down the old channel before creating a new one.
      if (mChannel instanceof GrpcChannel && !((GrpcChannel) mChannel).isShutdown()) {
        ((GrpcChannel) mChannel).shutdown();
      }
      mChannel = mChannelFactory.get(mAddr);
      if (LOG.isDebugEnabled()) {
        LOG.debug("Created new channel connecting to {}", mChannel.authority());
      }
      mClient = mClientFactory.apply(mChannel);
    }
  }

  /**
   * Create if necessary a new client if necessary, then return the client.
   *
   * @return the currently connected client
   * @throws AlluxioStatusException if a connection to the endpoint can't be made
   */
  public synchronized T get() throws AlluxioStatusException {
    beforeRpc();
    return mClient;
  }

  /**
   * @return the address that this RPC client connects to
   */
  public InetSocketAddress getAddress() {
    return mAddr;
  }

  @Override
  public synchronized void close() {
    if (mChannel instanceof GrpcChannel) {
      ((GrpcChannel) mChannel).shutdown();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy