blob: 215abaea0b331e523be59e965df370d91a768f62 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.rest;
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.apache.hadoop.hbase.HTableDescriptor;
import org.apache.hadoop.hbase.NamespaceDescriptor;
import org.apache.yetus.audience.InterfaceAudience;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.hadoop.hbase.client.Admin;
import org.apache.hadoop.hbase.rest.model.NamespacesInstanceModel;
import org.apache.hadoop.hbase.rest.model.TableListModel;
import org.apache.hadoop.hbase.rest.model.TableModel;
/**
* Implements the following REST end points:
* <p>
* <tt>/namespaces/{namespace} GET: get namespace properties.</tt>
* <tt>/namespaces/{namespace} POST: create namespace.</tt>
* <tt>/namespaces/{namespace} PUT: alter namespace.</tt>
* <tt>/namespaces/{namespace} DELETE: drop namespace.</tt>
* <tt>/namespaces/{namespace}/tables GET: list namespace's tables.</tt>
* <p>
*/
@InterfaceAudience.Private
public class NamespacesInstanceResource extends ResourceBase {
private static final Logger LOG = LoggerFactory.getLogger(NamespacesInstanceResource.class);
String namespace;
boolean queryTables = false;
/**
* Constructor for standard NamespaceInstanceResource.
* @throws IOException
*/
public NamespacesInstanceResource(String namespace) throws IOException {
this(namespace, false);
}
/**
* Constructor for querying namespace table list via NamespaceInstanceResource.
* @throws IOException
*/
public NamespacesInstanceResource(String namespace, boolean queryTables) throws IOException {
super();
this.namespace = namespace;
this.queryTables = queryTables;
}
/**
* Build a response for GET namespace description or GET list of namespace tables.
* @param context servlet context
* @param uriInfo (JAX-RS context variable) request URL
* @return A response containing NamespacesInstanceModel for a namespace descriptions and
* TableListModel for a list of namespace tables.
*/
@GET
@Produces({MIMETYPE_TEXT, MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
MIMETYPE_PROTOBUF_IETF})
public Response get(final @Context ServletContext context,
final @Context UriInfo uriInfo) {
if (LOG.isTraceEnabled()) {
LOG.trace("GET " + uriInfo.getAbsolutePath());
}
servlet.getMetrics().incrementRequests(1);
// Respond to list of namespace tables requests.
if(queryTables){
TableListModel tableModel = new TableListModel();
try{
HTableDescriptor[] tables = servlet.getAdmin().listTableDescriptorsByNamespace(namespace);
for(int i = 0; i < tables.length; i++){
tableModel.add(new TableModel(tables[i].getTableName().getQualifierAsString()));
}
servlet.getMetrics().incrementSucessfulGetRequests(1);
return Response.ok(tableModel).build();
}catch(IOException e) {
servlet.getMetrics().incrementFailedGetRequests(1);
throw new RuntimeException("Cannot retrieve table list for '" + namespace + "'.");
}
}
// Respond to namespace description requests.
try {
NamespacesInstanceModel rowModel =
new NamespacesInstanceModel(servlet.getAdmin(), namespace);
servlet.getMetrics().incrementSucessfulGetRequests(1);
return Response.ok(rowModel).build();
} catch (IOException e) {
servlet.getMetrics().incrementFailedGetRequests(1);
throw new RuntimeException("Cannot retrieve info for '" + namespace + "'.");
}
}
/**
* Build a response for PUT alter namespace with properties specified.
* @param model properties used for alter.
* @param uriInfo (JAX-RS context variable) request URL
* @return response code.
*/
@PUT
@Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
MIMETYPE_PROTOBUF_IETF})
public Response put(final NamespacesInstanceModel model, final @Context UriInfo uriInfo) {
return processUpdate(model, true, uriInfo);
}
/**
* Build a response for POST create namespace with properties specified.
* @param model properties used for create.
* @param uriInfo (JAX-RS context variable) request URL
* @return response code.
*/
@POST
@Consumes({MIMETYPE_XML, MIMETYPE_JSON, MIMETYPE_PROTOBUF,
MIMETYPE_PROTOBUF_IETF})
public Response post(final NamespacesInstanceModel model,
final @Context UriInfo uriInfo) {
return processUpdate(model, false, uriInfo);
}
// Check that POST or PUT is valid and then update namespace.
private Response processUpdate(NamespacesInstanceModel model, final boolean updateExisting,
final UriInfo uriInfo) {
if (LOG.isTraceEnabled()) {
LOG.trace((updateExisting ? "PUT " : "POST ") + uriInfo.getAbsolutePath());
}
if (model == null) {
try {
model = new NamespacesInstanceModel(namespace);
} catch(IOException ioe) {
servlet.getMetrics().incrementFailedPutRequests(1);
throw new RuntimeException("Cannot retrieve info for '" + namespace + "'.");
}
}
servlet.getMetrics().incrementRequests(1);
if (servlet.isReadOnly()) {
servlet.getMetrics().incrementFailedPutRequests(1);
return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT)
.entity("Forbidden" + CRLF).build();
}
Admin admin = null;
boolean namespaceExists = false;
try {
admin = servlet.getAdmin();
namespaceExists = doesNamespaceExist(admin, namespace);
}catch (IOException e) {
servlet.getMetrics().incrementFailedPutRequests(1);
return processException(e);
}
// Do not allow creation if namespace already exists.
if(!updateExisting && namespaceExists){
servlet.getMetrics().incrementFailedPutRequests(1);
return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT).
entity("Namespace '" + namespace + "' already exists. Use REST PUT " +
"to alter the existing namespace.").build();
}
// Do not allow altering if namespace does not exist.
if (updateExisting && !namespaceExists){
servlet.getMetrics().incrementFailedPutRequests(1);
return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT).
entity("Namespace '" + namespace + "' does not exist. Use " +
"REST POST to create the namespace.").build();
}
return createOrUpdate(model, uriInfo, admin, updateExisting);
}
// Do the actual namespace create or alter.
private Response createOrUpdate(final NamespacesInstanceModel model, final UriInfo uriInfo,
final Admin admin, final boolean updateExisting) {
NamespaceDescriptor.Builder builder = NamespaceDescriptor.create(namespace);
builder.addConfiguration(model.getProperties());
if(model.getProperties().size() > 0){
builder.addConfiguration(model.getProperties());
}
NamespaceDescriptor nsd = builder.build();
try{
if(updateExisting){
admin.modifyNamespace(nsd);
}else{
admin.createNamespace(nsd);
}
}catch (IOException e) {
servlet.getMetrics().incrementFailedPutRequests(1);
return processException(e);
}
servlet.getMetrics().incrementSucessfulPutRequests(1);
return updateExisting ? Response.ok(uriInfo.getAbsolutePath()).build() :
Response.created(uriInfo.getAbsolutePath()).build();
}
private boolean doesNamespaceExist(Admin admin, String namespaceName) throws IOException{
NamespaceDescriptor[] nd = admin.listNamespaceDescriptors();
for(int i = 0; i < nd.length; i++){
if(nd[i].getName().equals(namespaceName)){
return true;
}
}
return false;
}
/**
* Build a response for DELETE delete namespace.
* @param message value not used.
* @param headers value not used.
* @return response code.
*/
@DELETE
public Response deleteNoBody(final byte[] message,
final @Context UriInfo uriInfo, final @Context HttpHeaders headers) {
if (LOG.isTraceEnabled()) {
LOG.trace("DELETE " + uriInfo.getAbsolutePath());
}
if (servlet.isReadOnly()) {
servlet.getMetrics().incrementFailedDeleteRequests(1);
return Response.status(Response.Status.FORBIDDEN).type(MIMETYPE_TEXT)
.entity("Forbidden" + CRLF).build();
}
try{
Admin admin = servlet.getAdmin();
if (!doesNamespaceExist(admin, namespace)){
return Response.status(Response.Status.NOT_FOUND).type(MIMETYPE_TEXT).
entity("Namespace '" + namespace + "' does not exists. Cannot " +
"drop namespace.").build();
}
admin.deleteNamespace(namespace);
servlet.getMetrics().incrementSucessfulDeleteRequests(1);
return Response.ok().build();
} catch (IOException e) {
servlet.getMetrics().incrementFailedDeleteRequests(1);
return processException(e);
}
}
/**
* Dispatch to NamespaceInstanceResource for getting list of tables.
*/
@Path("tables")
public NamespacesInstanceResource getNamespaceInstanceResource(
final @PathParam("tables") String namespace) throws IOException {
return new NamespacesInstanceResource(this.namespace, true);
}
}