diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java index e3aa575..f76efa5 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-api/src/main/java/org/apache/hadoop/yarn/conf/YarnConfiguration.java @@ -1637,6 +1637,24 @@ private static void addDeprecatedKeys() { public static final long DEFAULT_NM_NODE_LABELS_FETCH_INTERVAL_MS = 10 * 60 * 1000; + public static final String NM_NODE_LABELS_SCRIPT_BASED_PREFIX = + NM_NODE_LABELS_PREFIX + "script-based."; + + public static final String NM_NODE_LABELS_SCRIPT_PROVIDER_SCRIPT_PATH = + NM_NODE_LABELS_SCRIPT_BASED_PREFIX + "script.path"; + + public static final String NM_NODE_LABELS_SCRIPT_BASED_FETCH_INTERVAL_MS = + NM_NODE_LABELS_CONFIG_BASED_PREFIX + "interval-ms"; + + public static final String NM_NODE_LABELS_SCRIPT_PROVIDER_TIMEOUT_MS = + NM_NODE_LABELS_SCRIPT_BASED_PREFIX + "timeout-ms"; + + public static final long DEFAULT_NM_NODE_LABELS_FETCH_SCRIPT_TIMEOUT_MS = + DEFAULT_NM_NODE_LABELS_FETCH_INTERVAL_MS * 2; + + public static final String NM_NODE_LABELS_FETCH_SCRIPT_OPTS = + NM_NODE_LABELS_SCRIPT_BASED_PREFIX + "script.opts"; + public YarnConfiguration() { super(); } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/nodelabels/ScriptBasedNodeLabelsProvider.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/nodelabels/ScriptBasedNodeLabelsProvider.java new file mode 100644 index 0000000..2738a71 --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/java/org/apache/hadoop/yarn/server/nodemanager/nodelabels/ScriptBasedNodeLabelsProvider.java @@ -0,0 +1,257 @@ +/** + * 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.nodemanager.nodelabels; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileUtil; +import org.apache.hadoop.util.Shell.ShellCommandExecutor; +import org.apache.hadoop.yarn.conf.YarnConfiguration; +import org.apache.hadoop.yarn.nodelabels.CommonNodeLabelsManager; + +/** + * The class which provides functionality of getting the labels of the node + * using the configured node labels provider script. "NODE_LABELS:" pattern will + * be used to search node labels from the out put of the NodeLabels provider + * script + */ +public class ScriptBasedNodeLabelsProvider extends NodeLabelsProviderService { + /** Absolute path to the node labels script. */ + private String nodeLabelsScript; + /** Delay after which node labels script to be executed */ + private long intervalTime; + /** Time after which the script should be timed out */ + private long scriptTimeout; + /** Timer used to schedule node labels fetching script execution */ + private Timer nodeLabelsScriptScheduler; + + /** ShellCommandExecutor used to execute monitoring script */ + ShellCommandExecutor shexec = null; + + /** Pattern used for searching in the output of the node labels script */ + public static final String NODE_LABEL_PATTERN = "NODE_LABELS:"; + + public static final String NODE_LABELS_SEPRATOR = ","; + + private TimerTask timerTask; + + protected Lock readLock = null; + protected Lock writeLock = null; + + @SuppressWarnings("unchecked") + private Set nodeLabels = Collections.EMPTY_SET; + private String[] scriptArgs; + + /** + * Class which is used by the {@link Timer} class to periodically execute the + * node labels script. + * + */ + private class NodeLabelsScriptRunner extends TimerTask { + + private final Log LOG = LogFactory.getLog(NodeLabelsScriptRunner.class); + + public NodeLabelsScriptRunner(String[] args) { + ArrayList execScript = new ArrayList(); + execScript.add(nodeLabelsScript); + if (args != null) { + execScript.addAll(Arrays.asList(args)); + } + shexec = + new ShellCommandExecutor(execScript.toArray(new String[execScript + .size()]), null, null, scriptTimeout); + } + + @Override + public void run() { + try { + shexec.execute(); + setNodeLabels(fetchLabelsFromScriptOutput(shexec.getOutput())); + } catch (Exception e) { + if (shexec.isTimedOut()) { + LOG.warn( + "Node Labels script timed out, Caught exception : " + + e.getMessage(), e); + } else { + LOG.warn( + "Execution of Node Labels script failed, Caught exception : " + + e.getMessage(), e); + } + } + } + + /** + * Method which collect lines from the output string which begins with + * Patterns provided. + * + * @param scriptOutput string + * @return true if output string has error pattern in it. + * @throws IOException + */ + private Set fetchLabelsFromScriptOutput(String scriptOutput) + throws IOException { + Set nodeLabels = new HashSet(); + String[] splits = scriptOutput.split("\n"); + for (String line : splits) { + String trimmedLine = line.trim(); + if (trimmedLine.startsWith(NODE_LABEL_PATTERN)) { + String[] labels = + trimmedLine.substring(NODE_LABEL_PATTERN.length()).split( + NODE_LABELS_SEPRATOR); + for (String label : labels) { + CommonNodeLabelsManager.checkAndThrowLabelName(label); + nodeLabels.add(label); + } + } + } + return nodeLabels; + } + } + + /** + * Method used to determine if or not node labels fetching script is + * configured and whether it is fit to run. Returns true if following + * conditions are met: + * + *
    + *
  1. Path to Node Labels fetch script is not empty
  2. + *
  3. Node Labels fetch script file exists
  4. + *
+ * + * @param conf + * @return true if node labels script can be run. + */ + public static boolean canRun(String nodeLabelsFetchScriptPath) { + if (nodeLabelsFetchScriptPath == null + || nodeLabelsFetchScriptPath.trim().isEmpty()) { + return false; + } + File f = new File(nodeLabelsFetchScriptPath); + return f.exists() && FileUtil.canExecute(f); + } + + public ScriptBasedNodeLabelsProvider() { + super(ScriptBasedNodeLabelsProvider.class.getName()); + } + + /* + * Method which initializes the values for the script path and interval time. + */ + @Override + protected void serviceInit(Configuration conf) throws Exception { + this.intervalTime = + conf.getLong( + YarnConfiguration.NM_NODE_LABELS_SCRIPT_BASED_FETCH_INTERVAL_MS, + YarnConfiguration.DEFAULT_NM_NODE_LABELS_FETCH_INTERVAL_MS); + this.nodeLabelsScript = + conf.get(YarnConfiguration.NM_NODE_LABELS_SCRIPT_PROVIDER_SCRIPT_PATH); + this.scriptTimeout = + conf.getLong( + YarnConfiguration.NM_NODE_LABELS_SCRIPT_PROVIDER_TIMEOUT_MS, + YarnConfiguration.DEFAULT_NM_NODE_LABELS_FETCH_SCRIPT_TIMEOUT_MS); + scriptArgs = + conf.getStrings(YarnConfiguration.NM_NODE_LABELS_FETCH_SCRIPT_OPTS, + new String[] {}); + + ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); + readLock = readWriteLock.readLock(); + writeLock = readWriteLock.writeLock(); + super.serviceInit(conf); + } + + /** + * Method used to start the Node Labels Fetch script. + * + */ + @Override + protected void serviceStart() throws Exception { + if (canRun(nodeLabelsScript)) { + timerTask = new NodeLabelsScriptRunner(scriptArgs); + nodeLabelsScriptScheduler = + new Timer("NodeLabelsScriptRunner-Timer", true); + // Start the timer task immediately and + // then periodically at interval time. + nodeLabelsScriptScheduler.scheduleAtFixedRate(timerTask, 0, intervalTime); + super.serviceStart(); + } + } + + /** + * Method used to terminate the Node Labels Fetch script. + * + */ + @Override + protected void serviceStop() { + if (canRun(nodeLabelsScript)) { + if (nodeLabelsScriptScheduler != null) { + nodeLabelsScriptScheduler.cancel(); + } + if (shexec != null) { + Process p = shexec.getProcess(); + if (p != null) { + p.destroy(); + } + } + } + } + + private void setNodeLabels(Set scriptOutput) { + writeLock.lock(); + try { + nodeLabels = scriptOutput; + } finally { + writeLock.unlock(); + } + } + + /** + * @return Returns output from node labels fetcher script. + */ + public Set getNodeLabels() { + readLock.lock(); + try { + return nodeLabels; + } finally { + readLock.unlock(); + } + } + + /** + * Used only by tests to access the timer task directly + * + * @return the timer task + */ + TimerTask getTimerTask() { + return timerTask; + } +} diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/nodelabels/TestScriptBasedNodeLabelsProvider.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/nodelabels/TestScriptBasedNodeLabelsProvider.java new file mode 100644 index 0000000..a3daf1b --- /dev/null +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/java/org/apache/hadoop/yarn/server/nodemanager/nodelabels/TestScriptBasedNodeLabelsProvider.java @@ -0,0 +1,194 @@ +/** + * 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.nodemanager.nodelabels; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.TimerTask; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileContext; +import org.apache.hadoop.fs.FileUtil; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.util.Shell; +import org.apache.hadoop.yarn.conf.YarnConfiguration; +import org.apache.hadoop.yarn.nodelabels.NodeLabelTestBase; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class TestScriptBasedNodeLabelsProvider extends NodeLabelTestBase { + + protected static File testRootDir = new File("target", + TestScriptBasedNodeLabelsProvider.class.getName() + "-localDir") + .getAbsoluteFile(); + + private File nodeLabelsScriptFile = new File(testRootDir, + Shell.appendScriptExtension("failingscript")); + + private ScriptBasedNodeLabelsProvider nodeLabelsProvider; + + @Before + public void setup() { + testRootDir.mkdirs(); + } + + @After + public void tearDown() throws Exception { + if (testRootDir.exists()) { + FileContext.getLocalFSFileContext().delete( + new Path(testRootDir.getAbsolutePath()), true); + } + if (nodeLabelsProvider != null) { + nodeLabelsProvider.stop(); + } + } + + private Configuration getConfForNodeLabelScript() { + Configuration conf = new Configuration(); + conf.set(YarnConfiguration.NM_NODE_LABELS_SCRIPT_PROVIDER_SCRIPT_PATH, + nodeLabelsScriptFile.getAbsolutePath()); + // set bigger interval so that test cases can be run + conf.setLong( + YarnConfiguration.NM_NODE_LABELS_SCRIPT_BASED_FETCH_INTERVAL_MS, + 1 * 60 * 60 * 1000l); + conf.setLong(YarnConfiguration.NM_NODE_LABELS_SCRIPT_PROVIDER_TIMEOUT_MS, + 1000); + return conf; + } + + private void writeNodeLabelsScriptFile(String scriptStr, boolean setExecutable) + throws IOException { + PrintWriter pw = null; + try { + FileUtil.setWritable(nodeLabelsScriptFile, true); + FileUtil.setReadable(nodeLabelsScriptFile, true); + pw = new PrintWriter(new FileOutputStream(nodeLabelsScriptFile)); + pw.println(scriptStr); + pw.flush(); + } catch (Exception e){ + e.printStackTrace(); + Assert.fail(); + }finally { + if (null != pw) { + pw.close(); + } + } + FileUtil.setExecutable(nodeLabelsScriptFile, setExecutable); + } + + @Test + public void testNodeLabelsScriptRunnerCreation() throws IOException { + // If no script configured then no timertask/NodeLabelsScriptRunner + // initialized + nodeLabelsProvider = new ScriptBasedNodeLabelsProvider(); + nodeLabelsProvider.init(new Configuration()); + nodeLabelsProvider.start(); + Assert.assertNull( + "By default Node Label Script runner should not be started ", + nodeLabelsProvider.getTimerTask()); + + nodeLabelsProvider.stop(); + + // If script configured is blank then no timertask/NodeLabelsScriptRunner + // initialized + nodeLabelsProvider = new ScriptBasedNodeLabelsProvider(); + Configuration conf = new Configuration(); + conf.set(YarnConfiguration.NM_NODE_LABELS_SCRIPT_PROVIDER_SCRIPT_PATH, ""); + nodeLabelsProvider.init(conf); + nodeLabelsProvider.start(); + Assert.assertNull("Node Label Script runner should not be started" + + " when script configuration is blank", + nodeLabelsProvider.getTimerTask()); + + nodeLabelsProvider.stop(); + + // If script configured is not executable then no timertask / + // NodeLabelsScriptRunner initialized + nodeLabelsProvider = new ScriptBasedNodeLabelsProvider(); + writeNodeLabelsScriptFile("", false); + nodeLabelsProvider.init(conf); + nodeLabelsProvider.start(); + Assert.assertNull("Node Label Script runner should not be started" + + " when script is not executable", nodeLabelsProvider.getTimerTask()); + + // If script configured is executable then timertask / + // NodeLabelsScriptRunner should be initialized + nodeLabelsProvider = new ScriptBasedNodeLabelsProvider(); + writeNodeLabelsScriptFile("", true); + nodeLabelsProvider.init(getConfForNodeLabelScript()); + nodeLabelsProvider.start(); + Assert.assertNotNull( + "Node Label Script runner should be started when script" + + " is executable", nodeLabelsProvider.getTimerTask()); + nodeLabelsProvider.stop(); + } + + @Test + public void testNodeLabelsScript() throws Exception { + String scriptWithoutLabels = ""; + String normalScript = "echo NODE_LABELS:Windows,X86"; + String scrptWithInvalidNodeLabels = + "echo NODE_LABELS:RAM,CPU\n echo NODE_LABELS:RED,JDK1.6"; + String scrptWithMultipleLinesHavingNodeLabels = + "echo NODE_LABELS:RAM,CPU\n echo NODE_LABELS:RED,JDK1_6"; + String timeOutScript = + Shell.WINDOWS ? "@echo off\nping -n 4 127.0.0.1 >nul\n" + + "echo NODE_LABELS:ALL" : "sleep 4\necho NODE_LABELS:ALL"; + + writeNodeLabelsScriptFile(scriptWithoutLabels, true); + nodeLabelsProvider = new ScriptBasedNodeLabelsProvider(); + nodeLabelsProvider.init(getConfForNodeLabelScript()); + nodeLabelsProvider.start(); + Thread.sleep(500l); + TimerTask timerTask = nodeLabelsProvider.getTimerTask(); + timerTask.run(); + Assert.assertEquals( + "Node Label Script runner should not return labels when script doesnt " + + "give any Labels output", 0, nodeLabelsProvider.getNodeLabels() + .size()); + + writeNodeLabelsScriptFile(normalScript, true); + timerTask.run(); + assertCollectionEquals(toSet("Windows", "X86"), + nodeLabelsProvider.getNodeLabels()); + + // lines with invalid labels. + writeNodeLabelsScriptFile(scrptWithInvalidNodeLabels, true); + timerTask.run(); + assertCollectionEquals(toSet("Windows", "X86"), + nodeLabelsProvider.getNodeLabels()); + + // multiple lines with labels tag. + writeNodeLabelsScriptFile(scrptWithMultipleLinesHavingNodeLabels, true); + timerTask.run(); + assertCollectionEquals(toSet("RAM", "CPU", "RED", "JDK1_6"), + nodeLabelsProvider.getNodeLabels()); + + // timeout script. + writeNodeLabelsScriptFile(timeOutScript, true); + timerTask.run(); + Assert.assertNotEquals("Node Labels should not be set after timeout ", 1, + nodeLabelsProvider.getNodeLabels().size()); + } + +}