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