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