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.assertNotNull; 022import static org.junit.jupiter.api.Assertions.assertNull; 023import static org.junit.jupiter.api.Assertions.assertTrue; 024 025import com.fasterxml.jackson.databind.ObjectMapper; 026import java.io.ByteArrayInputStream; 027import java.io.IOException; 028import java.io.StringWriter; 029import java.util.ArrayList; 030import java.util.Arrays; 031import java.util.HashMap; 032import java.util.List; 033import java.util.Map; 034import javax.xml.bind.JAXBContext; 035import javax.xml.bind.JAXBException; 036import org.apache.hadoop.conf.Configuration; 037import org.apache.hadoop.hbase.HBaseTestingUtil; 038import org.apache.hadoop.hbase.NamespaceDescriptor; 039import org.apache.hadoop.hbase.TableName; 040import org.apache.hadoop.hbase.client.Admin; 041import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 042import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 043import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 044import org.apache.hadoop.hbase.rest.client.Client; 045import org.apache.hadoop.hbase.rest.client.Cluster; 046import org.apache.hadoop.hbase.rest.client.Response; 047import org.apache.hadoop.hbase.rest.model.NamespacesInstanceModel; 048import org.apache.hadoop.hbase.rest.model.TableListModel; 049import org.apache.hadoop.hbase.rest.model.TableModel; 050import org.apache.hadoop.hbase.rest.model.TestNamespacesInstanceModel; 051import org.apache.hadoop.hbase.testclassification.MediumTests; 052import org.apache.hadoop.hbase.testclassification.RestTests; 053import org.apache.hadoop.hbase.util.Bytes; 054import org.apache.http.Header; 055import org.junit.jupiter.api.AfterAll; 056import org.junit.jupiter.api.BeforeAll; 057import org.junit.jupiter.api.Disabled; 058import org.junit.jupiter.api.Tag; 059import org.junit.jupiter.api.Test; 060 061import org.apache.hbase.thirdparty.com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; 062import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType; 063 064@Tag(RestTests.TAG) 065@Tag(MediumTests.TAG) 066public class TestNamespacesInstanceResource { 067 068 private static final String NAMESPACE1 = "TestNamespacesInstanceResource1"; 069 private static final Map<String, String> NAMESPACE1_PROPS = new HashMap<>(); 070 private static final String NAMESPACE2 = "TestNamespacesInstanceResource2"; 071 private static final Map<String, String> NAMESPACE2_PROPS = new HashMap<>(); 072 private static final String NAMESPACE3 = "TestNamespacesInstanceResource3"; 073 private static final Map<String, String> NAMESPACE3_PROPS = new HashMap<>(); 074 private static final String NAMESPACE4 = "TestNamespacesInstanceResource4"; 075 private static final Map<String, String> NAMESPACE4_PROPS = new HashMap<>(); 076 077 private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 078 private static final HBaseRESTTestingUtility REST_TEST_UTIL = new HBaseRESTTestingUtility(); 079 private static Client client; 080 private static JAXBContext context; 081 private static Configuration conf; 082 private static TestNamespacesInstanceModel testNamespacesInstanceModel; 083 protected static ObjectMapper jsonMapper; 084 085 @BeforeAll 086 public static void setUpBeforeClass() throws Exception { 087 conf = TEST_UTIL.getConfiguration(); 088 TEST_UTIL.startMiniCluster(); 089 REST_TEST_UTIL.startServletContainer(conf); 090 client = new Client(new Cluster().add("localhost", REST_TEST_UTIL.getServletPort())); 091 testNamespacesInstanceModel = new TestNamespacesInstanceModel(); 092 context = JAXBContext.newInstance(NamespacesInstanceModel.class, TableListModel.class); 093 jsonMapper = new JacksonJaxbJsonProvider().locateMapper(NamespacesInstanceModel.class, 094 MediaType.APPLICATION_JSON_TYPE); 095 NAMESPACE1_PROPS.put("key1", "value1"); 096 NAMESPACE2_PROPS.put("key2a", "value2a"); 097 NAMESPACE2_PROPS.put("key2b", "value2b"); 098 NAMESPACE3_PROPS.put("key3", "value3"); 099 NAMESPACE4_PROPS.put("key4a", "value4a"); 100 NAMESPACE4_PROPS.put("key4b", "value4b"); 101 } 102 103 @AfterAll 104 public static void tearDownAfterClass() throws Exception { 105 REST_TEST_UTIL.shutdownServletContainer(); 106 TEST_UTIL.shutdownMiniCluster(); 107 } 108 109 private static byte[] toXML(NamespacesInstanceModel model) throws JAXBException { 110 StringWriter writer = new StringWriter(); 111 context.createMarshaller().marshal(model, writer); 112 return Bytes.toBytes(writer.toString()); 113 } 114 115 @SuppressWarnings("unchecked") 116 private static <T> T fromXML(byte[] content, Class<T> clazz) throws JAXBException { 117 JAXBContext jaxbContext = JAXBContext.newInstance(clazz); 118 return (T) jaxbContext.createUnmarshaller().unmarshal(new ByteArrayInputStream(content)); 119 } 120 121 private NamespaceDescriptor findNamespace(Admin admin, String namespaceName) throws IOException { 122 NamespaceDescriptor[] nd = admin.listNamespaceDescriptors(); 123 for (NamespaceDescriptor namespaceDescriptor : nd) { 124 if (namespaceDescriptor.getName().equals(namespaceName)) { 125 return namespaceDescriptor; 126 } 127 } 128 return null; 129 } 130 131 private void checkNamespaceProperties(NamespaceDescriptor nd, Map<String, String> testProps) { 132 checkNamespaceProperties(nd.getConfiguration(), testProps); 133 } 134 135 private void checkNamespaceProperties(Map<String, String> namespaceProps, 136 Map<String, String> testProps) { 137 assertEquals(namespaceProps.size(), testProps.size()); 138 for (String key : testProps.keySet()) { 139 assertEquals(testProps.get(key), namespaceProps.get(key)); 140 } 141 } 142 143 private void checkNamespaceTables(List<TableModel> namespaceTables, List<String> testTables) { 144 assertEquals(namespaceTables.size(), testTables.size()); 145 for (TableModel namespaceTable : namespaceTables) { 146 String tableName = namespaceTable.getName(); 147 assertTrue(testTables.contains(tableName)); 148 } 149 } 150 151 @Test 152 public void testCannotDeleteDefaultAndHbaseNamespaces() throws IOException { 153 String defaultPath = "/namespaces/default"; 154 String hbasePath = "/namespaces/hbase"; 155 Response response; 156 157 // Check that doesn't exist via non-REST call. 158 Admin admin = TEST_UTIL.getAdmin(); 159 assertNotNull(findNamespace(admin, "default")); 160 assertNotNull(findNamespace(admin, "hbase")); 161 162 // Try (but fail) to delete namespaces via REST. 163 response = client.delete(defaultPath); 164 assertEquals(503, response.getCode()); 165 response = client.delete(hbasePath); 166 assertEquals(503, response.getCode()); 167 168 assertNotNull(findNamespace(admin, "default")); 169 assertNotNull(findNamespace(admin, "hbase")); 170 } 171 172 @Test 173 public void testGetNamespaceTablesAndCannotDeleteNamespace() throws IOException, JAXBException { 174 Admin admin = TEST_UTIL.getAdmin(); 175 String nsName = "TestNamespacesInstanceResource5"; 176 Response response; 177 178 // Create namespace via admin. 179 NamespaceDescriptor.Builder nsBuilder = NamespaceDescriptor.create(nsName); 180 NamespaceDescriptor nsd = nsBuilder.build(); 181 nsd.setConfiguration("key1", "value1"); 182 admin.createNamespace(nsd); 183 184 // Create two tables via admin. 185 TableName tn1 = TableName.valueOf(nsName + ":table1"); 186 TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(tn1); 187 ColumnFamilyDescriptor columnFamilyDescriptor = 188 ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("cf1")).build(); 189 tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor); 190 admin.createTable(tableDescriptorBuilder.build()); 191 TableName tn2 = TableName.valueOf(nsName + ":table2"); 192 tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(tn2); 193 tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor); 194 admin.createTable(tableDescriptorBuilder.build()); 195 196 Map<String, String> nsProperties = new HashMap<>(); 197 nsProperties.put("key1", "value1"); 198 List<String> nsTables = Arrays.asList("table1", "table2"); 199 200 // Check get namespace properties as XML, JSON and Protobuf. 201 String namespacePath = "/namespaces/" + nsName; 202 response = client.get(namespacePath); 203 assertEquals(200, response.getCode()); 204 205 response = client.get(namespacePath, Constants.MIMETYPE_XML); 206 assertEquals(200, response.getCode()); 207 NamespacesInstanceModel model = fromXML(response.getBody(), NamespacesInstanceModel.class); 208 checkNamespaceProperties(model.getProperties(), nsProperties); 209 210 response = client.get(namespacePath, Constants.MIMETYPE_JSON); 211 assertEquals(200, response.getCode()); 212 model = jsonMapper.readValue(response.getBody(), NamespacesInstanceModel.class); 213 checkNamespaceProperties(model.getProperties(), nsProperties); 214 215 response = client.get(namespacePath, Constants.MIMETYPE_PROTOBUF); 216 assertEquals(200, response.getCode()); 217 model.getObjectFromMessage(response.getBody()); 218 checkNamespaceProperties(model.getProperties(), nsProperties); 219 220 // Check get namespace tables as XML, JSON and Protobuf. 221 namespacePath = "/namespaces/" + nsName + "/tables"; 222 response = client.get(namespacePath); 223 assertEquals(200, response.getCode()); 224 225 response = client.get(namespacePath, Constants.MIMETYPE_XML); 226 assertEquals(200, response.getCode()); 227 TableListModel tablemodel = fromXML(response.getBody(), TableListModel.class); 228 checkNamespaceTables(tablemodel.getTables(), nsTables); 229 230 response = client.get(namespacePath, Constants.MIMETYPE_JSON); 231 assertEquals(200, response.getCode()); 232 tablemodel = jsonMapper.readValue(response.getBody(), TableListModel.class); 233 checkNamespaceTables(tablemodel.getTables(), nsTables); 234 235 response = client.get(namespacePath, Constants.MIMETYPE_PROTOBUF); 236 assertEquals(200, response.getCode()); 237 tablemodel.setTables(new ArrayList<>()); 238 tablemodel.getObjectFromMessage(response.getBody()); 239 checkNamespaceTables(tablemodel.getTables(), nsTables); 240 241 // Check cannot delete namespace via REST because it contains tables. 242 response = client.delete(namespacePath); 243 namespacePath = "/namespaces/" + nsName; 244 assertEquals(503, response.getCode()); 245 } 246 247 @Disabled("HBASE-19210") 248 @Test 249 public void testInvalidNamespacePostsAndPuts() throws IOException, JAXBException { 250 String namespacePath1 = "/namespaces/" + NAMESPACE1; 251 String namespacePath2 = "/namespaces/" + NAMESPACE2; 252 String namespacePath3 = "/namespaces/" + NAMESPACE3; 253 NamespacesInstanceModel model1; 254 NamespacesInstanceModel model2; 255 NamespacesInstanceModel model3; 256 Response response; 257 258 // Check that namespaces don't exist via non-REST call. 259 Admin admin = TEST_UTIL.getAdmin(); 260 assertNull(findNamespace(admin, NAMESPACE1)); 261 assertNull(findNamespace(admin, NAMESPACE2)); 262 assertNull(findNamespace(admin, NAMESPACE3)); 263 264 model1 = testNamespacesInstanceModel.buildTestModel(NAMESPACE1, NAMESPACE1_PROPS); 265 testNamespacesInstanceModel.checkModel(model1, NAMESPACE1, NAMESPACE1_PROPS); 266 model2 = testNamespacesInstanceModel.buildTestModel(NAMESPACE2, NAMESPACE2_PROPS); 267 testNamespacesInstanceModel.checkModel(model2, NAMESPACE2, NAMESPACE2_PROPS); 268 model3 = testNamespacesInstanceModel.buildTestModel(NAMESPACE3, NAMESPACE3_PROPS); 269 testNamespacesInstanceModel.checkModel(model3, NAMESPACE3, NAMESPACE3_PROPS); 270 271 // Try REST post and puts with invalid content. 272 response = client.post(namespacePath1, Constants.MIMETYPE_JSON, toXML(model1)); 273 assertEquals(500, response.getCode()); 274 String jsonString = jsonMapper.writeValueAsString(model2); 275 response = client.put(namespacePath2, Constants.MIMETYPE_XML, Bytes.toBytes(jsonString)); 276 assertEquals(400, response.getCode()); 277 response = client.post(namespacePath3, Constants.MIMETYPE_PROTOBUF, toXML(model3)); 278 assertEquals(500, response.getCode()); 279 280 NamespaceDescriptor nd1 = findNamespace(admin, NAMESPACE1); 281 NamespaceDescriptor nd2 = findNamespace(admin, NAMESPACE2); 282 NamespaceDescriptor nd3 = findNamespace(admin, NAMESPACE3); 283 assertNull(nd1); 284 assertNull(nd2); 285 assertNull(nd3); 286 } 287 288 @Test 289 public void testNamespaceCreateAndDeleteXMLAndJSON() throws IOException, JAXBException { 290 String namespacePath1 = "/namespaces/" + NAMESPACE1; 291 String namespacePath2 = "/namespaces/" + NAMESPACE2; 292 NamespacesInstanceModel model1; 293 NamespacesInstanceModel model2; 294 Response response; 295 296 // Check that namespaces don't exist via non-REST call. 297 Admin admin = TEST_UTIL.getAdmin(); 298 assertNull(findNamespace(admin, NAMESPACE1)); 299 assertNull(findNamespace(admin, NAMESPACE2)); 300 301 model1 = testNamespacesInstanceModel.buildTestModel(NAMESPACE1, NAMESPACE1_PROPS); 302 testNamespacesInstanceModel.checkModel(model1, NAMESPACE1, NAMESPACE1_PROPS); 303 model2 = testNamespacesInstanceModel.buildTestModel(NAMESPACE2, NAMESPACE2_PROPS); 304 testNamespacesInstanceModel.checkModel(model2, NAMESPACE2, NAMESPACE2_PROPS); 305 306 // Test cannot PUT (alter) non-existent namespace. 307 response = client.put(namespacePath1, Constants.MIMETYPE_XML, toXML(model1)); 308 assertEquals(403, response.getCode()); 309 String jsonString = jsonMapper.writeValueAsString(model2); 310 response = client.put(namespacePath2, Constants.MIMETYPE_JSON, Bytes.toBytes(jsonString)); 311 assertEquals(403, response.getCode()); 312 313 // Test cannot create tables when in read only mode. 314 conf.set("hbase.rest.readonly", "true"); 315 response = client.post(namespacePath1, Constants.MIMETYPE_XML, toXML(model1)); 316 assertEquals(403, response.getCode()); 317 jsonString = jsonMapper.writeValueAsString(model2); 318 response = client.post(namespacePath2, Constants.MIMETYPE_JSON, Bytes.toBytes(jsonString)); 319 assertEquals(403, response.getCode()); 320 NamespaceDescriptor nd1 = findNamespace(admin, NAMESPACE1); 321 NamespaceDescriptor nd2 = findNamespace(admin, NAMESPACE2); 322 assertNull(nd1); 323 assertNull(nd2); 324 conf.set("hbase.rest.readonly", "false"); 325 326 // Create namespace via XML and JSON. 327 response = client.post(namespacePath1, Constants.MIMETYPE_XML, toXML(model1)); 328 assertEquals(201, response.getCode()); 329 jsonString = jsonMapper.writeValueAsString(model2); 330 response = client.post(namespacePath2, Constants.MIMETYPE_JSON, Bytes.toBytes(jsonString)); 331 assertEquals(201, response.getCode()); 332 // check passing null content-type with a payload returns 415 333 Header[] nullHeaders = null; 334 response = client.post(namespacePath1, nullHeaders, toXML(model1)); 335 assertEquals(415, response.getCode()); 336 response = client.post(namespacePath1, nullHeaders, Bytes.toBytes(jsonString)); 337 assertEquals(415, response.getCode()); 338 339 // Check that created namespaces correctly. 340 nd1 = findNamespace(admin, NAMESPACE1); 341 nd2 = findNamespace(admin, NAMESPACE2); 342 assertNotNull(nd1); 343 assertNotNull(nd2); 344 checkNamespaceProperties(nd1, NAMESPACE1_PROPS); 345 checkNamespaceProperties(nd1, NAMESPACE1_PROPS); 346 347 // Test cannot delete tables when in read only mode. 348 conf.set("hbase.rest.readonly", "true"); 349 response = client.delete(namespacePath1); 350 assertEquals(403, response.getCode()); 351 response = client.delete(namespacePath2); 352 assertEquals(403, response.getCode()); 353 nd1 = findNamespace(admin, NAMESPACE1); 354 nd2 = findNamespace(admin, NAMESPACE2); 355 assertNotNull(nd1); 356 assertNotNull(nd2); 357 conf.set("hbase.rest.readonly", "false"); 358 359 // Delete namespaces via XML and JSON. 360 response = client.delete(namespacePath1); 361 assertEquals(200, response.getCode()); 362 response = client.delete(namespacePath2); 363 assertEquals(200, response.getCode()); 364 nd1 = findNamespace(admin, NAMESPACE1); 365 nd2 = findNamespace(admin, NAMESPACE2); 366 assertNull(nd1); 367 assertNull(nd2); 368 } 369 370 @Test 371 public void testNamespaceCreateAndDeletePBAndNoBody() throws IOException { 372 String namespacePath3 = "/namespaces/" + NAMESPACE3; 373 String namespacePath4 = "/namespaces/" + NAMESPACE4; 374 NamespacesInstanceModel model3; 375 NamespacesInstanceModel model4; 376 Response response; 377 378 // Check that namespaces don't exist via non-REST call. 379 Admin admin = TEST_UTIL.getAdmin(); 380 assertNull(findNamespace(admin, NAMESPACE3)); 381 assertNull(findNamespace(admin, NAMESPACE4)); 382 383 model3 = testNamespacesInstanceModel.buildTestModel(NAMESPACE3, NAMESPACE3_PROPS); 384 testNamespacesInstanceModel.checkModel(model3, NAMESPACE3, NAMESPACE3_PROPS); 385 model4 = testNamespacesInstanceModel.buildTestModel(NAMESPACE4, NAMESPACE4_PROPS); 386 testNamespacesInstanceModel.checkModel(model4, NAMESPACE4, NAMESPACE4_PROPS); 387 388 // Defines null headers for use in tests where no body content is provided, so that we set 389 // no content-type in the request 390 Header[] nullHeaders = null; 391 392 // Test cannot PUT (alter) non-existent namespace. 393 response = client.put(namespacePath3, nullHeaders, new byte[] {}); 394 assertEquals(403, response.getCode()); 395 response = 396 client.put(namespacePath4, Constants.MIMETYPE_PROTOBUF, model4.createProtobufOutput()); 397 assertEquals(403, response.getCode()); 398 399 // Test cannot create tables when in read only mode. 400 conf.set("hbase.rest.readonly", "true"); 401 response = client.post(namespacePath3, nullHeaders, new byte[] {}); 402 assertEquals(403, response.getCode()); 403 response = 404 client.put(namespacePath4, Constants.MIMETYPE_PROTOBUF, model4.createProtobufOutput()); 405 assertEquals(403, response.getCode()); 406 NamespaceDescriptor nd3 = findNamespace(admin, NAMESPACE3); 407 NamespaceDescriptor nd4 = findNamespace(admin, NAMESPACE4); 408 assertNull(nd3); 409 assertNull(nd4); 410 conf.set("hbase.rest.readonly", "false"); 411 412 // Create namespace with no body and binary content type. 413 response = client.post(namespacePath3, nullHeaders, new byte[] {}); 414 assertEquals(201, response.getCode()); 415 // Create namespace with protobuf content-type. 416 response = 417 client.post(namespacePath4, Constants.MIMETYPE_PROTOBUF, model4.createProtobufOutput()); 418 assertEquals(201, response.getCode()); 419 // check setting unsupported content-type returns 415 420 response = client.post(namespacePath3, Constants.MIMETYPE_BINARY, new byte[] {}); 421 assertEquals(415, response.getCode()); 422 423 // Check that created namespaces correctly. 424 nd3 = findNamespace(admin, NAMESPACE3); 425 nd4 = findNamespace(admin, NAMESPACE4); 426 assertNotNull(nd3); 427 assertNotNull(nd4); 428 checkNamespaceProperties(nd3, new HashMap<>()); 429 checkNamespaceProperties(nd4, NAMESPACE4_PROPS); 430 431 // Check cannot post tables that already exist. 432 response = client.post(namespacePath3, nullHeaders, new byte[] {}); 433 assertEquals(403, response.getCode()); 434 response = 435 client.post(namespacePath4, Constants.MIMETYPE_PROTOBUF, model4.createProtobufOutput()); 436 assertEquals(403, response.getCode()); 437 438 // Check cannot post tables when in read only mode. 439 conf.set("hbase.rest.readonly", "true"); 440 response = client.delete(namespacePath3); 441 assertEquals(403, response.getCode()); 442 response = client.delete(namespacePath4); 443 assertEquals(403, response.getCode()); 444 nd3 = findNamespace(admin, NAMESPACE3); 445 nd4 = findNamespace(admin, NAMESPACE4); 446 assertNotNull(nd3); 447 assertNotNull(nd4); 448 conf.set("hbase.rest.readonly", "false"); 449 450 // Delete namespaces via XML and JSON. 451 response = client.delete(namespacePath3); 452 assertEquals(200, response.getCode()); 453 response = client.delete(namespacePath4); 454 assertEquals(200, response.getCode()); 455 nd3 = findNamespace(admin, NAMESPACE3); 456 nd4 = findNamespace(admin, NAMESPACE4); 457 assertNull(nd3); 458 assertNull(nd4); 459 } 460}