/** * Copyright 2016 Yahoo 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 com.yahoo.pulsar.broker.admin; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response.Status; import org.apache.zookeeper.KeeperException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; import com.yahoo.pulsar.broker.web.RestException; import com.yahoo.pulsar.common.naming.NamespaceBundle; import com.yahoo.pulsar.common.naming.NamespaceName; import com.yahoo.pulsar.common.policies.data.Policies; import com.yahoo.pulsar.common.policies.data.ResourceQuota; @Path("/resource-quotas") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) @Api(value = "/resource-quotas", description = "Quota admin APIs", tags = "resource-quotas") public class ResourceQuotas extends AdminResource { private static final Logger log = LoggerFactory.getLogger(ResourceQuotas.class); @GET @ApiOperation(value = "Get the default quota", response = String.class, responseContainer = "Set") @ApiResponses(value = { @ApiResponse(code = 403, message = "Don't have admin permission") }) public ResourceQuota getDefaultResourceQuota() throws Exception { validateSuperUserAccess(); try { return pulsar().getLocalZkCacheService().getResourceQuotaCache().getDefaultQuota(); } catch (Exception e) { log.error("[{}] Failed to get default resource quota", clientAppId()); throw new RestException(e); } } @POST @ApiOperation(value = "Set the default quota", response = String.class, responseContainer = "Set") @ApiResponses(value = { @ApiResponse(code = 403, message = "Don't have admin permission") }) public void setDefaultResourceQuota(ResourceQuota quota) throws Exception { validateSuperUserAccess(); validatePoliciesReadOnlyAccess(); try { pulsar().getLocalZkCacheService().getResourceQuotaCache().setDefaultQuota(quota); } catch (Exception e) { log.error("[{}] Failed to get default resource quota", clientAppId()); throw new RestException(e); } } @GET @Path("/{property}/{cluster}/{namespace}/{bundle}") @ApiOperation(value = "Get resource quota of a namespace bundle.") @ApiResponses(value = { @ApiResponse(code = 403, message = "Don't have admin permission"), @ApiResponse(code = 404, message = "Namespace does not exist") }) public ResourceQuota getNamespaceBundleResourceQuota(@PathParam("property") String property, @PathParam("cluster") String cluster, @PathParam("namespace") String namespace, @PathParam("bundle") String bundleRange) { validateSuperUserAccess(); Policies policies = getNamespacePolicies(property, cluster, namespace); if (!cluster.equals(Namespaces.GLOBAL_CLUSTER)) { validateClusterOwnership(cluster); validateClusterForProperty(property, cluster); } NamespaceName fqnn = new NamespaceName(property, cluster, namespace); NamespaceBundle nsBundle = validateNamespaceBundleRange(fqnn, policies.bundles, bundleRange); try { return pulsar().getLocalZkCacheService().getResourceQuotaCache().getQuota(nsBundle); } catch (Exception e) { log.error("[{}] Failed to get resource quota for namespace bundle {}", clientAppId(), nsBundle.toString()); throw new RestException(e); } } @POST @Path("/{property}/{cluster}/{namespace}/{bundle}") @ApiOperation(value = "Set resource quota on a namespace.") @ApiResponses(value = { @ApiResponse(code = 403, message = "Don't have admin permission"), @ApiResponse(code = 409, message = "Concurrent modification") }) public void setNamespaceBundleResourceQuota(@PathParam("property") String property, @PathParam("cluster") String cluster, @PathParam("namespace") String namespace, @PathParam("bundle") String bundleRange, ResourceQuota quota) { validateSuperUserAccess(); validatePoliciesReadOnlyAccess(); Policies policies = getNamespacePolicies(property, cluster, namespace); if (!cluster.equals(Namespaces.GLOBAL_CLUSTER)) { validateClusterOwnership(cluster); validateClusterForProperty(property, cluster); } NamespaceName fqnn = new NamespaceName(property, cluster, namespace); NamespaceBundle nsBundle = validateNamespaceBundleRange(fqnn, policies.bundles, bundleRange); try { pulsar().getLocalZkCacheService().getResourceQuotaCache().setQuota(nsBundle, quota); log.info("[{}] Successfully set resource quota for namespace bundle {}", clientAppId(), nsBundle.toString()); } catch (KeeperException.NoNodeException e) { log.warn("[{}] Failed to set resource quota for namespace bundle {}: concurrent modification", clientAppId(), nsBundle.toString()); throw new RestException(Status.CONFLICT, "Cuncurrent modification on namespace bundle quota"); } catch (Exception e) { log.error("[{}] Failed to set resource quota for namespace bundle {}", clientAppId(), nsBundle.toString()); throw new RestException(e); } } @DELETE @Path("/{property}/{cluster}/{namespace}/{bundle}") @ApiOperation(value = "Remove resource quota for a namespace.") @ApiResponses(value = { @ApiResponse(code = 403, message = "Don't have admin permission"), @ApiResponse(code = 409, message = "Concurrent modification") }) public void removeNamespaceBundleResourceQuota(@PathParam("property") String property, @PathParam("cluster") String cluster, @PathParam("namespace") String namespace, @PathParam("bundle") String bundleRange) { validateSuperUserAccess(); validatePoliciesReadOnlyAccess(); Policies policies = getNamespacePolicies(property, cluster, namespace); if (!cluster.equals(Namespaces.GLOBAL_CLUSTER)) { validateClusterOwnership(cluster); validateClusterForProperty(property, cluster); } NamespaceName fqnn = new NamespaceName(property, cluster, namespace); NamespaceBundle nsBundle = validateNamespaceBundleRange(fqnn, policies.bundles, bundleRange); try { pulsar().getLocalZkCacheService().getResourceQuotaCache().unsetQuota(nsBundle); log.info("[{}] Successfully unset resource quota for namespace bundle {}", clientAppId(), nsBundle.toString()); } catch (KeeperException.NoNodeException e) { log.warn("[{}] Failed to unset resource quota for namespace bundle {}: concurrent modification", clientAppId(), nsBundle.toString()); throw new RestException(Status.CONFLICT, "Cuncurrent modification on namespace bundle quota"); } catch (Exception e) { log.error("[{}] Failed to unset resource quota for namespace bundle {}", clientAppId(), nsBundle.toString()); throw new RestException(e); } } }