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    package org.apache.hadoop.hdfs;
019    
020    import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_CLIENT_FAILOVER_MAX_ATTEMPTS_DEFAULT;
021    import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_CLIENT_FAILOVER_MAX_ATTEMPTS_KEY;
022    import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_CLIENT_FAILOVER_PROXY_PROVIDER_KEY_PREFIX;
023    import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_CLIENT_FAILOVER_SLEEPTIME_BASE_DEFAULT;
024    import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_CLIENT_FAILOVER_SLEEPTIME_BASE_KEY;
025    import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_CLIENT_FAILOVER_SLEEPTIME_MAX_DEFAULT;
026    import static org.apache.hadoop.hdfs.DFSConfigKeys.DFS_CLIENT_FAILOVER_SLEEPTIME_MAX_KEY;
027    
028    import java.io.IOException;
029    import java.lang.reflect.Constructor;
030    import java.lang.reflect.InvocationHandler;
031    import java.lang.reflect.Proxy;
032    import java.net.InetSocketAddress;
033    import java.net.URI;
034    import java.util.HashMap;
035    import java.util.Map;
036    import java.util.concurrent.TimeUnit;
037    
038    import org.apache.commons.logging.Log;
039    import org.apache.commons.logging.LogFactory;
040    import org.apache.hadoop.conf.Configuration;
041    import org.apache.hadoop.hdfs.DFSClient.Conf;
042    import org.apache.hadoop.hdfs.protocol.AlreadyBeingCreatedException;
043    import org.apache.hadoop.hdfs.protocol.ClientProtocol;
044    import org.apache.hadoop.hdfs.protocol.HdfsConstants;
045    import org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolPB;
046    import org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolTranslatorPB;
047    import org.apache.hadoop.hdfs.protocolPB.JournalProtocolPB;
048    import org.apache.hadoop.hdfs.protocolPB.JournalProtocolTranslatorPB;
049    import org.apache.hadoop.hdfs.protocolPB.NamenodeProtocolPB;
050    import org.apache.hadoop.hdfs.protocolPB.NamenodeProtocolTranslatorPB;
051    import org.apache.hadoop.hdfs.server.namenode.NameNode;
052    import org.apache.hadoop.hdfs.server.namenode.SafeModeException;
053    import org.apache.hadoop.hdfs.server.protocol.JournalProtocol;
054    import org.apache.hadoop.hdfs.server.protocol.NamenodeProtocol;
055    import org.apache.hadoop.hdfs.server.protocol.NamenodeProtocols;
056    import org.apache.hadoop.io.Text;
057    import org.apache.hadoop.io.retry.DefaultFailoverProxyProvider;
058    import org.apache.hadoop.io.retry.FailoverProxyProvider;
059    import org.apache.hadoop.io.retry.LossyRetryInvocationHandler;
060    import org.apache.hadoop.io.retry.RetryPolicies;
061    import org.apache.hadoop.io.retry.RetryPolicy;
062    import org.apache.hadoop.io.retry.RetryProxy;
063    import org.apache.hadoop.io.retry.RetryUtils;
064    import org.apache.hadoop.ipc.ProtobufRpcEngine;
065    import org.apache.hadoop.ipc.RPC;
066    import org.apache.hadoop.ipc.RemoteException;
067    import org.apache.hadoop.net.NetUtils;
068    import org.apache.hadoop.security.RefreshUserMappingsProtocol;
069    import org.apache.hadoop.security.SecurityUtil;
070    import org.apache.hadoop.security.UserGroupInformation;
071    import org.apache.hadoop.security.authorize.RefreshAuthorizationPolicyProtocol;
072    import org.apache.hadoop.security.protocolPB.RefreshAuthorizationPolicyProtocolClientSideTranslatorPB;
073    import org.apache.hadoop.security.protocolPB.RefreshAuthorizationPolicyProtocolPB;
074    import org.apache.hadoop.security.protocolPB.RefreshUserMappingsProtocolClientSideTranslatorPB;
075    import org.apache.hadoop.security.protocolPB.RefreshUserMappingsProtocolPB;
076    import org.apache.hadoop.tools.GetUserMappingsProtocol;
077    import org.apache.hadoop.tools.protocolPB.GetUserMappingsProtocolClientSideTranslatorPB;
078    import org.apache.hadoop.tools.protocolPB.GetUserMappingsProtocolPB;
079    
080    import com.google.common.annotations.VisibleForTesting;
081    import com.google.common.base.Preconditions;
082    
083    /**
084     * Create proxy objects to communicate with a remote NN. All remote access to an
085     * NN should be funneled through this class. Most of the time you'll want to use
086     * {@link NameNodeProxies#createProxy(Configuration, URI, Class)}, which will
087     * create either an HA- or non-HA-enabled client proxy as appropriate.
088     */
089    public class NameNodeProxies {
090      
091      private static final Log LOG = LogFactory.getLog(NameNodeProxies.class);
092    
093      /**
094       * Wrapper for a client proxy as well as its associated service ID.
095       * This is simply used as a tuple-like return type for
096       * {@link NameNodeProxies#createProxy} and
097       * {@link NameNodeProxies#createNonHAProxy}.
098       */
099      public static class ProxyAndInfo<PROXYTYPE> {
100        private final PROXYTYPE proxy;
101        private final Text dtService;
102        
103        public ProxyAndInfo(PROXYTYPE proxy, Text dtService) {
104          this.proxy = proxy;
105          this.dtService = dtService;
106        }
107        
108        public PROXYTYPE getProxy() {
109          return proxy;
110        }
111        
112        public Text getDelegationTokenService() {
113          return dtService;
114        }
115      }
116    
117      /**
118       * Creates the namenode proxy with the passed protocol. This will handle
119       * creation of either HA- or non-HA-enabled proxy objects, depending upon
120       * if the provided URI is a configured logical URI.
121       * 
122       * @param conf the configuration containing the required IPC
123       *        properties, client failover configurations, etc.
124       * @param nameNodeUri the URI pointing either to a specific NameNode
125       *        or to a logical nameservice.
126       * @param xface the IPC interface which should be created
127       * @return an object containing both the proxy and the associated
128       *         delegation token service it corresponds to
129       * @throws IOException if there is an error creating the proxy
130       **/
131      @SuppressWarnings("unchecked")
132      public static <T> ProxyAndInfo<T> createProxy(Configuration conf,
133          URI nameNodeUri, Class<T> xface) throws IOException {
134        Class<FailoverProxyProvider<T>> failoverProxyProviderClass =
135            getFailoverProxyProviderClass(conf, nameNodeUri, xface);
136      
137        if (failoverProxyProviderClass == null) {
138          // Non-HA case
139          return createNonHAProxy(conf, NameNode.getAddress(nameNodeUri), xface,
140              UserGroupInformation.getCurrentUser(), true);
141        } else {
142          // HA case
143          FailoverProxyProvider<T> failoverProxyProvider = NameNodeProxies
144              .createFailoverProxyProvider(conf, failoverProxyProviderClass, xface,
145                  nameNodeUri);
146          Conf config = new Conf(conf);
147          T proxy = (T) RetryProxy.create(xface, failoverProxyProvider, RetryPolicies
148              .failoverOnNetworkException(RetryPolicies.TRY_ONCE_THEN_FAIL,
149                  config.maxFailoverAttempts, config.failoverSleepBaseMillis,
150                  config.failoverSleepMaxMillis));
151          
152          Text dtService = HAUtil.buildTokenServiceForLogicalUri(nameNodeUri);
153          return new ProxyAndInfo<T>(proxy, dtService);
154        }
155      }
156      
157      /**
158       * Generate a dummy namenode proxy instance that utilizes our hacked
159       * {@link LossyRetryInvocationHandler}. Proxy instance generated using this
160       * method will proactively drop RPC responses. Currently this method only
161       * support HA setup. null will be returned if the given configuration is not 
162       * for HA.
163       * 
164       * @param config the configuration containing the required IPC
165       *        properties, client failover configurations, etc.
166       * @param nameNodeUri the URI pointing either to a specific NameNode
167       *        or to a logical nameservice.
168       * @param xface the IPC interface which should be created
169       * @param numResponseToDrop The number of responses to drop for each RPC call
170       * @return an object containing both the proxy and the associated
171       *         delegation token service it corresponds to. Will return null of the
172       *         given configuration does not support HA.
173       * @throws IOException if there is an error creating the proxy
174       */
175      @SuppressWarnings("unchecked")
176      public static <T> ProxyAndInfo<T> createProxyWithLossyRetryHandler(
177          Configuration config, URI nameNodeUri, Class<T> xface,
178          int numResponseToDrop) throws IOException {
179        Preconditions.checkArgument(numResponseToDrop > 0);
180        Class<FailoverProxyProvider<T>> failoverProxyProviderClass = 
181            getFailoverProxyProviderClass(config, nameNodeUri, xface);
182        if (failoverProxyProviderClass != null) { // HA case
183          FailoverProxyProvider<T> failoverProxyProvider = 
184              createFailoverProxyProvider(config, failoverProxyProviderClass, 
185                  xface, nameNodeUri);
186          int delay = config.getInt(
187              DFS_CLIENT_FAILOVER_SLEEPTIME_BASE_KEY,
188              DFS_CLIENT_FAILOVER_SLEEPTIME_BASE_DEFAULT);
189          int maxCap = config.getInt(
190              DFS_CLIENT_FAILOVER_SLEEPTIME_MAX_KEY,
191              DFS_CLIENT_FAILOVER_SLEEPTIME_MAX_DEFAULT);
192          int maxFailoverAttempts = config.getInt(
193              DFS_CLIENT_FAILOVER_MAX_ATTEMPTS_KEY,
194              DFS_CLIENT_FAILOVER_MAX_ATTEMPTS_DEFAULT);
195          InvocationHandler dummyHandler = new LossyRetryInvocationHandler<T>(
196                  numResponseToDrop, failoverProxyProvider,
197                  RetryPolicies.failoverOnNetworkException(
198                      RetryPolicies.TRY_ONCE_THEN_FAIL, 
199                      Math.max(numResponseToDrop + 1, maxFailoverAttempts), delay, 
200                      maxCap));
201          
202          T proxy = (T) Proxy.newProxyInstance(
203              failoverProxyProvider.getInterface().getClassLoader(),
204              new Class[] { xface }, dummyHandler);
205          Text dtService = HAUtil.buildTokenServiceForLogicalUri(nameNodeUri);
206          return new ProxyAndInfo<T>(proxy, dtService);
207        } else {
208          LOG.warn("Currently creating proxy using " +
209                    "LossyRetryInvocationHandler requires NN HA setup");
210          return null;
211        }
212      }
213    
214      /**
215       * Creates an explicitly non-HA-enabled proxy object. Most of the time you
216       * don't want to use this, and should instead use {@link NameNodeProxies#createProxy}.
217       * 
218       * @param conf the configuration object
219       * @param nnAddr address of the remote NN to connect to
220       * @param xface the IPC interface which should be created
221       * @param ugi the user who is making the calls on the proxy object
222       * @param withRetries certain interfaces have a non-standard retry policy
223       * @return an object containing both the proxy and the associated
224       *         delegation token service it corresponds to
225       * @throws IOException
226       */
227      @SuppressWarnings("unchecked")
228      public static <T> ProxyAndInfo<T> createNonHAProxy(
229          Configuration conf, InetSocketAddress nnAddr, Class<T> xface,
230          UserGroupInformation ugi, boolean withRetries) throws IOException {
231        Text dtService = SecurityUtil.buildTokenService(nnAddr);
232      
233        T proxy;
234        if (xface == ClientProtocol.class) {
235          proxy = (T) createNNProxyWithClientProtocol(nnAddr, conf, ugi,
236              withRetries);
237        } else if (xface == JournalProtocol.class) {
238          proxy = (T) createNNProxyWithJournalProtocol(nnAddr, conf, ugi);
239        } else if (xface == NamenodeProtocol.class) {
240          proxy = (T) createNNProxyWithNamenodeProtocol(nnAddr, conf, ugi,
241              withRetries);
242        } else if (xface == GetUserMappingsProtocol.class) {
243          proxy = (T) createNNProxyWithGetUserMappingsProtocol(nnAddr, conf, ugi);
244        } else if (xface == RefreshUserMappingsProtocol.class) {
245          proxy = (T) createNNProxyWithRefreshUserMappingsProtocol(nnAddr, conf, ugi);
246        } else if (xface == RefreshAuthorizationPolicyProtocol.class) {
247          proxy = (T) createNNProxyWithRefreshAuthorizationPolicyProtocol(nnAddr,
248              conf, ugi);
249        } else {
250          String message = "Upsupported protocol found when creating the proxy " +
251              "connection to NameNode: " +
252              ((xface != null) ? xface.getClass().getName() : "null");
253          LOG.error(message);
254          throw new IllegalStateException(message);
255        }
256        return new ProxyAndInfo<T>(proxy, dtService);
257      }
258      
259      private static JournalProtocol createNNProxyWithJournalProtocol(
260          InetSocketAddress address, Configuration conf, UserGroupInformation ugi)
261          throws IOException {
262        JournalProtocolPB proxy = (JournalProtocolPB) createNameNodeProxy(address,
263            conf, ugi, JournalProtocolPB.class);
264        return new JournalProtocolTranslatorPB(proxy);
265      }
266    
267      private static RefreshAuthorizationPolicyProtocol
268          createNNProxyWithRefreshAuthorizationPolicyProtocol(InetSocketAddress address,
269              Configuration conf, UserGroupInformation ugi) throws IOException {
270        RefreshAuthorizationPolicyProtocolPB proxy = (RefreshAuthorizationPolicyProtocolPB)
271            createNameNodeProxy(address, conf, ugi, RefreshAuthorizationPolicyProtocolPB.class);
272        return new RefreshAuthorizationPolicyProtocolClientSideTranslatorPB(proxy);
273      }
274      
275      private static RefreshUserMappingsProtocol
276          createNNProxyWithRefreshUserMappingsProtocol(InetSocketAddress address,
277              Configuration conf, UserGroupInformation ugi) throws IOException {
278        RefreshUserMappingsProtocolPB proxy = (RefreshUserMappingsProtocolPB)
279            createNameNodeProxy(address, conf, ugi, RefreshUserMappingsProtocolPB.class);
280        return new RefreshUserMappingsProtocolClientSideTranslatorPB(proxy);
281      }
282    
283      private static GetUserMappingsProtocol createNNProxyWithGetUserMappingsProtocol(
284          InetSocketAddress address, Configuration conf, UserGroupInformation ugi)
285          throws IOException {
286        GetUserMappingsProtocolPB proxy = (GetUserMappingsProtocolPB)
287            createNameNodeProxy(address, conf, ugi, GetUserMappingsProtocolPB.class);
288        return new GetUserMappingsProtocolClientSideTranslatorPB(proxy);
289      }
290      
291      private static NamenodeProtocol createNNProxyWithNamenodeProtocol(
292          InetSocketAddress address, Configuration conf, UserGroupInformation ugi,
293          boolean withRetries) throws IOException {
294        NamenodeProtocolPB proxy = (NamenodeProtocolPB) createNameNodeProxy(
295            address, conf, ugi, NamenodeProtocolPB.class);
296        if (withRetries) { // create the proxy with retries
297          RetryPolicy timeoutPolicy = RetryPolicies.exponentialBackoffRetry(5, 200,
298              TimeUnit.MILLISECONDS);
299          Map<Class<? extends Exception>, RetryPolicy> exceptionToPolicyMap 
300                         = new HashMap<Class<? extends Exception>, RetryPolicy>();
301          RetryPolicy methodPolicy = RetryPolicies.retryByException(timeoutPolicy,
302              exceptionToPolicyMap);
303          Map<String, RetryPolicy> methodNameToPolicyMap 
304                         = new HashMap<String, RetryPolicy>();
305          methodNameToPolicyMap.put("getBlocks", methodPolicy);
306          methodNameToPolicyMap.put("getAccessKeys", methodPolicy);
307          proxy = (NamenodeProtocolPB) RetryProxy.create(NamenodeProtocolPB.class,
308              proxy, methodNameToPolicyMap);
309        }
310        return new NamenodeProtocolTranslatorPB(proxy);
311      }
312      
313      private static ClientProtocol createNNProxyWithClientProtocol(
314          InetSocketAddress address, Configuration conf, UserGroupInformation ugi,
315          boolean withRetries) throws IOException {
316        RPC.setProtocolEngine(conf, ClientNamenodeProtocolPB.class, ProtobufRpcEngine.class);
317    
318        final RetryPolicy defaultPolicy = 
319            RetryUtils.getDefaultRetryPolicy(
320                conf, 
321                DFSConfigKeys.DFS_CLIENT_RETRY_POLICY_ENABLED_KEY, 
322                DFSConfigKeys.DFS_CLIENT_RETRY_POLICY_ENABLED_DEFAULT, 
323                DFSConfigKeys.DFS_CLIENT_RETRY_POLICY_SPEC_KEY,
324                DFSConfigKeys.DFS_CLIENT_RETRY_POLICY_SPEC_DEFAULT,
325                SafeModeException.class);
326        
327        final long version = RPC.getProtocolVersion(ClientNamenodeProtocolPB.class);
328        ClientNamenodeProtocolPB proxy = RPC.getProtocolProxy(
329            ClientNamenodeProtocolPB.class, version, address, ugi, conf,
330            NetUtils.getDefaultSocketFactory(conf),
331            org.apache.hadoop.ipc.Client.getTimeout(conf), defaultPolicy)
332                .getProxy();
333    
334        if (withRetries) { // create the proxy with retries
335    
336          RetryPolicy createPolicy = RetryPolicies
337              .retryUpToMaximumCountWithFixedSleep(5,
338                  HdfsConstants.LEASE_SOFTLIMIT_PERIOD, TimeUnit.MILLISECONDS);
339        
340          Map<Class<? extends Exception>, RetryPolicy> remoteExceptionToPolicyMap 
341                     = new HashMap<Class<? extends Exception>, RetryPolicy>();
342          remoteExceptionToPolicyMap.put(AlreadyBeingCreatedException.class,
343              createPolicy);
344        
345          Map<Class<? extends Exception>, RetryPolicy> exceptionToPolicyMap
346                     = new HashMap<Class<? extends Exception>, RetryPolicy>();
347          exceptionToPolicyMap.put(RemoteException.class, RetryPolicies
348              .retryByRemoteException(defaultPolicy,
349                  remoteExceptionToPolicyMap));
350          RetryPolicy methodPolicy = RetryPolicies.retryByException(
351              defaultPolicy, exceptionToPolicyMap);
352          Map<String, RetryPolicy> methodNameToPolicyMap 
353                     = new HashMap<String, RetryPolicy>();
354        
355          methodNameToPolicyMap.put("create", methodPolicy);
356        
357          proxy = (ClientNamenodeProtocolPB) RetryProxy.create(
358              ClientNamenodeProtocolPB.class,
359              new DefaultFailoverProxyProvider<ClientNamenodeProtocolPB>(
360                  ClientNamenodeProtocolPB.class, proxy),
361              methodNameToPolicyMap,
362              defaultPolicy);
363        }
364        return new ClientNamenodeProtocolTranslatorPB(proxy);
365      }
366      
367      private static Object createNameNodeProxy(InetSocketAddress address,
368          Configuration conf, UserGroupInformation ugi, Class<?> xface)
369          throws IOException {
370        RPC.setProtocolEngine(conf, xface, ProtobufRpcEngine.class);
371        Object proxy = RPC.getProxy(xface, RPC.getProtocolVersion(xface), address,
372            ugi, conf, NetUtils.getDefaultSocketFactory(conf));
373        return proxy;
374      }
375    
376      /** Gets the configured Failover proxy provider's class */
377      @VisibleForTesting
378      public static <T> Class<FailoverProxyProvider<T>> getFailoverProxyProviderClass(
379          Configuration conf, URI nameNodeUri, Class<T> xface) throws IOException {
380        if (nameNodeUri == null) {
381          return null;
382        }
383        String host = nameNodeUri.getHost();
384      
385        String configKey = DFS_CLIENT_FAILOVER_PROXY_PROVIDER_KEY_PREFIX + "."
386            + host;
387        try {
388          @SuppressWarnings("unchecked")
389          Class<FailoverProxyProvider<T>> ret = (Class<FailoverProxyProvider<T>>) conf
390              .getClass(configKey, null, FailoverProxyProvider.class);
391          if (ret != null) {
392            // If we found a proxy provider, then this URI should be a logical NN.
393            // Given that, it shouldn't have a non-default port number.
394            int port = nameNodeUri.getPort();
395            if (port > 0 && port != NameNode.DEFAULT_PORT) {
396              throw new IOException("Port " + port + " specified in URI "
397                  + nameNodeUri + " but host '" + host
398                  + "' is a logical (HA) namenode"
399                  + " and does not use port information.");
400            }
401          }
402          return ret;
403        } catch (RuntimeException e) {
404          if (e.getCause() instanceof ClassNotFoundException) {
405            throw new IOException("Could not load failover proxy provider class "
406                + conf.get(configKey) + " which is configured for authority "
407                + nameNodeUri, e);
408          } else {
409            throw e;
410          }
411        }
412      }
413    
414      /** Creates the Failover proxy provider instance*/
415      @VisibleForTesting
416      public static <T> FailoverProxyProvider<T> createFailoverProxyProvider(
417          Configuration conf, Class<FailoverProxyProvider<T>> failoverProxyProviderClass,
418          Class<T> xface, URI nameNodeUri) throws IOException {
419        Preconditions.checkArgument(
420            xface.isAssignableFrom(NamenodeProtocols.class),
421            "Interface %s is not a NameNode protocol", xface);
422        try {
423          Constructor<FailoverProxyProvider<T>> ctor = failoverProxyProviderClass
424              .getConstructor(Configuration.class, URI.class, Class.class);
425          FailoverProxyProvider<T> provider = ctor.newInstance(conf, nameNodeUri,
426              xface);
427          return provider;
428        } catch (Exception e) {
429          String message = "Couldn't create proxy provider " + failoverProxyProviderClass;
430          if (LOG.isDebugEnabled()) {
431            LOG.debug(message, e);
432          }
433          if (e.getCause() instanceof IOException) {
434            throw (IOException) e.getCause();
435          } else {
436            throw new IOException(message, e);
437          }
438        }
439      }
440    
441    }