001/* 002 * 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, software 014 * distributed under the License is distributed on an "AS IS" BASIS, 015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 016 * See the License for the specific language governing permissions and 017 * limitations under the License. 018 */ 019package org.apache.hadoop.hbase.rest; 020 021import java.io.IOException; 022 023import javax.servlet.ServletContext; 024import javax.ws.rs.Consumes; 025import javax.ws.rs.DELETE; 026import javax.ws.rs.GET; 027import javax.ws.rs.POST; 028import javax.ws.rs.PUT; 029import javax.ws.rs.Path; 030import javax.ws.rs.PathParam; 031import javax.ws.rs.Produces; 032import javax.ws.rs.core.Context; 033import javax.ws.rs.core.HttpHeaders; 034import javax.ws.rs.core.Response; 035import javax.ws.rs.core.UriInfo; 036 037import org.apache.hadoop.hbase.HTableDescriptor; 038import org.apache.hadoop.hbase.NamespaceDescriptor; 039import org.apache.yetus.audience.InterfaceAudience; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042import org.apache.hadoop.hbase.client.Admin; 043import org.apache.hadoop.hbase.rest.model.NamespacesInstanceModel; 044import org.apache.hadoop.hbase.rest.model.TableListModel; 045import org.apache.hadoop.hbase.rest.model.TableModel; 046 047/** 048 * Implements the following REST end points: 049 * <p> 050 * <tt>/namespaces/{namespace} GET: get namespace properties.</tt> 051 * <tt>/namespaces/{namespace} POST: create namespace.</tt> 052 * <tt>/namespaces/{namespace} PUT: alter namespace.</tt> 053 * <tt>/namespaces/{namespace} DELETE: drop namespace.</tt> 054 * <tt>/namespaces/{namespace}/tables GET: list namespace's tables.</tt> 055 * <p> 056 */ 057@InterfaceAudience.Private 058public class NamespacesInstanceResource extends ResourceBase { 059 060 private static final Logger LOG = LoggerFactory.getLogger(NamespacesInstanceResource.class); 061 String namespace; 062 boolean queryTables = false; 063 064 /** 065 * Constructor for standard NamespaceInstanceResource. 066 * @throws IOException 067 */ 068 public NamespacesInstanceResource(String namespace) throws IOException { 069 this(namespace, false); 070 } 071 072 /** 073 * Constructor for querying namespace table list via NamespaceInstanceResource. 074 * @throws IOException 075 */ 076 public NamespacesInstanceResource(String namespace, boolean queryTables) throws IOException { 077 super(); 078 this.namespace = namespace; 079 this.queryTables = queryTables; 080 } 081 082 /** 083 * Build a response for GET namespace description or GET list of namespace tables. 084 * @param context servlet context 085 * @param uriInfo (JAX-RS context variable) request URL 086 * @return A response containing NamespacesInstanceModel for a namespace descriptions and 087 * TableListModel for a list of namespace tables. 088 */ 089 @GET 090 @Produces({MIMETYPE_TEXT, MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF, 091 MIMETYPE_PROTOBUF_IETF}) 092 public Response get(final @Context ServletContext context, 093 final @Context UriInfo uriInfo) { 094 if (LOG.isTraceEnabled()) { 095 LOG.trace("GET " + uriInfo.getAbsolutePath()); 096 } 097 servlet.getMetrics().incrementRequests(1); 098 099 // 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}