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

org.apache.flink.runtime.rpc.akka.AkkaUtils Maven / Gradle / Ivy

There is a newer version: 2.0-preview1
Show newest version
/*
 * 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.rpc.akka;

import org.apache.flink.annotation.VisibleForTesting;
import org.apache.flink.configuration.AkkaOptions;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.configuration.SecurityOptions;
import org.apache.flink.runtime.concurrent.akka.AkkaFutureUtils;
import org.apache.flink.runtime.rpc.RpcSystem;
import org.apache.flink.util.NetUtils;
import org.apache.flink.util.TimeUtils;
import org.apache.flink.util.function.FunctionUtils;

import akka.actor.ActorRef;
import akka.actor.ActorSystem;
import akka.actor.Address;
import akka.actor.AddressFromURIString;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import org.jboss.netty.logging.InternalLoggerFactory;
import org.jboss.netty.logging.Slf4JLoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.time.Duration;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * This class contains utility functions for akka. It contains methods to start an actor system with
 * a given akka configuration. Furthermore, the akka configuration used for starting the different
 * actor systems resides in this class.
 */
class AkkaUtils {
    private static final Logger LOG = LoggerFactory.getLogger(AkkaUtils.class);

    private static final String FLINK_ACTOR_SYSTEM_NAME = "flink";

    public static String getFlinkActorSystemName() {
        return FLINK_ACTOR_SYSTEM_NAME;
    }

    /**
     * Gets the basic Akka config which is shared by remote and local actor systems.
     *
     * @param configuration instance which contains the user specified values for the configuration
     * @return Flink's basic Akka config
     */
    private static Config getBasicAkkaConfig(Configuration configuration) {
        final int akkaThroughput = configuration.getInteger(AkkaOptions.DISPATCHER_THROUGHPUT);
        final String jvmExitOnFatalError =
                booleanToOnOrOff(configuration.getBoolean(AkkaOptions.JVM_EXIT_ON_FATAL_ERROR));
        final String logLifecycleEvents =
                booleanToOnOrOff(configuration.getBoolean(AkkaOptions.LOG_LIFECYCLE_EVENTS));
        final String supervisorStrategy = EscalatingSupervisorStrategy.class.getCanonicalName();

        return new AkkaConfigBuilder()
                .add("akka {")
                .add("  daemonic = off")
                .add("  loggers = [\"akka.event.slf4j.Slf4jLogger\"]")
                .add("  logging-filter = \"akka.event.slf4j.Slf4jLoggingFilter\"")
                .add("  log-config-on-start = off")
                .add("  logger-startup-timeout = 50s")
                .add("  loglevel = " + getLogLevel())
                .add("  stdout-loglevel = OFF")
                .add("  log-dead-letters = " + logLifecycleEvents)
                .add("  log-dead-letters-during-shutdown = " + logLifecycleEvents)
                .add("  jvm-exit-on-fatal-error = " + jvmExitOnFatalError)
                .add("  serialize-messages = off")
                .add("  actor {")
                .add("    guardian-supervisor-strategy = " + supervisorStrategy)
                .add("    warn-about-java-serializer-usage = off")
                .add("    allow-java-serialization = on")
                .add("    default-dispatcher {")
                .add("      throughput = " + akkaThroughput)
                .add("    }")
                .add("    supervisor-dispatcher {")
                .add("      type = Dispatcher")
                .add("      executor = \"thread-pool-executor\"")
                .add("      thread-pool-executor {")
                .add("        core-pool-size-min = 1")
                .add("        core-pool-size-max = 1")
                .add("      }")
                .add("    }")
                .add("  }")
                .add("}")
                .build();
    }

    private static String getLogLevel() {
        if (LOG.isTraceEnabled()) {
            // TRACE is not supported by akka
            return "DEBUG";
        }
        if (LOG.isDebugEnabled()) {
            return "DEBUG";
        }
        if (LOG.isInfoEnabled()) {
            return "INFO";
        }
        if (LOG.isWarnEnabled()) {
            return "WARNING";
        }
        if (LOG.isErrorEnabled()) {
            return "ERROR";
        }
        return "OFF";
    }

    public static Config getThreadPoolExecutorConfig(
            RpcSystem.FixedThreadPoolExecutorConfiguration configuration) {
        final int threadPriority = configuration.getThreadPriority();
        final int minNumThreads = configuration.getMinNumThreads();
        final int maxNumThreads = configuration.getMaxNumThreads();

        return new AkkaConfigBuilder()
                .add("akka {")
                .add("  actor {")
                .add("    default-dispatcher {")
                .add("      type = org.apache.flink.runtime.rpc.akka.PriorityThreadsDispatcher")
                .add("      executor = thread-pool-executor")
                .add("      thread-priority = " + threadPriority)
                .add("      thread-pool-executor {")
                .add("          core-pool-size-min = " + minNumThreads)
                .add("          core-pool-size-max = " + maxNumThreads)
                .add("      }")
                .add("    }")
                .add("  }")
                .add("}")
                .build();
    }

    public static Config getForkJoinExecutorConfig(
            RpcSystem.ForkJoinExecutorConfiguration configuration) {
        final double parallelismFactor = configuration.getParallelismFactor();
        final int minNumThreads = configuration.getMinParallelism();
        final int maxNumThreads = configuration.getMaxParallelism();

        return new AkkaConfigBuilder()
                .add("akka {")
                .add("  actor {")
                .add("    default-dispatcher {")
                .add("      executor = fork-join-executor")
                .add("      fork-join-executor {")
                .add("          parallelism-factor = " + parallelismFactor)
                .add("          parallelism-min = " + minNumThreads)
                .add("          parallelism-max = " + maxNumThreads)
                .add("      }")
                .add("    }")
                .add("  }")
                .add("}")
                .build();
    }

    /**
     * Creates a Akka config for a remote actor system listening on port on the network interface
     * identified by bindAddress.
     *
     * @param configuration instance containing the user provided configuration values
     * @param bindAddress of the network interface to bind on
     * @param port to bind to or if 0 then Akka picks a free port automatically
     * @param externalHostname The host name to expect for Akka messages
     * @param externalPort The port to expect for Akka messages
     * @return Flink's Akka configuration for remote actor systems
     */
    private static Config getRemoteAkkaConfig(
            Configuration configuration,
            String bindAddress,
            int port,
            String externalHostname,
            int externalPort) {
        final AkkaConfigBuilder builder = new AkkaConfigBuilder();

        addBaseRemoteAkkaConfig(builder, configuration, port, externalPort);
        addHostnameRemoteAkkaConfig(builder, bindAddress, externalHostname);
        addSslRemoteAkkaConfig(builder, configuration);

        return builder.build();
    }

    private static void addBaseRemoteAkkaConfig(
            AkkaConfigBuilder akkaConfigBuilder,
            Configuration configuration,
            int port,
            int externalPort) {
        final Duration akkaAskTimeout = configuration.get(AkkaOptions.ASK_TIMEOUT_DURATION);

        final String startupTimeout =
                TimeUtils.getStringInMillis(
                        TimeUtils.parseDuration(
                                configuration.getString(
                                        AkkaOptions.STARTUP_TIMEOUT,
                                        TimeUtils.getStringInMillis(
                                                akkaAskTimeout.multipliedBy(10L)))));

        final String akkaTCPTimeout =
                TimeUtils.getStringInMillis(
                        TimeUtils.parseDuration(configuration.getString(AkkaOptions.TCP_TIMEOUT)));

        final String akkaFramesize = configuration.getString(AkkaOptions.FRAMESIZE);

        final int clientSocketWorkerPoolPoolSizeMin =
                configuration.get(AkkaOptions.CLIENT_SOCKET_WORKER_POOL_SIZE_MIN);
        final int clientSocketWorkerPoolPoolSizeMax =
                configuration.get(AkkaOptions.CLIENT_SOCKET_WORKER_POOL_SIZE_MAX);
        final double clientSocketWorkerPoolPoolSizeFactor =
                configuration.get(AkkaOptions.CLIENT_SOCKET_WORKER_POOL_SIZE_FACTOR);
        final int serverSocketWorkerPoolPoolSizeMin =
                configuration.get(AkkaOptions.SERVER_SOCKET_WORKER_POOL_SIZE_MIN);
        final int serverSocketWorkerPoolPoolSizeMax =
                configuration.get(AkkaOptions.SERVER_SOCKET_WORKER_POOL_SIZE_MAX);
        final double serverSocketWorkerPoolPoolSizeFactor =
                configuration.get(AkkaOptions.SERVER_SOCKET_WORKER_POOL_SIZE_FACTOR);

        final String logLifecycleEvents =
                booleanToOnOrOff(configuration.getBoolean(AkkaOptions.LOG_LIFECYCLE_EVENTS));

        final long retryGateClosedFor = configuration.getLong(AkkaOptions.RETRY_GATE_CLOSED_FOR);

        akkaConfigBuilder
                .add("akka {")
                .add("  actor {")
                .add("    provider = \"akka.remote.RemoteActorRefProvider\"")
                .add("  }")
                .add("  remote.artery.enabled = false")
                .add("  remote.startup-timeout = " + startupTimeout)
                .add("  remote.warn-about-direct-use = off")
                .add("  remote.use-unsafe-remote-features-outside-cluster = on")
                .add("  remote.classic {")
                .add("    # disable the transport failure detector by setting very high values")
                .add("    transport-failure-detector{")
                .add("      acceptable-heartbeat-pause = 6000 s")
                .add("      heartbeat-interval = 1000 s")
                .add("      threshold = 300")
                .add("    }")
                .add("    enabled-transports = [\"akka.remote.classic.netty.tcp\"]")
                .add("    netty {")
                .add("      tcp {")
                .add("        transport-class = \"akka.remote.transport.netty.NettyTransport\"")
                .add("        port = " + externalPort)
                .add("        bind-port = " + port)
                .add("        connection-timeout = " + akkaTCPTimeout)
                .add("        maximum-frame-size = " + akkaFramesize)
                .add("        tcp-nodelay = on")
                .add("        client-socket-worker-pool {")
                .add("          pool-size-min = " + clientSocketWorkerPoolPoolSizeMin)
                .add("          pool-size-max = " + clientSocketWorkerPoolPoolSizeMax)
                .add("          pool-size-factor = " + clientSocketWorkerPoolPoolSizeFactor)
                .add("        }")
                .add("        server-socket-worker-pool {")
                .add("          pool-size-min = " + serverSocketWorkerPoolPoolSizeMin)
                .add("          pool-size-max = " + serverSocketWorkerPoolPoolSizeMax)
                .add("          pool-size-factor = " + serverSocketWorkerPoolPoolSizeFactor)
                .add("        }")
                .add("      }")
                .add("    }")
                .add("    log-remote-lifecycle-events = " + logLifecycleEvents)
                .add("    retry-gate-closed-for = " + retryGateClosedFor + " ms")
                .add("  }")
                .add("}");
    }

    private static void addHostnameRemoteAkkaConfig(
            AkkaConfigBuilder akkaConfigBuilder, String bindAddress, String externalHostname) {
        final String normalizedExternalHostname =
                NetUtils.unresolvedHostToNormalizedString(externalHostname);
        final String effectiveHostname =
                normalizedExternalHostname != null && !normalizedExternalHostname.isEmpty()
                        ? normalizedExternalHostname
                        // if bindAddress is null or empty, then leave bindAddress unspecified. Akka
                        // will pick InetAddress.getLocalHost.getHostAddress
                        : "";

        akkaConfigBuilder
                .add("akka {")
                .add("  remote.classic {")
                .add("    netty {")
                .add("      tcp {")
                .add("        hostname = \"" + effectiveHostname + "\"")
                .add("        bind-hostname = \"" + bindAddress + "\"")
                .add("      }")
                .add("    }")
                .add("  }")
                .add("}");
    }

    private static void addSslRemoteAkkaConfig(
            AkkaConfigBuilder akkaConfigBuilder, Configuration configuration) {

        final boolean akkaEnableSSLConfig =
                configuration.getBoolean(AkkaOptions.SSL_ENABLED)
                        && SecurityOptions.isInternalSSLEnabled(configuration);

        final String akkaEnableSSL = booleanToOnOrOff(akkaEnableSSLConfig);

        final String akkaSSLKeyStore =
                configuration.getString(
                        SecurityOptions.SSL_INTERNAL_KEYSTORE,
                        configuration.getString(SecurityOptions.SSL_KEYSTORE));

        final String akkaSSLKeyStorePassword =
                configuration.getString(
                        SecurityOptions.SSL_INTERNAL_KEYSTORE_PASSWORD,
                        configuration.getString(SecurityOptions.SSL_KEYSTORE_PASSWORD));

        final String akkaSSLKeyPassword =
                configuration.getString(
                        SecurityOptions.SSL_INTERNAL_KEY_PASSWORD,
                        configuration.getString(SecurityOptions.SSL_KEY_PASSWORD));

        final String akkaSSLTrustStore =
                configuration.getString(
                        SecurityOptions.SSL_INTERNAL_TRUSTSTORE,
                        configuration.getString(SecurityOptions.SSL_TRUSTSTORE));

        final String akkaSSLTrustStorePassword =
                configuration.getString(
                        SecurityOptions.SSL_INTERNAL_TRUSTSTORE_PASSWORD,
                        configuration.getString(SecurityOptions.SSL_TRUSTSTORE_PASSWORD));

        final String akkaSSLCertFingerprintString =
                configuration.getString(SecurityOptions.SSL_INTERNAL_CERT_FINGERPRINT);

        final String akkaSSLCertFingerprints =
                akkaSSLCertFingerprintString != null
                        ? Arrays.stream(akkaSSLCertFingerprintString.split(","))
                                .collect(Collectors.joining("\",\"", "[\"", "\"]"))
                        : "[]";

        final String akkaSSLProtocol = configuration.getString(SecurityOptions.SSL_PROTOCOL);

        final String akkaSSLAlgorithmsString =
                configuration.getString(SecurityOptions.SSL_ALGORITHMS);
        final String akkaSSLAlgorithms =
                Arrays.stream(akkaSSLAlgorithmsString.split(","))
                        .collect(Collectors.joining(",", "[", "]"));

        final String sslEngineProviderName = CustomSSLEngineProvider.class.getCanonicalName();

        akkaConfigBuilder
                .add("akka {")
                .add("  remote.classic {")
                .add("    enabled-transports = [\"akka.remote.classic.netty.ssl\"]")
                .add("    netty {")
                .add("      ssl = ${akka.remote.classic.netty.tcp}")
                .add("      ssl {")
                .add("        enable-ssl = " + akkaEnableSSL)
                .add("        ssl-engine-provider = " + sslEngineProviderName)
                .add("        security {")
                .add("          key-store = \"" + akkaSSLKeyStore + "\"")
                .add("          key-store-password = \"" + akkaSSLKeyStorePassword + "\"")
                .add("          key-password = \"" + akkaSSLKeyPassword + "\"")
                .add("          trust-store = \"" + akkaSSLTrustStore + "\"")
                .add("          trust-store-password = \"" + akkaSSLTrustStorePassword + "\"")
                .add("          protocol = " + akkaSSLProtocol + "")
                .add("          enabled-algorithms = " + akkaSSLAlgorithms + "")
                .add("          random-number-generator = \"\"")
                .add("          require-mutual-authentication = on")
                .add("          cert-fingerprints = " + akkaSSLCertFingerprints + "")
                .add("        }")
                .add("      }")
                .add("    }")
                .add("  }")
                .add("}");
    }

    /**
     * Creates a local actor system without remoting.
     *
     * @param configuration instance containing the user provided configuration values
     * @return The created actor system
     */
    public static ActorSystem createLocalActorSystem(Configuration configuration) {
        final Config akkaConfig = getAkkaConfig(configuration, null);
        return createActorSystem(akkaConfig);
    }

    /**
     * Creates an actor system with the given akka config.
     *
     * @param akkaConfig configuration for the actor system
     * @return created actor system
     */
    private static ActorSystem createActorSystem(Config akkaConfig) {
        return createActorSystem(getFlinkActorSystemName(), akkaConfig);
    }

    /**
     * Creates an actor system with the given akka config.
     *
     * @param actorSystemName name of the actor system
     * @param akkaConfig configuration for the actor system
     * @return created actor system
     */
    public static ActorSystem createActorSystem(String actorSystemName, Config akkaConfig) {
        // Initialize slf4j as logger of Akka's Netty instead of java.util.logging (FLINK-1650)
        InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory());
        return RobustActorSystem.create(actorSystemName, akkaConfig);
    }

    /**
     * Creates an actor system with the default config and listening on a random port of the
     * localhost.
     *
     * @return default actor system listening on a random port of the localhost
     */
    @VisibleForTesting
    public static ActorSystem createDefaultActorSystem() {
        return createActorSystem(getDefaultAkkaConfig());
    }

    /**
     * Creates the default akka configuration which listens on a random port on the local machine.
     * All configuration values are set to default values.
     *
     * @return Flink's Akka default config
     */
    private static Config getDefaultAkkaConfig() {
        return getAkkaConfig(new Configuration(), new HostAndPort("", 0));
    }

    /**
     * Creates an akka config with the provided configuration values. If the listening address is
     * specified, then the actor system will listen on the respective address.
     *
     * @param configuration instance containing the user provided configuration values
     * @param externalAddress optional tuple of bindAddress and port to be reachable at. If null is
     *     given, then an Akka config for local actor system will be returned
     * @return Akka config
     */
    public static Config getAkkaConfig(
            Configuration configuration, @Nullable HostAndPort externalAddress) {
        return getAkkaConfig(
                configuration,
                externalAddress,
                null,
                AkkaUtils.getForkJoinExecutorConfig(
                        AkkaBootstrapTools.getForkJoinExecutorConfiguration(configuration)));
    }

    /**
     * Creates an akka config with the provided configuration values. If the listening address is
     * specified, then the actor system will listen on the respective address.
     *
     * @param configuration instance containing the user provided configuration values
     * @param externalAddress optional tuple of external address and port to be reachable at. If
     *     null is given, then an Akka config for local actor system will be returned
     * @param bindAddress optional tuple of bind address and port to be used locally. If null is
     *     given, wildcard IP address and the external port wil be used. Takes effect only if
     *     externalAddress is not null.
     * @param executorConfig config defining the used executor by the default dispatcher
     * @return Akka config
     */
    public static Config getAkkaConfig(
            Configuration configuration,
            @Nullable HostAndPort externalAddress,
            @Nullable HostAndPort bindAddress,
            Config executorConfig) {

        final Config defaultConfig =
                AkkaUtils.getBasicAkkaConfig(configuration).withFallback(executorConfig);

        if (externalAddress != null) {
            if (bindAddress != null) {
                final Config remoteConfig =
                        AkkaUtils.getRemoteAkkaConfig(
                                configuration,
                                bindAddress.getHost(),
                                bindAddress.getPort(),
                                externalAddress.getHost(),
                                externalAddress.getPort());

                return remoteConfig.withFallback(defaultConfig);
            } else {
                final Config remoteConfig =
                        AkkaUtils.getRemoteAkkaConfig(
                                configuration,
                                NetUtils.getWildcardIPAddress(),
                                externalAddress.getPort(),
                                externalAddress.getHost(),
                                externalAddress.getPort());

                return remoteConfig.withFallback(defaultConfig);
            }
        }

        return defaultConfig;
    }

    /**
     * Returns the address of the given {@link ActorSystem}. The {@link Address} object contains the
     * port and the host under which the actor system is reachable.
     *
     * @param system {@link ActorSystem} for which the {@link Address} shall be retrieved
     * @return {@link Address} of the given {@link ActorSystem}
     */
    public static Address getAddress(ActorSystem system) {
        return new RemoteAddressExtension().apply(system).getAddress();
    }

    /**
     * Returns the given {@link ActorRef}'s path string representation with host and port of the
     * {@link ActorSystem} in which the actor is running.
     *
     * @param system {@link ActorSystem} in which the given {@link ActorRef} is running
     * @param actor {@link ActorRef} of the actor for which the URL has to be generated
     * @return String containing the {@link ActorSystem} independent URL of the actor
     */
    public static String getAkkaURL(ActorSystem system, ActorRef actor) {
        final Address address = getAddress(system);
        return actor.path().toStringWithAddress(address);
    }

    /**
     * Extracts the {@link Address} from the given akka URL.
     *
     * @param akkaURL to extract the {@link Address} from
     * @throws MalformedURLException if the {@link Address} could not be parsed from the given akka
     *     URL
     * @return Extracted {@link Address} from the given akka URL
     */
    @SuppressWarnings("RedundantThrows") // hidden checked exception coming from Akka
    public static Address getAddressFromAkkaURL(String akkaURL) throws MalformedURLException {
        return AddressFromURIString.apply(akkaURL);
    }

    /**
     * Extracts the hostname and the port of the remote actor system from the given Akka URL. The
     * result is an {@link InetSocketAddress} instance containing the extracted hostname and port.
     * If the Akka URL does not contain the hostname and port information, e.g. a local Akka URL is
     * provided, then an {@link Exception} is thrown.
     *
     * @param akkaURL The URL to extract the host and port from.
     * @throws java.lang.Exception Thrown, if the given string does not represent a proper url
     * @return The InetSocketAddress with the extracted host and port.
     */
    public static InetSocketAddress getInetSocketAddressFromAkkaURL(String akkaURL)
            throws Exception {
        // AkkaURLs have the form schema://systemName@host:port/.... if it's a remote Akka URL
        try {
            final Address address = getAddressFromAkkaURL(akkaURL);

            if (address.host().isDefined() && address.port().isDefined()) {
                return new InetSocketAddress(address.host().get(), (int) address.port().get());
            } else {
                throw new MalformedURLException();
            }
        } catch (MalformedURLException e) {
            throw new Exception("Could not retrieve InetSocketAddress from Akka URL " + akkaURL);
        }
    }

    /**
     * Terminates the given {@link ActorSystem} and returns its termination future.
     *
     * @param actorSystem to terminate
     * @return Termination future
     */
    public static CompletableFuture terminateActorSystem(ActorSystem actorSystem) {
        return AkkaFutureUtils.toJava(actorSystem.terminate()).thenAccept(FunctionUtils.ignoreFn());
    }

    private static String booleanToOnOrOff(boolean flag) {
        return flag ? "on" : "off";
    }

    private static class AkkaConfigBuilder {
        private final StringWriter stringWriter = new StringWriter();
        private final PrintWriter printWriter = new PrintWriter(stringWriter);

        public AkkaConfigBuilder add(String configLine) {
            printWriter.println(configLine);
            return this;
        }

        public Config build() {
            return ConfigFactory.parseString(stringWriter.toString()).resolve();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy