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.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
93
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
102
103
104
105
106
107
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
119
120
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
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
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
326
327
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
345
346
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;
355
356
357
358
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
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
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
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
401 int backlog = conf.getInt(BACKLOG_CONF_KEY, 0);
402
403
404
405 String host = null;
406 String name = null;
407
408 UserProvider userProvider = UserProvider.instantiate(conf);
409
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
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
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
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
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
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 }