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}