HBASE-3161 Provide option for Stargate to only serve GET requests

git-svn-id: https://svn.apache.org/repos/asf/hbase/trunk@1033626 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Michael Stack 2010-11-10 18:12:23 +00:00
parent e4e423bd83
commit 6b7055eb24
12 changed files with 231 additions and 55 deletions

View File

@ -1141,6 +1141,8 @@ Release 0.90.0 - Unreleased
HBASE-3168 Sanity date and time check when a region server joins the
cluster (Jeff Whiting and jgray)
HBASE-3090 Don't include hbase-default in conf/ assembly
HBASE-3161 Provide option for Stargate to only serve GET requests
(Bennett Neale via Stack)
NEW FEATURES

View File

@ -28,6 +28,8 @@ public interface Constants {
public static final int DEFAULT_MAX_AGE = 60 * 60 * 4; // 4 hours
public static final int DEFAULT_LISTEN_PORT = 8080;
public static final String MIMETYPE_TEXT = "text/plain";
public static final String MIMETYPE_HTML = "text/html";
public static final String MIMETYPE_XML = "text/xml";

View File

@ -21,18 +21,18 @@
package org.apache.hadoop.hbase.rest;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.PosixParser;
import org.apache.commons.cli.ParseException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseConfiguration;
import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.servlet.Context;
@ -42,54 +42,79 @@ import com.sun.jersey.spi.container.servlet.ServletContainer;
/**
* Main class for launching REST gateway as a servlet hosted by Jetty.
* <p>
* <p>
* The following options are supported:
* <ul>
* <li>-p: service port</li>
* <li>-p --port : service port</li>
* <li>-ro --readonly : server mode</li>
* </ul>
*/
public class Main implements Constants {
private static final String DEFAULT_LISTEN_PORT = "8080";
private static void printUsageAndExit(Options options, int exitCode) {
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp("REST", null, options,
"To start the REST server run 'bin/hbase-daemon.sh start rest'\n" +
"To shutdown the REST server run 'bin/hbase-daemon.sh stop rest' or" +
" send a kill signal to the rest server pid",
true);
formatter.printHelp("bin/hbase rest start", "", options,
"\nTo run the REST server as a daemon, execute " +
"bin/hbase-daemon.sh start|stop rest [-p <port>] [-ro]\n", true);
System.exit(exitCode);
}
/**
* The main method for the HBase rest server.
* @param args command-line arguments
* @throws Exception exception
*/
public static void main(String[] args) throws Exception {
Log LOG = LogFactory.getLog("RESTServer");
Configuration conf = HBaseConfiguration.create();
RESTServlet servlet = RESTServlet.getInstance(conf);
Options options = new Options();
options.addOption("p", "port", true, "Port to bind to [default:" +
DEFAULT_LISTEN_PORT + "]");
CommandLineParser parser = new PosixParser();
CommandLine cmd = parser.parse(options, args);
/**
* This is so complicated to please both bin/hbase and bin/hbase-daemon.
* hbase-daemon provides "start" and "stop" arguments
* hbase should print the help if no argument is provided
*/
List<String> commandLine = Arrays.asList(args);
boolean stop = commandLine.contains("stop");
boolean start = commandLine.contains("start");
if (cmd.hasOption("help") || !start || stop) {
printUsageAndExit(options, 1);
}
// Get port to bind to
int port = 0;
options.addOption("ro", "readonly", false, "Respond only to GET HTTP " +
"method requests [default: false]");
CommandLine commandLine = null;
try {
port = Integer.parseInt(cmd.getOptionValue("port", DEFAULT_LISTEN_PORT));
} catch (NumberFormatException e) {
LOG.error("Could not parse the value provided for the port option", e);
commandLine = new PosixParser().parse(options, args);
} catch (ParseException e) {
LOG.error("Could not parse: ", e);
printUsageAndExit(options, -1);
}
// set up the Jersey servlet container for Jetty
// check for user-defined port setting, if so override the conf
if (commandLine != null && commandLine.hasOption("port")) {
String val = commandLine.getOptionValue("port");
servlet.getConfiguration()
.setInt("hbase.rest.port", Integer.valueOf(val));
LOG.debug("port set to " + val);
}
// check if server should only process GET requests, if so override the conf
if (commandLine != null && commandLine.hasOption("readonly")) {
servlet.getConfiguration().setBoolean("hbase.rest.readonly", true);
LOG.debug("readonly set to true");
}
@SuppressWarnings("unchecked")
List<String> remainingArgs = commandLine != null ?
commandLine.getArgList() : new ArrayList<String>();
if (remainingArgs.size() != 1) {
printUsageAndExit(options, 1);
}
String command = remainingArgs.get(0);
if ("start".equals(command)) {
// continue and start container
} else if ("stop".equals(command)) {
System.exit(1);
} else {
printUsageAndExit(options, 1);
}
// set up the Jersey servlet container for Jetty
ServletHolder sh = new ServletHolder(ServletContainer.class);
sh.setInitParameter(
"com.sun.jersey.config.property.resourceConfigClass",
@ -99,9 +124,8 @@ public class Main implements Constants {
// set up Jetty and run the embedded server
Configuration conf = HBaseConfiguration.create();
RESTServlet servlet = RESTServlet.getInstance(conf);
port = servlet.getConfiguration().getInt("hbase.rest.port", port);
int port = servlet.getConfiguration().getInt("hbase.rest.port",
DEFAULT_LISTEN_PORT);
Server server = new Server(port);
server.setSendServerVersion(false);

View File

@ -60,7 +60,7 @@ public class RESTServlet implements Constants {
public synchronized static void stop() {
if (INSTANCE != null) INSTANCE = null;
}
/**
* Constructor with existing configuration
* @param conf existing configuration
@ -82,4 +82,13 @@ public class RESTServlet implements Constants {
RESTMetrics getMetrics() {
return metrics;
}
}
/**
* Helper method to determine if server should
* only respond to GET HTTP method requests.
* @return boolean for server read-only state
*/
boolean isReadOnly() {
return getConfiguration().getBoolean("hbase.rest.readonly", false);
}
}

View File

@ -151,6 +151,9 @@ public class RowResource extends ResourceBase {
Response update(final CellSetModel model, final boolean replace) {
servlet.getMetrics().incrementRequests(1);
if (servlet.isReadOnly()) {
throw new WebApplicationException(Response.Status.FORBIDDEN);
}
HTablePool pool = servlet.getTablePool();
HTableInterface table = null;
try {
@ -195,8 +198,11 @@ public class RowResource extends ResourceBase {
Response updateBinary(final byte[] message, final HttpHeaders headers,
final boolean replace) {
servlet.getMetrics().incrementRequests(1);
if (servlet.isReadOnly()) {
throw new WebApplicationException(Response.Status.FORBIDDEN);
}
HTablePool pool = servlet.getTablePool();
HTableInterface table = null;
HTableInterface table = null;
try {
byte[] row = rowspec.getRow();
byte[][] columns = rowspec.getColumns();
@ -293,6 +299,9 @@ public class RowResource extends ResourceBase {
LOG.debug("DELETE " + uriInfo.getAbsolutePath());
}
servlet.getMetrics().incrementRequests(1);
if (servlet.isReadOnly()) {
throw new WebApplicationException(Response.Status.FORBIDDEN);
}
Delete delete = null;
if (rowspec.hasTimestamp())
delete = new Delete(rowspec.getRow(), rowspec.getTimestamp(), null);

View File

@ -159,6 +159,9 @@ public class ScannerInstanceResource extends ResourceBase {
LOG.debug("DELETE " + uriInfo.getAbsolutePath());
}
servlet.getMetrics().incrementRequests(1);
if (servlet.isReadOnly()) {
throw new WebApplicationException(Response.Status.FORBIDDEN);
}
ScannerResource.delete(id);
return Response.ok().build();
}

View File

@ -70,9 +70,12 @@ public class ScannerResource extends ResourceBase {
}
}
Response update(final ScannerModel model, final boolean replace,
Response update(final ScannerModel model, final boolean replace,
final UriInfo uriInfo) {
servlet.getMetrics().incrementRequests(1);
if (servlet.isReadOnly()) {
throw new WebApplicationException(Response.Status.FORBIDDEN);
}
byte[] endRow = model.hasEndRow() ? model.getEndRow() : null;
RowSpec spec = new RowSpec(model.getStartRow(), endRow,
model.getColumns(), model.getStartTime(), model.getEndTime(), 1);

View File

@ -107,6 +107,9 @@ public class SchemaResource extends ResourceBase {
private Response replace(final byte[] name, final TableSchemaModel model,
final UriInfo uriInfo, final HBaseAdmin admin) {
if (servlet.isReadOnly()) {
throw new WebApplicationException(Response.Status.FORBIDDEN);
}
try {
HTableDescriptor htd = new HTableDescriptor(name);
for (Map.Entry<QName,Object> e: model.getAny().entrySet()) {
@ -133,11 +136,14 @@ public class SchemaResource extends ResourceBase {
} catch (IOException e) {
throw new WebApplicationException(e,
Response.Status.SERVICE_UNAVAILABLE);
}
}
}
}
private Response update(final byte[] name, final TableSchemaModel model,
final UriInfo uriInfo, final HBaseAdmin admin) {
if (servlet.isReadOnly()) {
throw new WebApplicationException(Response.Status.FORBIDDEN);
}
try {
HTableDescriptor htd = admin.getTableDescriptor(name);
admin.disableTable(name);

View File

@ -560,4 +560,18 @@
</description>
</property>
<!-- End of properties that are directly mapped from ZooKeeper's zoo.cfg -->
<property>
<name>hbase.rest.port</name>
<value>8080</value>
<description>The port for the HBase REST server.</description>
</property>
<property>
<name>hbase.rest.readonly</name>
<value>false</value>
<description>
Defines the mode the REST server will be started in. Possible values are:
false: All HTTP methods are permitted - GET/PUT/POST/DELETE.
true: Only the GET method is permitted.
</description>
</property>
</configuration>

View File

@ -31,6 +31,7 @@ import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import org.apache.commons.httpclient.Header;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HConstants;
@ -71,11 +72,13 @@ public class TestRowResource {
private static JAXBContext context;
private static Marshaller marshaller;
private static Unmarshaller unmarshaller;
private static Configuration conf;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
conf = TEST_UTIL.getConfiguration();
TEST_UTIL.startMiniCluster(3);
REST_TEST_UTIL.startServletContainer(TEST_UTIL.getConfiguration());
REST_TEST_UTIL.startServletContainer(conf);
context = JAXBContext.newInstance(
CellModel.class,
CellSetModel.class,
@ -273,6 +276,33 @@ public class TestRowResource {
assertEquals(response.getCode(), 404);
}
@Test
public void testForbidden() throws IOException, JAXBException {
Response response;
conf.set("hbase.rest.readonly", "true");
response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
assertEquals(response.getCode(), 403);
response = putValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
assertEquals(response.getCode(), 403);
response = deleteValue(TABLE, ROW_1, COLUMN_1);
assertEquals(response.getCode(), 403);
response = deleteRow(TABLE, ROW_1);
assertEquals(response.getCode(), 403);
conf.set("hbase.rest.readonly", "false");
response = putValueXML(TABLE, ROW_1, COLUMN_1, VALUE_1);
assertEquals(response.getCode(), 200);
response = putValuePB(TABLE, ROW_1, COLUMN_1, VALUE_1);
assertEquals(response.getCode(), 200);
response = deleteValue(TABLE, ROW_1, COLUMN_1);
assertEquals(response.getCode(), 200);
response = deleteRow(TABLE, ROW_1);
assertEquals(response.getCode(), 200);
}
@Test
public void testSingleCellGetPutXML() throws IOException, JAXBException {
Response response = getValueXML(TABLE, ROW_1, COLUMN_1);
@ -286,7 +316,7 @@ public class TestRowResource {
checkValueXML(TABLE, ROW_1, COLUMN_1, VALUE_2);
response = deleteRow(TABLE, ROW_1);
assertEquals(response.getCode(), 200);
assertEquals(response.getCode(), 200);
}
@Test
@ -306,7 +336,7 @@ public class TestRowResource {
checkValuePB(TABLE, ROW_1, COLUMN_1, VALUE_2);
response = deleteRow(TABLE, ROW_1);
assertEquals(response.getCode(), 200);
assertEquals(response.getCode(), 200);
}
@Test
@ -412,7 +442,7 @@ public class TestRowResource {
checkValueXML(TABLE, ROW_2, COLUMN_2, VALUE_4);
response = deleteRow(TABLE, ROW_1);
assertEquals(response.getCode(), 200);
assertEquals(response.getCode(), 200);
response = deleteRow(TABLE, ROW_2);
assertEquals(response.getCode(), 200);
}
@ -449,7 +479,7 @@ public class TestRowResource {
checkValuePB(TABLE, ROW_2, COLUMN_2, VALUE_4);
response = deleteRow(TABLE, ROW_1);
assertEquals(response.getCode(), 200);
assertEquals(response.getCode(), 200);
response = deleteRow(TABLE, ROW_2);
assertEquals(response.getCode(), 200);
}

View File

@ -32,6 +32,7 @@ import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import org.apache.commons.httpclient.Header;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.HColumnDescriptor;
import org.apache.hadoop.hbase.HTableDescriptor;
@ -70,6 +71,7 @@ public class TestScannerResource {
private static Unmarshaller unmarshaller;
private static int expectedRows1;
private static int expectedRows2;
private static Configuration conf;
private static int insertData(String tableName, String column, double prob)
throws IOException {
@ -146,9 +148,10 @@ public class TestScannerResource {
@BeforeClass
public static void setUpBeforeClass() throws Exception {
conf = TEST_UTIL.getConfiguration();
TEST_UTIL.startMiniCluster(3);
REST_TEST_UTIL.startServletContainer(TEST_UTIL.getConfiguration());
client = new Client(new Cluster().add("localhost",
REST_TEST_UTIL.startServletContainer(conf);
client = new Client(new Cluster().add("localhost",
REST_TEST_UTIL.getServletPort()));
context = JAXBContext.newInstance(
CellModel.class,
@ -174,7 +177,7 @@ public class TestScannerResource {
REST_TEST_UTIL.shutdownServletContainer();
TEST_UTIL.shutdownMiniCluster();
}
@Test
public void testSimpleScannerXML() throws IOException, JAXBException {
final int BATCH_SIZE = 5;
@ -185,10 +188,21 @@ public class TestScannerResource {
StringWriter writer = new StringWriter();
marshaller.marshal(model, writer);
byte[] body = Bytes.toBytes(writer.toString());
// test put operation is forbidden in read-only mode
conf.set("hbase.rest.readonly", "true");
Response response = client.put("/" + TABLE + "/scanner",
Constants.MIMETYPE_XML, body);
assertEquals(response.getCode(), 201);
assertEquals(response.getCode(), 403);
String scannerURI = response.getLocation();
assertNull(scannerURI);
// recall previous put operation with read-only off
conf.set("hbase.rest.readonly", "false");
response = client.put("/" + TABLE + "/scanner", Constants.MIMETYPE_XML,
body);
assertEquals(response.getCode(), 201);
scannerURI = response.getLocation();
assertNotNull(scannerURI);
// get a cell set
@ -199,7 +213,13 @@ public class TestScannerResource {
// confirm batch size conformance
assertEquals(countCellSet(cellSet), BATCH_SIZE);
// delete the scanner
// test delete scanner operation is forbidden in read-only mode
conf.set("hbase.rest.readonly", "true");
response = client.delete(scannerURI);
assertEquals(response.getCode(), 403);
// recall previous delete scanner operation with read-only off
conf.set("hbase.rest.readonly", "false");
response = client.delete(scannerURI);
assertEquals(response.getCode(), 200);
}
@ -211,10 +231,21 @@ public class TestScannerResource {
ScannerModel model = new ScannerModel();
model.setBatch(BATCH_SIZE);
model.addColumn(Bytes.toBytes(COLUMN_1));
// test put operation is forbidden in read-only mode
conf.set("hbase.rest.readonly", "true");
Response response = client.put("/" + TABLE + "/scanner",
Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
assertEquals(response.getCode(), 201);
assertEquals(response.getCode(), 403);
String scannerURI = response.getLocation();
assertNull(scannerURI);
// recall previous put operation with read-only off
conf.set("hbase.rest.readonly", "false");
response = client.put("/" + TABLE + "/scanner",
Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
assertEquals(response.getCode(), 201);
scannerURI = response.getLocation();
assertNotNull(scannerURI);
// get a cell set
@ -225,7 +256,13 @@ public class TestScannerResource {
// confirm batch size conformance
assertEquals(countCellSet(cellSet), BATCH_SIZE);
// delete the scanner
// test delete scanner operation is forbidden in read-only mode
conf.set("hbase.rest.readonly", "true");
response = client.delete(scannerURI);
assertEquals(response.getCode(), 403);
// recall previous delete scanner operation with read-only off
conf.set("hbase.rest.readonly", "false");
response = client.delete(scannerURI);
assertEquals(response.getCode(), 200);
}
@ -236,10 +273,21 @@ public class TestScannerResource {
ScannerModel model = new ScannerModel();
model.setBatch(1);
model.addColumn(Bytes.toBytes(COLUMN_1));
// test put operation is forbidden in read-only mode
conf.set("hbase.rest.readonly", "true");
Response response = client.put("/" + TABLE + "/scanner",
Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
assertEquals(response.getCode(), 201);
assertEquals(response.getCode(), 403);
String scannerURI = response.getLocation();
assertNull(scannerURI);
// recall previous put operation with read-only off
conf.set("hbase.rest.readonly", "false");
response = client.put("/" + TABLE + "/scanner",
Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
assertEquals(response.getCode(), 201);
scannerURI = response.getLocation();
assertNotNull(scannerURI);
// get a cell
@ -263,7 +311,13 @@ public class TestScannerResource {
assertTrue(foundColumnHeader);
assertTrue(foundTimestampHeader);
// delete the scanner
// test delete scanner operation is forbidden in read-only mode
conf.set("hbase.rest.readonly", "true");
response = client.delete(scannerURI);
assertEquals(response.getCode(), 403);
// recall previous delete scanner operation with read-only off
conf.set("hbase.rest.readonly", "false");
response = client.delete(scannerURI);
assertEquals(response.getCode(), 200);
}

View File

@ -27,6 +27,7 @@ import java.io.StringWriter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.HBaseTestingUtility;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.rest.client.Client;
@ -47,16 +48,18 @@ public class TestSchemaResource {
private static String TABLE2 = "TestSchemaResource2";
private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
private static final HBaseRESTTestingUtility REST_TEST_UTIL =
private static final HBaseRESTTestingUtility REST_TEST_UTIL =
new HBaseRESTTestingUtility();
private static Client client;
private static JAXBContext context;
private static Configuration conf;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
conf = TEST_UTIL.getConfiguration();
TEST_UTIL.startMiniCluster(3);
REST_TEST_UTIL.startServletContainer(TEST_UTIL.getConfiguration());
client = new Client(new Cluster().add("localhost",
REST_TEST_UTIL.startServletContainer(conf);
client = new Client(new Cluster().add("localhost",
REST_TEST_UTIL.getServletPort()));
context = JAXBContext.newInstance(
ColumnSchemaModel.class,
@ -96,6 +99,11 @@ public class TestSchemaResource {
response = client.put(schemaPath, Constants.MIMETYPE_XML, toXML(model));
assertEquals(response.getCode(), 201);
// recall the same put operation but in read-only mode
conf.set("hbase.rest.readonly", "true");
response = client.put(schemaPath, Constants.MIMETYPE_XML, toXML(model));
assertEquals(response.getCode(), 403);
// make sure HBase concurs, and wait for the table to come online
admin.enableTable(TABLE1);
@ -110,6 +118,9 @@ public class TestSchemaResource {
// make sure HBase concurs
assertFalse(admin.tableExists(TABLE1));
// return read-only setting back to default
conf.set("hbase.rest.readonly", "false");
}
@Test
@ -128,6 +139,12 @@ public class TestSchemaResource {
model.createProtobufOutput());
assertEquals(response.getCode(), 201);
// recall the same put operation but in read-only mode
conf.set("hbase.rest.readonly", "true");
response = client.put(schemaPath, Constants.MIMETYPE_PROTOBUF,
model.createProtobufOutput());
assertEquals(response.getCode(), 403);
// make sure HBase concurs, and wait for the table to come online
admin.enableTable(TABLE2);
@ -143,5 +160,8 @@ public class TestSchemaResource {
// make sure HBase concurs
assertFalse(admin.tableExists(TABLE2));
// return read-only setting back to default
conf.set("hbase.rest.readonly", "false");
}
}