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