1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.conf.Configuration;
52 import org.apache.hadoop.hbase.HBaseConfiguration;
53 import org.apache.hadoop.hbase.HBaseInterfaceAudience;
54 import org.apache.hadoop.hbase.classification.InterfaceAudience;
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.SaslRpcServer.SaslGssCallbackHandler;
68 import org.apache.hadoop.security.UserGroupInformation;
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.server.TThreadedSelectorServer;
81 import org.apache.thrift.transport.TFramedTransport;
82 import org.apache.thrift.transport.TNonblockingServerSocket;
83 import org.apache.thrift.transport.TNonblockingServerTransport;
84 import org.apache.thrift.transport.TSaslServerTransport;
85 import org.apache.thrift.transport.TServerSocket;
86 import org.apache.thrift.transport.TServerTransport;
87 import org.apache.thrift.transport.TTransportException;
88 import org.apache.thrift.transport.TTransportFactory;
89
90 import com.google.common.util.concurrent.ThreadFactoryBuilder;
91
92
93
94
95
96 @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.TOOLS)
97 @SuppressWarnings({ "rawtypes", "unchecked" })
98 public class ThriftServer {
99 private static final Log log = LogFactory.getLog(ThriftServer.class);
100
101
102
103
104
105
106
107
108
109
110 static final String THRIFT_QOP_KEY = "hbase.thrift.security.qop";
111
112 static final String BACKLOG_CONF_KEY = "hbase.regionserver.thrift.backlog";
113
114 public static final int DEFAULT_LISTEN_PORT = 9090;
115
116 private static final String READ_TIMEOUT_OPTION = "readTimeout";
117
118
119
120
121
122
123 public static final String THRIFT_SERVER_SOCKET_READ_TIMEOUT_KEY =
124 "hbase.thrift.server.socket.read.timeout";
125 public static final int THRIFT_SERVER_SOCKET_READ_TIMEOUT_DEFAULT = 60000;
126
127 public ThriftServer() {
128 }
129
130 private static void printUsage() {
131 HelpFormatter formatter = new HelpFormatter();
132 formatter.printHelp("Thrift", null, getOptions(),
133 "To start the Thrift server run 'bin/hbase-daemon.sh start thrift2'\n" +
134 "To shutdown the thrift server run 'bin/hbase-daemon.sh stop thrift2' or" +
135 " send a kill signal to the thrift server pid",
136 true);
137 }
138
139 private static Options getOptions() {
140 Options options = new Options();
141 options.addOption("b", "bind", true,
142 "Address to bind the Thrift server to. [default: 0.0.0.0]");
143 options.addOption("p", "port", true, "Port to bind to [default: " + DEFAULT_LISTEN_PORT + "]");
144 options.addOption("f", "framed", false, "Use framed transport");
145 options.addOption("c", "compact", false, "Use the compact protocol");
146 options.addOption("w", "workers", true, "How many worker threads to use.");
147 options.addOption("s", "selectors", true, "How many selector threads to use.");
148 options.addOption("q", "callQueueSize", true,
149 "Max size of request queue (unbounded by default)");
150 options.addOption("h", "help", false, "Print help information");
151 options.addOption(null, "infoport", true, "Port for web UI");
152 options.addOption("t", READ_TIMEOUT_OPTION, true,
153 "Amount of time in milliseconds before a server thread will timeout " +
154 "waiting for client to send data on a connected socket. Currently, " +
155 "only applies to TBoundedThreadPoolServer");
156 OptionGroup servers = new OptionGroup();
157 servers.addOption(
158 new Option("nonblocking", false,
159 "Use the TNonblockingServer. This implies the framed transport."));
160 servers.addOption(new Option("hsha", false,
161 "Use the THsHaServer. This implies the framed transport."));
162 servers.addOption(new Option("selector", false,
163 "Use the TThreadedSelectorServer. This implies the framed transport."));
164 servers.addOption(new Option("threadpool", false,
165 "Use the TThreadPoolServer. This is the default."));
166 options.addOptionGroup(servers);
167 return options;
168 }
169
170 private static CommandLine parseArguments(Configuration conf, Options options, String[] args)
171 throws ParseException, IOException {
172 GenericOptionsParser genParser = new GenericOptionsParser(conf, args);
173 String[] remainingArgs = genParser.getRemainingArgs();
174 CommandLineParser parser = new PosixParser();
175 return parser.parse(options, remainingArgs);
176 }
177
178 private static TProtocolFactory getTProtocolFactory(boolean isCompact) {
179 if (isCompact) {
180 log.debug("Using compact protocol");
181 return new TCompactProtocol.Factory();
182 } else {
183 log.debug("Using binary protocol");
184 return new TBinaryProtocol.Factory();
185 }
186 }
187
188 private static TTransportFactory getTTransportFactory(
189 SaslUtil.QualityOfProtection qop, String name, String host,
190 boolean framed, int frameSize) {
191 if (framed) {
192 if (qop != null) {
193 throw new RuntimeException("Thrift server authentication"
194 + " doesn't work with framed transport yet");
195 }
196 log.debug("Using framed transport");
197 return new TFramedTransport.Factory(frameSize);
198 } else if (qop == null) {
199 return new TTransportFactory();
200 } else {
201 Map<String, String> saslProperties = new HashMap<String, String>();
202 saslProperties.put(Sasl.QOP, qop.getSaslQop());
203 saslProperties.put(Sasl.SERVER_AUTH, "true");
204 TSaslServerTransport.Factory saslFactory = new TSaslServerTransport.Factory();
205 saslFactory.addServerDefinition("GSSAPI", name, host, saslProperties,
206 new SaslGssCallbackHandler() {
207 @Override
208 public void handle(Callback[] callbacks)
209 throws UnsupportedCallbackException {
210 AuthorizeCallback ac = null;
211 for (Callback callback : callbacks) {
212 if (callback instanceof AuthorizeCallback) {
213 ac = (AuthorizeCallback) callback;
214 } else {
215 throw new UnsupportedCallbackException(callback,
216 "Unrecognized SASL GSSAPI Callback");
217 }
218 }
219 if (ac != null) {
220 String authid = ac.getAuthenticationID();
221 String authzid = ac.getAuthorizationID();
222 if (!authid.equals(authzid)) {
223 ac.setAuthorized(false);
224 } else {
225 ac.setAuthorized(true);
226 String userName = SecurityUtil.getUserFromPrincipal(authzid);
227 log.info("Effective user: " + userName);
228 ac.setAuthorizedID(userName);
229 }
230 }
231 }
232 });
233 return saslFactory;
234 }
235 }
236
237
238
239
240 private static InetSocketAddress bindToPort(String bindValue, int listenPort)
241 throws UnknownHostException {
242 try {
243 if (bindValue == null) {
244 return new InetSocketAddress(listenPort);
245 } else {
246 return new InetSocketAddress(InetAddress.getByName(bindValue), listenPort);
247 }
248 } catch (UnknownHostException e) {
249 throw new RuntimeException("Could not bind to provided ip address", e);
250 }
251 }
252
253 private static TServer getTNonBlockingServer(TProtocolFactory protocolFactory, TProcessor processor,
254 TTransportFactory transportFactory, InetSocketAddress inetSocketAddress) throws TTransportException {
255 TNonblockingServerTransport serverTransport = new TNonblockingServerSocket(inetSocketAddress);
256 log.info("starting HBase Nonblocking Thrift server on " + inetSocketAddress.toString());
257 TNonblockingServer.Args serverArgs = new TNonblockingServer.Args(serverTransport);
258 serverArgs.processor(processor);
259 serverArgs.transportFactory(transportFactory);
260 serverArgs.protocolFactory(protocolFactory);
261 return new TNonblockingServer(serverArgs);
262 }
263
264 private static TServer getTHsHaServer(TProtocolFactory protocolFactory,
265 TProcessor processor, TTransportFactory transportFactory,
266 int workerThreads, int maxCallQueueSize,
267 InetSocketAddress inetSocketAddress, ThriftMetrics metrics)
268 throws TTransportException {
269 TNonblockingServerTransport serverTransport = new TNonblockingServerSocket(inetSocketAddress);
270 log.info("starting HBase HsHA Thrift server on " + inetSocketAddress.toString());
271 THsHaServer.Args serverArgs = new THsHaServer.Args(serverTransport);
272 if (workerThreads > 0) {
273
274 serverArgs.minWorkerThreads(workerThreads).maxWorkerThreads(workerThreads);
275 }
276 ExecutorService executorService = createExecutor(
277 workerThreads, maxCallQueueSize, metrics);
278 serverArgs.executorService(executorService);
279 serverArgs.processor(processor);
280 serverArgs.transportFactory(transportFactory);
281 serverArgs.protocolFactory(protocolFactory);
282 return new THsHaServer(serverArgs);
283 }
284
285 private static TServer getTThreadedSelectorServer(TProtocolFactory protocolFactory,
286 TProcessor processor, TTransportFactory transportFactory,
287 int workerThreads, int selectorThreads, int maxCallQueueSize,
288 InetSocketAddress inetSocketAddress, ThriftMetrics metrics)
289 throws TTransportException {
290 TNonblockingServerTransport serverTransport = new TNonblockingServerSocket(inetSocketAddress);
291 log.info("starting HBase ThreadedSelector Thrift server on " + inetSocketAddress.toString());
292 TThreadedSelectorServer.Args serverArgs = new TThreadedSelectorServer.Args(serverTransport);
293 if (workerThreads > 0) {
294 serverArgs.workerThreads(workerThreads);
295 }
296 if (selectorThreads > 0) {
297 serverArgs.selectorThreads(selectorThreads);
298 }
299
300 ExecutorService executorService = createExecutor(workerThreads, maxCallQueueSize, metrics);
301 serverArgs.executorService(executorService);
302 serverArgs.processor(processor);
303 serverArgs.transportFactory(transportFactory);
304 serverArgs.protocolFactory(protocolFactory);
305 return new TThreadedSelectorServer(serverArgs);
306 }
307
308 private static ExecutorService createExecutor(
309 int workerThreads, int maxCallQueueSize, ThriftMetrics metrics) {
310 CallQueue callQueue;
311 if (maxCallQueueSize > 0) {
312 callQueue = new CallQueue(new LinkedBlockingQueue<Call>(maxCallQueueSize), metrics);
313 } else {
314 callQueue = new CallQueue(new LinkedBlockingQueue<Call>(), metrics);
315 }
316
317 ThreadFactoryBuilder tfb = new ThreadFactoryBuilder();
318 tfb.setDaemon(true);
319 tfb.setNameFormat("thrift2-worker-%d");
320 ThreadPoolExecutor pool = new THBaseThreadPoolExecutor(workerThreads, workerThreads,
321 Long.MAX_VALUE, TimeUnit.SECONDS, callQueue, tfb.build(), metrics);
322 pool.prestartAllCoreThreads();
323 return pool;
324 }
325
326 private static TServer getTThreadPoolServer(TProtocolFactory protocolFactory,
327 TProcessor processor,
328 TTransportFactory transportFactory,
329 int workerThreads,
330 InetSocketAddress inetSocketAddress,
331 int backlog,
332 int clientTimeout,
333 ThriftMetrics metrics)
334 throws TTransportException {
335 TServerTransport serverTransport = new TServerSocket(
336 new TServerSocket.ServerSocketTransportArgs().
337 bindAddr(inetSocketAddress).backlog(backlog).
338 clientTimeout(clientTimeout));
339 log.info("starting HBase ThreadPool Thrift server on " + inetSocketAddress.toString());
340 TThreadPoolServer.Args serverArgs = new TThreadPoolServer.Args(serverTransport);
341 serverArgs.processor(processor);
342 serverArgs.transportFactory(transportFactory);
343 serverArgs.protocolFactory(protocolFactory);
344 if (workerThreads > 0) {
345 serverArgs.maxWorkerThreads(workerThreads);
346 }
347 ThreadPoolExecutor executor = new THBaseThreadPoolExecutor(serverArgs.minWorkerThreads,
348 serverArgs.maxWorkerThreads, serverArgs.stopTimeoutVal, TimeUnit.SECONDS,
349 new SynchronousQueue<Runnable>(), metrics);
350 serverArgs.executorService(executor);
351
352 return new TThreadPoolServer(serverArgs);
353 }
354
355
356
357
358
359
360 protected static void registerFilters(Configuration conf) {
361 String[] filters = conf.getStrings("hbase.thrift.filters");
362 if(filters != null) {
363 for(String filterClass: filters) {
364 String[] filterPart = filterClass.split(":");
365 if(filterPart.length != 2) {
366 log.warn("Invalid filter specification " + filterClass + " - skipping");
367 } else {
368 ParseFilter.registerFilter(filterPart[0], filterPart[1]);
369 }
370 }
371 }
372 }
373
374
375
376
377
378
379 public static void main(String[] args) throws Exception {
380 TServer server = null;
381 Options options = getOptions();
382 Configuration conf = HBaseConfiguration.create();
383 CommandLine cmd = parseArguments(conf, options, args);
384 int workerThreads = 0;
385 int selectorThreads = 0;
386 int maxCallQueueSize = -1;
387
388
389
390
391
392 List<?> argList = cmd.getArgList();
393 if (cmd.hasOption("help") || !argList.contains("start") || argList.contains("stop")) {
394 printUsage();
395 System.exit(1);
396 }
397
398
399 String bindAddress;
400 if (cmd.hasOption("bind")) {
401 bindAddress = cmd.getOptionValue("bind");
402 conf.set("hbase.thrift.info.bindAddress", bindAddress);
403 } else {
404 bindAddress = conf.get("hbase.thrift.info.bindAddress");
405 }
406
407
408 int readTimeout = THRIFT_SERVER_SOCKET_READ_TIMEOUT_DEFAULT;
409 if (cmd.hasOption(READ_TIMEOUT_OPTION)) {
410 try {
411 readTimeout = Integer.parseInt(cmd.getOptionValue(READ_TIMEOUT_OPTION));
412 } catch (NumberFormatException e) {
413 throw new RuntimeException("Could not parse the value provided for the timeout option", e);
414 }
415 } else {
416 readTimeout = conf.getInt(THRIFT_SERVER_SOCKET_READ_TIMEOUT_KEY,
417 THRIFT_SERVER_SOCKET_READ_TIMEOUT_DEFAULT);
418 }
419
420
421 int listenPort = 0;
422 try {
423 if (cmd.hasOption("port")) {
424 listenPort = Integer.parseInt(cmd.getOptionValue("port"));
425 } else {
426 listenPort = conf.getInt("hbase.regionserver.thrift.port", DEFAULT_LISTEN_PORT);
427 }
428 } catch (NumberFormatException e) {
429 throw new RuntimeException("Could not parse the value provided for the port option", e);
430 }
431
432
433 int backlog = conf.getInt(BACKLOG_CONF_KEY, 0);
434
435
436
437 String host = null;
438 String name = null;
439
440 UserProvider userProvider = UserProvider.instantiate(conf);
441
442 boolean securityEnabled = userProvider.isHadoopSecurityEnabled()
443 && userProvider.isHBaseSecurityEnabled();
444 if (securityEnabled) {
445 host = Strings.domainNamePointerToHostName(DNS.getDefaultHost(
446 conf.get("hbase.thrift.dns.interface", "default"),
447 conf.get("hbase.thrift.dns.nameserver", "default")));
448 userProvider.login("hbase.thrift.keytab.file",
449 "hbase.thrift.kerberos.principal", host);
450 }
451
452 UserGroupInformation realUser = userProvider.getCurrent().getUGI();
453 String stringQop = conf.get(THRIFT_QOP_KEY);
454 SaslUtil.QualityOfProtection qop = null;
455 if (stringQop != null) {
456 qop = SaslUtil.getQop(stringQop);
457 if (!securityEnabled) {
458 throw new IOException("Thrift server must"
459 + " run in secure mode to support authentication");
460 }
461
462 name = SecurityUtil.getUserFromPrincipal(
463 conf.get("hbase.thrift.kerberos.principal"));
464 }
465
466 boolean nonblocking = cmd.hasOption("nonblocking");
467 boolean hsha = cmd.hasOption("hsha");
468 boolean selector = cmd.hasOption("selector");
469
470 ThriftMetrics metrics = new ThriftMetrics(conf, ThriftMetrics.ThriftServerType.TWO);
471
472 String implType = "threadpool";
473 if (nonblocking) {
474 implType = "nonblocking";
475 } else if (hsha) {
476 implType = "hsha";
477 } else if (selector) {
478 implType = "selector";
479 }
480
481 conf.set("hbase.regionserver.thrift.server.type", implType);
482 conf.setInt("hbase.regionserver.thrift.port", listenPort);
483 registerFilters(conf);
484
485
486 boolean compact = cmd.hasOption("compact") ||
487 conf.getBoolean("hbase.regionserver.thrift.compact", false);
488 TProtocolFactory protocolFactory = getTProtocolFactory(compact);
489 final ThriftHBaseServiceHandler hbaseHandler =
490 new ThriftHBaseServiceHandler(conf, userProvider);
491 THBaseService.Iface handler =
492 ThriftHBaseServiceHandler.newInstance(hbaseHandler, metrics);
493 final THBaseService.Processor p = new THBaseService.Processor(handler);
494 conf.setBoolean("hbase.regionserver.thrift.compact", compact);
495 TProcessor processor = p;
496
497 boolean framed = cmd.hasOption("framed") ||
498 conf.getBoolean("hbase.regionserver.thrift.framed", false) || nonblocking || hsha;
499 TTransportFactory transportFactory = getTTransportFactory(qop, name, host, framed,
500 conf.getInt("hbase.regionserver.thrift.framed.max_frame_size_in_mb", 2) * 1024 * 1024);
501 InetSocketAddress inetSocketAddress = bindToPort(bindAddress, listenPort);
502 conf.setBoolean("hbase.regionserver.thrift.framed", framed);
503 if (qop != null) {
504
505 processor = new TProcessor() {
506 @Override
507 public boolean process(TProtocol inProt,
508 TProtocol outProt) throws TException {
509 TSaslServerTransport saslServerTransport =
510 (TSaslServerTransport)inProt.getTransport();
511 SaslServer saslServer = saslServerTransport.getSaslServer();
512 String principal = saslServer.getAuthorizationID();
513 hbaseHandler.setEffectiveUser(principal);
514 return p.process(inProt, outProt);
515 }
516 };
517 }
518
519 if (cmd.hasOption("w")) {
520 workerThreads = Integer.parseInt(cmd.getOptionValue("w"));
521 }
522 if (cmd.hasOption("s")) {
523 selectorThreads = Integer.parseInt(cmd.getOptionValue("s"));
524 }
525 if (cmd.hasOption("q")) {
526 maxCallQueueSize = Integer.parseInt(cmd.getOptionValue("q"));
527 }
528
529
530 try {
531 if (cmd.hasOption("infoport")) {
532 String val = cmd.getOptionValue("infoport");
533 conf.setInt("hbase.thrift.info.port", Integer.parseInt(val));
534 log.debug("Web UI port set to " + val);
535 }
536 } catch (NumberFormatException e) {
537 log.error("Could not parse the value provided for the infoport option", e);
538 printUsage();
539 System.exit(1);
540 }
541
542
543 int port = conf.getInt("hbase.thrift.info.port", 9095);
544 if (port >= 0) {
545 conf.setLong("startcode", System.currentTimeMillis());
546 String a = conf.get("hbase.thrift.info.bindAddress", "0.0.0.0");
547 InfoServer infoServer = new InfoServer("thrift", a, port, false, conf);
548 infoServer.setAttribute("hbase.conf", conf);
549 infoServer.start();
550 }
551
552 if (nonblocking) {
553 server = getTNonBlockingServer(protocolFactory,
554 processor,
555 transportFactory,
556 inetSocketAddress);
557 } else if (hsha) {
558 server = getTHsHaServer(protocolFactory,
559 processor,
560 transportFactory,
561 workerThreads,
562 maxCallQueueSize,
563 inetSocketAddress,
564 metrics);
565 } else if (selector) {
566 server = getTThreadedSelectorServer(protocolFactory,
567 processor,
568 transportFactory,
569 workerThreads,
570 selectorThreads,
571 maxCallQueueSize,
572 inetSocketAddress,
573 metrics);
574 } else {
575 server = getTThreadPoolServer(protocolFactory,
576 processor,
577 transportFactory,
578 workerThreads,
579 inetSocketAddress,
580 backlog,
581 readTimeout,
582 metrics);
583 }
584
585 final TServer tserver = server;
586 realUser.doAs(
587 new PrivilegedAction<Object>() {
588 @Override
589 public Object run() {
590 tserver.serve();
591 return null;
592 }
593 });
594 }
595 }