001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019 package org.apache.hadoop.net; 020 021 import java.util.*; 022 import java.io.*; 023 024 import org.apache.commons.logging.Log; 025 import org.apache.commons.logging.LogFactory; 026 import org.apache.hadoop.util.Shell.ShellCommandExecutor; 027 import org.apache.hadoop.classification.InterfaceAudience; 028 import org.apache.hadoop.classification.InterfaceStability; 029 import org.apache.hadoop.conf.Configurable; 030 import org.apache.hadoop.conf.Configuration; 031 import org.apache.hadoop.fs.CommonConfigurationKeys; 032 033 /** 034 * This class implements the {@link DNSToSwitchMapping} interface using a 035 * script configured via the 036 * {@link CommonConfigurationKeys#NET_TOPOLOGY_SCRIPT_FILE_NAME_KEY} option. 037 * <p/> 038 * It contains a static class <code>RawScriptBasedMapping</code> that performs 039 * the work: reading the configuration parameters, executing any defined 040 * script, handling errors and such like. The outer 041 * class extends {@link CachedDNSToSwitchMapping} to cache the delegated 042 * queries. 043 * <p/> 044 * This DNS mapper's {@link #isSingleSwitch()} predicate returns 045 * true if and only if a script is defined. 046 */ 047 @InterfaceAudience.Public 048 @InterfaceStability.Evolving 049 public final class ScriptBasedMapping extends CachedDNSToSwitchMapping { 050 051 /** 052 * Minimum number of arguments: {@value} 053 */ 054 static final int MIN_ALLOWABLE_ARGS = 1; 055 056 /** 057 * Default number of arguments: {@value} 058 */ 059 static final int DEFAULT_ARG_COUNT = 060 CommonConfigurationKeys.NET_TOPOLOGY_SCRIPT_NUMBER_ARGS_DEFAULT; 061 062 /** 063 * key to the script filename {@value} 064 */ 065 static final String SCRIPT_FILENAME_KEY = 066 CommonConfigurationKeys.NET_TOPOLOGY_SCRIPT_FILE_NAME_KEY ; 067 /** 068 * key to the argument count that the script supports 069 */ 070 static final String SCRIPT_ARG_COUNT_KEY = 071 CommonConfigurationKeys.NET_TOPOLOGY_SCRIPT_NUMBER_ARGS_KEY ; 072 073 /** 074 * Create an instance with the default configuration. 075 * </p> 076 * Calling {@link #setConf(Configuration)} will trigger a 077 * re-evaluation of the configuration settings and so be used to 078 * set up the mapping script. 079 * 080 */ 081 public ScriptBasedMapping() { 082 super(new RawScriptBasedMapping()); 083 } 084 085 /** 086 * Create an instance from the given configuration 087 * @param conf configuration 088 */ 089 public ScriptBasedMapping(Configuration conf) { 090 this(); 091 setConf(conf); 092 } 093 094 /** 095 * Get the cached mapping and convert it to its real type 096 * @return the inner raw script mapping. 097 */ 098 private RawScriptBasedMapping getRawMapping() { 099 return (RawScriptBasedMapping)rawMapping; 100 } 101 102 @Override 103 public Configuration getConf() { 104 return getRawMapping().getConf(); 105 } 106 107 /** 108 * {@inheritDoc} 109 * <p/> 110 * This will get called in the superclass constructor, so a check is needed 111 * to ensure that the raw mapping is defined before trying to relaying a null 112 * configuration. 113 * @param conf 114 */ 115 @Override 116 public void setConf(Configuration conf) { 117 super.setConf(conf); 118 getRawMapping().setConf(conf); 119 } 120 121 /** 122 * This is the uncached script mapping that is fed into the cache managed 123 * by the superclass {@link CachedDNSToSwitchMapping} 124 */ 125 private static final class RawScriptBasedMapping 126 extends AbstractDNSToSwitchMapping { 127 private String scriptName; 128 private int maxArgs; //max hostnames per call of the script 129 private static final Log LOG = 130 LogFactory.getLog(ScriptBasedMapping.class); 131 132 /** 133 * Set the configuration and extract the configuration parameters of interest 134 * @param conf the new configuration 135 */ 136 @Override 137 public void setConf (Configuration conf) { 138 super.setConf(conf); 139 if (conf != null) { 140 scriptName = conf.get(SCRIPT_FILENAME_KEY); 141 maxArgs = conf.getInt(SCRIPT_ARG_COUNT_KEY, DEFAULT_ARG_COUNT); 142 } else { 143 scriptName = null; 144 maxArgs = 0; 145 } 146 } 147 148 /** 149 * Constructor. The mapping is not ready to use until 150 * {@link #setConf(Configuration)} has been called 151 */ 152 public RawScriptBasedMapping() {} 153 154 @Override 155 public List<String> resolve(List<String> names) { 156 List<String> m = new ArrayList<String>(names.size()); 157 158 if (names.isEmpty()) { 159 return m; 160 } 161 162 if (scriptName == null) { 163 for (String name : names) { 164 m.add(NetworkTopology.DEFAULT_RACK); 165 } 166 return m; 167 } 168 169 String output = runResolveCommand(names); 170 if (output != null) { 171 StringTokenizer allSwitchInfo = new StringTokenizer(output); 172 while (allSwitchInfo.hasMoreTokens()) { 173 String switchInfo = allSwitchInfo.nextToken(); 174 m.add(switchInfo); 175 } 176 177 if (m.size() != names.size()) { 178 // invalid number of entries returned by the script 179 LOG.error("Script " + scriptName + " returned " 180 + Integer.toString(m.size()) + " values when " 181 + Integer.toString(names.size()) + " were expected."); 182 return null; 183 } 184 } else { 185 // an error occurred. return null to signify this. 186 // (exn was already logged in runResolveCommand) 187 return null; 188 } 189 190 return m; 191 } 192 193 /** 194 * Build and execute the resolution command. The command is 195 * executed in the directory specified by the system property 196 * "user.dir" if set; otherwise the current working directory is used 197 * @param args a list of arguments 198 * @return null if the number of arguments is out of range, 199 * or the output of the command. 200 */ 201 private String runResolveCommand(List<String> args) { 202 int loopCount = 0; 203 if (args.size() == 0) { 204 return null; 205 } 206 StringBuilder allOutput = new StringBuilder(); 207 int numProcessed = 0; 208 if (maxArgs < MIN_ALLOWABLE_ARGS) { 209 LOG.warn("Invalid value " + Integer.toString(maxArgs) 210 + " for " + SCRIPT_ARG_COUNT_KEY + "; must be >= " 211 + Integer.toString(MIN_ALLOWABLE_ARGS)); 212 return null; 213 } 214 215 while (numProcessed != args.size()) { 216 int start = maxArgs * loopCount; 217 List<String> cmdList = new ArrayList<String>(); 218 cmdList.add(scriptName); 219 for (numProcessed = start; numProcessed < (start + maxArgs) && 220 numProcessed < args.size(); numProcessed++) { 221 cmdList.add(args.get(numProcessed)); 222 } 223 File dir = null; 224 String userDir; 225 if ((userDir = System.getProperty("user.dir")) != null) { 226 dir = new File(userDir); 227 } 228 ShellCommandExecutor s = new ShellCommandExecutor( 229 cmdList.toArray(new String[cmdList.size()]), dir); 230 try { 231 s.execute(); 232 allOutput.append(s.getOutput()).append(" "); 233 } catch (Exception e) { 234 LOG.warn("Exception: ", e); 235 return null; 236 } 237 loopCount++; 238 } 239 return allOutput.toString(); 240 } 241 242 /** 243 * Declare that the mapper is single-switched if a script was not named 244 * in the configuration. 245 * @return true iff there is no script 246 */ 247 @Override 248 public boolean isSingleSwitch() { 249 return scriptName == null; 250 } 251 } 252 }