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 return processUpdate(model, true, uriInfo); 139 } 140 141 /** 142 * Build a response for POST create namespace with properties specified. 143 * @param model properties used for create. 144 * @param uriInfo (JAX-RS context variable) request URL 145 * @return response code. 146 */ 147 @POST 148 @Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF, 149 MIMETYPE_PROTOBUF_IETF}) 150 public Response post(final NamespacesInstanceModel model, 151 final @Context UriInfo uriInfo) { 152 return processUpdate(model, false, uriInfo); 153 } 154 155 156 // Check that POST or PUT is valid and then update namespace. 157 private Response processUpdate(NamespacesInstanceModel model, final boolean updateExisting, 158 final UriInfo uriInfo) { 159 if (LOG.isTraceEnabled()) { 160 LOG.trace((updateExisting ? "PUT " : "POST ") + uriInfo.getAbsolutePath()); 161 } 162 if (model == null) { 163 try { 164 model = new NamespacesInstanceModel(namespace); 165 } catch(IOException ioe) { 166 servlet.getMetrics().incrementFailedPutRequests(1); 167 throw new RuntimeException("Cannot retrieve info for '" + namespace + "'."); 168 } 169 } 170 servlet.getMetrics().incrementRequests(1); 171 172 if (servlet.isReadOnly()) { 173 servlet.getMetrics().incrementFailedPutRequests(1); 174 return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT) 175 .entity("Forbidden" + CRLF).build(); 176 } 177 178 Admin admin = null; 179 boolean namespaceExists = false; 180 try { 181 admin = servlet.getAdmin(); 182 namespaceExists = doesNamespaceExist(admin, namespace); 183 }catch (IOException e) { 184 servlet.getMetrics().incrementFailedPutRequests(1); 185 return processException(e); 186 } 187 188 // Do not allow creation if namespace already exists. 189 if(!updateExisting && namespaceExists){ 190 servlet.getMetrics().incrementFailedPutRequests(1); 191 return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT). 192 entity("Namespace '" + namespace + "' already exists. Use REST PUT " + 193 "to alter the existing namespace.").build(); 194 } 195 196 // Do not allow altering if namespace does not exist. 197 if (updateExisting && !namespaceExists){ 198 servlet.getMetrics().incrementFailedPutRequests(1); 199 return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT). 200 entity("Namespace '" + namespace + "' does not exist. Use " + 201 "REST POST to create the namespace.").build(); 202 } 203 204 return createOrUpdate(model, uriInfo, admin, updateExisting); 205 } 206 207 // Do the actual namespace create or alter. 208 private Response createOrUpdate(final NamespacesInstanceModel model, final UriInfo uriInfo, 209 final Admin admin, final boolean updateExisting) { 210 NamespaceDescriptor.Builder builder = NamespaceDescriptor.create(namespace); 211 builder.addConfiguration(model.getProperties()); 212 if(model.getProperties().size() > 0){ 213 builder.addConfiguration(model.getProperties()); 214 } 215 NamespaceDescriptor nsd = builder.build(); 216 217 try{ 218 if(updateExisting){ 219 admin.modifyNamespace(nsd); 220 }else{ 221 admin.createNamespace(nsd); 222 } 223 }catch (IOException e) { 224 servlet.getMetrics().incrementFailedPutRequests(1); 225 return processException(e); 226 } 227 228 servlet.getMetrics().incrementSucessfulPutRequests(1); 229 230 return updateExisting ? Response.ok(uriInfo.getAbsolutePath()).build() : 231 Response.created(uriInfo.getAbsolutePath()).build(); 232 } 233 234 private boolean doesNamespaceExist(Admin admin, String namespaceName) throws IOException{ 235 NamespaceDescriptor[] nd = admin.listNamespaceDescriptors(); 236 for(int i = 0; i < nd.length; i++){ 237 if(nd[i].getName().equals(namespaceName)){ 238 return true; 239 } 240 } 241 return false; 242 } 243 244 /** 245 * Build a response for DELETE delete namespace. 246 * @param message value not used. 247 * @param headers value not used. 248 * @return response code. 249 */ 250 @DELETE 251 public Response deleteNoBody(final byte[] message, 252 final @Context UriInfo uriInfo, final @Context HttpHeaders headers) { 253 if (LOG.isTraceEnabled()) { 254 LOG.trace("DELETE " + uriInfo.getAbsolutePath()); 255 } 256 if (servlet.isReadOnly()) { 257 servlet.getMetrics().incrementFailedDeleteRequests(1); 258 return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT) 259 .entity("Forbidden" + CRLF).build(); 260 } 261 262 try{ 263 Admin admin = servlet.getAdmin(); 264 if (!doesNamespaceExist(admin, namespace)){ 265 return Response.status(Response.Status.NOT_FOUND).type(MIMETYPE_TEXT). 266 entity("Namespace '" + namespace + "' does not exists. Cannot " + 267 "drop namespace.").build(); 268 } 269 270 admin.deleteNamespace(namespace); 271 servlet.getMetrics().incrementSucessfulDeleteRequests(1); 272 return Response.ok().build(); 273 274 } catch (IOException e) { 275 servlet.getMetrics().incrementFailedDeleteRequests(1); 276 return processException(e); 277 } 278 } 279 280 /** 281 * Dispatch to NamespaceInstanceResource for getting list of tables. 282 */ 283 @Path("tables") 284 public NamespacesInstanceResource getNamespaceInstanceResource( 285 final @PathParam("tables") String namespace) throws IOException { 286 return new NamespacesInstanceResource(this.namespace, true); 287 } 288}