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