View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.hadoop.hbase.ipc;
20  
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.apache.hadoop.conf.Configuration;
24  import org.apache.hadoop.hbase.io.HbaseObjectWritable;
25  import org.apache.hadoop.hbase.monitoring.MonitoredRPCHandler;
26  import org.apache.hadoop.hbase.security.HBasePolicyProvider;
27  import org.apache.hadoop.hbase.security.HBaseSaslRpcServer;
28  import org.apache.hadoop.hbase.security.User;
29  import org.apache.hadoop.hbase.security.token.AuthenticationTokenSecretManager;
30  import org.apache.hadoop.hbase.util.Objects;
31  import org.apache.hadoop.io.Writable;
32  import org.apache.hadoop.security.authorize.ServiceAuthorizationManager;
33  
34  import java.io.IOException;
35  import java.lang.reflect.*;
36  import java.net.InetSocketAddress;
37  
38  /**
39   * A loadable RPC engine supporting SASL authentication of connections, using
40   * GSSAPI for Kerberos authentication or DIGEST-MD5 for authentication via
41   * signed tokens.
42   *
43   * <p>
44   * This is a fork of the {@code org.apache.hadoop.ipc.WriteableRpcEngine} from
45   * secure Hadoop, reworked to eliminate code duplication with the existing
46   * HBase {@link WritableRpcEngine}.
47   * </p>
48   *
49   * @see SecureClient
50   * @see SecureServer
51   */
52  public class SecureRpcEngine implements RpcEngine {
53    // Leave this out in the hadoop ipc package but keep class name.  Do this
54    // so that we do not get the logging of this class' invocations by doing our
55    // blanket enabling DEBUG on the o.a.h.h. package.
56    protected static final Log LOG =
57      LogFactory.getLog("org.apache.hadoop.ipc.SecureRpcEngine");
58  
59    private Configuration conf;
60    private SecureClient client;
61  
62    @Override
63    public void setConf(Configuration config) {
64      this.conf = config;
65      if (User.isHBaseSecurityEnabled(conf)) {
66        HBaseSaslRpcServer.init(conf);
67      }
68      // check for an already created client
69      if (this.client != null) {
70        this.client.stop();
71      }
72      this.client = new SecureClient(HbaseObjectWritable.class, conf);
73    }
74  
75    @Override
76    public Configuration getConf() {
77      return this.conf;
78    }
79  
80    private static class Invoker implements InvocationHandler {
81      private Class<? extends VersionedProtocol> protocol;
82      private InetSocketAddress address;
83      private User ticket;
84      private SecureClient client;
85      final private int rpcTimeout;
86  
87      public Invoker(SecureClient client,
88          Class<? extends VersionedProtocol> protocol,
89          InetSocketAddress address, User ticket, int rpcTimeout) {
90        this.protocol = protocol;
91        this.address = address;
92        this.ticket = ticket;
93        this.client = client;
94        this.rpcTimeout = rpcTimeout;
95      }
96  
97      public Object invoke(Object proxy, Method method, Object[] args)
98          throws Throwable {
99        final boolean logDebug = LOG.isDebugEnabled();
100       long startTime = 0;
101       if (logDebug) {
102         startTime = System.currentTimeMillis();
103       }
104       HbaseObjectWritable value = (HbaseObjectWritable)
105         client.call(new Invocation(method, protocol, args), address,
106                     protocol, ticket, rpcTimeout);
107       if (logDebug) {
108         long callTime = System.currentTimeMillis() - startTime;
109         LOG.debug("Call: " + method.getName() + " " + callTime);
110       }
111       return value.get();
112     }
113   }
114 
115   /**
116    * Construct a client-side proxy object that implements the named protocol,
117    * talking to a server at the named address.
118    *
119    * @param protocol interface
120    * @param clientVersion version we are expecting
121    * @param addr remote address
122    * @param conf configuration
123    * @return proxy
124    * @throws java.io.IOException e
125    */
126   @Override
127   public <T extends VersionedProtocol> T getProxy(
128       Class<T> protocol, long clientVersion,
129       InetSocketAddress addr,
130       Configuration conf, int rpcTimeout)
131   throws IOException {
132     if (this.client == null) {
133       throw new IOException("Client must be initialized by calling setConf(Configuration)");
134     }
135 
136     T proxy =
137         (T) Proxy.newProxyInstance(
138             protocol.getClassLoader(), new Class[] { protocol },
139             new Invoker(this.client, protocol, addr, User.getCurrent(),
140                 HBaseRPC.getRpcTimeout(rpcTimeout)));
141     /*
142      * TODO: checking protocol version only needs to be done once when we setup a new
143      * SecureClient.Connection.  Doing it every time we retrieve a proxy instance is resulting
144      * in unnecessary RPC traffic.
145      */
146     long serverVersion = proxy.getProtocolVersion(protocol.getName(),
147                                                   clientVersion);
148     if (serverVersion != clientVersion) {
149       throw new HBaseRPC.VersionMismatch(protocol.getName(), clientVersion,
150                                 serverVersion);
151     }
152     return proxy;
153   }
154 
155   /** Expert: Make multiple, parallel calls to a set of servers. */
156   @Override
157   public Object[] call(Method method, Object[][] params,
158                        InetSocketAddress[] addrs,
159                        Class<? extends VersionedProtocol> protocol,
160                        User ticket, Configuration conf)
161     throws IOException, InterruptedException {
162     if (this.client == null) {
163       throw new IOException("Client must be initialized by calling setConf(Configuration)");
164     }
165 
166     Invocation[] invocations = new Invocation[params.length];
167     for (int i = 0; i < params.length; i++) {
168       invocations[i] = new Invocation(method, protocol, params[i]);
169     }
170 
171     Writable[] wrappedValues =
172       client.call(invocations, addrs, protocol, ticket);
173 
174     if (method.getReturnType() == Void.TYPE) {
175       return null;
176     }
177 
178     Object[] values =
179         (Object[])Array.newInstance(method.getReturnType(), wrappedValues.length);
180     for (int i = 0; i < values.length; i++)
181       if (wrappedValues[i] != null)
182         values[i] = ((HbaseObjectWritable)wrappedValues[i]).get();
183 
184     return values;
185   }
186 
187   @Override
188   public void close() {
189     if (this.client != null) {
190       this.client.stop();
191     }
192   }
193 
194   /** Construct a server for a protocol implementation instance listening on a
195    * port and address, with a secret manager. */
196   @Override
197   public Server getServer(Class<? extends VersionedProtocol> protocol,
198       final Object instance,
199       Class<?>[] ifaces,
200       final String bindAddress, final int port,
201       final int numHandlers,
202       int metaHandlerCount, final boolean verbose, Configuration conf,
203        int highPriorityLevel)
204     throws IOException {
205     Server server = new Server(instance, ifaces, conf, bindAddress, port,
206             numHandlers, metaHandlerCount, verbose,
207             highPriorityLevel);
208     return server;
209   }
210 
211   /** An RPC Server. */
212   public static class Server extends SecureServer {
213     private Object instance;
214     private Class<?> implementation;
215     private Class<?>[] ifaces;
216     private boolean verbose;
217 
218     private static String classNameBase(String className) {
219       String[] names = className.split("\\.", -1);
220       if (names == null || names.length == 0) {
221         return className;
222       }
223       return names[names.length-1];
224     }
225 
226     /** Construct an RPC server.
227      * @param instance the instance whose methods will be called
228      * @param conf the configuration to use
229      * @param bindAddress the address to bind on to listen for connection
230      * @param port the port to listen for connections on
231      * @param numHandlers the number of method handler threads to run
232      * @param verbose whether each call should be logged
233      * @throws java.io.IOException e
234      */
235     public Server(Object instance, final Class<?>[] ifaces,
236                   Configuration conf, String bindAddress,  int port,
237                   int numHandlers, int metaHandlerCount, boolean verbose,
238                   int highPriorityLevel)
239         throws IOException {
240       super(bindAddress, port, Invocation.class, numHandlers, metaHandlerCount, conf,
241           classNameBase(instance.getClass().getName()), highPriorityLevel);
242       this.instance = instance;
243       this.implementation = instance.getClass();
244       this.verbose = verbose;
245 
246       this.ifaces = ifaces;
247 
248       // create metrics for the advertised interfaces this server implements.
249       this.rpcMetrics.createMetrics(this.ifaces);
250     }
251 
252     public AuthenticationTokenSecretManager createSecretManager(){
253       if (instance instanceof org.apache.hadoop.hbase.Server) {
254         org.apache.hadoop.hbase.Server server =
255             (org.apache.hadoop.hbase.Server)instance;
256         Configuration conf = server.getConfiguration();
257         long keyUpdateInterval =
258             conf.getLong("hbase.auth.key.update.interval", 24*60*60*1000);
259         long maxAge =
260             conf.getLong("hbase.auth.token.max.lifetime", 7*24*60*60*1000);
261         return new AuthenticationTokenSecretManager(conf, server.getZooKeeper(),
262             server.getServerName().toString(), keyUpdateInterval, maxAge);
263       }
264       return null;
265     }
266 
267     @Override
268     public void startThreads() {
269       AuthenticationTokenSecretManager mgr = createSecretManager();
270       if (mgr != null) {
271         setSecretManager(mgr);
272         mgr.start();
273       }
274       this.authManager = new ServiceAuthorizationManager();
275       HBasePolicyProvider.init(conf, authManager);
276 
277       // continue with base startup
278       super.startThreads();
279     }
280 
281     @Override
282     public Writable call(Class<? extends VersionedProtocol> protocol,
283         Writable param, long receivedTime, MonitoredRPCHandler status)
284     throws IOException {
285       try {
286         Invocation call = (Invocation)param;
287         if(call.getMethodName() == null) {
288           throw new IOException("Could not find requested method, the usual " +
289               "cause is a version mismatch between client and server.");
290         }
291         if (verbose) log("Call: " + call);
292 
293         Method method =
294           protocol.getMethod(call.getMethodName(),
295                                    call.getParameterClasses());
296         method.setAccessible(true);
297 
298         Object impl = null;
299         if (protocol.isAssignableFrom(this.implementation)) {
300           impl = this.instance;
301         }
302         else {
303           throw new HBaseRPC.UnknownProtocolException(protocol);
304         }
305 
306         long startTime = System.currentTimeMillis();
307         Object[] params = call.getParameters();
308         Object value = method.invoke(impl, params);
309         int processingTime = (int) (System.currentTimeMillis() - startTime);
310         int qTime = (int) (startTime-receivedTime);
311         if (TRACELOG.isDebugEnabled()) {
312           TRACELOG.debug("Call #" + CurCall.get().id +
313               "; Served: " + protocol.getSimpleName()+"#"+call.getMethodName() +
314               " queueTime=" + qTime +
315               " processingTime=" + processingTime +
316               " contents=" + Objects.describeQuantity(params));
317         }
318         rpcMetrics.rpcQueueTime.inc(qTime);
319         rpcMetrics.rpcProcessingTime.inc(processingTime);
320         rpcMetrics.inc(call.getMethodName(), processingTime);
321         if (verbose) log("Return: "+value);
322 
323         return new HbaseObjectWritable(method.getReturnType(), value);
324       } catch (InvocationTargetException e) {
325         Throwable target = e.getTargetException();
326         if (target instanceof IOException) {
327           throw (IOException)target;
328         }
329         IOException ioe = new IOException(target.toString());
330         ioe.setStackTrace(target.getStackTrace());
331         throw ioe;
332       } catch (Throwable e) {
333         if (!(e instanceof IOException)) {
334           LOG.error("Unexpected throwable object ", e);
335         }
336         IOException ioe = new IOException(e.toString());
337         ioe.setStackTrace(e.getStackTrace());
338         throw ioe;
339       }
340     }
341   }
342 
343   protected static void log(String value) {
344     String v = value;
345     if (v != null && v.length() > 55)
346       v = v.substring(0, 55)+"...";
347     LOG.info(v);
348   }
349 }