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.HBaseTestingUtil; 039import org.apache.hadoop.hbase.NamespaceDescriptor; 040import org.apache.hadoop.hbase.TableName; 041import org.apache.hadoop.hbase.client.Admin; 042import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor; 043import org.apache.hadoop.hbase.client.ColumnFamilyDescriptorBuilder; 044import org.apache.hadoop.hbase.client.TableDescriptorBuilder; 045import org.apache.hadoop.hbase.rest.client.Client; 046import org.apache.hadoop.hbase.rest.client.Cluster; 047import org.apache.hadoop.hbase.rest.client.Response; 048import org.apache.hadoop.hbase.rest.model.NamespacesInstanceModel; 049import org.apache.hadoop.hbase.rest.model.TableListModel; 050import org.apache.hadoop.hbase.rest.model.TableModel; 051import org.apache.hadoop.hbase.rest.model.TestNamespacesInstanceModel; 052import org.apache.hadoop.hbase.testclassification.MediumTests; 053import org.apache.hadoop.hbase.testclassification.RestTests; 054import org.apache.hadoop.hbase.util.Bytes; 055import org.apache.http.Header; 056import org.junit.AfterClass; 057import org.junit.BeforeClass; 058import org.junit.ClassRule; 059import org.junit.Ignore; 060import org.junit.Test; 061import org.junit.experimental.categories.Category; 062 063import org.apache.hbase.thirdparty.com.fasterxml.jackson.jaxrs.json.JacksonJaxbJsonProvider; 064import org.apache.hbase.thirdparty.javax.ws.rs.core.MediaType; 065 066@Category({ RestTests.class, MediumTests.class }) 067public class TestNamespacesInstanceResource { 068 @ClassRule 069 public static final HBaseClassTestRule CLASS_RULE = 070 HBaseClassTestRule.forClass(TestNamespacesInstanceResource.class); 071 072 private static String NAMESPACE1 = "TestNamespacesInstanceResource1"; 073 private static Map<String, String> NAMESPACE1_PROPS = new HashMap<>(); 074 private static String NAMESPACE2 = "TestNamespacesInstanceResource2"; 075 private static Map<String, String> NAMESPACE2_PROPS = new HashMap<>(); 076 private static String NAMESPACE3 = "TestNamespacesInstanceResource3"; 077 private static Map<String, String> NAMESPACE3_PROPS = new HashMap<>(); 078 private static String NAMESPACE4 = "TestNamespacesInstanceResource4"; 079 private static Map<String, String> NAMESPACE4_PROPS = new HashMap<>(); 080 081 private static final HBaseTestingUtil TEST_UTIL = new HBaseTestingUtil(); 082 private static final HBaseRESTTestingUtility REST_TEST_UTIL = new HBaseRESTTestingUtility(); 083 private static Client client; 084 private static JAXBContext context; 085 private static Configuration conf; 086 private static TestNamespacesInstanceModel testNamespacesInstanceModel; 087 protected static ObjectMapper jsonMapper; 088 089 @BeforeClass 090 public static void setUpBeforeClass() throws Exception { 091 conf = TEST_UTIL.getConfiguration(); 092 TEST_UTIL.startMiniCluster(); 093 REST_TEST_UTIL.startServletContainer(conf); 094 client = new Client(new Cluster().add("localhost", REST_TEST_UTIL.getServletPort())); 095 testNamespacesInstanceModel = new TestNamespacesInstanceModel(); 096 context = JAXBContext.newInstance(NamespacesInstanceModel.class, TableListModel.class); 097 jsonMapper = new JacksonJaxbJsonProvider().locateMapper(NamespacesInstanceModel.class, 098 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) throws JAXBException { 121 return (T) context.createUnmarshaller().unmarshal(new ByteArrayInputStream(content)); 122 } 123 124 private NamespaceDescriptor findNamespace(Admin admin, String namespaceName) throws IOException { 125 NamespaceDescriptor[] nd = admin.listNamespaceDescriptors(); 126 for (NamespaceDescriptor namespaceDescriptor : nd) { 127 if (namespaceDescriptor.getName().equals(namespaceName)) { 128 return namespaceDescriptor; 129 } 130 } 131 return null; 132 } 133 134 private void checkNamespaceProperties(NamespaceDescriptor nd, Map<String, String> testProps) { 135 checkNamespaceProperties(nd.getConfiguration(), testProps); 136 } 137 138 private void checkNamespaceProperties(Map<String, String> namespaceProps, 139 Map<String, String> testProps) { 140 assertTrue(namespaceProps.size() == testProps.size()); 141 for (String key : testProps.keySet()) { 142 assertEquals(testProps.get(key), namespaceProps.get(key)); 143 } 144 } 145 146 private void checkNamespaceTables(List<TableModel> namespaceTables, List<String> testTables) { 147 assertEquals(namespaceTables.size(), testTables.size()); 148 for (TableModel namespaceTable : namespaceTables) { 149 String tableName = namespaceTable.getName(); 150 assertTrue(testTables.contains(tableName)); 151 } 152 } 153 154 @Test 155 public void testCannotDeleteDefaultAndHbaseNamespaces() throws IOException { 156 String defaultPath = "/namespaces/default"; 157 String hbasePath = "/namespaces/hbase"; 158 Response response; 159 160 // Check that doesn't exist via non-REST call. 161 Admin admin = TEST_UTIL.getAdmin(); 162 assertNotNull(findNamespace(admin, "default")); 163 assertNotNull(findNamespace(admin, "hbase")); 164 165 // Try (but fail) to delete namespaces via REST. 166 response = client.delete(defaultPath); 167 assertEquals(503, response.getCode()); 168 response = client.delete(hbasePath); 169 assertEquals(503, response.getCode()); 170 171 assertNotNull(findNamespace(admin, "default")); 172 assertNotNull(findNamespace(admin, "hbase")); 173 } 174 175 @Test 176 public void testGetNamespaceTablesAndCannotDeleteNamespace() throws IOException, JAXBException { 177 Admin admin = TEST_UTIL.getAdmin(); 178 String nsName = "TestNamespacesInstanceResource5"; 179 Response response; 180 181 // Create namespace via admin. 182 NamespaceDescriptor.Builder nsBuilder = NamespaceDescriptor.create(nsName); 183 NamespaceDescriptor nsd = nsBuilder.build(); 184 nsd.setConfiguration("key1", "value1"); 185 admin.createNamespace(nsd); 186 187 // Create two tables via admin. 188 TableName tn1 = TableName.valueOf(nsName + ":table1"); 189 TableDescriptorBuilder tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(tn1); 190 ColumnFamilyDescriptor columnFamilyDescriptor = 191 ColumnFamilyDescriptorBuilder.newBuilder(Bytes.toBytes("cf1")).build(); 192 tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor); 193 admin.createTable(tableDescriptorBuilder.build()); 194 TableName tn2 = TableName.valueOf(nsName + ":table2"); 195 tableDescriptorBuilder = TableDescriptorBuilder.newBuilder(tn2); 196 tableDescriptorBuilder.setColumnFamily(columnFamilyDescriptor); 197 admin.createTable(tableDescriptorBuilder.build()); 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 = 399 client.put(namespacePath4, Constants.MIMETYPE_PROTOBUF, 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 = 407 client.put(namespacePath4, Constants.MIMETYPE_PROTOBUF, 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 = 420 client.post(namespacePath4, Constants.MIMETYPE_PROTOBUF, 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 = 438 client.post(namespacePath4, Constants.MIMETYPE_PROTOBUF, 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}