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.Configurable; 030import org.apache.hadoop.conf.Configuration; 031import 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 049public 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 * {@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 super(new RawScriptBasedMapping()); 089 } 090 091 /** 092 * Create an instance from the given configuration 093 * @param conf configuration 094 */ 095 public ScriptBasedMapping(Configuration conf) { 096 this(); 097 setConf(conf); 098 } 099 100 /** 101 * Get the cached mapping and convert it to its real type 102 * @return the inner raw script mapping. 103 */ 104 private RawScriptBasedMapping getRawMapping() { 105 return (RawScriptBasedMapping)rawMapping; 106 } 107 108 @Override 109 public Configuration getConf() { 110 return getRawMapping().getConf(); 111 } 112 113 @Override 114 public String toString() { 115 return "script-based mapping with " + getRawMapping().toString(); 116 } 117 118 /** 119 * {@inheritDoc} 120 * <p/> 121 * This will get called in the superclass constructor, so a check is needed 122 * to ensure that the raw mapping is defined before trying to relaying a null 123 * configuration. 124 * @param conf 125 */ 126 @Override 127 public void setConf(Configuration conf) { 128 super.setConf(conf); 129 getRawMapping().setConf(conf); 130 } 131 132 /** 133 * This is the uncached script mapping that is fed into the cache managed 134 * by the superclass {@link CachedDNSToSwitchMapping} 135 */ 136 private static final class RawScriptBasedMapping 137 extends AbstractDNSToSwitchMapping { 138 private String scriptName; 139 private int maxArgs; //max hostnames per call of the script 140 private static final Log LOG = 141 LogFactory.getLog(ScriptBasedMapping.class); 142 143 /** 144 * Set the configuration and extract the configuration parameters of interest 145 * @param conf the new configuration 146 */ 147 @Override 148 public void setConf (Configuration conf) { 149 super.setConf(conf); 150 if (conf != null) { 151 scriptName = conf.get(SCRIPT_FILENAME_KEY); 152 maxArgs = conf.getInt(SCRIPT_ARG_COUNT_KEY, DEFAULT_ARG_COUNT); 153 } else { 154 scriptName = null; 155 maxArgs = 0; 156 } 157 } 158 159 /** 160 * Constructor. The mapping is not ready to use until 161 * {@link #setConf(Configuration)} has been called 162 */ 163 public RawScriptBasedMapping() {} 164 165 @Override 166 public List<String> resolve(List<String> names) { 167 List<String> m = new ArrayList<String>(names.size()); 168 169 if (names.isEmpty()) { 170 return m; 171 } 172 173 if (scriptName == null) { 174 for (String name : names) { 175 m.add(NetworkTopology.DEFAULT_RACK); 176 } 177 return m; 178 } 179 180 String output = runResolveCommand(names); 181 if (output != null) { 182 StringTokenizer allSwitchInfo = new StringTokenizer(output); 183 while (allSwitchInfo.hasMoreTokens()) { 184 String switchInfo = allSwitchInfo.nextToken(); 185 m.add(switchInfo); 186 } 187 188 if (m.size() != names.size()) { 189 // invalid number of entries returned by the script 190 LOG.error("Script " + scriptName + " returned " 191 + Integer.toString(m.size()) + " values when " 192 + Integer.toString(names.size()) + " were expected."); 193 return null; 194 } 195 } else { 196 // an error occurred. return null to signify this. 197 // (exn was already logged in runResolveCommand) 198 return null; 199 } 200 201 return m; 202 } 203 204 /** 205 * Build and execute the resolution command. The command is 206 * executed in the directory specified by the system property 207 * "user.dir" if set; otherwise the current working directory is used 208 * @param args a list of arguments 209 * @return null if the number of arguments is out of range, 210 * or the output of the command. 211 */ 212 private String runResolveCommand(List<String> args) { 213 int loopCount = 0; 214 if (args.size() == 0) { 215 return null; 216 } 217 StringBuilder allOutput = new StringBuilder(); 218 int numProcessed = 0; 219 if (maxArgs < MIN_ALLOWABLE_ARGS) { 220 LOG.warn("Invalid value " + Integer.toString(maxArgs) 221 + " for " + SCRIPT_ARG_COUNT_KEY + "; must be >= " 222 + Integer.toString(MIN_ALLOWABLE_ARGS)); 223 return null; 224 } 225 226 while (numProcessed != args.size()) { 227 int start = maxArgs * loopCount; 228 List<String> cmdList = new ArrayList<String>(); 229 cmdList.add(scriptName); 230 for (numProcessed = start; numProcessed < (start + maxArgs) && 231 numProcessed < args.size(); numProcessed++) { 232 cmdList.add(args.get(numProcessed)); 233 } 234 File dir = null; 235 String userDir; 236 if ((userDir = System.getProperty("user.dir")) != null) { 237 dir = new File(userDir); 238 } 239 ShellCommandExecutor s = new ShellCommandExecutor( 240 cmdList.toArray(new String[cmdList.size()]), dir); 241 try { 242 s.execute(); 243 allOutput.append(s.getOutput()).append(" "); 244 } catch (Exception e) { 245 LOG.warn("Exception running " + s, e); 246 return null; 247 } 248 loopCount++; 249 } 250 return allOutput.toString(); 251 } 252 253 /** 254 * Declare that the mapper is single-switched if a script was not named 255 * in the configuration. 256 * @return true iff there is no script 257 */ 258 @Override 259 public boolean isSingleSwitch() { 260 return scriptName == null; 261 } 262 263 @Override 264 public String toString() { 265 return scriptName != null ? ("script " + scriptName) : NO_SCRIPT; 266 } 267 } 268}