/* * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hbase.rest; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.StringWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.ws.rs.core.MediaType; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.hbase.HBaseTestingUtility; import org.apache.hadoop.hbase.HColumnDescriptor; import org.apache.hadoop.hbase.HTableDescriptor; import org.apache.hadoop.hbase.NamespaceDescriptor; import org.apache.hadoop.hbase.TableName; import org.apache.hadoop.hbase.client.Admin; import org.apache.hadoop.hbase.rest.client.Client; import org.apache.hadoop.hbase.rest.client.Cluster; import org.apache.hadoop.hbase.rest.client.Response; import org.apache.hadoop.hbase.rest.model.NamespacesInstanceModel; import org.apache.hadoop.hbase.rest.model.TableListModel; import org.apache.hadoop.hbase.rest.model.TableModel; import org.apache.hadoop.hbase.rest.model.TestNamespacesInstanceModel; import org.apache.hadoop.hbase.testclassification.MediumTests; import org.apache.hadoop.hbase.testclassification.RestTests; import org.apache.hadoop.hbase.util.Bytes; import org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider; import org.codehaus.jackson.map.ObjectMapper; import static org.junit.Assert.*; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.junit.experimental.categories.Category; @Category({RestTests.class, MediumTests.class}) public class TestNamespacesInstanceResource { private static String NAMESPACE1 = "TestNamespacesInstanceResource1"; private static Map<String,String> NAMESPACE1_PROPS = new HashMap<>(); private static String NAMESPACE2 = "TestNamespacesInstanceResource2"; private static Map<String,String> NAMESPACE2_PROPS = new HashMap<>(); private static String NAMESPACE3 = "TestNamespacesInstanceResource3"; private static Map<String,String> NAMESPACE3_PROPS = new HashMap<>(); private static String NAMESPACE4 = "TestNamespacesInstanceResource4"; private static Map<String,String> NAMESPACE4_PROPS = new HashMap<>(); private static final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility(); private static final HBaseRESTTestingUtility REST_TEST_UTIL = new HBaseRESTTestingUtility(); private static Client client; private static JAXBContext context; private static Configuration conf; private static TestNamespacesInstanceModel testNamespacesInstanceModel; protected static ObjectMapper jsonMapper; @BeforeClass public static void setUpBeforeClass() throws Exception { conf = TEST_UTIL.getConfiguration(); TEST_UTIL.startMiniCluster(); REST_TEST_UTIL.startServletContainer(conf); client = new Client(new Cluster().add("localhost", REST_TEST_UTIL.getServletPort())); testNamespacesInstanceModel = new TestNamespacesInstanceModel(); context = JAXBContext.newInstance(NamespacesInstanceModel.class, TableListModel.class); jsonMapper = new JacksonJaxbJsonProvider() .locateMapper(NamespacesInstanceModel.class, MediaType.APPLICATION_JSON_TYPE); NAMESPACE1_PROPS.put("key1", "value1"); NAMESPACE2_PROPS.put("key2a", "value2a"); NAMESPACE2_PROPS.put("key2b", "value2b"); NAMESPACE3_PROPS.put("key3", "value3"); NAMESPACE4_PROPS.put("key4a", "value4a"); NAMESPACE4_PROPS.put("key4b", "value4b"); } @AfterClass public static void tearDownAfterClass() throws Exception { REST_TEST_UTIL.shutdownServletContainer(); TEST_UTIL.shutdownMiniCluster(); } private static byte[] toXML(NamespacesInstanceModel model) throws JAXBException { StringWriter writer = new StringWriter(); context.createMarshaller().marshal(model, writer); return Bytes.toBytes(writer.toString()); } @SuppressWarnings("unchecked") private static <T> T fromXML(byte[] content) throws JAXBException { return (T) context.createUnmarshaller().unmarshal(new ByteArrayInputStream(content)); } private NamespaceDescriptor findNamespace(Admin admin, String namespaceName) throws IOException{ NamespaceDescriptor[] nd = admin.listNamespaceDescriptors(); for(int i = 0; i < nd.length; i++){ if(nd[i].getName().equals(namespaceName)){ return nd[i]; } } return null; } private void checkNamespaceProperties(NamespaceDescriptor nd, Map<String,String> testProps){ checkNamespaceProperties(nd.getConfiguration(), testProps); } private void checkNamespaceProperties(Map<String,String> namespaceProps, Map<String,String> testProps){ assertTrue(namespaceProps.size() == testProps.size()); for(String key: testProps.keySet()){ assertEquals(testProps.get(key), namespaceProps.get(key)); } } private void checkNamespaceTables(List<TableModel> namespaceTables, List<String> testTables){ assertEquals(namespaceTables.size(), testTables.size()); for(int i = 0 ; i < namespaceTables.size() ; i++){ String tableName = ((TableModel) namespaceTables.get(i)).getName(); assertTrue(testTables.contains(tableName)); } } @Test public void testCannotDeleteDefaultAndHbaseNamespaces() throws IOException { String defaultPath = "/namespaces/default"; String hbasePath = "/namespaces/hbase"; Response response; // Check that doesn't exist via non-REST call. Admin admin = TEST_UTIL.getAdmin(); assertNotNull(findNamespace(admin, "default")); assertNotNull(findNamespace(admin, "hbase")); // Try (but fail) to delete namespaces via REST. response = client.delete(defaultPath); assertEquals(503, response.getCode()); response = client.delete(hbasePath); assertEquals(503, response.getCode()); assertNotNull(findNamespace(admin, "default")); assertNotNull(findNamespace(admin, "hbase")); } @Test public void testGetNamespaceTablesAndCannotDeleteNamespace() throws IOException, JAXBException { Admin admin = TEST_UTIL.getAdmin(); String nsName = "TestNamespacesInstanceResource5"; Response response; // Create namespace via admin. NamespaceDescriptor.Builder nsBuilder = NamespaceDescriptor.create(nsName); NamespaceDescriptor nsd = nsBuilder.build(); nsd.setConfiguration("key1", "value1"); admin.createNamespace(nsd); // Create two tables via admin. HColumnDescriptor colDesc = new HColumnDescriptor("cf1"); TableName tn1 = TableName.valueOf(nsName + ":table1"); HTableDescriptor table = new HTableDescriptor(tn1); table.addFamily(colDesc); admin.createTable(table); TableName tn2 = TableName.valueOf(nsName + ":table2"); table = new HTableDescriptor(tn2); table.addFamily(colDesc); admin.createTable(table); Map<String, String> nsProperties = new HashMap<>(); nsProperties.put("key1", "value1"); List<String> nsTables = Arrays.asList("table1", "table2"); // Check get namespace properties as XML, JSON and Protobuf. String namespacePath = "/namespaces/" + nsName; response = client.get(namespacePath); assertEquals(200, response.getCode()); response = client.get(namespacePath, Constants.MIMETYPE_XML); assertEquals(200, response.getCode()); NamespacesInstanceModel model = fromXML(response.getBody()); checkNamespaceProperties(model.getProperties(), nsProperties); response = client.get(namespacePath, Constants.MIMETYPE_JSON); assertEquals(200, response.getCode()); model = jsonMapper.readValue(response.getBody(), NamespacesInstanceModel.class); checkNamespaceProperties(model.getProperties(), nsProperties); response = client.get(namespacePath, Constants.MIMETYPE_PROTOBUF); assertEquals(200, response.getCode()); model.getObjectFromMessage(response.getBody()); checkNamespaceProperties(model.getProperties(), nsProperties); // Check get namespace tables as XML, JSON and Protobuf. namespacePath = "/namespaces/" + nsName + "/tables"; response = client.get(namespacePath); assertEquals(200, response.getCode()); response = client.get(namespacePath, Constants.MIMETYPE_XML); assertEquals(200, response.getCode()); TableListModel tablemodel = fromXML(response.getBody()); checkNamespaceTables(tablemodel.getTables(), nsTables); response = client.get(namespacePath, Constants.MIMETYPE_JSON); assertEquals(200, response.getCode()); tablemodel = jsonMapper.readValue(response.getBody(), TableListModel.class); checkNamespaceTables(tablemodel.getTables(), nsTables); response = client.get(namespacePath, Constants.MIMETYPE_PROTOBUF); assertEquals(200, response.getCode()); tablemodel.setTables(new ArrayList<>()); tablemodel.getObjectFromMessage(response.getBody()); checkNamespaceTables(tablemodel.getTables(), nsTables); // Check cannot delete namespace via REST because it contains tables. response = client.delete(namespacePath); namespacePath = "/namespaces/" + nsName; assertEquals(503, response.getCode()); } @Test public void testInvalidNamespacePostsAndPuts() throws IOException, JAXBException { String namespacePath1 = "/namespaces/" + NAMESPACE1; String namespacePath2 = "/namespaces/" + NAMESPACE2; String namespacePath3 = "/namespaces/" + NAMESPACE3; NamespacesInstanceModel model1; NamespacesInstanceModel model2; NamespacesInstanceModel model3; Response response; // Check that namespaces don't exist via non-REST call. Admin admin = TEST_UTIL.getAdmin(); assertNull(findNamespace(admin, NAMESPACE1)); assertNull(findNamespace(admin, NAMESPACE2)); assertNull(findNamespace(admin, NAMESPACE3)); model1 = testNamespacesInstanceModel.buildTestModel(NAMESPACE1, NAMESPACE1_PROPS); testNamespacesInstanceModel.checkModel(model1, NAMESPACE1, NAMESPACE1_PROPS); model2 = testNamespacesInstanceModel.buildTestModel(NAMESPACE2, NAMESPACE2_PROPS); testNamespacesInstanceModel.checkModel(model2, NAMESPACE2, NAMESPACE2_PROPS); model3 = testNamespacesInstanceModel.buildTestModel(NAMESPACE3, NAMESPACE3_PROPS); testNamespacesInstanceModel.checkModel(model3, NAMESPACE3, NAMESPACE3_PROPS); // Try REST post and puts with invalid content. response = client.post(namespacePath1, Constants.MIMETYPE_JSON, toXML(model1)); assertEquals(400, response.getCode()); String jsonString = jsonMapper.writeValueAsString(model2); response = client.put(namespacePath2, Constants.MIMETYPE_XML, Bytes.toBytes(jsonString)); assertEquals(400, response.getCode()); response = client.post(namespacePath3, Constants.MIMETYPE_PROTOBUF, toXML(model1)); assertEquals(500, response.getCode()); NamespaceDescriptor nd1 = findNamespace(admin, NAMESPACE1); NamespaceDescriptor nd2 = findNamespace(admin, NAMESPACE2); NamespaceDescriptor nd3 = findNamespace(admin, NAMESPACE3); assertNull(nd1); assertNull(nd2); assertNull(nd3); } @Test public void testNamespaceCreateAndDeleteXMLAndJSON() throws IOException, JAXBException { String namespacePath1 = "/namespaces/" + NAMESPACE1; String namespacePath2 = "/namespaces/" + NAMESPACE2; NamespacesInstanceModel model1; NamespacesInstanceModel model2; Response response; // Check that namespaces don't exist via non-REST call. Admin admin = TEST_UTIL.getAdmin(); assertNull(findNamespace(admin, NAMESPACE1)); assertNull(findNamespace(admin, NAMESPACE2)); model1 = testNamespacesInstanceModel.buildTestModel(NAMESPACE1, NAMESPACE1_PROPS); testNamespacesInstanceModel.checkModel(model1, NAMESPACE1, NAMESPACE1_PROPS); model2 = testNamespacesInstanceModel.buildTestModel(NAMESPACE2, NAMESPACE2_PROPS); testNamespacesInstanceModel.checkModel(model2, NAMESPACE2, NAMESPACE2_PROPS); // Test cannot PUT (alter) non-existent namespace. response = client.put(namespacePath1, Constants.MIMETYPE_XML, toXML(model1)); assertEquals(403, response.getCode()); String jsonString = jsonMapper.writeValueAsString(model2); response = client.put(namespacePath2, Constants.MIMETYPE_JSON, Bytes.toBytes(jsonString)); assertEquals(403, response.getCode()); // Test cannot create tables when in read only mode. conf.set("hbase.rest.readonly", "true"); response = client.post(namespacePath1, Constants.MIMETYPE_XML, toXML(model1)); assertEquals(403, response.getCode()); jsonString = jsonMapper.writeValueAsString(model2); response = client.post(namespacePath2, Constants.MIMETYPE_JSON, Bytes.toBytes(jsonString)); assertEquals(403, response.getCode()); NamespaceDescriptor nd1 = findNamespace(admin, NAMESPACE1); NamespaceDescriptor nd2 = findNamespace(admin, NAMESPACE2); assertNull(nd1); assertNull(nd2); conf.set("hbase.rest.readonly", "false"); // Create namespace via XML and JSON. response = client.post(namespacePath1, Constants.MIMETYPE_XML, toXML(model1)); assertEquals(201, response.getCode()); jsonString = jsonMapper.writeValueAsString(model2); response = client.post(namespacePath2, Constants.MIMETYPE_JSON, Bytes.toBytes(jsonString)); assertEquals(201, response.getCode()); // Check that created namespaces correctly. nd1 = findNamespace(admin, NAMESPACE1); nd2 = findNamespace(admin, NAMESPACE2); assertNotNull(nd1); assertNotNull(nd2); checkNamespaceProperties(nd1, NAMESPACE1_PROPS); checkNamespaceProperties(nd1, NAMESPACE1_PROPS); // Test cannot delete tables when in read only mode. conf.set("hbase.rest.readonly", "true"); response = client.delete(namespacePath1); assertEquals(403, response.getCode()); response = client.delete(namespacePath2); assertEquals(403, response.getCode()); nd1 = findNamespace(admin, NAMESPACE1); nd2 = findNamespace(admin, NAMESPACE2); assertNotNull(nd1); assertNotNull(nd2); conf.set("hbase.rest.readonly", "false"); // Delete namespaces via XML and JSON. response = client.delete(namespacePath1); assertEquals(200, response.getCode()); response = client.delete(namespacePath2); assertEquals(200, response.getCode()); nd1 = findNamespace(admin, NAMESPACE1); nd2 = findNamespace(admin, NAMESPACE2); assertNull(nd1); assertNull(nd2); } @Test public void testNamespaceCreateAndDeletePBAndNoBody() throws IOException, JAXBException { String namespacePath3 = "/namespaces/" + NAMESPACE3; String namespacePath4 = "/namespaces/" + NAMESPACE4; NamespacesInstanceModel model3; NamespacesInstanceModel model4; Response response; // Check that namespaces don't exist via non-REST call. Admin admin = TEST_UTIL.getAdmin(); assertNull(findNamespace(admin, NAMESPACE3)); assertNull(findNamespace(admin, NAMESPACE4)); model3 = testNamespacesInstanceModel.buildTestModel(NAMESPACE3, NAMESPACE3_PROPS); testNamespacesInstanceModel.checkModel(model3, NAMESPACE3, NAMESPACE3_PROPS); model4 = testNamespacesInstanceModel.buildTestModel(NAMESPACE4, NAMESPACE4_PROPS); testNamespacesInstanceModel.checkModel(model4, NAMESPACE4, NAMESPACE4_PROPS); // Test cannot PUT (alter) non-existent namespace. response = client.put(namespacePath3, Constants.MIMETYPE_BINARY, new byte[]{}); assertEquals(403, response.getCode()); response = client.put(namespacePath4, Constants.MIMETYPE_PROTOBUF, model4.createProtobufOutput()); assertEquals(403, response.getCode()); // Test cannot create tables when in read only mode. conf.set("hbase.rest.readonly", "true"); response = client.post(namespacePath3, Constants.MIMETYPE_BINARY, new byte[]{}); assertEquals(403, response.getCode()); response = client.put(namespacePath4, Constants.MIMETYPE_PROTOBUF, model4.createProtobufOutput()); assertEquals(403, response.getCode()); NamespaceDescriptor nd3 = findNamespace(admin, NAMESPACE3); NamespaceDescriptor nd4 = findNamespace(admin, NAMESPACE4); assertNull(nd3); assertNull(nd4); conf.set("hbase.rest.readonly", "false"); // Create namespace via no body and protobuf. response = client.post(namespacePath3, Constants.MIMETYPE_BINARY, new byte[]{}); assertEquals(201, response.getCode()); response = client.post(namespacePath4, Constants.MIMETYPE_PROTOBUF, model4.createProtobufOutput()); assertEquals(201, response.getCode()); // Check that created namespaces correctly. nd3 = findNamespace(admin, NAMESPACE3); nd4 = findNamespace(admin, NAMESPACE4); assertNotNull(nd3); assertNotNull(nd4); checkNamespaceProperties(nd3, new HashMap<>()); checkNamespaceProperties(nd4, NAMESPACE4_PROPS); // Check cannot post tables that already exist. response = client.post(namespacePath3, Constants.MIMETYPE_BINARY, new byte[]{}); assertEquals(403, response.getCode()); response = client.post(namespacePath4, Constants.MIMETYPE_PROTOBUF, model4.createProtobufOutput()); assertEquals(403, response.getCode()); // Check cannot post tables when in read only mode. conf.set("hbase.rest.readonly", "true"); response = client.delete(namespacePath3); assertEquals(403, response.getCode()); response = client.delete(namespacePath4); assertEquals(403, response.getCode()); nd3 = findNamespace(admin, NAMESPACE3); nd4 = findNamespace(admin, NAMESPACE4); assertNotNull(nd3); assertNotNull(nd4); conf.set("hbase.rest.readonly", "false"); // Delete namespaces via XML and JSON. response = client.delete(namespacePath3); assertEquals(200, response.getCode()); response = client.delete(namespacePath4); assertEquals(200, response.getCode()); nd3 = findNamespace(admin, NAMESPACE3); nd4 = findNamespace(admin, NAMESPACE4); assertNull(nd3); assertNull(nd4); } }