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 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 }