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