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 */
018package org.apache.hadoop.hbase.thrift;
019
020import static org.apache.hadoop.hbase.http.HttpServerUtil.PATH_SPEC_ANY;
021import static org.apache.hadoop.hbase.thrift.Constants.BACKLOG_CONF_DEAFULT;
022import static org.apache.hadoop.hbase.thrift.Constants.BACKLOG_CONF_KEY;
023import static org.apache.hadoop.hbase.thrift.Constants.BIND_CONF_KEY;
024import static org.apache.hadoop.hbase.thrift.Constants.BIND_OPTION;
025import static org.apache.hadoop.hbase.thrift.Constants.COMPACT_CONF_DEFAULT;
026import static org.apache.hadoop.hbase.thrift.Constants.COMPACT_CONF_KEY;
027import static org.apache.hadoop.hbase.thrift.Constants.COMPACT_OPTION;
028import static org.apache.hadoop.hbase.thrift.Constants.DEFAULT_BIND_ADDR;
029import static org.apache.hadoop.hbase.thrift.Constants.DEFAULT_HTTP_MAX_HEADER_SIZE;
030import static org.apache.hadoop.hbase.thrift.Constants.DEFAULT_LISTEN_PORT;
031import static org.apache.hadoop.hbase.thrift.Constants.FRAMED_CONF_DEFAULT;
032import static org.apache.hadoop.hbase.thrift.Constants.FRAMED_CONF_KEY;
033import static org.apache.hadoop.hbase.thrift.Constants.FRAMED_OPTION;
034import static org.apache.hadoop.hbase.thrift.Constants.HTTP_MAX_THREADS_KEY;
035import static org.apache.hadoop.hbase.thrift.Constants.HTTP_MAX_THREADS_KEY_DEFAULT;
036import static org.apache.hadoop.hbase.thrift.Constants.HTTP_MIN_THREADS_KEY;
037import static org.apache.hadoop.hbase.thrift.Constants.HTTP_MIN_THREADS_KEY_DEFAULT;
038import static org.apache.hadoop.hbase.thrift.Constants.INFOPORT_OPTION;
039import static org.apache.hadoop.hbase.thrift.Constants.KEEP_ALIVE_SEC_OPTION;
040import static org.apache.hadoop.hbase.thrift.Constants.MAX_FRAME_SIZE_CONF_DEFAULT;
041import static org.apache.hadoop.hbase.thrift.Constants.MAX_FRAME_SIZE_CONF_KEY;
042import static org.apache.hadoop.hbase.thrift.Constants.MAX_QUEUE_SIZE_OPTION;
043import static org.apache.hadoop.hbase.thrift.Constants.MAX_WORKERS_OPTION;
044import static org.apache.hadoop.hbase.thrift.Constants.MIN_WORKERS_OPTION;
045import static org.apache.hadoop.hbase.thrift.Constants.PORT_CONF_KEY;
046import static org.apache.hadoop.hbase.thrift.Constants.PORT_OPTION;
047import static org.apache.hadoop.hbase.thrift.Constants.READ_TIMEOUT_OPTION;
048import static org.apache.hadoop.hbase.thrift.Constants.SELECTOR_NUM_OPTION;
049import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_FILTERS;
050import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_HTTP_ALLOW_OPTIONS_METHOD;
051import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_HTTP_ALLOW_OPTIONS_METHOD_DEFAULT;
052import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_INFO_SERVER_BINDING_ADDRESS;
053import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_INFO_SERVER_BINDING_ADDRESS_DEFAULT;
054import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_INFO_SERVER_PORT;
055import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_INFO_SERVER_PORT_DEFAULT;
056import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_KERBEROS_PRINCIPAL_KEY;
057import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_KEYTAB_FILE_KEY;
058import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_QOP_KEY;
059import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SELECTOR_NUM;
060import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SERVER_SOCKET_READ_TIMEOUT_DEFAULT;
061import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SERVER_SOCKET_READ_TIMEOUT_KEY;
062import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SPNEGO_KEYTAB_FILE_KEY;
063import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SPNEGO_PRINCIPAL_KEY;
064import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SSL_CLIENT_AUTH_MODE_KEY;
065import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SSL_ENABLED_KEY;
066import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SSL_EXCLUDE_CIPHER_SUITES_KEY;
067import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SSL_EXCLUDE_PROTOCOLS_KEY;
068import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SSL_INCLUDE_CIPHER_SUITES_KEY;
069import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SSL_INCLUDE_PROTOCOLS_KEY;
070import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SSL_KEYSTORE_TYPE_DEFAULT;
071import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SSL_SERVER_TRUSTSTORE_PASSWORD_KEY;
072import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SSL_SERVER_TRUSTSTORE_STORE_KEY;
073import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SSL_SERVER_TRUSTSTORE_TYPE_KEY;
074import static org.apache.hadoop.hbase.thrift.Constants.THRIFT_SUPPORT_PROXYUSER_KEY;
075import static org.apache.hadoop.hbase.thrift.Constants.USE_HTTP_CONF_KEY;
076
077import java.io.IOException;
078import java.net.InetAddress;
079import java.net.InetSocketAddress;
080import java.net.UnknownHostException;
081import java.security.PrivilegedAction;
082import java.util.List;
083import java.util.Map;
084import java.util.concurrent.BlockingQueue;
085import java.util.concurrent.ExecutorService;
086import java.util.concurrent.LinkedBlockingQueue;
087import java.util.concurrent.ThreadPoolExecutor;
088import java.util.concurrent.TimeUnit;
089import javax.security.auth.callback.Callback;
090import javax.security.auth.callback.UnsupportedCallbackException;
091import javax.security.sasl.AuthorizeCallback;
092import javax.security.sasl.SaslServer;
093import org.apache.commons.lang3.ArrayUtils;
094import org.apache.commons.lang3.StringUtils;
095import org.apache.hadoop.conf.Configuration;
096import org.apache.hadoop.conf.Configured;
097import org.apache.hadoop.hbase.HBaseConfiguration;
098import org.apache.hadoop.hbase.HBaseInterfaceAudience;
099import org.apache.hadoop.hbase.filter.ParseFilter;
100import org.apache.hadoop.hbase.http.HttpServerUtil;
101import org.apache.hadoop.hbase.http.InfoServer;
102import org.apache.hadoop.hbase.io.crypto.tls.TLSStore;
103import org.apache.hadoop.hbase.io.crypto.tls.X509Util;
104import org.apache.hadoop.hbase.log.HBaseMarkers;
105import org.apache.hadoop.hbase.security.SaslUtil;
106import org.apache.hadoop.hbase.security.SecurityUtil;
107import org.apache.hadoop.hbase.security.UserProvider;
108import org.apache.hadoop.hbase.thrift.generated.Hbase;
109import org.apache.hadoop.hbase.util.DNS;
110import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
111import org.apache.hadoop.hbase.util.JvmPauseMonitor;
112import org.apache.hadoop.hbase.util.VersionInfo;
113import org.apache.hadoop.security.SaslRpcServer;
114import org.apache.hadoop.security.UserGroupInformation;
115import org.apache.hadoop.security.authorize.ProxyUsers;
116import org.apache.hadoop.util.Shell.ExitCodeException;
117import org.apache.hadoop.util.Tool;
118import org.apache.hadoop.util.ToolRunner;
119import org.apache.yetus.audience.InterfaceAudience;
120import org.slf4j.Logger;
121import org.slf4j.LoggerFactory;
122
123import org.apache.hbase.thirdparty.com.google.common.base.Joiner;
124import org.apache.hbase.thirdparty.com.google.common.base.Splitter;
125import org.apache.hbase.thirdparty.com.google.common.util.concurrent.ThreadFactoryBuilder;
126import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLine;
127import org.apache.hbase.thirdparty.org.apache.commons.cli.CommandLineParser;
128import org.apache.hbase.thirdparty.org.apache.commons.cli.DefaultParser;
129import org.apache.hbase.thirdparty.org.apache.commons.cli.HelpFormatter;
130import org.apache.hbase.thirdparty.org.apache.commons.cli.Options;
131import org.apache.hbase.thirdparty.org.apache.thrift.TProcessor;
132import org.apache.hbase.thirdparty.org.apache.thrift.protocol.TBinaryProtocol;
133import org.apache.hbase.thirdparty.org.apache.thrift.protocol.TCompactProtocol;
134import org.apache.hbase.thirdparty.org.apache.thrift.protocol.TProtocolFactory;
135import org.apache.hbase.thirdparty.org.apache.thrift.server.THsHaServer;
136import org.apache.hbase.thirdparty.org.apache.thrift.server.TNonblockingServer;
137import org.apache.hbase.thirdparty.org.apache.thrift.server.TServer;
138import org.apache.hbase.thirdparty.org.apache.thrift.server.TServlet;
139import org.apache.hbase.thirdparty.org.apache.thrift.server.TThreadedSelectorServer;
140import org.apache.hbase.thirdparty.org.apache.thrift.transport.TNonblockingServerSocket;
141import org.apache.hbase.thirdparty.org.apache.thrift.transport.TNonblockingServerTransport;
142import org.apache.hbase.thirdparty.org.apache.thrift.transport.TSaslServerTransport;
143import org.apache.hbase.thirdparty.org.apache.thrift.transport.TServerSocket;
144import org.apache.hbase.thirdparty.org.apache.thrift.transport.TServerTransport;
145import org.apache.hbase.thirdparty.org.apache.thrift.transport.TTransportFactory;
146import org.apache.hbase.thirdparty.org.apache.thrift.transport.layered.TFramedTransport;
147import org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.servlet.ServletContextHandler;
148import org.apache.hbase.thirdparty.org.eclipse.jetty.ee8.servlet.ServletHolder;
149import org.apache.hbase.thirdparty.org.eclipse.jetty.http.HttpVersion;
150import org.apache.hbase.thirdparty.org.eclipse.jetty.server.HttpConfiguration;
151import org.apache.hbase.thirdparty.org.eclipse.jetty.server.HttpConnectionFactory;
152import org.apache.hbase.thirdparty.org.eclipse.jetty.server.SecureRequestCustomizer;
153import org.apache.hbase.thirdparty.org.eclipse.jetty.server.Server;
154import org.apache.hbase.thirdparty.org.eclipse.jetty.server.ServerConnector;
155import org.apache.hbase.thirdparty.org.eclipse.jetty.server.SslConnectionFactory;
156import org.apache.hbase.thirdparty.org.eclipse.jetty.util.ssl.SslContextFactory;
157import org.apache.hbase.thirdparty.org.eclipse.jetty.util.thread.QueuedThreadPool;
158
159/**
160 * ThriftServer- this class starts up a Thrift server which implements the Hbase API specified in
161 * the Hbase.thrift IDL file. The server runs in an independent process.
162 */
163@InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.TOOLS)
164public class ThriftServer extends Configured implements Tool {
165
166  private static final Logger LOG = LoggerFactory.getLogger(ThriftServer.class);
167
168  protected Configuration conf;
169
170  protected InfoServer infoServer;
171
172  protected TProcessor processor;
173
174  protected ThriftMetrics metrics;
175  protected HBaseServiceHandler hbaseServiceHandler;
176  protected UserGroupInformation serviceUGI;
177  protected UserGroupInformation httpUGI;
178  protected boolean httpEnabled;
179
180  protected SaslUtil.QualityOfProtection qop;
181  protected String host;
182  protected int listenPort;
183
184  protected boolean securityEnabled;
185  protected boolean doAsEnabled;
186
187  protected JvmPauseMonitor pauseMonitor;
188
189  protected volatile TServer tserver;
190  protected volatile Server httpServer;
191
192  //
193  // Main program and support routines
194  //
195
196  public ThriftServer(Configuration conf) {
197    this.conf = HBaseConfiguration.create(conf);
198  }
199
200  protected ThriftMetrics createThriftMetrics(Configuration conf) {
201    return new ThriftMetrics(conf, ThriftMetrics.ThriftServerType.ONE);
202  }
203
204  protected void setupParamters() throws IOException {
205    // login the server principal (if using secure Hadoop)
206    UserProvider userProvider = UserProvider.instantiate(conf);
207    securityEnabled =
208      userProvider.isHadoopSecurityEnabled() && userProvider.isHBaseSecurityEnabled();
209    if (securityEnabled) {
210      host = DNS.getHostname(conf, DNS.ServerType.THRIFT);
211      userProvider.login(THRIFT_KEYTAB_FILE_KEY, THRIFT_KERBEROS_PRINCIPAL_KEY, host);
212
213      // Setup the SPNEGO user for HTTP if configured
214      String spnegoPrincipal = getSpengoPrincipal(conf, host);
215      String spnegoKeytab = getSpnegoKeytab(conf);
216      UserGroupInformation.setConfiguration(conf);
217      // login the SPNEGO principal using UGI to avoid polluting the login user
218      this.httpUGI =
219        UserGroupInformation.loginUserFromKeytabAndReturnUGI(spnegoPrincipal, spnegoKeytab);
220    }
221    this.serviceUGI = userProvider.getCurrent().getUGI();
222    if (httpUGI == null) {
223      this.httpUGI = serviceUGI;
224    }
225
226    this.listenPort = conf.getInt(PORT_CONF_KEY, DEFAULT_LISTEN_PORT);
227    this.metrics = createThriftMetrics(conf);
228    this.pauseMonitor = new JvmPauseMonitor(conf, this.metrics.getSource());
229    this.hbaseServiceHandler = createHandler(conf, userProvider);
230    this.hbaseServiceHandler.initMetrics(metrics);
231    this.processor = createProcessor();
232
233    httpEnabled = conf.getBoolean(USE_HTTP_CONF_KEY, false);
234    doAsEnabled = conf.getBoolean(THRIFT_SUPPORT_PROXYUSER_KEY, false);
235    if (doAsEnabled && !httpEnabled) {
236      LOG.warn("Fail to enable the doAs feature. " + USE_HTTP_CONF_KEY + " is not configured");
237    }
238
239    String strQop = conf.get(THRIFT_QOP_KEY);
240    if (strQop != null) {
241      this.qop = SaslUtil.getQop(strQop);
242    }
243    if (qop != null) {
244      if (
245        qop != SaslUtil.QualityOfProtection.AUTHENTICATION
246          && qop != SaslUtil.QualityOfProtection.INTEGRITY
247          && qop != SaslUtil.QualityOfProtection.PRIVACY
248      ) {
249        throw new IOException(String.format("Invalid %s: It must be one of %s, %s, or %s.",
250          THRIFT_QOP_KEY, SaslUtil.QualityOfProtection.AUTHENTICATION.name(),
251          SaslUtil.QualityOfProtection.INTEGRITY.name(),
252          SaslUtil.QualityOfProtection.PRIVACY.name()));
253      }
254      checkHttpSecurity(qop, conf);
255      if (!securityEnabled) {
256        throw new IOException("Thrift server must run in secure mode to support authentication");
257      }
258    }
259    registerFilters(conf);
260    pauseMonitor.start();
261  }
262
263  private String getSpengoPrincipal(Configuration conf, String host) throws IOException {
264    String principal = conf.get(THRIFT_SPNEGO_PRINCIPAL_KEY);
265    if (principal == null) {
266      // We cannot use the Hadoop configuration deprecation handling here since
267      // the THRIFT_KERBEROS_PRINCIPAL_KEY config is still valid for regular Kerberos
268      // communication. The preference should be to use the THRIFT_SPNEGO_PRINCIPAL_KEY
269      // config so that THRIFT_KERBEROS_PRINCIPAL_KEY doesn't control both backend
270      // Kerberos principal and SPNEGO principal.
271      LOG.info("Using deprecated {} config for SPNEGO principal. Use {} instead.",
272        THRIFT_KERBEROS_PRINCIPAL_KEY, THRIFT_SPNEGO_PRINCIPAL_KEY);
273      principal = conf.get(THRIFT_KERBEROS_PRINCIPAL_KEY);
274    }
275    // Handle _HOST in principal value
276    return org.apache.hadoop.security.SecurityUtil.getServerPrincipal(principal, host);
277  }
278
279  private String getSpnegoKeytab(Configuration conf) {
280    String keytab = conf.get(THRIFT_SPNEGO_KEYTAB_FILE_KEY);
281    if (keytab == null) {
282      // We cannot use the Hadoop configuration deprecation handling here since
283      // the THRIFT_KEYTAB_FILE_KEY config is still valid for regular Kerberos
284      // communication. The preference should be to use the THRIFT_SPNEGO_KEYTAB_FILE_KEY
285      // config so that THRIFT_KEYTAB_FILE_KEY doesn't control both backend
286      // Kerberos keytab and SPNEGO keytab.
287      LOG.info("Using deprecated {} config for SPNEGO keytab. Use {} instead.",
288        THRIFT_KEYTAB_FILE_KEY, THRIFT_SPNEGO_KEYTAB_FILE_KEY);
289      keytab = conf.get(THRIFT_KEYTAB_FILE_KEY);
290    }
291    return keytab;
292  }
293
294  protected void startInfoServer() throws IOException {
295    // Put up info server.
296    int port = conf.getInt(THRIFT_INFO_SERVER_PORT, THRIFT_INFO_SERVER_PORT_DEFAULT);
297
298    if (port >= 0) {
299      conf.setLong("startcode", EnvironmentEdgeManager.currentTime());
300      String a =
301        conf.get(THRIFT_INFO_SERVER_BINDING_ADDRESS, THRIFT_INFO_SERVER_BINDING_ADDRESS_DEFAULT);
302      infoServer = new InfoServer("thrift", a, port, false, conf);
303      infoServer.setAttribute("hbase.conf", conf);
304      infoServer.setAttribute("hbase.thrift.server.type", metrics.getThriftServerType().name());
305      infoServer.start();
306    }
307  }
308
309  protected void checkHttpSecurity(SaslUtil.QualityOfProtection qop, Configuration conf) {
310    if (
311      qop == SaslUtil.QualityOfProtection.PRIVACY && conf.getBoolean(USE_HTTP_CONF_KEY, false)
312        && !conf.getBoolean(THRIFT_SSL_ENABLED_KEY, false)
313    ) {
314      throw new IllegalArgumentException(
315        "Thrift HTTP Server's QoP is privacy, but " + THRIFT_SSL_ENABLED_KEY + " is false");
316    }
317  }
318
319  protected HBaseServiceHandler createHandler(Configuration conf, UserProvider userProvider)
320    throws IOException {
321    return new ThriftHBaseServiceHandler(conf, userProvider);
322  }
323
324  protected TProcessor createProcessor() {
325    return new Hbase.Processor<>(
326      HbaseHandlerMetricsProxy.newInstance((Hbase.Iface) hbaseServiceHandler, metrics, conf));
327  }
328
329  /**
330   * the thrift server, not null means the server is started, for test only
331   * @return the tServer
332   */
333  @InterfaceAudience.Private
334  public TServer getTserver() {
335    return tserver;
336  }
337
338  /**
339   * the Jetty server, not null means the HTTP server is started, for test only
340   * @return the http server
341   */
342  @InterfaceAudience.Private
343  public Server getHttpServer() {
344    return httpServer;
345  }
346
347  protected void printUsageAndExit(Options options, int exitCode) throws ExitCodeException {
348    HelpFormatter formatter = new HelpFormatter();
349    formatter.printHelp("Thrift", null, options,
350      "To start the Thrift server run 'hbase-daemon.sh start thrift' or " + "'hbase thrift'\n"
351        + "To shutdown the thrift server run 'hbase-daemon.sh stop "
352        + "thrift' or send a kill signal to the thrift server pid",
353      true);
354    System.exit(exitCode);
355  }
356
357  /**
358   * Create a Servlet for the http server
359   * @param protocolFactory protocolFactory
360   * @return the servlet
361   */
362  protected TServlet createTServlet(TProtocolFactory protocolFactory) {
363    return new ThriftHttpServlet(processor, protocolFactory, serviceUGI, httpUGI,
364      hbaseServiceHandler, securityEnabled, doAsEnabled);
365  }
366
367  /**
368   * Setup an HTTP Server using Jetty to serve calls from THttpClient
369   * @throws IOException IOException
370   */
371  protected void setupHTTPServer() throws IOException {
372    TProtocolFactory protocolFactory = new TBinaryProtocol.Factory();
373    TServlet thriftHttpServlet = createTServlet(protocolFactory);
374
375    // Set the default max thread number to 100 to limit
376    // the number of concurrent requests so that Thrfit HTTP server doesn't OOM easily.
377    // Jetty set the default max thread number to 250, if we don't set it.
378    //
379    // Our default min thread number 2 is the same as that used by Jetty.
380    int minThreads = conf.getInt(HTTP_MIN_THREADS_KEY, conf
381      .getInt(TBoundedThreadPoolServer.MIN_WORKER_THREADS_CONF_KEY, HTTP_MIN_THREADS_KEY_DEFAULT));
382    int maxThreads = conf.getInt(HTTP_MAX_THREADS_KEY, conf
383      .getInt(TBoundedThreadPoolServer.MAX_WORKER_THREADS_CONF_KEY, HTTP_MAX_THREADS_KEY_DEFAULT));
384    QueuedThreadPool threadPool = new QueuedThreadPool(maxThreads);
385    threadPool.setMinThreads(minThreads);
386    httpServer = new Server(threadPool);
387
388    // Context handler
389    boolean isSecure = conf.getBoolean(THRIFT_SSL_ENABLED_KEY, false);
390    ServletContextHandler ctxHandler =
391      new ServletContextHandler(httpServer, "/", ServletContextHandler.SESSIONS);
392    HttpServerUtil.addClickjackingPreventionFilter(ctxHandler, conf, PATH_SPEC_ANY);
393    HttpServerUtil.addSecurityHeadersFilter(ctxHandler, conf, isSecure, PATH_SPEC_ANY);
394    ctxHandler.addServlet(new ServletHolder(thriftHttpServlet), PATH_SPEC_ANY);
395    HttpServerUtil.constrainHttpMethods(ctxHandler,
396      conf.getBoolean(THRIFT_HTTP_ALLOW_OPTIONS_METHOD, THRIFT_HTTP_ALLOW_OPTIONS_METHOD_DEFAULT));
397
398    // set up Jetty and run the embedded server
399    HttpConfiguration httpConfig = new HttpConfiguration();
400    httpConfig.setSecureScheme("https");
401    httpConfig.setSecurePort(listenPort);
402    httpConfig.setHeaderCacheSize(DEFAULT_HTTP_MAX_HEADER_SIZE);
403    httpConfig.setRequestHeaderSize(DEFAULT_HTTP_MAX_HEADER_SIZE);
404    httpConfig.setResponseHeaderSize(DEFAULT_HTTP_MAX_HEADER_SIZE);
405    httpConfig.setSendServerVersion(false);
406    httpConfig.setSendDateHeader(false);
407
408    ServerConnector serverConnector;
409    if (isSecure) {
410      HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig);
411      httpsConfig.addCustomizer(new SecureRequestCustomizer());
412
413      SslContextFactory.Server sslCtxFactory = new SslContextFactory.Server();
414      // Prefer the role-scoped hbase.thrift.ssl.server.* keys, falling back to the historical
415      // unscoped hbase.thrift.ssl.* keys for backward compatibility with existing deployments. A
416      // store resolves entirely from one prefix, so a role-scoped keystore is never paired with the
417      // legacy keystore's password or type.
418      TLSStore keyStore = TLSStore.resolve(conf, "hbase.thrift.ssl.server.", "hbase.thrift.ssl.",
419        TLSStore.Keys.SERVLET_KEYSTORE);
420      sslCtxFactory.setKeyStorePath(keyStore.getLocation());
421      sslCtxFactory.setKeyStorePassword(keyStore.getPassword());
422      sslCtxFactory.setKeyManagerPassword(keyStore.getKeyPassword());
423      sslCtxFactory.setKeyStoreType(
424        StringUtils.defaultIfBlank(keyStore.getType(), THRIFT_SSL_KEYSTORE_TYPE_DEFAULT));
425
426      // Truststore is entirely new for Thrift — no legacy fallback because there is no historical
427      // hbase.thrift.ssl.truststore.* configuration. Leaving it unset is only valid when client
428      // auth is NONE
429      String trustStore = conf.get(THRIFT_SSL_SERVER_TRUSTSTORE_STORE_KEY);
430      if (StringUtils.isNotBlank(trustStore)) {
431        sslCtxFactory.setTrustStorePath(trustStore);
432        String trustStorePassword =
433          HBaseConfiguration.getPassword(conf, THRIFT_SSL_SERVER_TRUSTSTORE_PASSWORD_KEY, null);
434        if (StringUtils.isNotBlank(trustStorePassword)) {
435          sslCtxFactory.setTrustStorePassword(trustStorePassword);
436        }
437        String trustStoreType = conf.get(THRIFT_SSL_SERVER_TRUSTSTORE_TYPE_KEY);
438        if (StringUtils.isNotBlank(trustStoreType)) {
439          sslCtxFactory.setTrustStoreType(trustStoreType);
440        }
441      }
442
443      // Activate mTLS if configured. Default is NONE, which preserves today's behavior of never
444      // requesting a client certificate on the Thrift-over-HTTP connector. Set
445      // hbase.thrift.ssl.server.client.auth.mode to WANT or NEED to opt in.
446      // getTrimmed + defaultIfBlank so that an empty or whitespace-only <value></value> in
447      // hbase-site.xml behaves like an absent key. A bare fromPropertyValue("") would return NEED.
448      X509Util.ClientAuth clientAuth =
449        X509Util.ClientAuth.fromPropertyValue(StringUtils.defaultIfBlank(
450          conf.getTrimmed(THRIFT_SSL_CLIENT_AUTH_MODE_KEY), X509Util.ClientAuth.NONE.name()));
451      X509Util.validateClientAuthTrustStore(clientAuth, trustStore, THRIFT_SSL_CLIENT_AUTH_MODE_KEY,
452        THRIFT_SSL_SERVER_TRUSTSTORE_STORE_KEY);
453      switch (clientAuth) {
454        case NEED:
455          sslCtxFactory.setNeedClientAuth(true);
456          break;
457        case WANT:
458          sslCtxFactory.setWantClientAuth(true);
459          break;
460        case NONE:
461        default:
462          // no-op; both flags default to false on SslContextFactory.Server
463          break;
464      }
465
466      String[] excludeCiphers =
467        conf.getStrings(THRIFT_SSL_EXCLUDE_CIPHER_SUITES_KEY, ArrayUtils.EMPTY_STRING_ARRAY);
468      if (excludeCiphers.length != 0) {
469        sslCtxFactory.setExcludeCipherSuites(excludeCiphers);
470      }
471      String[] includeCiphers =
472        conf.getStrings(THRIFT_SSL_INCLUDE_CIPHER_SUITES_KEY, ArrayUtils.EMPTY_STRING_ARRAY);
473      if (includeCiphers.length != 0) {
474        sslCtxFactory.setIncludeCipherSuites(includeCiphers);
475      }
476
477      // Disable SSLv3 by default due to "Poodle" Vulnerability - CVE-2014-3566
478      String[] excludeProtocols = conf.getStrings(THRIFT_SSL_EXCLUDE_PROTOCOLS_KEY, "SSLv3");
479      if (excludeProtocols.length != 0) {
480        sslCtxFactory.setExcludeProtocols(excludeProtocols);
481      }
482      String[] includeProtocols =
483        conf.getStrings(THRIFT_SSL_INCLUDE_PROTOCOLS_KEY, ArrayUtils.EMPTY_STRING_ARRAY);
484      if (includeProtocols.length != 0) {
485        sslCtxFactory.setIncludeProtocols(includeProtocols);
486      }
487
488      serverConnector = new ServerConnector(httpServer,
489        new SslConnectionFactory(sslCtxFactory, HttpVersion.HTTP_1_1.toString()),
490        new HttpConnectionFactory(httpsConfig));
491    } else {
492      serverConnector = new ServerConnector(httpServer, new HttpConnectionFactory(httpConfig));
493    }
494    serverConnector.setPort(listenPort);
495    serverConnector.setHost(getBindAddress(conf).getHostAddress());
496    httpServer.addConnector(serverConnector);
497    httpServer.setStopAtShutdown(true);
498
499    if (doAsEnabled) {
500      ProxyUsers.refreshSuperUserGroupsConfiguration(conf);
501    }
502    LOG.info("Starting Thrift HTTP Server on {}", Integer.toString(listenPort));
503  }
504
505  /**
506   * Setting up the thrift TServer
507   */
508  protected void setupServer() throws Exception {
509    // Construct correct ProtocolFactory
510    TProtocolFactory protocolFactory = getProtocolFactory();
511
512    ImplType implType = ImplType.getServerImpl(conf);
513    TProcessor processorToUse = processor;
514
515    // Construct correct TransportFactory
516    TTransportFactory transportFactory;
517    if (conf.getBoolean(FRAMED_CONF_KEY, FRAMED_CONF_DEFAULT) || implType.isAlwaysFramed) {
518      if (qop != null) {
519        throw new RuntimeException(
520          "Thrift server authentication" + " doesn't work with framed transport yet");
521      }
522      transportFactory = new TFramedTransport.Factory(
523        conf.getInt(MAX_FRAME_SIZE_CONF_KEY, MAX_FRAME_SIZE_CONF_DEFAULT) * 1024 * 1024);
524      LOG.debug("Using framed transport");
525    } else if (qop == null) {
526      transportFactory = new TTransportFactory();
527    } else {
528      // Extract the name from the principal
529      String thriftKerberosPrincipal = conf.get(THRIFT_KERBEROS_PRINCIPAL_KEY);
530      if (thriftKerberosPrincipal == null) {
531        throw new IllegalArgumentException(THRIFT_KERBEROS_PRINCIPAL_KEY + " cannot be null");
532      }
533      String name = SecurityUtil.getUserFromPrincipal(thriftKerberosPrincipal);
534      Map<String, String> saslProperties = SaslUtil.initSaslProperties(qop.name());
535      TSaslServerTransport.Factory saslFactory = new TSaslServerTransport.Factory();
536      saslFactory.addServerDefinition("GSSAPI", name, host, saslProperties,
537        new SaslRpcServer.SaslGssCallbackHandler() {
538          @Override
539          public void handle(Callback[] callbacks) throws UnsupportedCallbackException {
540            AuthorizeCallback ac = null;
541            for (Callback callback : callbacks) {
542              if (callback instanceof AuthorizeCallback) {
543                ac = (AuthorizeCallback) callback;
544              } else {
545                throw new UnsupportedCallbackException(callback,
546                  "Unrecognized SASL GSSAPI Callback");
547              }
548            }
549            if (ac != null) {
550              String authid = ac.getAuthenticationID();
551              String authzid = ac.getAuthorizationID();
552              if (!authid.equals(authzid)) {
553                ac.setAuthorized(false);
554              } else {
555                ac.setAuthorized(true);
556                String userName = SecurityUtil.getUserFromPrincipal(authzid);
557                LOG.info("Effective user: {}", userName);
558                ac.setAuthorizedID(userName);
559              }
560            }
561          }
562        });
563      transportFactory = saslFactory;
564
565      // Create a processor wrapper, to get the caller
566      processorToUse = (inProt, outProt) -> {
567        TSaslServerTransport saslServerTransport = (TSaslServerTransport) inProt.getTransport();
568        SaslServer saslServer = saslServerTransport.getSaslServer();
569        String principal = saslServer.getAuthorizationID();
570        hbaseServiceHandler.setEffectiveUser(principal);
571        processor.process(inProt, outProt);
572      };
573    }
574
575    if (conf.get(BIND_CONF_KEY) != null && !implType.canSpecifyBindIP) {
576      LOG.error(
577        "Server types {} don't support IP address binding at the moment. See "
578          + "https://issues.apache.org/jira/browse/HBASE-2155 for details.",
579        Joiner.on(", ").join(ImplType.serversThatCannotSpecifyBindIP()));
580      throw new RuntimeException("-" + BIND_CONF_KEY + " not supported with " + implType);
581    }
582
583    InetSocketAddress inetSocketAddress = new InetSocketAddress(getBindAddress(conf), listenPort);
584    if (
585      implType == ImplType.HS_HA || implType == ImplType.NONBLOCKING
586        || implType == ImplType.THREADED_SELECTOR
587    ) {
588      TNonblockingServerTransport serverTransport = new TNonblockingServerSocket(inetSocketAddress);
589      if (implType == ImplType.NONBLOCKING) {
590        tserver = getTNonBlockingServer(serverTransport, protocolFactory, processorToUse,
591          transportFactory, inetSocketAddress);
592      } else if (implType == ImplType.HS_HA) {
593        tserver = getTHsHaServer(serverTransport, protocolFactory, processorToUse, transportFactory,
594          inetSocketAddress);
595      } else { // THREADED_SELECTOR
596        tserver = getTThreadedSelectorServer(serverTransport, protocolFactory, processorToUse,
597          transportFactory, inetSocketAddress);
598      }
599      LOG.info("starting HBase {} server on {}", implType.simpleClassName(),
600        Integer.toString(listenPort));
601    } else if (implType == ImplType.THREAD_POOL) {
602      this.tserver =
603        getTThreadPoolServer(protocolFactory, processorToUse, transportFactory, inetSocketAddress);
604    } else {
605      throw new AssertionError(
606        "Unsupported Thrift server implementation: " + implType.simpleClassName());
607    }
608
609    // A sanity check that we instantiated the right type of server.
610    if (tserver.getClass() != implType.serverClass) {
611      throw new AssertionError("Expected to create Thrift server class "
612        + implType.serverClass.getName() + " but got " + tserver.getClass().getName());
613    }
614  }
615
616  protected TServer getTNonBlockingServer(TNonblockingServerTransport serverTransport,
617    TProtocolFactory protocolFactory, TProcessor processor, TTransportFactory transportFactory,
618    InetSocketAddress inetSocketAddress) {
619    LOG.info("starting HBase Nonblocking Thrift server on " + inetSocketAddress.toString());
620    TNonblockingServer.Args serverArgs = new TNonblockingServer.Args(serverTransport);
621    serverArgs.processor(processor);
622    serverArgs.transportFactory(transportFactory);
623    serverArgs.protocolFactory(protocolFactory);
624    return new TNonblockingServer(serverArgs);
625  }
626
627  protected TServer getTHsHaServer(TNonblockingServerTransport serverTransport,
628    TProtocolFactory protocolFactory, TProcessor processor, TTransportFactory transportFactory,
629    InetSocketAddress inetSocketAddress) {
630    LOG.info("starting HBase HsHA Thrift server on " + inetSocketAddress.toString());
631    THsHaServer.Args serverArgs = new THsHaServer.Args(serverTransport);
632    int queueSize = conf.getInt(TBoundedThreadPoolServer.MAX_QUEUED_REQUESTS_CONF_KEY,
633      TBoundedThreadPoolServer.DEFAULT_MAX_QUEUED_REQUESTS);
634    CallQueue callQueue = new CallQueue(new LinkedBlockingQueue<>(queueSize), metrics);
635    int workerThread = conf.getInt(TBoundedThreadPoolServer.MAX_WORKER_THREADS_CONF_KEY,
636      serverArgs.getMaxWorkerThreads());
637    ExecutorService executorService = createExecutor(callQueue, workerThread, workerThread);
638    serverArgs.executorService(executorService).processor(processor)
639      .transportFactory(transportFactory).protocolFactory(protocolFactory);
640    return new THsHaServer(serverArgs);
641  }
642
643  protected TServer getTThreadedSelectorServer(TNonblockingServerTransport serverTransport,
644    TProtocolFactory protocolFactory, TProcessor processor, TTransportFactory transportFactory,
645    InetSocketAddress inetSocketAddress) {
646    LOG.info("starting HBase ThreadedSelector Thrift server on " + inetSocketAddress.toString());
647    TThreadedSelectorServer.Args serverArgs =
648      new HThreadedSelectorServerArgs(serverTransport, conf);
649    int queueSize = conf.getInt(TBoundedThreadPoolServer.MAX_QUEUED_REQUESTS_CONF_KEY,
650      TBoundedThreadPoolServer.DEFAULT_MAX_QUEUED_REQUESTS);
651    CallQueue callQueue = new CallQueue(new LinkedBlockingQueue<>(queueSize), metrics);
652    int workerThreads = conf.getInt(TBoundedThreadPoolServer.MAX_WORKER_THREADS_CONF_KEY,
653      serverArgs.getWorkerThreads());
654    int selectorThreads = conf.getInt(THRIFT_SELECTOR_NUM, serverArgs.getSelectorThreads());
655    serverArgs.selectorThreads(selectorThreads);
656    ExecutorService executorService = createExecutor(callQueue, workerThreads, workerThreads);
657    serverArgs.executorService(executorService).processor(processor)
658      .transportFactory(transportFactory).protocolFactory(protocolFactory);
659    return new TThreadedSelectorServer(serverArgs);
660  }
661
662  protected TServer getTThreadPoolServer(TProtocolFactory protocolFactory, TProcessor processor,
663    TTransportFactory transportFactory, InetSocketAddress inetSocketAddress) throws Exception {
664    LOG.info("starting HBase ThreadPool Thrift server on " + inetSocketAddress.toString());
665    // Thrift's implementation uses '0' as a placeholder for 'use the default.'
666    int backlog = conf.getInt(BACKLOG_CONF_KEY, BACKLOG_CONF_DEAFULT);
667    int readTimeout =
668      conf.getInt(THRIFT_SERVER_SOCKET_READ_TIMEOUT_KEY, THRIFT_SERVER_SOCKET_READ_TIMEOUT_DEFAULT);
669    TServerTransport serverTransport =
670      new TServerSocket(new TServerSocket.ServerSocketTransportArgs().bindAddr(inetSocketAddress)
671        .backlog(backlog).clientTimeout(readTimeout));
672
673    TBoundedThreadPoolServer.Args serverArgs =
674      new TBoundedThreadPoolServer.Args(serverTransport, conf);
675    serverArgs.processor(processor).transportFactory(transportFactory)
676      .protocolFactory(protocolFactory);
677    return new TBoundedThreadPoolServer(serverArgs, metrics);
678  }
679
680  protected TProtocolFactory getProtocolFactory() {
681    TProtocolFactory protocolFactory;
682
683    if (conf.getBoolean(COMPACT_CONF_KEY, COMPACT_CONF_DEFAULT)) {
684      LOG.debug("Using compact protocol");
685      protocolFactory = new TCompactProtocol.Factory();
686    } else {
687      LOG.debug("Using binary protocol");
688      protocolFactory = new TBinaryProtocol.Factory();
689    }
690
691    return protocolFactory;
692  }
693
694  protected ExecutorService createExecutor(BlockingQueue<Runnable> callQueue, int minWorkers,
695    int maxWorkers) {
696    ThreadFactoryBuilder tfb = new ThreadFactoryBuilder();
697    tfb.setDaemon(true);
698    tfb.setNameFormat("thrift-worker-%d");
699    ThreadPoolExecutor threadPool = new THBaseThreadPoolExecutor(minWorkers, maxWorkers,
700      Long.MAX_VALUE, TimeUnit.SECONDS, callQueue, tfb.build(), metrics);
701    threadPool.allowCoreThreadTimeOut(true);
702    return threadPool;
703  }
704
705  protected InetAddress getBindAddress(Configuration conf) throws UnknownHostException {
706    String bindAddressStr = conf.get(BIND_CONF_KEY, DEFAULT_BIND_ADDR);
707    return InetAddress.getByName(bindAddressStr);
708  }
709
710  public static void registerFilters(Configuration conf) {
711    String[] filters = conf.getStrings(THRIFT_FILTERS);
712    Splitter splitter = Splitter.on(':');
713    if (filters != null) {
714      for (String filterClass : filters) {
715        List<String> filterPart = splitter.splitToList(filterClass);
716        if (filterPart.size() != 2) {
717          LOG.warn("Invalid filter specification " + filterClass + " - skipping");
718        } else {
719          ParseFilter.registerFilter(filterPart.get(0), filterPart.get(1));
720        }
721      }
722    }
723  }
724
725  /**
726   * Add options to command lines
727   * @param options options
728   */
729  protected void addOptions(Options options) {
730    options.addOption("b", BIND_OPTION, true,
731      "Address to bind " + "the Thrift server to. [default: " + DEFAULT_BIND_ADDR + "]");
732    options.addOption("p", PORT_OPTION, true,
733      "Port to bind to [default: " + DEFAULT_LISTEN_PORT + "]");
734    options.addOption("f", FRAMED_OPTION, false, "Use framed transport");
735    options.addOption("c", COMPACT_OPTION, false, "Use the compact protocol");
736    options.addOption("h", "help", false, "Print help information");
737    options.addOption("s", SELECTOR_NUM_OPTION, true, "How many selector threads to use.");
738    options.addOption(null, INFOPORT_OPTION, true, "Port for web UI");
739
740    options.addOption("m", MIN_WORKERS_OPTION, true,
741      "The minimum number of worker threads for " + ImplType.THREAD_POOL.simpleClassName());
742
743    options.addOption("w", MAX_WORKERS_OPTION, true,
744      "The maximum number of worker threads for " + ImplType.THREAD_POOL.simpleClassName());
745
746    options.addOption("q", MAX_QUEUE_SIZE_OPTION, true,
747      "The maximum number of queued requests in " + ImplType.THREAD_POOL.simpleClassName());
748
749    options.addOption("k", KEEP_ALIVE_SEC_OPTION, true,
750      "The amount of time in secods to keep a thread alive when idle in "
751        + ImplType.THREAD_POOL.simpleClassName());
752
753    options.addOption("t", READ_TIMEOUT_OPTION, true,
754      "Amount of time in milliseconds before a server thread will timeout "
755        + "waiting for client to send data on a connected socket. Currently, "
756        + "only applies to TBoundedThreadPoolServer");
757
758    options.addOptionGroup(ImplType.createOptionGroup());
759  }
760
761  protected void parseCommandLine(CommandLine cmd, Options options) throws ExitCodeException {
762    // Get port to bind to
763    try {
764      if (cmd.hasOption(PORT_OPTION)) {
765        int listenPort = Integer.parseInt(cmd.getOptionValue(PORT_OPTION));
766        conf.setInt(PORT_CONF_KEY, listenPort);
767      }
768    } catch (NumberFormatException e) {
769      LOG.error("Could not parse the value provided for the port option", e);
770      printUsageAndExit(options, -1);
771    }
772    // check for user-defined info server port setting, if so override the conf
773    try {
774      if (cmd.hasOption(INFOPORT_OPTION)) {
775        String val = cmd.getOptionValue(INFOPORT_OPTION);
776        conf.setInt(THRIFT_INFO_SERVER_PORT, Integer.parseInt(val));
777        LOG.debug("Web UI port set to " + val);
778      }
779    } catch (NumberFormatException e) {
780      LOG.error("Could not parse the value provided for the " + INFOPORT_OPTION + " option", e);
781      printUsageAndExit(options, -1);
782    }
783    // Make optional changes to the configuration based on command-line options
784    optionToConf(cmd, MIN_WORKERS_OPTION, conf,
785      TBoundedThreadPoolServer.MIN_WORKER_THREADS_CONF_KEY);
786    optionToConf(cmd, MAX_WORKERS_OPTION, conf,
787      TBoundedThreadPoolServer.MAX_WORKER_THREADS_CONF_KEY);
788    optionToConf(cmd, MAX_QUEUE_SIZE_OPTION, conf,
789      TBoundedThreadPoolServer.MAX_QUEUED_REQUESTS_CONF_KEY);
790    optionToConf(cmd, KEEP_ALIVE_SEC_OPTION, conf,
791      TBoundedThreadPoolServer.THREAD_KEEP_ALIVE_TIME_SEC_CONF_KEY);
792    optionToConf(cmd, READ_TIMEOUT_OPTION, conf, THRIFT_SERVER_SOCKET_READ_TIMEOUT_KEY);
793    optionToConf(cmd, SELECTOR_NUM_OPTION, conf, THRIFT_SELECTOR_NUM);
794
795    // Set general thrift server options
796    boolean compact = cmd.hasOption(COMPACT_OPTION) || conf.getBoolean(COMPACT_CONF_KEY, false);
797    conf.setBoolean(COMPACT_CONF_KEY, compact);
798    boolean framed = cmd.hasOption(FRAMED_OPTION) || conf.getBoolean(FRAMED_CONF_KEY, false);
799    conf.setBoolean(FRAMED_CONF_KEY, framed);
800
801    optionToConf(cmd, BIND_OPTION, conf, BIND_CONF_KEY);
802
803    ImplType.setServerImpl(cmd, conf);
804  }
805
806  /**
807   * Parse the command line options to set parameters the conf.
808   */
809  protected void processOptions(final String[] args) throws Exception {
810    if (args == null || args.length == 0) {
811      return;
812    }
813    Options options = new Options();
814    addOptions(options);
815
816    CommandLineParser parser = new DefaultParser();
817    CommandLine cmd = parser.parse(options, args);
818
819    if (cmd.hasOption("help")) {
820      printUsageAndExit(options, 0);
821    }
822    parseCommandLine(cmd, options);
823  }
824
825  public void stop() {
826    if (this.infoServer != null) {
827      LOG.info("Stopping infoServer");
828      try {
829        this.infoServer.stop();
830      } catch (Exception ex) {
831        LOG.error("Failed to stop infoServer", ex);
832      }
833    }
834    if (pauseMonitor != null) {
835      pauseMonitor.stop();
836    }
837    if (tserver != null) {
838      tserver.stop();
839      tserver = null;
840    }
841    if (httpServer != null) {
842      try {
843        httpServer.stop();
844        httpServer = null;
845      } catch (Exception e) {
846        LOG.error("Problem encountered in shutting down HTTP server", e);
847      }
848      httpServer = null;
849    }
850  }
851
852  protected static void optionToConf(CommandLine cmd, String option, Configuration conf,
853    String destConfKey) {
854    if (cmd.hasOption(option)) {
855      String value = cmd.getOptionValue(option);
856      LOG.info("Set configuration key:" + destConfKey + " value:" + value);
857      conf.set(destConfKey, value);
858    }
859  }
860
861  /**
862   * Run without any command line arguments
863   * @return exit code
864   * @throws Exception exception
865   */
866  public int run() throws Exception {
867    return run(null);
868  }
869
870  @Override
871  public int run(String[] strings) throws Exception {
872    processOptions(strings);
873    setupParamters();
874    if (httpEnabled) {
875      setupHTTPServer();
876    } else {
877      setupServer();
878    }
879    return serviceUGI.doAs(new PrivilegedAction<Integer>() {
880      @Override
881      public Integer run() {
882        try {
883          startInfoServer();
884          if (httpEnabled) {
885            httpServer.start();
886            httpServer.join();
887          } else {
888            tserver.serve();
889          }
890          return 0;
891        } catch (Exception e) {
892          LOG.error(HBaseMarkers.FATAL, "Cannot run ThriftServer", e);
893          return -1;
894        }
895      }
896    });
897  }
898
899  public static void main(String[] args) throws Exception {
900    LOG.info("***** STARTING service '" + ThriftServer.class.getSimpleName() + "' *****");
901    VersionInfo.logVersion();
902    final Configuration conf = HBaseConfiguration.create();
903    // for now, only time we return is on an argument error.
904    final int status = ToolRunner.run(conf, new ThriftServer(conf), args);
905    LOG.info("***** STOPPING service '" + ThriftServer.class.getSimpleName() + "' *****");
906    System.exit(status);
907  }
908}