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 java.io.IOException; 021import java.util.List; 022import javax.servlet.ServletContext; 023import org.apache.hadoop.hbase.NamespaceDescriptor; 024import org.apache.hadoop.hbase.client.Admin; 025import org.apache.hadoop.hbase.client.TableDescriptor; 026import org.apache.hadoop.hbase.rest.model.NamespacesInstanceModel; 027import org.apache.hadoop.hbase.rest.model.TableListModel; 028import org.apache.hadoop.hbase.rest.model.TableModel; 029import org.apache.hadoop.hbase.util.Bytes; 030import org.apache.yetus.audience.InterfaceAudience; 031import org.slf4j.Logger; 032import org.slf4j.LoggerFactory; 033 034import org.apache.hbase.thirdparty.javax.ws.rs.Consumes; 035import org.apache.hbase.thirdparty.javax.ws.rs.DELETE; 036import org.apache.hbase.thirdparty.javax.ws.rs.GET; 037import org.apache.hbase.thirdparty.javax.ws.rs.POST; 038import org.apache.hbase.thirdparty.javax.ws.rs.PUT; 039import org.apache.hbase.thirdparty.javax.ws.rs.Path; 040import org.apache.hbase.thirdparty.javax.ws.rs.PathParam; 041import org.apache.hbase.thirdparty.javax.ws.rs.Produces; 042import org.apache.hbase.thirdparty.javax.ws.rs.core.Context; 043import org.apache.hbase.thirdparty.javax.ws.rs.core.HttpHeaders; 044import org.apache.hbase.thirdparty.javax.ws.rs.core.Response; 045import org.apache.hbase.thirdparty.javax.ws.rs.core.UriInfo; 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 */ 067 public NamespacesInstanceResource(String namespace) throws IOException { 068 this(namespace, false); 069 } 070 071 /** 072 * Constructor for querying namespace table list via NamespaceInstanceResource. 073 */ 074 public NamespacesInstanceResource(String namespace, boolean queryTables) throws IOException { 075 super(); 076 this.namespace = namespace; 077 this.queryTables = queryTables; 078 } 079 080 /** 081 * Build a response for GET namespace description or GET list of namespace tables. 082 * @param context servlet context 083 * @param uriInfo (JAX-RS context variable) request URL 084 * @return A response containing NamespacesInstanceModel for a namespace descriptions and 085 * TableListModel for a list of namespace tables. 086 */ 087 @GET 088 @Produces({ MIMETYPE_TEXT, MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF, 089 MIMETYPE_PROTOBUF_IETF }) 090 public Response get(final @Context ServletContext context, final @Context UriInfo uriInfo) { 091 if (LOG.isTraceEnabled()) { 092 LOG.trace("GET " + uriInfo.getAbsolutePath()); 093 } 094 servlet.getMetrics().incrementRequests(1); 095 096 // Respond to list of namespace tables requests. 097 if (queryTables) { 098 TableListModel tableModel = new TableListModel(); 099 try { 100 List<TableDescriptor> tables = 101 servlet.getAdmin().listTableDescriptorsByNamespace(Bytes.toBytes(namespace)); 102 for (TableDescriptor table : tables) { 103 tableModel.add(new TableModel(table.getTableName().getQualifierAsString())); 104 } 105 106 servlet.getMetrics().incrementSucessfulGetRequests(1); 107 return Response.ok(tableModel).build(); 108 } catch (IOException e) { 109 servlet.getMetrics().incrementFailedGetRequests(1); 110 throw new RuntimeException("Cannot retrieve table list for '" + namespace + "'."); 111 } 112 } 113 114 // Respond to namespace description requests. 115 try { 116 NamespacesInstanceModel rowModel = new NamespacesInstanceModel(servlet.getAdmin(), namespace); 117 servlet.getMetrics().incrementSucessfulGetRequests(1); 118 return Response.ok(rowModel).build(); 119 } catch (IOException e) { 120 servlet.getMetrics().incrementFailedGetRequests(1); 121 throw new RuntimeException("Cannot retrieve info for '" + namespace + "'."); 122 } 123 } 124 125 /** 126 * Build a response for PUT alter namespace with properties specified. 127 * @param model properties used for alter. 128 * @param uriInfo (JAX-RS context variable) request URL 129 * @return response code. 130 */ 131 @PUT 132 @Consumes({ MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF, MIMETYPE_PROTOBUF_IETF }) 133 public Response put(final NamespacesInstanceModel model, final @Context UriInfo uriInfo) { 134 return processUpdate(model, true, uriInfo); 135 } 136 137 /** 138 * Build a response for POST create namespace with properties specified. 139 * @param model properties used for create. 140 * @param uriInfo (JAX-RS context variable) request URL 141 * @return response code. 142 */ 143 @POST 144 @Consumes({ MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF, MIMETYPE_PROTOBUF_IETF }) 145 public Response post(final NamespacesInstanceModel model, final @Context UriInfo uriInfo) { 146 return processUpdate(model, false, uriInfo); 147 } 148 149 // Check that POST or PUT is valid and then update namespace. 150 private Response processUpdate(NamespacesInstanceModel model, final boolean updateExisting, 151 final UriInfo uriInfo) { 152 if (LOG.isTraceEnabled()) { 153 LOG.trace((updateExisting ? "PUT " : "POST ") + uriInfo.getAbsolutePath()); 154 } 155 if (model == null) { 156 try { 157 model = new NamespacesInstanceModel(namespace); 158 } catch (IOException ioe) { 159 servlet.getMetrics().incrementFailedPutRequests(1); 160 throw new RuntimeException("Cannot retrieve info for '" + namespace + "'."); 161 } 162 } 163 servlet.getMetrics().incrementRequests(1); 164 165 if (servlet.isReadOnly()) { 166 servlet.getMetrics().incrementFailedPutRequests(1); 167 return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT) 168 .entity("Forbidden" + CRLF).build(); 169 } 170 171 Admin admin = null; 172 boolean namespaceExists = false; 173 try { 174 admin = servlet.getAdmin(); 175 namespaceExists = doesNamespaceExist(admin, namespace); 176 } catch (IOException e) { 177 servlet.getMetrics().incrementFailedPutRequests(1); 178 return processException(e); 179 } 180 181 // Do not allow creation if namespace already exists. 182 if (!updateExisting && namespaceExists) { 183 servlet.getMetrics().incrementFailedPutRequests(1); 184 return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT).entity("Namespace '" 185 + namespace + "' already exists. Use REST PUT " + "to alter the existing namespace.") 186 .build(); 187 } 188 189 // Do not allow altering if namespace does not exist. 190 if (updateExisting && !namespaceExists) { 191 servlet.getMetrics().incrementFailedPutRequests(1); 192 return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT).entity( 193 "Namespace '" + namespace + "' does not exist. Use " + "REST POST to create the namespace.") 194 .build(); 195 } 196 197 return createOrUpdate(model, uriInfo, admin, updateExisting); 198 } 199 200 // Do the actual namespace create or alter. 201 private Response createOrUpdate(final NamespacesInstanceModel model, final UriInfo uriInfo, 202 final Admin admin, final boolean updateExisting) { 203 NamespaceDescriptor.Builder builder = NamespaceDescriptor.create(namespace); 204 builder.addConfiguration(model.getProperties()); 205 if (model.getProperties().size() > 0) { 206 builder.addConfiguration(model.getProperties()); 207 } 208 NamespaceDescriptor nsd = builder.build(); 209 210 try { 211 if (updateExisting) { 212 admin.modifyNamespace(nsd); 213 } else { 214 admin.createNamespace(nsd); 215 } 216 } catch (IOException e) { 217 servlet.getMetrics().incrementFailedPutRequests(1); 218 return processException(e); 219 } 220 221 servlet.getMetrics().incrementSucessfulPutRequests(1); 222 223 return updateExisting 224 ? Response.ok(uriInfo.getAbsolutePath()).build() 225 : Response.created(uriInfo.getAbsolutePath()).build(); 226 } 227 228 private boolean doesNamespaceExist(Admin admin, String namespaceName) throws IOException { 229 NamespaceDescriptor[] nd = admin.listNamespaceDescriptors(); 230 for (int i = 0; i < nd.length; i++) { 231 if (nd[i].getName().equals(namespaceName)) { 232 return true; 233 } 234 } 235 return false; 236 } 237 238 /** 239 * Build a response for DELETE delete namespace. 240 * @param message value not used. 241 * @param headers value not used. 242 * @return response code. 243 */ 244 @DELETE 245 public Response deleteNoBody(final byte[] message, final @Context UriInfo uriInfo, 246 final @Context HttpHeaders headers) { 247 if (LOG.isTraceEnabled()) { 248 LOG.trace("DELETE " + uriInfo.getAbsolutePath()); 249 } 250 if (servlet.isReadOnly()) { 251 servlet.getMetrics().incrementFailedDeleteRequests(1); 252 return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT) 253 .entity("Forbidden" + CRLF).build(); 254 } 255 256 try { 257 Admin admin = servlet.getAdmin(); 258 if (!doesNamespaceExist(admin, namespace)) { 259 return Response.status(Response.Status.NOT_FOUND).type(MIMETYPE_TEXT) 260 .entity("Namespace '" + namespace + "' does not exists. Cannot " + "drop namespace.") 261 .build(); 262 } 263 264 admin.deleteNamespace(namespace); 265 servlet.getMetrics().incrementSucessfulDeleteRequests(1); 266 return Response.ok().build(); 267 268 } catch (IOException e) { 269 servlet.getMetrics().incrementFailedDeleteRequests(1); 270 return processException(e); 271 } 272 } 273 274 /** 275 * Dispatch to NamespaceInstanceResource for getting list of tables. 276 */ 277 @Path("tables") 278 public NamespacesInstanceResource 279 getNamespaceInstanceResource(final @PathParam("tables") String namespace) throws IOException { 280 return new NamespacesInstanceResource(this.namespace, true); 281 } 282}