View Javadoc

1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.rest;
20  
21  import java.io.IOException;
22  
23  import javax.servlet.ServletContext;
24  import javax.ws.rs.Consumes;
25  import javax.ws.rs.DELETE;
26  import javax.ws.rs.GET;
27  import javax.ws.rs.POST;
28  import javax.ws.rs.PUT;
29  import javax.ws.rs.Path;
30  import javax.ws.rs.PathParam;
31  import javax.ws.rs.Produces;
32  import javax.ws.rs.core.Context;
33  import javax.ws.rs.core.HttpHeaders;
34  import javax.ws.rs.core.Response;
35  import javax.ws.rs.core.UriInfo;
36  
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  import org.apache.hadoop.hbase.HTableDescriptor;
40  import org.apache.hadoop.hbase.NamespaceDescriptor;
41  import org.apache.hadoop.hbase.classification.InterfaceAudience;
42  import org.apache.hadoop.hbase.client.Admin;
43  import org.apache.hadoop.hbase.rest.model.NamespacesInstanceModel;
44  import org.apache.hadoop.hbase.rest.model.TableListModel;
45  import org.apache.hadoop.hbase.rest.model.TableModel;
46  
47  /**
48   * Implements the following REST end points:
49   * <p>
50   * <tt>/namespaces/{namespace} GET: get namespace properties.</tt>
51   * <tt>/namespaces/{namespace} POST: create namespace.</tt>
52   * <tt>/namespaces/{namespace} PUT: alter namespace.</tt>
53   * <tt>/namespaces/{namespace} DELETE: drop namespace.</tt>
54   * <tt>/namespaces/{namespace}/tables GET: list namespace's tables.</tt>
55   * <p>
56   */
57  @InterfaceAudience.Private
58  public class NamespacesInstanceResource extends ResourceBase {
59  
60    private static final Log LOG = LogFactory.getLog(NamespacesInstanceResource.class);
61    String namespace;
62    boolean queryTables = false;
63  
64    /**
65     * Constructor for standard NamespaceInstanceResource.
66     * @throws IOException
67     */
68    public NamespacesInstanceResource(String namespace) throws IOException {
69      this(namespace, false);
70    }
71  
72    /**
73     * Constructor for querying namespace table list via NamespaceInstanceResource.
74     * @throws IOException
75     */
76    public NamespacesInstanceResource(String namespace, boolean queryTables) throws IOException {
77      super();
78      this.namespace = namespace;
79      this.queryTables = queryTables;
80    }
81  
82    /**
83     * Build a response for GET namespace description or GET list of namespace tables.
84     * @param context servlet context
85     * @param uriInfo (JAX-RS context variable) request URL
86     * @return A response containing NamespacesInstanceModel for a namespace descriptions and
87     * TableListModel for a list of namespace tables.
88     */
89    @GET
90    @Produces({MIMETYPE_TEXT, MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
91      MIMETYPE_PROTOBUF_IETF})
92    public Response get(final @Context ServletContext context,
93        final @Context UriInfo uriInfo) {
94      if (LOG.isTraceEnabled()) {
95        LOG.trace("GET " + uriInfo.getAbsolutePath());
96      }
97      servlet.getMetrics().incrementRequests(1);
98  
99      // Respond to list of namespace tables requests.
100     if(queryTables){
101       TableListModel tableModel = new TableListModel();
102       try{
103         HTableDescriptor[] tables = servlet.getAdmin().listTableDescriptorsByNamespace(namespace);
104         for(int i = 0; i < tables.length; i++){
105           tableModel.add(new TableModel(tables[i].getTableName().getQualifierAsString()));
106         }
107 
108         servlet.getMetrics().incrementSucessfulGetRequests(1);
109         return Response.ok(tableModel).build();
110       }catch(IOException e) {
111         servlet.getMetrics().incrementFailedGetRequests(1);
112         throw new RuntimeException("Cannot retrieve table list for '" + namespace + "'.");
113       }
114     }
115 
116     // Respond to namespace description requests.
117     try {
118       NamespacesInstanceModel rowModel =
119           new NamespacesInstanceModel(servlet.getAdmin(), namespace);
120       servlet.getMetrics().incrementSucessfulGetRequests(1);
121       return Response.ok(rowModel).build();
122     } catch (IOException e) {
123       servlet.getMetrics().incrementFailedGetRequests(1);
124       throw new RuntimeException("Cannot retrieve info for '" + namespace + "'.");
125     }
126   }
127 
128   /**
129    * Build a response for PUT alter namespace with properties specified.
130    * @param model properties used for alter.
131    * @param uriInfo (JAX-RS context variable) request URL
132    * @return response code.
133    */
134   @PUT
135   @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
136     MIMETYPE_PROTOBUF_IETF})
137   public Response put(final NamespacesInstanceModel model, final @Context UriInfo uriInfo) {
138     if (LOG.isTraceEnabled()) {
139       LOG.trace("PUT " + uriInfo.getAbsolutePath());
140     }
141     servlet.getMetrics().incrementRequests(1);
142     return processUpdate(model, true, uriInfo);
143   }
144 
145   /**
146    * Build a response for PUT alter namespace with no properties specified.
147    * @param message value not used.
148    * @param headers value not used.
149    * @return response code.
150    */
151   @PUT
152   public Response putNoBody(final byte[] message,
153       final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
154     if (LOG.isTraceEnabled()) {
155       LOG.trace("PUT " + uriInfo.getAbsolutePath());
156     }
157     servlet.getMetrics().incrementRequests(1);
158     try{
159       NamespacesInstanceModel model = new NamespacesInstanceModel(namespace);
160       return processUpdate(model, true, uriInfo);
161     }catch(IOException ioe){
162       servlet.getMetrics().incrementFailedPutRequests(1);
163       throw new RuntimeException("Cannot retrieve info for '" + namespace + "'.");
164     }
165   }
166 
167   /**
168    * Build a response for POST create namespace with properties specified.
169    * @param model properties used for create.
170    * @param uriInfo (JAX-RS context variable) request URL
171    * @return response code.
172    */
173   @POST
174   @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
175     MIMETYPE_PROTOBUF_IETF})
176   public Response post(final NamespacesInstanceModel model,
177       final @Context UriInfo uriInfo) {
178 
179     if (LOG.isTraceEnabled()) {
180       LOG.trace("POST " + uriInfo.getAbsolutePath());
181     }
182     servlet.getMetrics().incrementRequests(1);
183     return processUpdate(model, false, uriInfo);
184   }
185 
186   /**
187    * Build a response for POST create namespace with no properties specified.
188    * @param message value not used.
189    * @param headers value not used.
190    * @return response code.
191    */
192   @POST
193   public Response postNoBody(final byte[] message,
194       final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
195     if (LOG.isTraceEnabled()) {
196       LOG.trace("POST " + uriInfo.getAbsolutePath());
197     }
198     servlet.getMetrics().incrementRequests(1);
199     try{
200       NamespacesInstanceModel model = new NamespacesInstanceModel(namespace);
201       return processUpdate(model, false, uriInfo);
202     }catch(IOException ioe){
203       servlet.getMetrics().incrementFailedPutRequests(1);
204       throw new RuntimeException("Cannot retrieve info for '" + namespace + "'.");
205     }
206   }
207 
208   // Check that POST or PUT is valid and then update namespace.
209   private Response processUpdate(final NamespacesInstanceModel model, final boolean updateExisting,
210       final UriInfo uriInfo) {
211     if (servlet.isReadOnly()) {
212       servlet.getMetrics().incrementFailedPutRequests(1);
213       return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT)
214           .entity("Forbidden" + CRLF).build();
215     }
216 
217     Admin admin = null;
218     boolean namespaceExists = false;
219     try {
220       admin = servlet.getAdmin();
221       namespaceExists = doesNamespaceExist(admin, namespace);
222     }catch (IOException e) {
223       servlet.getMetrics().incrementFailedPutRequests(1);
224       return processException(e);
225     }
226 
227     // Do not allow creation if namespace already exists.
228     if(!updateExisting && namespaceExists){
229       servlet.getMetrics().incrementFailedPutRequests(1);
230       return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT).
231           entity("Namespace '" + namespace + "' already exists.  Use REST PUT " +
232           "to alter the existing namespace.").build();
233     }
234 
235     // Do not allow altering if namespace does not exist.
236     if (updateExisting && !namespaceExists){
237       servlet.getMetrics().incrementFailedPutRequests(1);
238       return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT).
239           entity("Namespace '" + namespace + "' does not exist. Use " +
240           "REST POST to create the namespace.").build();
241     }
242 
243     return createOrUpdate(model, uriInfo, admin, updateExisting);
244   }
245 
246   // Do the actual namespace create or alter.
247   private Response createOrUpdate(final NamespacesInstanceModel model, final UriInfo uriInfo,
248       final Admin admin, final boolean updateExisting) {
249     NamespaceDescriptor.Builder builder = NamespaceDescriptor.create(namespace);
250     builder.addConfiguration(model.getProperties());
251     if(model.getProperties().size() > 0){
252       builder.addConfiguration(model.getProperties());
253     }
254     NamespaceDescriptor nsd = builder.build();
255 
256     try{
257       if(updateExisting){
258         admin.modifyNamespace(nsd);
259       }else{
260         admin.createNamespace(nsd);
261       }
262     }catch (IOException e) {
263       servlet.getMetrics().incrementFailedPutRequests(1);
264       return processException(e);
265     }
266 
267     servlet.getMetrics().incrementSucessfulPutRequests(1);
268     return Response.created(uriInfo.getAbsolutePath()).build();
269   }
270 
271   private boolean doesNamespaceExist(Admin admin, String namespaceName) throws IOException{
272     NamespaceDescriptor[] nd = admin.listNamespaceDescriptors();
273     for(int i = 0; i < nd.length; i++){
274       if(nd[i].getName().equals(namespaceName)){
275         return true;
276       }
277     }
278     return false;
279   }
280 
281   /**
282    * Build a response for DELETE delete namespace.
283    * @param message value not used.
284    * @param headers value not used.
285    * @return response code.
286    */
287   @DELETE
288   public Response deleteNoBody(final byte[] message,
289       final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
290     if (LOG.isTraceEnabled()) {
291       LOG.trace("DELETE " + uriInfo.getAbsolutePath());
292     }
293     if (servlet.isReadOnly()) {
294       servlet.getMetrics().incrementFailedDeleteRequests(1);
295       return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT)
296           .entity("Forbidden" + CRLF).build();
297     }
298 
299     try{
300       Admin admin = servlet.getAdmin();
301       if (!doesNamespaceExist(admin, namespace)){
302         return Response.status(Response.Status.NOT_FOUND).type(MIMETYPE_TEXT).
303             entity("Namespace '" + namespace + "' does not exists.  Cannot " +
304             "drop namespace.").build();
305       }
306 
307       admin.deleteNamespace(namespace);
308       servlet.getMetrics().incrementSucessfulDeleteRequests(1);
309       return Response.ok().build();
310 
311     } catch (IOException e) {
312       servlet.getMetrics().incrementFailedDeleteRequests(1);
313       return processException(e);
314     }
315   }
316 
317   /**
318    * Dispatch to NamespaceInstanceResource for getting list of tables.
319    */
320   @Path("tables")
321   public NamespacesInstanceResource getNamespaceInstanceResource(
322       final @PathParam("tables") String namespace) throws IOException {
323     return new NamespacesInstanceResource(this.namespace, true);
324   }
325 }