View Javadoc

1   /*
2    * Copyright 2010 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  
21  package org.apache.hadoop.hbase.rest;
22  
23  import org.apache.commons.cli.CommandLine;
24  import org.apache.commons.cli.HelpFormatter;
25  import org.apache.commons.cli.Options;
26  import org.apache.commons.cli.PosixParser;
27  import org.apache.commons.cli.ParseException;
28  
29  import org.apache.commons.logging.Log;
30  import org.apache.commons.logging.LogFactory;
31  import org.apache.hadoop.conf.Configuration;
32  import org.apache.hadoop.hbase.HBaseConfiguration;
33  import org.apache.hadoop.hbase.client.UserProvider;
34  import org.apache.hadoop.hbase.util.HttpServerUtil;
35  import org.apache.hadoop.hbase.rest.filter.GzipFilter;
36  import org.apache.hadoop.hbase.security.User;
37  import org.apache.hadoop.hbase.util.InfoServer;
38  import org.apache.hadoop.hbase.util.Strings;
39  import org.apache.hadoop.hbase.util.VersionInfo;
40  import org.apache.hadoop.net.DNS;
41  
42  import java.util.List;
43  import java.util.ArrayList;
44  import java.util.Map;
45  import java.util.Map.Entry;
46  
47  import org.mortbay.jetty.Connector;
48  import org.mortbay.jetty.Server;
49  import org.mortbay.jetty.nio.SelectChannelConnector;
50  import org.mortbay.jetty.servlet.Context;
51  import org.mortbay.jetty.servlet.ServletHolder;
52  import org.mortbay.thread.QueuedThreadPool;
53  
54  import com.sun.jersey.api.json.JSONConfiguration;
55  import com.sun.jersey.spi.container.servlet.ServletContainer;
56  
57  /**
58   * Main class for launching REST gateway as a servlet hosted by Jetty.
59   * <p>
60   * The following options are supported:
61   * <ul>
62   * <li>-p --port : service port</li>
63   * <li>-ro --readonly : server mode</li>
64   * </ul>
65   */
66  public class Main implements Constants {
67  
68    private static void printUsageAndExit(Options options, int exitCode) {
69      HelpFormatter formatter = new HelpFormatter();
70      formatter.printHelp("bin/hbase rest start", "", options,
71        "\nTo run the REST server as a daemon, execute " +
72        "bin/hbase-daemon.sh start|stop rest [--infoport <port>] [-p <port>] [-ro]\n", true);
73      System.exit(exitCode);
74    }
75  
76    /**
77     * The main method for the HBase rest server.
78     * @param args command-line arguments
79     * @throws Exception exception
80     */
81    public static void main(String[] args) throws Exception {
82      Log LOG = LogFactory.getLog("RESTServer");
83  
84      VersionInfo.logVersion();
85      Configuration conf = HBaseConfiguration.create();
86      // login the server principal (if using secure Hadoop)
87      UserProvider provider = UserProvider.instantiate(conf);
88      if (provider.isHadoopSecurityEnabled() && provider.isHBaseSecurityEnabled()) {
89        String machineName = Strings.domainNamePointerToHostName(
90          DNS.getDefaultHost(conf.get("hbase.rest.dns.interface", "default"),
91            conf.get("hbase.rest.dns.nameserver", "default")));
92        provider.login("hbase.rest.keytab.file", "hbase.rest.kerberos.principal",
93          machineName);
94      }
95  
96      RESTServlet servlet = RESTServlet.getInstance(conf);
97  
98      Options options = new Options();
99      options.addOption("p", "port", true, "Port to bind to [default: 8080]");
100     options.addOption("ro", "readonly", false, "Respond only to GET HTTP " +
101       "method requests [default: false]");
102     options.addOption(null, "infoport", true, "Port for web UI");
103 
104     CommandLine commandLine = null;
105     try {
106       commandLine = new PosixParser().parse(options, args);
107     } catch (ParseException e) {
108       LOG.error("Could not parse: ", e);
109       printUsageAndExit(options, -1);
110     }
111 
112     // check for user-defined port setting, if so override the conf
113     if (commandLine != null && commandLine.hasOption("port")) {
114       String val = commandLine.getOptionValue("port");
115       servlet.getConfiguration()
116           .setInt("hbase.rest.port", Integer.valueOf(val));
117       LOG.debug("port set to " + val);
118     }
119 
120     // check if server should only process GET requests, if so override the conf
121     if (commandLine != null && commandLine.hasOption("readonly")) {
122       servlet.getConfiguration().setBoolean("hbase.rest.readonly", true);
123       LOG.debug("readonly set to true");
124     }
125 
126     // check for user-defined info server port setting, if so override the conf
127     if (commandLine != null && commandLine.hasOption("infoport")) {
128       String val = commandLine.getOptionValue("infoport");
129       servlet.getConfiguration()
130           .setInt("hbase.rest.info.port", Integer.valueOf(val));
131       LOG.debug("Web UI port set to " + val);
132     }
133 
134     @SuppressWarnings("unchecked")
135     List<String> remainingArgs = commandLine != null ?
136         commandLine.getArgList() : new ArrayList<String>();
137     if (remainingArgs.size() != 1) {
138       printUsageAndExit(options, 1);
139     }
140 
141     String command = remainingArgs.get(0);
142     if ("start".equals(command)) {
143       // continue and start container
144     } else if ("stop".equals(command)) {
145       System.exit(1);
146     } else {
147       printUsageAndExit(options, 1);
148     }
149 
150     // set up the Jersey servlet container for Jetty
151     ServletHolder sh = new ServletHolder(ServletContainer.class);
152     sh.setInitParameter(
153       "com.sun.jersey.config.property.resourceConfigClass",
154       ResourceConfig.class.getCanonicalName());
155     sh.setInitParameter("com.sun.jersey.config.property.packages",
156       "jetty");
157     // The servlet holder below is instantiated to only handle the case
158     // of the /status/cluster returning arrays of nodes (live/dead). Without
159     // this servlet holder, the problem is that the node arrays in the response 
160     // are collapsed to single nodes. We want to be able to treat the 
161     // node lists as POJO in the response to /status/cluster servlet call, 
162     // but not change the behavior for any of the other servlets
163     // Hence we don't use the servlet holder for all servlets / paths
164     ServletHolder shPojoMap = new ServletHolder(ServletContainer.class);
165     @SuppressWarnings("unchecked")
166     Map<String, String> shInitMap = sh.getInitParameters();
167     for (Entry<String, String> e : shInitMap.entrySet()) {
168       shPojoMap.setInitParameter(e.getKey(), e.getValue());
169     }
170     shPojoMap.setInitParameter(JSONConfiguration.FEATURE_POJO_MAPPING, "true");
171 
172     // set up Jetty and run the embedded server
173 
174     Server server = new Server();
175 
176     Connector connector = new SelectChannelConnector();
177     connector.setPort(servlet.getConfiguration().getInt("hbase.rest.port", 8080));
178     connector.setHost(servlet.getConfiguration().get("hbase.rest.host", "0.0.0.0"));
179 
180     server.addConnector(connector);
181 
182     // Set the default max thread number to 100 to limit
183     // the number of concurrent requests so that REST server doesn't OOM easily.
184     // Jetty set the default max thread number to 250, if we don't set it.
185     //
186     // Our default min thread number 2 is the same as that used by Jetty.
187     int maxThreads = servlet.getConfiguration().getInt("hbase.rest.threads.max", 100);
188     int minThreads = servlet.getConfiguration().getInt("hbase.rest.threads.min", 2);
189     QueuedThreadPool threadPool = new QueuedThreadPool(maxThreads);
190     threadPool.setMinThreads(minThreads);
191     server.setThreadPool(threadPool);
192 
193     server.setSendServerVersion(false);
194     server.setSendDateHeader(false);
195     server.setStopAtShutdown(true);
196 
197     // set up context
198     Context context = new Context(server, "/", Context.SESSIONS);
199     context.addServlet(shPojoMap, "/status/cluster");
200     context.addServlet(sh, "/*");
201     context.addFilter(GzipFilter.class, "/*", 0);
202     HttpServerUtil.constrainHttpMethods(context);
203 
204     // Put up info server.
205     int port = conf.getInt("hbase.rest.info.port", 8085);
206     if (port >= 0) {
207       conf.setLong("startcode", System.currentTimeMillis());
208       String a = conf.get("hbase.rest.info.bindAddress", "0.0.0.0");
209       InfoServer infoServer = new InfoServer("rest", a, port, false, conf);
210       infoServer.setAttribute("hbase.conf", conf);
211       infoServer.start();
212     }
213 
214     // start server
215     server.start();
216     server.join();
217   }
218 }