/* * Copyright © 2014-2016 Cask Data, Inc. * * Licensed 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 co.cask.cdap.gateway.handlers; import co.cask.cdap.common.AlreadyExistsException; import co.cask.cdap.common.BadRequestException; import co.cask.cdap.common.conf.CConfiguration; import co.cask.cdap.common.conf.Constants; import co.cask.cdap.common.namespace.NamespaceAdmin; import co.cask.cdap.gateway.handlers.util.AbstractAppFabricHttpHandler; import co.cask.cdap.proto.Id; import co.cask.cdap.proto.NamespaceConfig; import co.cask.cdap.proto.NamespaceMeta; import co.cask.http.HttpHandler; import co.cask.http.HttpResponder; import com.google.common.base.Strings; import com.google.gson.JsonSyntaxException; import com.google.inject.Inject; import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.HttpResponseStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; /** * The {@link HttpHandler} for handling REST calls to namespace endpoints. */ @Path(Constants.Gateway.API_VERSION_3) public class NamespaceHttpHandler extends AbstractAppFabricHttpHandler { private static final Logger LOG = LoggerFactory.getLogger(NamespaceHttpHandler.class); private final CConfiguration cConf; private final NamespaceAdmin namespaceAdmin; @Inject NamespaceHttpHandler(CConfiguration cConf, NamespaceAdmin namespaceAdmin) { this.cConf = cConf; this.namespaceAdmin = namespaceAdmin; } @GET @Path("/namespaces") public void getAllNamespaces(HttpRequest request, HttpResponder responder) throws Exception { responder.sendJson(HttpResponseStatus.OK, namespaceAdmin.list()); } @GET @Path("/namespaces/{namespace-id}") public void getNamespace(HttpRequest request, HttpResponder responder, @PathParam("namespace-id") String namespaceId) throws Exception { NamespaceMeta ns = namespaceAdmin.get(Id.Namespace.from(namespaceId)); responder.sendJson(HttpResponseStatus.OK, ns); } @PUT @Path("/namespaces/{namespace-id}/properties") public void updateNamespaceProperties(HttpRequest request, HttpResponder responder, @PathParam("namespace-id") String namespaceId) throws Exception { NamespaceMeta meta = parseBody(request, NamespaceMeta.class); namespaceAdmin.updateProperties(Id.Namespace.from(namespaceId), meta); responder.sendString(HttpResponseStatus.OK, String.format("Updated properties for namespace '%s'.", namespaceId)); } @PUT @Path("/namespaces/{namespace-id}") public void create(HttpRequest request, HttpResponder responder, @PathParam("namespace-id") String namespaceId) throws Exception { Id.Namespace namespace; try { namespace = Id.Namespace.from(namespaceId); } catch (IllegalArgumentException e) { throw new BadRequestException("Namespace id can contain only alphanumeric characters or '_'."); } NamespaceMeta metadata; try { metadata = parseBody(request, NamespaceMeta.class); } catch (JsonSyntaxException e) { throw new BadRequestException("Invalid json object provided in request body."); } if (isReserved(namespaceId)) { throw new BadRequestException(String.format("Cannot create the namespace '%s'. '%s' is a reserved namespace.", namespaceId, namespaceId)); } NamespaceMeta.Builder builder = new NamespaceMeta.Builder().setName(namespace); // Handle optional params if (metadata != null) { if (metadata.getDescription() != null) { builder.setDescription(metadata.getDescription()); } NamespaceConfig config = metadata.getConfig(); if (config != null && !Strings.isNullOrEmpty(config.getSchedulerQueueName())) { builder.setSchedulerQueueName(config.getSchedulerQueueName()); } } try { namespaceAdmin.create(builder.build()); responder.sendString(HttpResponseStatus.OK, String.format("Namespace '%s' created successfully.", namespaceId)); } catch (AlreadyExistsException e) { responder.sendString(HttpResponseStatus.OK, String.format("Namespace '%s' already exists.", namespaceId)); } } @DELETE @Path("/unrecoverable/namespaces/{namespace-id}") public void delete(HttpRequest request, HttpResponder responder, @PathParam("namespace-id") String namespace) throws Exception { if (!cConf.getBoolean(Constants.Dangerous.UNRECOVERABLE_RESET, Constants.Dangerous.DEFAULT_UNRECOVERABLE_RESET)) { responder.sendString(HttpResponseStatus.FORBIDDEN, String.format("Namespace '%s' cannot be deleted because '%s' is not enabled. " + "Please enable it and restart CDAP Master.", namespace, Constants.Dangerous.UNRECOVERABLE_RESET)); return; } Id.Namespace namespaceId = Id.Namespace.from(namespace); namespaceAdmin.delete(namespaceId); responder.sendStatus(HttpResponseStatus.OK); } @DELETE @Path("/unrecoverable/namespaces/{namespace-id}/datasets") public void deleteDatasets(HttpRequest request, HttpResponder responder, @PathParam("namespace-id") String namespace) throws Exception { if (!cConf.getBoolean(Constants.Dangerous.UNRECOVERABLE_RESET, Constants.Dangerous.DEFAULT_UNRECOVERABLE_RESET)) { responder.sendString(HttpResponseStatus.FORBIDDEN, String.format("All datasets in namespace %s cannot be deleted because '%s' is not enabled." + " Please enable it and restart CDAP Master.", namespace, Constants.Dangerous.UNRECOVERABLE_RESET)); return; } Id.Namespace namespaceId = Id.Namespace.from(namespace); namespaceAdmin.deleteDatasets(namespaceId); responder.sendStatus(HttpResponseStatus.OK); } private boolean isReserved(String namespaceId) { return Id.Namespace.DEFAULT.getId().equals(namespaceId) || Id.Namespace.SYSTEM.getId().equals(namespaceId) || Id.Namespace.CDAP.getId().equals(namespaceId); } }