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