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 019package org.apache.hadoop.net; 020 021import java.util.*; 022import java.io.*; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026import org.apache.hadoop.util.Shell.ShellCommandExecutor; 027import org.apache.hadoop.classification.InterfaceAudience; 028import org.apache.hadoop.classification.InterfaceStability; 029import org.apache.hadoop.conf.Configuration; 030import org.apache.hadoop.fs.CommonConfigurationKeys; 031 032/** 033 * This class implements the {@link DNSToSwitchMapping} interface using a 034 * script configured via the 035 * {@link CommonConfigurationKeys#NET_TOPOLOGY_SCRIPT_FILE_NAME_KEY} option. 036 * <p/> 037 * It contains a static class <code>RawScriptBasedMapping</code> that performs 038 * the work: reading the configuration parameters, executing any defined 039 * script, handling errors and such like. The outer 040 * class extends {@link CachedDNSToSwitchMapping} to cache the delegated 041 * queries. 042 * <p/> 043 * This DNS mapper's {@link #isSingleSwitch()} predicate returns 044 * true if and only if a script is defined. 045 */ 046@InterfaceAudience.Public 047@InterfaceStability.Evolving 048public class ScriptBasedMapping extends CachedDNSToSwitchMapping { 049 050 /** 051 * Minimum number of arguments: {@value} 052 */ 053 static final int MIN_ALLOWABLE_ARGS = 1; 054 055 /** 056 * Default number of arguments: {@value} 057 */ 058 static final int DEFAULT_ARG_COUNT = 059 CommonConfigurationKeys.NET_TOPOLOGY_SCRIPT_NUMBER_ARGS_DEFAULT; 060 061 /** 062 * key to the script filename {@value} 063 */ 064 static final String SCRIPT_FILENAME_KEY = 065 CommonConfigurationKeys.NET_TOPOLOGY_SCRIPT_FILE_NAME_KEY ; 066 067 /** 068 * key to the argument count that the script supports 069 * {@value} 070 */ 071 static final String SCRIPT_ARG_COUNT_KEY = 072 CommonConfigurationKeys.NET_TOPOLOGY_SCRIPT_NUMBER_ARGS_KEY ; 073 /** 074 * Text used in the {@link #toString()} method if there is no string 075 * {@value} 076 */ 077 public static final String NO_SCRIPT = "no script"; 078 079 /** 080 * Create an instance with the default configuration. 081 * </p> 082 * Calling {@link #setConf(Configuration)} will trigger a 083 * re-evaluation of the configuration settings and so be used to 084 * set up the mapping script. 085 * 086 */ 087 public ScriptBasedMapping() { 088 this(new RawScriptBasedMapping()); 089 } 090 091 /** 092 * Create an instance from the given raw mapping 093 * @param rawMap raw DNSTOSwithMapping 094 */ 095 public ScriptBasedMapping(DNSToSwitchMapping rawMap) { 096 super(rawMap); 097 } 098 099 /** 100 * Create an instance from the given configuration 101 * @param conf configuration 102 */ 103 public ScriptBasedMapping(Configuration conf) { 104 this(); 105 setConf(conf); 106 } 107 108 /** 109 * Get the cached mapping and convert it to its real type 110 * @return the inner raw script mapping. 111 */ 112 private RawScriptBasedMapping getRawMapping() { 113 return (RawScriptBasedMapping)rawMapping; 114 } 115 116 @Override 117 public Configuration getConf() { 118 return getRawMapping().getConf(); 119 } 120 121 @Override 122 public String toString() { 123 return "script-based mapping with " + getRawMapping().toString(); 124 } 125 126 /** 127 * {@inheritDoc} 128 * <p/> 129 * This will get called in the superclass constructor, so a check is needed 130 * to ensure that the raw mapping is defined before trying to relaying a null 131 * configuration. 132 * @param conf 133 */ 134 @Override 135 public void setConf(Configuration conf) { 136 super.setConf(conf); 137 getRawMapping().setConf(conf); 138 } 139 140 /** 141 * This is the uncached script mapping that is fed into the cache managed 142 * by the superclass {@link CachedDNSToSwitchMapping} 143 */ 144 protected static class RawScriptBasedMapping 145 extends AbstractDNSToSwitchMapping { 146 private String scriptName; 147 private int maxArgs; //max hostnames per call of the script 148 private static final Log LOG = 149 LogFactory.getLog(ScriptBasedMapping.class); 150 151 /** 152 * Set the configuration and extract the configuration parameters of interest 153 * @param conf the new configuration 154 */ 155 @Override 156 public void setConf (Configuration conf) { 157 super.setConf(conf); 158 if (conf != null) { 159 scriptName = conf.get(SCRIPT_FILENAME_KEY); 160 maxArgs = conf.getInt(SCRIPT_ARG_COUNT_KEY, DEFAULT_ARG_COUNT); 161 } else { 162 scriptName = null; 163 maxArgs = 0; 164 } 165 } 166 167 /** 168 * Constructor. The mapping is not ready to use until 169 * {@link #setConf(Configuration)} has been called 170 */ 171 public RawScriptBasedMapping() {} 172 173 @Override 174 public List<String> resolve(List<String> names) { 175 List<String> m = new ArrayList<String>(names.size()); 176 177 if (names.isEmpty()) { 178 return m; 179 } 180 181 if (scriptName == null) { 182 for (String name : names) { 183 m.add(NetworkTopology.DEFAULT_RACK); 184 } 185 return m; 186 } 187 188 String output = runResolveCommand(names, scriptName); 189 if (output != null) { 190 StringTokenizer allSwitchInfo = new StringTokenizer(output); 191 while (allSwitchInfo.hasMoreTokens()) { 192 String switchInfo = allSwitchInfo.nextToken(); 193 m.add(switchInfo); 194 } 195 196 if (m.size() != names.size()) { 197 // invalid number of entries returned by the script 198 LOG.error("Script " + scriptName + " returned " 199 + Integer.toString(m.size()) + " values when " 200 + Integer.toString(names.size()) + " were expected."); 201 return null; 202 } 203 } else { 204 // an error occurred. return null to signify this. 205 // (exn was already logged in runResolveCommand) 206 return null; 207 } 208 209 return m; 210 } 211 212 /** 213 * Build and execute the resolution command. The command is 214 * executed in the directory specified by the system property 215 * "user.dir" if set; otherwise the current working directory is used 216 * @param args a list of arguments 217 * @return null if the number of arguments is out of range, 218 * or the output of the command. 219 */ 220 protected String runResolveCommand(List<String> args, 221 String commandScriptName) { 222 int loopCount = 0; 223 if (args.size() == 0) { 224 return null; 225 } 226 StringBuilder allOutput = new StringBuilder(); 227 int numProcessed = 0; 228 if (maxArgs < MIN_ALLOWABLE_ARGS) { 229 LOG.warn("Invalid value " + Integer.toString(maxArgs) 230 + " for " + SCRIPT_ARG_COUNT_KEY + "; must be >= " 231 + Integer.toString(MIN_ALLOWABLE_ARGS)); 232 return null; 233 } 234 235 while (numProcessed != args.size()) { 236 int start = maxArgs * loopCount; 237 List<String> cmdList = new ArrayList<String>(); 238 cmdList.add(commandScriptName); 239 for (numProcessed = start; numProcessed < (start + maxArgs) && 240 numProcessed < args.size(); numProcessed++) { 241 cmdList.add(args.get(numProcessed)); 242 } 243 File dir = null; 244 String userDir; 245 if ((userDir = System.getProperty("user.dir")) != null) { 246 dir = new File(userDir); 247 } 248 ShellCommandExecutor s = new ShellCommandExecutor( 249 cmdList.toArray(new String[cmdList.size()]), dir); 250 try { 251 s.execute(); 252 allOutput.append(s.getOutput()).append(" "); 253 } catch (Exception e) { 254 LOG.warn("Exception running " + s, e); 255 return null; 256 } 257 loopCount++; 258 } 259 return allOutput.toString(); 260 } 261 262 /** 263 * Declare that the mapper is single-switched if a script was not named 264 * in the configuration. 265 * @return true iff there is no script 266 */ 267 @Override 268 public boolean isSingleSwitch() { 269 return scriptName == null; 270 } 271 272 @Override 273 public String toString() { 274 return scriptName != null ? ("script " + scriptName) : NO_SCRIPT; 275 } 276 277 @Override 278 public void reloadCachedMappings() { 279 // Nothing to do here, since RawScriptBasedMapping has no cache, and 280 // does not inherit from CachedDNSToSwitchMapping 281 } 282 283 @Override 284 public void reloadCachedMappings(List<String> names) { 285 // Nothing to do here, since RawScriptBasedMapping has no cache, and 286 // does not inherit from CachedDNSToSwitchMapping 287 } 288 } 289}