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}