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

org.apache.flink.runtime.clusterframework.BootstrapTools 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.clusterframework;

import org.apache.flink.configuration.ConfigConstants;
import org.apache.flink.configuration.ConfigOption;
import org.apache.flink.configuration.ConfigOptions;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.configuration.CoreOptions;
import org.apache.flink.runtime.entrypoint.parser.CommandLineOptions;
import org.apache.flink.runtime.util.config.memory.ProcessMemoryUtils;
import org.apache.flink.util.OperatingSystem;

import org.apache.flink.shaded.guava30.com.google.common.escape.Escaper;
import org.apache.flink.shaded.guava30.com.google.common.escape.Escapers;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

import static org.apache.flink.configuration.ConfigOptions.key;

/** Tools for starting JobManager and TaskManager processes. */
public class BootstrapTools {
    /** Internal option which says if default value is used for {@link CoreOptions#TMP_DIRS}. */
    private static final ConfigOption USE_LOCAL_DEFAULT_TMP_DIRS =
            key("internal.io.tmpdirs.use-local-default").booleanType().defaultValue(false);

    private static final Logger LOG = LoggerFactory.getLogger(BootstrapTools.class);

    private static final Escaper UNIX_SINGLE_QUOTE_ESCAPER =
            Escapers.builder().addEscape('\'', "'\\''").build();

    private static final Escaper WINDOWS_DOUBLE_QUOTE_ESCAPER =
            Escapers.builder().addEscape('"', "\\\"").addEscape('^', "\"^^\"").build();

    /**
     * Writes a Flink YAML config file from a Flink Configuration object.
     *
     * @param cfg The Flink config
     * @param file The File to write to
     * @throws IOException
     */
    public static void writeConfiguration(Configuration cfg, File file) throws IOException {
        try (FileWriter fwrt = new FileWriter(file);
                PrintWriter out = new PrintWriter(fwrt)) {
            for (Map.Entry entry : cfg.toMap().entrySet()) {
                out.print(entry.getKey());
                out.print(": ");
                out.println(entry.getValue());
            }
        }
    }

    /**
     * Sets the value of a new config key to the value of a deprecated config key.
     *
     * @param config Config to write
     * @param deprecated The old config key
     * @param designated The new config key
     */
    public static void substituteDeprecatedConfigKey(
            Configuration config, String deprecated, String designated) {
        // set the designated key only if it is not set already
        if (!config.containsKey(designated)) {
            final String valueForDeprecated = config.getString(deprecated, null);
            if (valueForDeprecated != null) {
                config.setString(designated, valueForDeprecated);
            }
        }
    }

    /**
     * Sets the value of a new config key to the value of a deprecated config key. Taking into
     * account the changed prefix.
     *
     * @param config Config to write
     * @param deprecatedPrefix Old prefix of key
     * @param designatedPrefix New prefix of key
     */
    public static void substituteDeprecatedConfigPrefix(
            Configuration config, String deprecatedPrefix, String designatedPrefix) {

        // set the designated key only if it is not set already
        final int prefixLen = deprecatedPrefix.length();

        Configuration replacement = new Configuration();

        for (String key : config.keySet()) {
            if (key.startsWith(deprecatedPrefix)) {
                String newKey = designatedPrefix + key.substring(prefixLen);
                if (!config.containsKey(newKey)) {
                    replacement.setString(newKey, config.getString(key, null));
                }
            }
        }

        config.addAll(replacement);
    }

    private static final String DYNAMIC_PROPERTIES_OPT = "D";

    /**
     * Get an instance of the dynamic properties option.
     *
     * 

Dynamic properties allow the user to specify additional configuration values with -D, such * as -Dfs.overwrite-files=true -Dtaskmanager.memory.network.min=536346624 */ public static Option newDynamicPropertiesOption() { return new Option(DYNAMIC_PROPERTIES_OPT, true, "Dynamic properties"); } /** Parse the dynamic properties (passed on the command line). */ public static Configuration parseDynamicProperties(CommandLine cmd) { final Configuration config = new Configuration(); String[] values = cmd.getOptionValues(DYNAMIC_PROPERTIES_OPT); if (values != null) { for (String value : values) { String[] pair = value.split("=", 2); if (pair.length == 1) { config.setString(pair[0], Boolean.TRUE.toString()); } else if (pair.length == 2) { config.setString(pair[0], pair[1]); } } } return config; } /** * Generates the shell command to start a task manager. * * @param flinkConfig The Flink configuration. * @param tmParams Parameters for the task manager. * @param configDirectory The configuration directory for the flink-conf.yaml * @param logDirectory The log directory. * @param hasLogback Uses logback? * @param hasLog4j Uses log4j? * @param mainClass The main class to start with. * @return A String containing the task manager startup command. */ public static String getTaskManagerShellCommand( Configuration flinkConfig, ContaineredTaskManagerParameters tmParams, String configDirectory, String logDirectory, boolean hasLogback, boolean hasLog4j, boolean hasKrb5, Class mainClass, String mainArgs) { final Map startCommandValues = new HashMap<>(); startCommandValues.put("java", "$JAVA_HOME/bin/java"); final TaskExecutorProcessSpec taskExecutorProcessSpec = tmParams.getTaskExecutorProcessSpec(); startCommandValues.put( "jvmmem", ProcessMemoryUtils.generateJvmParametersStr(taskExecutorProcessSpec)); String javaOpts = flinkConfig.getString(CoreOptions.FLINK_JVM_OPTIONS); if (flinkConfig.getString(CoreOptions.FLINK_TM_JVM_OPTIONS).length() > 0) { javaOpts += " " + flinkConfig.getString(CoreOptions.FLINK_TM_JVM_OPTIONS); } // krb5.conf file will be available as local resource in JM/TM container if (hasKrb5) { javaOpts += " -Djava.security.krb5.conf=krb5.conf"; } startCommandValues.put("jvmopts", javaOpts); String logging = ""; if (hasLogback || hasLog4j) { logging = "-Dlog.file=" + logDirectory + "/taskmanager.log"; if (hasLogback) { logging += " -Dlogback.configurationFile=file:" + configDirectory + "/logback.xml"; } if (hasLog4j) { logging += " -Dlog4j.configuration=file:" + configDirectory + "/log4j.properties"; logging += " -Dlog4j.configurationFile=file:" + configDirectory + "/log4j.properties"; } } startCommandValues.put("logging", logging); startCommandValues.put("class", mainClass.getName()); startCommandValues.put( "redirects", "1> " + logDirectory + "/taskmanager.out " + "2> " + logDirectory + "/taskmanager.err"); String argsStr = TaskExecutorProcessUtils.generateDynamicConfigsStr(taskExecutorProcessSpec) + " --configDir " + configDirectory; if (!mainArgs.isEmpty()) { argsStr += " " + mainArgs; } startCommandValues.put("args", argsStr); final String commandTemplate = flinkConfig.getString( ConfigConstants.YARN_CONTAINER_START_COMMAND_TEMPLATE, ConfigConstants.DEFAULT_YARN_CONTAINER_START_COMMAND_TEMPLATE); String startCommand = getStartCommand(commandTemplate, startCommandValues); LOG.debug("TaskManager start command: " + startCommand); return startCommand; } // ------------------------------------------------------------------------ /** Private constructor to prevent instantiation. */ private BootstrapTools() {} /** * Replaces placeholders in the template start command with values from startCommandValues. * *

If the default template {@link * ConfigConstants#DEFAULT_YARN_CONTAINER_START_COMMAND_TEMPLATE} is used, the following keys * must be present in the map or the resulting command will still contain placeholders: * *

    *
  • java = path to the Java executable *
  • jvmmem = JVM memory limits and tweaks *
  • jvmopts = misc options for the Java VM *
  • logging = logging-related configuration settings *
  • class = main class to execute *
  • args = arguments for the main class *
  • redirects = output redirects *
* * @param template a template start command with placeholders * @param startCommandValues a replacement map placeholder -> value * @return the start command with placeholders filled in */ public static String getStartCommand(String template, Map startCommandValues) { for (Map.Entry variable : startCommandValues.entrySet()) { template = template.replace("%" + variable.getKey() + "%", variable.getValue()) .replace(" ", " ") .trim(); } return template; } /** * Set temporary configuration directories if necessary. * * @param configuration flink config to patch * @param defaultDirs in case no tmp directories is set, next directories will be applied */ public static void updateTmpDirectoriesInConfiguration( Configuration configuration, @Nullable String defaultDirs) { if (configuration.contains(CoreOptions.TMP_DIRS)) { LOG.info( "Overriding Flink's temporary file directories with those " + "specified in the Flink config: {}", configuration.getValue(CoreOptions.TMP_DIRS)); } else if (defaultDirs != null) { LOG.info("Setting directories for temporary files to: {}", defaultDirs); configuration.setString(CoreOptions.TMP_DIRS, defaultDirs); configuration.setBoolean(USE_LOCAL_DEFAULT_TMP_DIRS, true); } } /** * Clones the given configuration and resets instance specific config options. * * @param configuration to clone * @return Cloned configuration with reset instance specific config options */ public static Configuration cloneConfiguration(Configuration configuration) { final Configuration clonedConfiguration = new Configuration(configuration); if (clonedConfiguration.getBoolean(USE_LOCAL_DEFAULT_TMP_DIRS)) { clonedConfiguration.removeConfig(CoreOptions.TMP_DIRS); clonedConfiguration.removeConfig(USE_LOCAL_DEFAULT_TMP_DIRS); } return clonedConfiguration; } /** * Get dynamic properties based on two Flink configurations. If base config does not contain and * target config contains the key or the value is different, it should be added to results. * Otherwise, if the base config contains and target config does not contain the key, it will be * ignored. * * @param baseConfig The base configuration. * @param targetConfig The target configuration. * @return Dynamic properties as string, separated by whitespace. */ public static String getDynamicPropertiesAsString( Configuration baseConfig, Configuration targetConfig) { String[] newAddedConfigs = targetConfig.keySet().stream() .flatMap( (String key) -> { final String baseValue = baseConfig.getString( ConfigOptions.key(key) .stringType() .noDefaultValue()); final String targetValue = targetConfig.getString( ConfigOptions.key(key) .stringType() .noDefaultValue()); if (!baseConfig.keySet().contains(key) || !baseValue.equals(targetValue)) { return Stream.of( "-" + CommandLineOptions.DYNAMIC_PROPERTY_OPTION .getOpt() + key + CommandLineOptions.DYNAMIC_PROPERTY_OPTION .getValueSeparator() + escapeForDifferentOS(targetValue)); } else { return Stream.empty(); } }) .toArray(String[]::new); return String.join(" ", newAddedConfigs); } /** * Escape all the dynamic property values. For Unix-like OS(Linux, MacOS, FREE_BSD, etc.), each * value will be surrounded with single quotes. This works for all chars except single quote * itself. To escape the single quote, close the quoting before it, insert the escaped single * quote, and then re-open the quoting. For example, the value is foo'bar and the escaped value * is 'foo'\''bar'. See https://stackoverflow.com/questions/15783701/which-characters-need-to-be-escaped-when-using-bash * for more information about Unix escaping. * *

For Windows OS, each value will be surrounded with double quotes. The double quote itself * needs to be escaped with back slash. Also the caret symbol need to be escaped with double * carets since Windows uses it to escape characters. See https://en.wikibooks.org/wiki/Windows_Batch_Scripting * for more information about Windows escaping. * * @param value value to be escaped * @return escaped value */ public static String escapeForDifferentOS(String value) { if (OperatingSystem.isWindows()) { return escapeWithDoubleQuote(value); } else { return escapeWithSingleQuote(value); } } public static String escapeWithSingleQuote(String value) { return "'" + UNIX_SINGLE_QUOTE_ESCAPER.escape(value) + "'"; } public static String escapeWithDoubleQuote(String value) { return "\"" + WINDOWS_DOUBLE_QUOTE_ESCAPER.escape(value) + "\""; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy