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