blob: 41e7ba520377872e87eafc2eb1847795c0fcf670 [file] [log] [blame]
/*
* 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.hadoop.yarn.server.resourcemanager.scheduler.fair.converter;
import java.io.File;
import java.util.function.Supplier;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.GnuParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.hadoop.yarn.conf.YarnConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hadoop.thirdparty.com.google.common.annotations.VisibleForTesting;
/**
* Parses arguments passed to the FS->CS converter.
* If the arguments are valid, it calls the converter itself.
*
*/
public class FSConfigToCSConfigArgumentHandler {
private static final Logger LOG =
LoggerFactory.getLogger(FSConfigToCSConfigArgumentHandler.class);
private static final String ALREADY_CONTAINS_EXCEPTION_MSG =
"The %s (provided with %s|%s arguments) contains " +
"the %s provided with the %s|%s options.";
private static final String ALREADY_CONTAINS_FILE_EXCEPTION_MSG =
"The %s %s (provided with %s|%s arguments) already contains a file " +
"or directory named %s which will be the output of the conversion!";
private FSConfigToCSConfigRuleHandler ruleHandler;
private FSConfigToCSConfigConverterParams converterParams;
private ConversionOptions conversionOptions;
private ConvertedConfigValidator validator;
private Supplier<FSConfigToCSConfigConverter>
converterFunc = this::getConverter;
public FSConfigToCSConfigArgumentHandler() {
this.conversionOptions = new ConversionOptions(new DryRunResultHolder(),
false);
this.validator = new ConvertedConfigValidator();
}
@VisibleForTesting
FSConfigToCSConfigArgumentHandler(ConversionOptions conversionOptions,
ConvertedConfigValidator validator) {
this.conversionOptions = conversionOptions;
this.validator = validator;
}
/**
* Represents options for the converter CLI.
*
*/
public enum CliOption {
YARN_SITE("yarn-site.xml", "y", "yarnsiteconfig",
"Path to a valid yarn-site.xml config file", true),
// fair-scheduler.xml is not mandatory
// if FairSchedulerConfiguration.ALLOCATION_FILE is defined in yarn-site.xml
FAIR_SCHEDULER("fair-scheduler.xml", "f", "fsconfig",
"Path to a valid fair-scheduler.xml config file", true),
CONVERSION_RULES("conversion rules config file", "r", "rulesconfig",
"Optional parameter. If given, should specify a valid path to the " +
"conversion rules file (property format).", true),
CONSOLE_MODE("console mode", "p", "print",
"If defined, the converted configuration will " +
"only be emitted to the console.", false),
CLUSTER_RESOURCE("cluster resource", "c", "cluster-resource",
"Needs to be given if maxResources is defined as percentages " +
"for any queue, otherwise this parameter can be omitted.",
true),
OUTPUT_DIR("output directory", "o", "output-directory",
"Output directory for yarn-site.xml and" +
" capacity-scheduler.xml files." +
"Must have write permission for user who is running this script.",
true),
DRY_RUN("dry run", "d", "dry-run", "Performs a dry-run of the conversion." +
"Outputs whether the conversion is possible or not.", false),
NO_TERMINAL_RULE_CHECK("no terminal rule check", "t",
"no-terminal-rule-check",
"Disables checking whether a placement rule is terminal to maintain" +
" backward compatibility with configs that were made before YARN-8967.",
false),
SKIP_VERIFICATION("skip verification", "s",
"skip-verification",
"Skips the verification of the converted configuration", false),
SKIP_PLACEMENT_RULES_CONVERSION("skip placement rules conversion",
"sp", "skip-convert-placement-rules",
"Do not convert placement rules", false),
ENABLE_ASYNC_SCHEDULER("enable asynchronous scheduler", "a", "enable-async-scheduler",
"Enables the Asynchronous scheduler which decouples the CapacityScheduler" +
" scheduling from Node Heartbeats.", false),
RULES_TO_FILE("rules to external file", "e", "rules-to-file",
"Generates the converted placement rules to an external JSON file " +
"called mapping-rules.json", false),
CONVERT_PERCENTAGES("convert weights to percentages",
"pc", "percentage",
"Converts FS queue weights to percentages",
false),
DISABLE_PREEMPTION("disable preemption", "dp", "disable-preemption",
"Disable the preemption with nopolicy or observeonly mode. " +
"Preemption is enabled by default. " +
"nopolicy removes ProportionalCapacityPreemptionPolicy from " +
"the list of monitor policies, " +
"observeonly sets " +
"yarn.resourcemanager.monitor.capacity.preemption.observe_only " +
"to true.", true),
HELP("help", "h", "help", "Displays the list of options", false);
private final String name;
private final String shortSwitch;
private final String longSwitch;
private final String description;
private final boolean hasArg;
CliOption(String name, String shortSwitch, String longSwitch,
String description, boolean hasArg) {
this.name = name;
this.shortSwitch = shortSwitch;
this.longSwitch = longSwitch;
this.description = description;
this.hasArg = hasArg;
}
public Option createCommonsCliOption() {
Option option = new Option(shortSwitch, longSwitch, hasArg, description);
return option;
}
}
int parseAndConvert(String[] args) throws Exception {
Options opts = createOptions();
int retVal = 0;
try {
if (args.length == 0) {
LOG.info("Missing command line arguments");
printHelp(opts);
return 0;
}
CommandLine cliParser = new GnuParser().parse(opts, args);
if (cliParser.hasOption(CliOption.HELP.shortSwitch)) {
printHelp(opts);
return 0;
}
FSConfigToCSConfigConverter converter =
prepareAndGetConverter(cliParser);
converter.convert(converterParams);
String outputDir = converterParams.getOutputDirectory();
boolean skipVerification =
cliParser.hasOption(CliOption.SKIP_VERIFICATION.shortSwitch);
if (outputDir != null && !skipVerification) {
validator.validateConvertedConfig(
converterParams.getOutputDirectory());
}
} catch (ParseException e) {
String msg = "Options parsing failed: " + e.getMessage();
logAndStdErr(e, msg);
printHelp(opts);
retVal = -1;
} catch (PreconditionException e) {
String msg = "Cannot start FS config conversion due to the following"
+ " precondition error: " + e.getMessage();
handleException(e, msg);
retVal = -1;
} catch (UnsupportedPropertyException e) {
String msg = "Unsupported property/setting encountered during FS config "
+ "conversion: " + e.getMessage();
handleException(e, msg);
retVal = -1;
} catch (ConversionException | IllegalArgumentException e) {
String msg = "Fatal error during FS config conversion: " + e.getMessage();
handleException(e, msg);
retVal = -1;
} catch (VerificationException e) {
Throwable cause = e.getCause();
String msg = "Verification failed: " + e.getCause().getMessage();
conversionOptions.handleVerificationFailure(cause, msg);
retVal = -1;
}
conversionOptions.handleParsingFinished();
return retVal;
}
private void handleException(Exception e, String msg) {
conversionOptions.handleGenericException(e, msg);
}
static void logAndStdErr(Throwable t, String msg) {
LOG.debug("Stack trace", t);
LOG.error(msg);
System.err.println(msg);
}
private Options createOptions() {
Options opts = new Options();
for (CliOption cliOption : CliOption.values()) {
opts.addOption(cliOption.createCommonsCliOption());
}
return opts;
}
private FSConfigToCSConfigConverter prepareAndGetConverter(
CommandLine cliParser) {
boolean dryRun =
cliParser.hasOption(CliOption.DRY_RUN.shortSwitch);
conversionOptions.setDryRun(dryRun);
conversionOptions.setNoTerminalRuleCheck(
cliParser.hasOption(CliOption.NO_TERMINAL_RULE_CHECK.shortSwitch));
conversionOptions.setEnableAsyncScheduler(
cliParser.hasOption(CliOption.ENABLE_ASYNC_SCHEDULER.shortSwitch));
checkOptionPresent(cliParser, CliOption.YARN_SITE);
checkOutputDefined(cliParser, dryRun);
converterParams = validateInputFiles(cliParser);
ruleHandler = new FSConfigToCSConfigRuleHandler(conversionOptions);
return converterFunc.get();
}
private FSConfigToCSConfigConverterParams validateInputFiles(
CommandLine cliParser) {
String yarnSiteXmlFile =
cliParser.getOptionValue(CliOption.YARN_SITE.shortSwitch);
String fairSchedulerXmlFile =
cliParser.getOptionValue(CliOption.FAIR_SCHEDULER.shortSwitch);
String conversionRulesFile =
cliParser.getOptionValue(CliOption.CONVERSION_RULES.shortSwitch);
String outputDir =
cliParser.getOptionValue(CliOption.OUTPUT_DIR.shortSwitch);
FSConfigToCSConfigConverterParams.
PreemptionMode preemptionMode =
FSConfigToCSConfigConverterParams.
PreemptionMode.fromString(cliParser.
getOptionValue(CliOption.DISABLE_PREEMPTION.shortSwitch));
boolean convertPlacementRules =
!cliParser.hasOption(
CliOption.SKIP_PLACEMENT_RULES_CONVERSION.shortSwitch);
checkFile(CliOption.YARN_SITE, yarnSiteXmlFile);
checkFile(CliOption.FAIR_SCHEDULER, fairSchedulerXmlFile);
checkFile(CliOption.CONVERSION_RULES, conversionRulesFile);
checkDirectory(CliOption.OUTPUT_DIR, outputDir);
checkOutputDirDoesNotContainXmls(yarnSiteXmlFile, outputDir);
if (cliParser.hasOption(CliOption.
DISABLE_PREEMPTION.shortSwitch)) {
checkDisablePreemption(preemptionMode);
}
// check mapping-rules.json if we intend to generate it
if (!cliParser.hasOption(CliOption.CONSOLE_MODE.shortSwitch) &&
cliParser.hasOption(CliOption.RULES_TO_FILE.shortSwitch)) {
checkFileNotInOutputDir(new File(outputDir),
FSConfigToCSConfigConverter.MAPPING_RULES_JSON);
}
return FSConfigToCSConfigConverterParams.Builder.create()
.withYarnSiteXmlConfig(yarnSiteXmlFile)
.withFairSchedulerXmlConfig(fairSchedulerXmlFile)
.withConversionRulesConfig(conversionRulesFile)
.withClusterResource(
cliParser.getOptionValue(CliOption.CLUSTER_RESOURCE.shortSwitch))
.withConsole(cliParser.hasOption(CliOption.CONSOLE_MODE.shortSwitch))
.withOutputDirectory(outputDir)
.withConvertPlacementRules(convertPlacementRules)
.withPlacementRulesToFile(
cliParser.hasOption(CliOption.RULES_TO_FILE.shortSwitch))
.withUsePercentages(
cliParser.hasOption(CliOption.CONVERT_PERCENTAGES.shortSwitch))
.withDisablePreemption(preemptionMode)
.build();
}
private static void checkOutputDirDoesNotContainXmls(String yarnSiteXmlFile,
String outputDir) {
if (yarnSiteXmlFile == null || outputDir == null) {
return;
}
// check whether yarn-site.xml is not in the output folder
File xmlFile = new File(yarnSiteXmlFile);
File xmlParentFolder = xmlFile.getParentFile();
File output = new File(outputDir);
if (output.equals(xmlParentFolder)) {
throw new IllegalArgumentException(
String.format(ALREADY_CONTAINS_EXCEPTION_MSG,
CliOption.OUTPUT_DIR.name, CliOption.OUTPUT_DIR.shortSwitch,
CliOption.OUTPUT_DIR.longSwitch, CliOption.YARN_SITE.name,
CliOption.YARN_SITE.shortSwitch,
CliOption.YARN_SITE.longSwitch));
}
// check whether the output folder does not contain nor yarn-site.xml
// neither capacity-scheduler.xml
checkFileNotInOutputDir(output,
YarnConfiguration.YARN_SITE_CONFIGURATION_FILE);
checkFileNotInOutputDir(output,
YarnConfiguration.CS_CONFIGURATION_FILE);
}
private static void checkFileNotInOutputDir(File output, String fileName) {
File file = new File(output, fileName);
if (file.exists()) {
throw new IllegalArgumentException(
String.format(ALREADY_CONTAINS_FILE_EXCEPTION_MSG,
CliOption.OUTPUT_DIR.name, output,
CliOption.OUTPUT_DIR.shortSwitch,
CliOption.OUTPUT_DIR.longSwitch,
fileName));
}
}
private void printHelp(Options opts) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("General options are: ", opts);
}
private static void checkOptionPresent(CommandLine cliParser,
CliOption cliOption) {
if (!cliParser.hasOption(cliOption.shortSwitch)) {
throw new PreconditionException(
String.format("Missing %s parameter " + "(switch: %s|%s).",
cliOption.name, cliOption.shortSwitch, cliOption.longSwitch));
}
}
private static void checkOutputDefined(CommandLine cliParser,
boolean dryRun) {
boolean hasOutputDir =
cliParser.hasOption(CliOption.OUTPUT_DIR.shortSwitch);
boolean console =
cliParser.hasOption(CliOption.CONSOLE_MODE.shortSwitch);
if (!console && !hasOutputDir && !dryRun) {
throw new PreconditionException(
"Output directory or console mode was not defined. Please" +
" use -h or --help to see command line switches");
}
}
private static void checkFile(CliOption cliOption, String filePath) {
checkFileInternal(cliOption, filePath, true);
}
private static void checkDirectory(CliOption cliOption, String dirPath) {
checkFileInternal(cliOption, dirPath, false);
}
private static void checkFileInternal(CliOption cliOption, String filePath,
boolean isFile) {
//We can safely ignore null here as files / dirs were checked before
if (filePath == null) {
return;
}
File file = new File(filePath);
if (isFile && file.isDirectory()) {
throw new PreconditionException(
String.format("Specified path %s is a directory but should be " +
" a file (As value of parameter %s)", filePath, cliOption.name));
} else if (!isFile && !file.isDirectory()) {
throw new PreconditionException(
String.format("Specified path %s is not a directory " +
"(As value of parameter %s)", filePath, cliOption.name));
} else if (!file.exists()) {
throw new PreconditionException(
String.format("Specified path %s does not exist " +
"(As value of parameter %s)", filePath, cliOption.name));
}
}
private static void checkDisablePreemption(FSConfigToCSConfigConverterParams.
PreemptionMode preemptionMode) {
if (preemptionMode == FSConfigToCSConfigConverterParams.
PreemptionMode.ENABLED) {
throw new PreconditionException(
"Specified disable-preemption mode is illegal, " +
" use nopolicy or observeonly.");
}
}
private FSConfigToCSConfigConverter getConverter() {
return new FSConfigToCSConfigConverter(ruleHandler, conversionOptions);
}
@VisibleForTesting
void setConverterSupplier(Supplier<FSConfigToCSConfigConverter>
supplier) {
this.converterFunc = supplier;
}
}