View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.thrift2;
20  
21  import java.io.IOException;
22  import java.net.InetAddress;
23  import java.net.InetSocketAddress;
24  import java.net.UnknownHostException;
25  import java.security.PrivilegedAction;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.concurrent.ExecutorService;
30  import java.util.concurrent.LinkedBlockingQueue;
31  import java.util.concurrent.SynchronousQueue;
32  import java.util.concurrent.ThreadPoolExecutor;
33  import java.util.concurrent.TimeUnit;
34  
35  import javax.security.auth.callback.Callback;
36  import javax.security.auth.callback.UnsupportedCallbackException;
37  import javax.security.sasl.AuthorizeCallback;
38  import javax.security.sasl.Sasl;
39  import javax.security.sasl.SaslServer;
40  
41  import org.apache.commons.cli.CommandLine;
42  import org.apache.commons.cli.CommandLineParser;
43  import org.apache.commons.cli.HelpFormatter;
44  import org.apache.commons.cli.Option;
45  import org.apache.commons.cli.OptionGroup;
46  import org.apache.commons.cli.Options;
47  import org.apache.commons.cli.ParseException;
48  import org.apache.commons.cli.PosixParser;
49  import org.apache.commons.logging.Log;
50  import org.apache.commons.logging.LogFactory;
51  import org.apache.hadoop.hbase.classification.InterfaceAudience;
52  import org.apache.hadoop.conf.Configuration;
53  import org.apache.hadoop.hbase.HBaseConfiguration;
54  import org.apache.hadoop.hbase.HBaseInterfaceAudience;
55  import org.apache.hadoop.hbase.filter.ParseFilter;
56  import org.apache.hadoop.hbase.http.InfoServer;
57  import org.apache.hadoop.hbase.security.SaslUtil;
58  import org.apache.hadoop.hbase.security.SecurityUtil;
59  import org.apache.hadoop.hbase.security.UserProvider;
60  import org.apache.hadoop.hbase.thrift.CallQueue;
61  import org.apache.hadoop.hbase.thrift.CallQueue.Call;
62  import org.apache.hadoop.hbase.thrift.THBaseThreadPoolExecutor;
63  import org.apache.hadoop.hbase.thrift.ThriftMetrics;
64  import org.apache.hadoop.hbase.thrift2.generated.THBaseService;
65  import org.apache.hadoop.hbase.util.DNS;
66  import org.apache.hadoop.hbase.util.Strings;
67  import org.apache.hadoop.security.UserGroupInformation;
68  import org.apache.hadoop.security.SaslRpcServer.SaslGssCallbackHandler;
69  import org.apache.hadoop.util.GenericOptionsParser;
70  import org.apache.thrift.TException;
71  import org.apache.thrift.TProcessor;
72  import org.apache.thrift.protocol.TBinaryProtocol;
73  import org.apache.thrift.protocol.TCompactProtocol;
74  import org.apache.thrift.protocol.TProtocol;
75  import org.apache.thrift.protocol.TProtocolFactory;
76  import org.apache.thrift.server.THsHaServer;
77  import org.apache.thrift.server.TNonblockingServer;
78  import org.apache.thrift.server.TServer;
79  import org.apache.thrift.server.TThreadPoolServer;
80  import org.apache.thrift.transport.TFramedTransport;
81  import org.apache.thrift.transport.TNonblockingServerSocket;
82  import org.apache.thrift.transport.TNonblockingServerTransport;
83  import org.apache.thrift.transport.TSaslServerTransport;
84  import org.apache.thrift.transport.TServerSocket;
85  import org.apache.thrift.transport.TServerTransport;
86  import org.apache.thrift.transport.TTransportException;
87  import org.apache.thrift.transport.TTransportFactory;
88  
89  import com.google.common.util.concurrent.ThreadFactoryBuilder;
90  
91  /**
92   * ThriftServer - this class starts up a Thrift server which implements the HBase API specified in the
93   * HbaseClient.thrift IDL file.
94   */
95  @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.TOOLS)
96  @SuppressWarnings({ "rawtypes", "unchecked" })
97  public class ThriftServer {
98    private static final Log log = LogFactory.getLog(ThriftServer.class);
99  
100   /**
101    * Thrift quality of protection configuration key. Valid values can be:
102    * privacy: authentication, integrity and confidentiality checking
103    * integrity: authentication and integrity checking
104    * authentication: authentication only
105    *
106    * This is used to authenticate the callers and support impersonation.
107    * The thrift server and the HBase cluster must run in secure mode.
108    */
109   static final String THRIFT_QOP_KEY = "hbase.thrift.security.qop";
110 
111   static final String BACKLOG_CONF_KEY = "hbase.regionserver.thrift.backlog";
112 
113   public static final int DEFAULT_LISTEN_PORT = 9090;
114 
115   private static final String READ_TIMEOUT_OPTION = "readTimeout";
116 
117   /**
118    * Amount of time in milliseconds before a server thread will timeout
119    * waiting for client to send data on a connected socket. Currently,
120    * applies only to TBoundedThreadPoolServer
121    */
122   public static final String THRIFT_SERVER_SOCKET_READ_TIMEOUT_KEY =
123     "hbase.thrift.server.socket.read.timeout";
124   public static final int THRIFT_SERVER_SOCKET_READ_TIMEOUT_DEFAULT = 60000;
125 
126   public ThriftServer() {
127   }
128 
129   private static void printUsage() {
130     HelpFormatter formatter = new HelpFormatter();
131     formatter.printHelp("Thrift", null, getOptions(),
132         "To start the Thrift server run 'bin/hbase-daemon.sh start thrift2'\n" +
133             "To shutdown the thrift server run 'bin/hbase-daemon.sh stop thrift2' or" +
134             " send a kill signal to the thrift server pid",
135         true);
136   }
137 
138   private static Options getOptions() {
139     Options options = new Options();
140     options.addOption("b", "bind", true,
141         "Address to bind the Thrift server to. [default: 0.0.0.0]");
142     options.addOption("p", "port", true, "Port to bind to [default: " + DEFAULT_LISTEN_PORT + "]");
143     options.addOption("f", "framed", false, "Use framed transport");
144     options.addOption("c", "compact", false, "Use the compact protocol");
145     options.addOption("w", "workers", true, "How many worker threads to use.");
146     options.addOption("q", "callQueueSize", true,
147       "Max size of request queue (unbounded by default)");
148     options.addOption("h", "help", false, "Print help information");
149     options.addOption(null, "infoport", true, "Port for web UI");
150     options.addOption("t", READ_TIMEOUT_OPTION, true,
151       "Amount of time in milliseconds before a server thread will timeout " +
152       "waiting for client to send data on a connected socket. Currently, " +
153       "only applies to TBoundedThreadPoolServer");
154     OptionGroup servers = new OptionGroup();
155     servers.addOption(
156         new Option("nonblocking", false, "Use the TNonblockingServer. This implies the framed transport."));
157     servers.addOption(new Option("hsha", false, "Use the THsHaServer. This implies the framed transport."));
158     servers.addOption(new Option("threadpool", false, "Use the TThreadPoolServer. This is the default."));
159     options.addOptionGroup(servers);
160     return options;
161   }
162 
163   private static CommandLine parseArguments(Configuration conf, Options options, String[] args)
164       throws ParseException, IOException {
165     GenericOptionsParser genParser = new GenericOptionsParser(conf, args);
166     String[] remainingArgs = genParser.getRemainingArgs();
167     CommandLineParser parser = new PosixParser();
168     return parser.parse(options, remainingArgs);
169   }
170 
171   private static TProtocolFactory getTProtocolFactory(boolean isCompact) {
172     if (isCompact) {
173       log.debug("Using compact protocol");
174       return new TCompactProtocol.Factory();
175     } else {
176       log.debug("Using binary protocol");
177       return new TBinaryProtocol.Factory();
178     }
179   }
180 
181   private static TTransportFactory getTTransportFactory(
182       SaslUtil.QualityOfProtection qop, String name, String host,
183       boolean framed, int frameSize) {
184     if (framed) {
185       if (qop != null) {
186         throw new RuntimeException("Thrift server authentication"
187           + " doesn't work with framed transport yet");
188       }
189       log.debug("Using framed transport");
190       return new TFramedTransport.Factory(frameSize);
191     } else if (qop == null) {
192       return new TTransportFactory();
193     } else {
194       Map<String, String> saslProperties = new HashMap<String, String>();
195       saslProperties.put(Sasl.QOP, qop.getSaslQop());
196       TSaslServerTransport.Factory saslFactory = new TSaslServerTransport.Factory();
197       saslFactory.addServerDefinition("GSSAPI", name, host, saslProperties,
198         new SaslGssCallbackHandler() {
199           @Override
200           public void handle(Callback[] callbacks)
201               throws UnsupportedCallbackException {
202             AuthorizeCallback ac = null;
203             for (Callback callback : callbacks) {
204               if (callback instanceof AuthorizeCallback) {
205                 ac = (AuthorizeCallback) callback;
206               } else {
207                 throw new UnsupportedCallbackException(callback,
208                     "Unrecognized SASL GSSAPI Callback");
209               }
210             }
211             if (ac != null) {
212               String authid = ac.getAuthenticationID();
213               String authzid = ac.getAuthorizationID();
214               if (!authid.equals(authzid)) {
215                 ac.setAuthorized(false);
216               } else {
217                 ac.setAuthorized(true);
218                 String userName = SecurityUtil.getUserFromPrincipal(authzid);
219                 log.info("Effective user: " + userName);
220                 ac.setAuthorizedID(userName);
221               }
222             }
223           }
224         });
225       return saslFactory;
226     }
227   }
228 
229   /*
230    * If bindValue is null, we don't bind.
231    */
232   private static InetSocketAddress bindToPort(String bindValue, int listenPort)
233       throws UnknownHostException {
234     try {
235       if (bindValue == null) {
236         return new InetSocketAddress(listenPort);
237       } else {
238         return new InetSocketAddress(InetAddress.getByName(bindValue), listenPort);
239       }
240     } catch (UnknownHostException e) {
241       throw new RuntimeException("Could not bind to provided ip address", e);
242     }
243   }
244 
245   private static TServer getTNonBlockingServer(TProtocolFactory protocolFactory, TProcessor processor,
246       TTransportFactory transportFactory, InetSocketAddress inetSocketAddress) throws TTransportException {
247     TNonblockingServerTransport serverTransport = new TNonblockingServerSocket(inetSocketAddress);
248     log.info("starting HBase Nonblocking Thrift server on " + inetSocketAddress.toString());
249     TNonblockingServer.Args serverArgs = new TNonblockingServer.Args(serverTransport);
250     serverArgs.processor(processor);
251     serverArgs.transportFactory(transportFactory);
252     serverArgs.protocolFactory(protocolFactory);
253     return new TNonblockingServer(serverArgs);
254   }
255 
256   private static TServer getTHsHaServer(TProtocolFactory protocolFactory,
257       TProcessor processor, TTransportFactory transportFactory,
258       int workerThreads, int maxCallQueueSize,
259       InetSocketAddress inetSocketAddress, ThriftMetrics metrics)
260       throws TTransportException {
261     TNonblockingServerTransport serverTransport = new TNonblockingServerSocket(inetSocketAddress);
262     log.info("starting HBase HsHA Thrift server on " + inetSocketAddress.toString());
263     THsHaServer.Args serverArgs = new THsHaServer.Args(serverTransport);
264     if (workerThreads > 0) {
265       // Could support the min & max threads, avoiding to preserve existing functionality.
266       serverArgs.minWorkerThreads(workerThreads).maxWorkerThreads(workerThreads);
267     }
268     ExecutorService executorService = createExecutor(
269         workerThreads, maxCallQueueSize, metrics);
270     serverArgs.executorService(executorService);
271     serverArgs.processor(processor);
272     serverArgs.transportFactory(transportFactory);
273     serverArgs.protocolFactory(protocolFactory);
274     return new THsHaServer(serverArgs);
275   }
276 
277   private static ExecutorService createExecutor(
278       int workerThreads, int maxCallQueueSize, ThriftMetrics metrics) {
279     CallQueue callQueue;
280     if (maxCallQueueSize > 0) {
281       callQueue = new CallQueue(new LinkedBlockingQueue<Call>(maxCallQueueSize), metrics);
282     } else {
283       callQueue = new CallQueue(new LinkedBlockingQueue<Call>(), metrics);
284     }
285 
286     ThreadFactoryBuilder tfb = new ThreadFactoryBuilder();
287     tfb.setDaemon(true);
288     tfb.setNameFormat("thrift2-worker-%d");
289     ThreadPoolExecutor pool = new THBaseThreadPoolExecutor(workerThreads, workerThreads,
290             Long.MAX_VALUE, TimeUnit.SECONDS, callQueue, tfb.build(), metrics);
291     pool.prestartAllCoreThreads();
292     return pool;
293   }
294 
295   private static TServer getTThreadPoolServer(TProtocolFactory protocolFactory,
296                                               TProcessor processor,
297                                               TTransportFactory transportFactory,
298                                               int workerThreads,
299                                               InetSocketAddress inetSocketAddress,
300                                               int backlog,
301                                               int clientTimeout,
302                                               ThriftMetrics metrics)
303       throws TTransportException {
304     TServerTransport serverTransport = new TServerSocket(
305                                            new TServerSocket.ServerSocketTransportArgs().
306                                                bindAddr(inetSocketAddress).backlog(backlog).
307                                                clientTimeout(clientTimeout));
308     log.info("starting HBase ThreadPool Thrift server on " + inetSocketAddress.toString());
309     TThreadPoolServer.Args serverArgs = new TThreadPoolServer.Args(serverTransport);
310     serverArgs.processor(processor);
311     serverArgs.transportFactory(transportFactory);
312     serverArgs.protocolFactory(protocolFactory);
313     if (workerThreads > 0) {
314       serverArgs.maxWorkerThreads(workerThreads);
315     }
316     ThreadPoolExecutor executor = new THBaseThreadPoolExecutor(serverArgs.minWorkerThreads,
317         serverArgs.maxWorkerThreads, serverArgs.stopTimeoutVal, TimeUnit.SECONDS,
318         new SynchronousQueue<Runnable>(), metrics);
319     serverArgs.executorService(executor);
320 
321     return new TThreadPoolServer(serverArgs);
322   }
323 
324   /**
325    * Adds the option to pre-load filters at startup.
326    *
327    * @param conf  The current configuration instance.
328    */
329   protected static void registerFilters(Configuration conf) {
330     String[] filters = conf.getStrings("hbase.thrift.filters");
331     if(filters != null) {
332       for(String filterClass: filters) {
333         String[] filterPart = filterClass.split(":");
334         if(filterPart.length != 2) {
335           log.warn("Invalid filter specification " + filterClass + " - skipping");
336         } else {
337           ParseFilter.registerFilter(filterPart[0], filterPart[1]);
338         }
339       }
340     }
341   }
342 
343   /**
344    * Start up the Thrift2 server.
345    *
346    * @param args
347    */
348   public static void main(String[] args) throws Exception {
349     TServer server = null;
350     Options options = getOptions();
351     Configuration conf = HBaseConfiguration.create();
352     CommandLine cmd = parseArguments(conf, options, args);
353     int workerThreads = 0;
354     int maxCallQueueSize = -1; // use unbounded queue by default
355 
356     /**
357      * This is to please both bin/hbase and bin/hbase-daemon. hbase-daemon provides "start" and "stop" arguments hbase
358      * should print the help if no argument is provided
359      */
360     List<?> argList = cmd.getArgList();
361     if (cmd.hasOption("help") || !argList.contains("start") || argList.contains("stop")) {
362       printUsage();
363       System.exit(1);
364     }
365 
366     // Get address to bind
367     String bindAddress;
368     if (cmd.hasOption("bind")) {
369       bindAddress = cmd.getOptionValue("bind");
370       conf.set("hbase.thrift.info.bindAddress", bindAddress);
371     } else {
372       bindAddress = conf.get("hbase.thrift.info.bindAddress");
373     }
374 
375     // Get read timeout
376     int readTimeout = THRIFT_SERVER_SOCKET_READ_TIMEOUT_DEFAULT;
377     if (cmd.hasOption(READ_TIMEOUT_OPTION)) {
378       try {
379         readTimeout = Integer.parseInt(cmd.getOptionValue(READ_TIMEOUT_OPTION));
380       } catch (NumberFormatException e) {
381         throw new RuntimeException("Could not parse the value provided for the timeout option", e);
382       }
383     } else {
384       readTimeout = conf.getInt(THRIFT_SERVER_SOCKET_READ_TIMEOUT_KEY,
385         THRIFT_SERVER_SOCKET_READ_TIMEOUT_DEFAULT);
386     }
387 
388     // Get port to bind to
389     int listenPort = 0;
390     try {
391       if (cmd.hasOption("port")) {
392         listenPort = Integer.parseInt(cmd.getOptionValue("port"));
393       } else {
394         listenPort = conf.getInt("hbase.regionserver.thrift.port", DEFAULT_LISTEN_PORT);
395       }
396     } catch (NumberFormatException e) {
397       throw new RuntimeException("Could not parse the value provided for the port option", e);
398     }
399 
400     // Thrift's implementation uses '0' as a placeholder for 'use the default.'
401     int backlog = conf.getInt(BACKLOG_CONF_KEY, 0);
402 
403     // Local hostname and user name,
404     // used only if QOP is configured.
405     String host = null;
406     String name = null;
407 
408     UserProvider userProvider = UserProvider.instantiate(conf);
409     // login the server principal (if using secure Hadoop)
410     boolean securityEnabled = userProvider.isHadoopSecurityEnabled()
411       && userProvider.isHBaseSecurityEnabled();
412     if (securityEnabled) {
413       host = Strings.domainNamePointerToHostName(DNS.getDefaultHost(
414         conf.get("hbase.thrift.dns.interface", "default"),
415         conf.get("hbase.thrift.dns.nameserver", "default")));
416       userProvider.login("hbase.thrift.keytab.file",
417         "hbase.thrift.kerberos.principal", host);
418     }
419 
420     UserGroupInformation realUser = userProvider.getCurrent().getUGI();
421     String stringQop = conf.get(THRIFT_QOP_KEY);
422     SaslUtil.QualityOfProtection qop = null;
423     if (stringQop != null) {
424       qop = SaslUtil.getQop(stringQop);
425       if (!securityEnabled) {
426         throw new IOException("Thrift server must"
427           + " run in secure mode to support authentication");
428       }
429       // Extract the name from the principal
430       name = SecurityUtil.getUserFromPrincipal(
431         conf.get("hbase.thrift.kerberos.principal"));
432     }
433 
434     boolean nonblocking = cmd.hasOption("nonblocking");
435     boolean hsha = cmd.hasOption("hsha");
436 
437     ThriftMetrics metrics = new ThriftMetrics(conf, ThriftMetrics.ThriftServerType.TWO);
438 
439     String implType = "threadpool";
440     if (nonblocking) {
441       implType = "nonblocking";
442     } else if (hsha) {
443       implType = "hsha";
444     }
445 
446     conf.set("hbase.regionserver.thrift.server.type", implType);
447     conf.setInt("hbase.regionserver.thrift.port", listenPort);
448     registerFilters(conf);
449 
450     // Construct correct ProtocolFactory
451     boolean compact = cmd.hasOption("compact") ||
452         conf.getBoolean("hbase.regionserver.thrift.compact", false);
453     TProtocolFactory protocolFactory = getTProtocolFactory(compact);
454     final ThriftHBaseServiceHandler hbaseHandler =
455       new ThriftHBaseServiceHandler(conf, userProvider);
456     THBaseService.Iface handler =
457       ThriftHBaseServiceHandler.newInstance(hbaseHandler, metrics);
458     final THBaseService.Processor p = new THBaseService.Processor(handler);
459     conf.setBoolean("hbase.regionserver.thrift.compact", compact);
460     TProcessor processor = p;
461 
462     boolean framed = cmd.hasOption("framed") ||
463         conf.getBoolean("hbase.regionserver.thrift.framed", false) || nonblocking || hsha;
464     TTransportFactory transportFactory = getTTransportFactory(qop, name, host, framed,
465         conf.getInt("hbase.regionserver.thrift.framed.max_frame_size_in_mb", 2) * 1024 * 1024);
466     InetSocketAddress inetSocketAddress = bindToPort(bindAddress, listenPort);
467     conf.setBoolean("hbase.regionserver.thrift.framed", framed);
468     if (qop != null) {
469       // Create a processor wrapper, to get the caller
470       processor = new TProcessor() {
471         @Override
472         public boolean process(TProtocol inProt,
473             TProtocol outProt) throws TException {
474           TSaslServerTransport saslServerTransport =
475             (TSaslServerTransport)inProt.getTransport();
476           SaslServer saslServer = saslServerTransport.getSaslServer();
477           String principal = saslServer.getAuthorizationID();
478           hbaseHandler.setEffectiveUser(principal);
479           return p.process(inProt, outProt);
480         }
481       };
482     }
483 
484     if (cmd.hasOption("w")) {
485       workerThreads = Integer.parseInt(cmd.getOptionValue("w"));
486     }
487 
488     if (cmd.hasOption("q")) {
489       maxCallQueueSize = Integer.parseInt(cmd.getOptionValue("q"));
490     }
491 
492     // check for user-defined info server port setting, if so override the conf
493     try {
494       if (cmd.hasOption("infoport")) {
495         String val = cmd.getOptionValue("infoport");
496         conf.setInt("hbase.thrift.info.port", Integer.parseInt(val));
497         log.debug("Web UI port set to " + val);
498       }
499     } catch (NumberFormatException e) {
500       log.error("Could not parse the value provided for the infoport option", e);
501       printUsage();
502       System.exit(1);
503     }
504 
505     // Put up info server.
506     int port = conf.getInt("hbase.thrift.info.port", 9095);
507     if (port >= 0) {
508       conf.setLong("startcode", System.currentTimeMillis());
509       String a = conf.get("hbase.thrift.info.bindAddress", "0.0.0.0");
510       InfoServer infoServer = new InfoServer("thrift", a, port, false, conf);
511       infoServer.setAttribute("hbase.conf", conf);
512       infoServer.start();
513     }
514 
515     if (nonblocking) {
516       server = getTNonBlockingServer(protocolFactory,
517           processor,
518           transportFactory,
519           inetSocketAddress);
520     } else if (hsha) {
521       server = getTHsHaServer(protocolFactory,
522           processor,
523           transportFactory,
524           workerThreads,
525           maxCallQueueSize,
526           inetSocketAddress,
527           metrics);
528     } else {
529       server = getTThreadPoolServer(protocolFactory,
530           processor,
531           transportFactory,
532           workerThreads,
533           inetSocketAddress,
534           backlog,
535           readTimeout,
536           metrics);
537     }
538 
539     final TServer tserver = server;
540     realUser.doAs(
541       new PrivilegedAction<Object>() {
542         @Override
543         public Object run() {
544           tserver.serve();
545           return null;
546         }
547       });
548   }
549 }