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