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.rest; 019 020import static org.junit.Assert.assertEquals; 021import static org.junit.Assert.assertFalse; 022import static org.junit.Assert.assertNotNull; 023 024import java.io.ByteArrayInputStream; 025import java.io.IOException; 026import java.io.StringWriter; 027import java.util.Collection; 028import javax.xml.bind.JAXBContext; 029import javax.xml.bind.JAXBException; 030import org.apache.hadoop.conf.Configuration; 031import org.apache.hadoop.hbase.HBaseClassTestRule; 032import org.apache.hadoop.hbase.HBaseCommonTestingUtility; 033import org.apache.hadoop.hbase.HBaseTestingUtility; 034import org.apache.hadoop.hbase.TableName; 035import org.apache.hadoop.hbase.client.Admin; 036import org.apache.hadoop.hbase.rest.client.Client; 037import org.apache.hadoop.hbase.rest.client.Cluster; 038import org.apache.hadoop.hbase.rest.client.Response; 039import org.apache.hadoop.hbase.rest.model.ColumnSchemaModel; 040import org.apache.hadoop.hbase.rest.model.TableSchemaModel; 041import org.apache.hadoop.hbase.rest.model.TestTableSchemaModel; 042import org.apache.hadoop.hbase.testclassification.MediumTests; 043import org.apache.hadoop.hbase.testclassification.RestTests; 044import org.apache.hadoop.hbase.util.Bytes; 045import org.apache.http.Header; 046import org.apache.http.message.BasicHeader; 047import org.junit.After; 048import org.junit.AfterClass; 049import org.junit.BeforeClass; 050import org.junit.ClassRule; 051import org.junit.Test; 052import org.junit.experimental.categories.Category; 053import org.junit.runner.RunWith; 054import org.junit.runners.Parameterized; 055import org.slf4j.Logger; 056import org.slf4j.LoggerFactory; 057 058@Category({RestTests.class, MediumTests.class}) 059@RunWith(Parameterized.class) 060public class TestSchemaResource { 061 062 @ClassRule 063 public static final HBaseClassTestRule CLASS_RULE = 064 HBaseClassTestRule.forClass(TestSchemaResource.class); 065 066 private static final Logger LOG = LoggerFactory.getLogger(TestSchemaResource.class); 067 068 private static String TABLE1 = "TestSchemaResource1"; 069 private static String TABLE2 = "TestSchemaResource2"; 070 071 private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); 072 private static final HBaseRESTTestingUtility REST_TEST_UTIL = 073 new HBaseRESTTestingUtility(); 074 private static Client client; 075 private static JAXBContext context; 076 private static Configuration conf; 077 private static TestTableSchemaModel testTableSchemaModel; 078 private static Header extraHdr = null; 079 080 private static boolean csrfEnabled = true; 081 082 @Parameterized.Parameters 083 public static Collection<Object[]> parameters() { 084 return HBaseCommonTestingUtility.BOOLEAN_PARAMETERIZED; 085 } 086 087 public TestSchemaResource(Boolean csrf) { 088 csrfEnabled = csrf; 089 } 090 091 @BeforeClass 092 public static void setUpBeforeClass() throws Exception { 093 conf = TEST_UTIL.getConfiguration(); 094 conf.setBoolean(RESTServer.REST_CSRF_ENABLED_KEY, csrfEnabled); 095 if (csrfEnabled) { 096 conf.set(RESTServer.REST_CSRF_BROWSER_USERAGENTS_REGEX_KEY, ".*"); 097 } 098 extraHdr = new BasicHeader(RESTServer.REST_CSRF_CUSTOM_HEADER_DEFAULT, ""); 099 TEST_UTIL.startMiniCluster(); 100 REST_TEST_UTIL.startServletContainer(conf); 101 client = new Client(new Cluster().add("localhost", 102 REST_TEST_UTIL.getServletPort())); 103 testTableSchemaModel = new TestTableSchemaModel(); 104 context = JAXBContext.newInstance( 105 ColumnSchemaModel.class, 106 TableSchemaModel.class); 107 } 108 109 @AfterClass 110 public static void tearDownAfterClass() throws Exception { 111 REST_TEST_UTIL.shutdownServletContainer(); 112 TEST_UTIL.shutdownMiniCluster(); 113 } 114 115 @After 116 public void tearDown() throws Exception { 117 Admin admin = TEST_UTIL.getAdmin(); 118 119 for (String table : new String[] {TABLE1, TABLE2}) { 120 TableName t = TableName.valueOf(table); 121 if (admin.tableExists(t)) { 122 admin.disableTable(t); 123 admin.deleteTable(t); 124 } 125 } 126 127 conf.set("hbase.rest.readonly", "false"); 128 } 129 130 private static byte[] toXML(TableSchemaModel model) throws JAXBException { 131 StringWriter writer = new StringWriter(); 132 context.createMarshaller().marshal(model, writer); 133 return Bytes.toBytes(writer.toString()); 134 } 135 136 private static TableSchemaModel fromXML(byte[] content) 137 throws JAXBException { 138 return (TableSchemaModel) context.createUnmarshaller() 139 .unmarshal(new ByteArrayInputStream(content)); 140 } 141 142 @Test 143 public void testTableCreateAndDeleteXML() throws IOException, JAXBException { 144 String schemaPath = "/" + TABLE1 + "/schema"; 145 TableSchemaModel model; 146 Response response; 147 148 Admin admin = TEST_UTIL.getAdmin(); 149 assertFalse("Table " + TABLE1 + " should not exist", admin.tableExists(TableName.valueOf(TABLE1))); 150 151 // create the table 152 model = testTableSchemaModel.buildTestModel(TABLE1); 153 testTableSchemaModel.checkModel(model, TABLE1); 154 if (csrfEnabled) { 155 // test put operation is forbidden without custom header 156 response = client.put(schemaPath, Constants.MIMETYPE_XML, toXML(model)); 157 assertEquals(400, response.getCode()); 158 } 159 160 response = client.put(schemaPath, Constants.MIMETYPE_XML, toXML(model), extraHdr); 161 assertEquals("put failed with csrf " + (csrfEnabled ? "enabled" : "disabled"), 162 201, response.getCode()); 163 164 // recall the same put operation but in read-only mode 165 conf.set("hbase.rest.readonly", "true"); 166 response = client.put(schemaPath, Constants.MIMETYPE_XML, toXML(model), extraHdr); 167 assertEquals(403, response.getCode()); 168 169 // retrieve the schema and validate it 170 response = client.get(schemaPath, Constants.MIMETYPE_XML); 171 assertEquals(200, response.getCode()); 172 assertEquals(Constants.MIMETYPE_XML, response.getHeader("content-type")); 173 model = fromXML(response.getBody()); 174 testTableSchemaModel.checkModel(model, TABLE1); 175 176 // with json retrieve the schema and validate it 177 response = client.get(schemaPath, Constants.MIMETYPE_JSON); 178 assertEquals(200, response.getCode()); 179 assertEquals(Constants.MIMETYPE_JSON, response.getHeader("content-type")); 180 model = testTableSchemaModel.fromJSON(Bytes.toString(response.getBody())); 181 testTableSchemaModel.checkModel(model, TABLE1); 182 183 if (csrfEnabled) { 184 // test delete schema operation is forbidden without custom header 185 response = client.delete(schemaPath); 186 assertEquals(400, response.getCode()); 187 } 188 189 // test delete schema operation is forbidden in read-only mode 190 response = client.delete(schemaPath, extraHdr); 191 assertEquals(403, response.getCode()); 192 193 // return read-only setting back to default 194 conf.set("hbase.rest.readonly", "false"); 195 196 // delete the table and make sure HBase concurs 197 response = client.delete(schemaPath, extraHdr); 198 assertEquals(200, response.getCode()); 199 assertFalse(admin.tableExists(TableName.valueOf(TABLE1))); 200 } 201 202 @Test 203 public void testTableCreateAndDeletePB() throws IOException, JAXBException { 204 String schemaPath = "/" + TABLE2 + "/schema"; 205 TableSchemaModel model; 206 Response response; 207 208 Admin admin = TEST_UTIL.getAdmin(); 209 assertFalse(admin.tableExists(TableName.valueOf(TABLE2))); 210 211 // create the table 212 model = testTableSchemaModel.buildTestModel(TABLE2); 213 testTableSchemaModel.checkModel(model, TABLE2); 214 215 if (csrfEnabled) { 216 // test put operation is forbidden without custom header 217 response = client.put(schemaPath, Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput()); 218 assertEquals(400, response.getCode()); 219 } 220 response = client.put(schemaPath, Constants.MIMETYPE_PROTOBUF, 221 model.createProtobufOutput(), extraHdr); 222 assertEquals("put failed with csrf " + (csrfEnabled ? "enabled" : "disabled"), 223 201, response.getCode()); 224 225 // recall the same put operation but in read-only mode 226 conf.set("hbase.rest.readonly", "true"); 227 response = client.put(schemaPath, Constants.MIMETYPE_PROTOBUF, 228 model.createProtobufOutput(), extraHdr); 229 assertNotNull(extraHdr); 230 assertEquals(403, response.getCode()); 231 232 // retrieve the schema and validate it 233 response = client.get(schemaPath, Constants.MIMETYPE_PROTOBUF); 234 assertEquals(200, response.getCode()); 235 assertEquals(Constants.MIMETYPE_PROTOBUF, response.getHeader("content-type")); 236 model = new TableSchemaModel(); 237 model.getObjectFromMessage(response.getBody()); 238 testTableSchemaModel.checkModel(model, TABLE2); 239 240 // retrieve the schema and validate it with alternate pbuf type 241 response = client.get(schemaPath, Constants.MIMETYPE_PROTOBUF_IETF); 242 assertEquals(200, response.getCode()); 243 assertEquals(Constants.MIMETYPE_PROTOBUF_IETF, response.getHeader("content-type")); 244 model = new TableSchemaModel(); 245 model.getObjectFromMessage(response.getBody()); 246 testTableSchemaModel.checkModel(model, TABLE2); 247 248 if (csrfEnabled) { 249 // test delete schema operation is forbidden without custom header 250 response = client.delete(schemaPath); 251 assertEquals(400, response.getCode()); 252 } 253 254 // test delete schema operation is forbidden in read-only mode 255 response = client.delete(schemaPath, extraHdr); 256 assertEquals(403, response.getCode()); 257 258 // return read-only setting back to default 259 conf.set("hbase.rest.readonly", "false"); 260 261 // delete the table and make sure HBase concurs 262 response = client.delete(schemaPath, extraHdr); 263 assertEquals(200, response.getCode()); 264 assertFalse(admin.tableExists(TableName.valueOf(TABLE2))); 265 } 266 267} 268