Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
Expand Down Expand Up @@ -64,8 +65,10 @@
import org.apache.pulsar.common.policies.data.FailureDomain;
import org.apache.pulsar.common.policies.data.LocalPolicies;
import org.apache.pulsar.common.policies.data.Policies;
import org.apache.pulsar.common.policies.data.RetentionPolicies;
import org.apache.pulsar.common.policies.data.SubscribeRate;
import org.apache.pulsar.common.policies.data.TenantInfo;
import org.apache.pulsar.common.policies.data.TopicPolicies;
import org.apache.pulsar.common.policies.impl.NamespaceIsolationPolicies;
import org.apache.pulsar.common.util.Codec;
import org.apache.pulsar.common.util.FutureUtil;
Expand Down Expand Up @@ -508,6 +511,39 @@ protected BacklogQuota namespaceBacklogQuota(String namespace, String namespaceP
return pulsar().getBrokerService().getBacklogQuotaManager().getBacklogQuota(namespace, namespacePath);
}

protected Optional<TopicPolicies> getTopicPolicies(TopicName topicName) {
try {
checkTopicLevelPolicyEnable();
return Optional.ofNullable(pulsar().getTopicPoliciesService().getTopicPolicies(topicName));
} catch (RestException re) {
throw re;
} catch (Exception e) {
log.error("[{}] Failed to get topic policies {}", clientAppId(), topicName, e);
throw new RestException(e);
}
}

protected boolean checkBacklogQuota(BacklogQuota quota, RetentionPolicies retention) {
if (retention == null || retention.getRetentionSizeInMB() == 0 ||
retention.getRetentionSizeInMB() == -1) {
return true;
}
if (quota == null) {
quota = pulsar().getBrokerService().getBacklogQuotaManager().getDefaultQuota();
}
if (quota.getLimit() >= ( retention.getRetentionSizeInMB() * 1024 * 1024)) {
return false;
}
return true;
}

protected void checkTopicLevelPolicyEnable() {
if (!config().isTopicLevelPoliciesEnabled()) {
throw new RestException(Status.METHOD_NOT_ALLOWED,
"Topic level policies is disabled, to enable the topic level policy and retry.");
}
}

protected DispatchRate dispatchRate() {
return new DispatchRate(
pulsar().getConfiguration().getDispatchThrottlingRatePerTopicInMsg(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2008,21 +2008,12 @@ protected RetentionPolicies internalGetRetention() {
}

private boolean checkQuotas(Policies policies, RetentionPolicies retention) {
Map<BacklogQuota.BacklogQuotaType, BacklogQuota> backlog_quota_map = policies.backlog_quota_map;
if (backlog_quota_map.isEmpty() || retention.getRetentionSizeInMB() == 0 || retention.getRetentionSizeInMB() == -1) {
Map<BacklogQuota.BacklogQuotaType, BacklogQuota> backlogQuotaMap = policies.backlog_quota_map;
if (backlogQuotaMap.isEmpty()) {
return true;
}
BacklogQuota quota = backlog_quota_map.get(BacklogQuotaType.destination_storage);
if (quota == null) {
quota = pulsar().getBrokerService().getBacklogQuotaManager().getDefaultQuota();
}
if (quota.getLimit() < 0 && (retention.getRetentionSizeInMB() > 0 || retention.getRetentionTimeInMinutes() > 0)) {
return false;
}
if (quota.getLimit() >= (retention.getRetentionSizeInMB() * 1024 * 1024)) {
return false;
}
return true;
BacklogQuota quota = backlogQuotaMap.get(BacklogQuotaType.destination_storage);
return checkBacklogQuota(quota, retention);
}

private void clearBacklog(NamespaceName nsName, String bundleRange, String subscription) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
import org.apache.pulsar.broker.admin.AdminResource;
import org.apache.pulsar.broker.admin.ZkAdminPaths;
import org.apache.pulsar.broker.authentication.AuthenticationDataSource;
import org.apache.pulsar.broker.service.BrokerServiceException;
import org.apache.pulsar.broker.service.BrokerServiceException.AlreadyRunningException;
import org.apache.pulsar.broker.service.BrokerServiceException.NotAllowedException;
import org.apache.pulsar.broker.service.BrokerServiceException.SubscriptionBusyException;
Expand Down Expand Up @@ -104,12 +105,15 @@
import org.apache.pulsar.common.partition.PartitionedTopicMetadata;
import org.apache.pulsar.common.policies.data.AuthAction;
import org.apache.pulsar.common.policies.data.AuthPolicies;
import org.apache.pulsar.common.policies.data.BacklogQuota;
import org.apache.pulsar.common.policies.data.PartitionedTopicInternalStats;
import org.apache.pulsar.common.policies.data.PartitionedTopicStats;
import org.apache.pulsar.common.policies.data.PersistentOfflineTopicStats;
import org.apache.pulsar.common.policies.data.PersistentTopicInternalStats;
import org.apache.pulsar.common.policies.data.Policies;
import org.apache.pulsar.common.policies.data.RetentionPolicies;
import org.apache.pulsar.common.policies.data.SubscriptionStats;
import org.apache.pulsar.common.policies.data.TopicPolicies;
import org.apache.pulsar.common.policies.data.TopicStats;
import org.apache.pulsar.common.util.DateFormatter;
import org.apache.pulsar.common.util.FutureUtil;
Expand Down Expand Up @@ -2000,6 +2004,81 @@ protected PersistentOfflineTopicStats internalGetBacklog(boolean authoritative)
return offlineTopicStats;
}

protected void internalSetBacklogQuota(AsyncResponse asyncResponse, BacklogQuota.BacklogQuotaType backlogQuotaType, BacklogQuota backlogQuota) {
validateAdminAccessForTenant(namespaceName.getTenant());
validatePoliciesReadOnlyAccess();
if (topicName.isGlobal()) {
validateGlobalNamespaceOwnership(namespaceName);
}
if (backlogQuotaType == null) {
backlogQuotaType = BacklogQuota.BacklogQuotaType.destination_storage;
}
checkTopicLevelPolicyEnable();
TopicPolicies topicPolicies;
try {
topicPolicies = pulsar().getTopicPoliciesService().getTopicPolicies(topicName);
} catch (BrokerServiceException.TopicPoliciesCacheNotInitException e) {
log.warn("Topic {} policies cache have not init.", topicName);
asyncResponse.resume(new RestException(e));
return;
}
if (topicPolicies == null){
topicPolicies = new TopicPolicies();
}

RetentionPolicies retentionPolicies = getRetentionPolicies(topicName, topicPolicies);
if(!checkBacklogQuota(backlogQuota,retentionPolicies)){
log.warn(
"[{}] Failed to update backlog configuration for topic {}: conflicts with retention quota",
clientAppId(), topicName);
asyncResponse.resume(new RestException(Status.PRECONDITION_FAILED,
"Backlog Quota exceeds configured retention quota for topic. " +
"Please increase retention quota and retry"));
}

if(backlogQuota != null){
topicPolicies.getBackLogQuotaMap().put(backlogQuotaType.name(), backlogQuota);
}else {
topicPolicies.getBackLogQuotaMap().remove(backlogQuotaType.name());
}
Map<String, BacklogQuota> backLogQuotaMap = topicPolicies.getBackLogQuotaMap();
pulsar().getTopicPoliciesService().updateTopicPoliciesAsync(topicName, topicPolicies)
.whenComplete((r, ex) -> {
if (ex != null) {
log.error("Failed updated backlog quota map",ex);
asyncResponse.resume(new RestException(ex));
} else {
try {
log.info("[{}] Successfully updated backlog quota map: namespace={}, topic={}, map={}",
clientAppId(),
namespaceName,
topicName.getLocalName(),
jsonMapper().writeValueAsString(backLogQuotaMap));
} catch (JsonProcessingException ignore) { }
asyncResponse.resume(Response.noContent().build());
}
});
}

private RetentionPolicies getRetentionPolicies(TopicName topicName, TopicPolicies topicPolicies) {
RetentionPolicies retentionPolicies = topicPolicies.getRetentionPolicies();
if (retentionPolicies == null){
try {
retentionPolicies = getNamespacePoliciesAsync(topicName.getNamespaceObject())
.thenApply(policies -> policies.retention_policies)
.get(1L, TimeUnit.SECONDS);
} catch (Exception e) {
throw new RestException(e);
}
}
return retentionPolicies;
}

protected void internalRemoveBacklogQuota(AsyncResponse asyncResponse,
BacklogQuota.BacklogQuotaType backlogQuotaType) {
internalSetBacklogQuota(asyncResponse, backlogQuotaType, null);
}

protected MessageId internalTerminate(boolean authoritative) {
if (topicName.isGlobal()) {
validateGlobalNamespaceOwnership(namespaceName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.apache.pulsar.broker.admin.v2;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand All @@ -38,6 +39,7 @@
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import com.google.common.collect.Maps;
import org.apache.pulsar.broker.admin.impl.PersistentTopicsBase;
import org.apache.pulsar.broker.web.RestException;
import org.apache.pulsar.client.admin.LongRunningProcessStatus;
Expand All @@ -46,8 +48,10 @@
import org.apache.pulsar.client.impl.MessageIdImpl;
import org.apache.pulsar.common.partition.PartitionedTopicMetadata;
import org.apache.pulsar.common.policies.data.AuthAction;
import org.apache.pulsar.common.policies.data.BacklogQuota;
import org.apache.pulsar.common.policies.data.PersistentOfflineTopicStats;
import org.apache.pulsar.common.policies.data.PersistentTopicInternalStats;
import org.apache.pulsar.common.policies.data.TopicPolicies;
import org.apache.pulsar.common.policies.data.TopicStats;

import io.swagger.annotations.Api;
Expand Down Expand Up @@ -977,6 +981,57 @@ public PersistentOfflineTopicStats getBacklog(
return internalGetBacklog(authoritative);
}

@GET
@Path("/{tenant}/{namespace}/{topic}/backlogQuotaMap")
Comment thread
jianyun8023 marked this conversation as resolved.
@ApiOperation(value = "Get backlog quota map on a topic.")
@ApiResponses(value = { @ApiResponse(code = 403, message = "Don't have admin permission"),
@ApiResponse(code = 404, message = "Topic policy does not exist"),
@ApiResponse(code = 405, message = "Topic level policy is disabled, to enable the topic level policy and retry")})
public Map<BacklogQuota.BacklogQuotaType, BacklogQuota> getBacklogQuotaMap(@PathParam("tenant") String tenant,
@PathParam("namespace") String namespace,
@PathParam("topic") @Encoded String encodedTopic) {
validateTopicName(tenant, namespace, encodedTopic);
return getTopicPolicies(topicName)
.map(TopicPolicies::getBackLogQuotaMap)
.map(map -> {
HashMap<BacklogQuota.BacklogQuotaType, BacklogQuota> hashMap = Maps.newHashMap();
map.forEach((key,value) -> {
hashMap.put(BacklogQuota.BacklogQuotaType.valueOf(key),value);
});
return hashMap;
})
.orElse(Maps.newHashMap());
}

@POST
@Path("/{tenant}/{namespace}/{topic}/backlogQuota")
@ApiOperation(value = " Set a backlog quota for a topic.")
@ApiResponses(value = { @ApiResponse(code = 403, message = "Don't have admin permission"),
@ApiResponse(code = 404, message = "Topic does not exist"),
@ApiResponse(code = 409, message = "Concurrent modification"),
@ApiResponse(code = 405, message = "Topic level policy is disabled, to enable the topic level policy and retry"),
@ApiResponse(code = 412, message = "Specified backlog quota exceeds retention quota. Increase retention quota and retry request") })
public void setBacklogQuota(@Suspended final AsyncResponse asyncResponse, @PathParam("tenant") String tenant, @PathParam("namespace") String namespace,
@PathParam("topic") @Encoded String encodedTopic,
@QueryParam("backlogQuotaType") BacklogQuota.BacklogQuotaType backlogQuotaType, BacklogQuota backlogQuota) {
validateTopicName(tenant, namespace, encodedTopic);
internalSetBacklogQuota(asyncResponse, backlogQuotaType, backlogQuota);
}

@DELETE
@Path("/{tenant}/{namespace}/{topic}/backlogQuota")
@ApiOperation(value = "Remove a backlog quota policy from a topic.")
@ApiResponses(value = { @ApiResponse(code = 403, message = "Don't have admin permission"),
@ApiResponse(code = 404, message = "Topic does not exist"),
@ApiResponse(code = 405, message = "Topic level policy is disabled, to enable the topic level policy and retry"),
@ApiResponse(code = 409, message = "Concurrent modification") })
public void removeBacklogQuota(@Suspended final AsyncResponse asyncResponse, @PathParam("tenant") String tenant, @PathParam("namespace") String namespace,
@PathParam("topic") @Encoded String encodedTopic,
@QueryParam("backlogQuotaType") BacklogQuota.BacklogQuotaType backlogQuotaType) {
validateTopicName(tenant, namespace, encodedTopic);
internalRemoveBacklogQuota(asyncResponse, backlogQuotaType);
}

@POST
@Path("/{tenant}/{namespace}/{topic}/terminate")
@ApiOperation(value = "Terminate a topic. A topic that is terminated will not accept any more "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

import org.apache.bookkeeper.mledger.ManagedCursor;
Expand All @@ -32,9 +33,8 @@
import org.apache.pulsar.common.policies.data.BacklogQuota;
import org.apache.pulsar.common.policies.data.Policies;
import org.apache.pulsar.common.policies.data.BacklogQuota.BacklogQuotaType;
import org.apache.pulsar.common.policies.data.BacklogQuota.RetentionPolicy;
import org.apache.pulsar.common.policies.data.TopicPolicies;
import org.apache.pulsar.common.util.FutureUtil;
import org.apache.pulsar.common.util.collections.ConcurrentOpenHashSet;
import org.apache.pulsar.zookeeper.ZooKeeperDataCache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -47,12 +47,17 @@ public class BacklogQuotaManager {
private static final Logger log = LoggerFactory.getLogger(BacklogQuotaManager.class);
private final BacklogQuota defaultQuota;
private final ZooKeeperDataCache<Policies> zkCache;
private final PulsarService pulsar;
private final boolean isTopicLevelPoliciesEnable;


public BacklogQuotaManager(PulsarService pulsar) {
this.isTopicLevelPoliciesEnable = pulsar.getConfiguration().isTopicLevelPoliciesEnabled();
this.defaultQuota = new BacklogQuota(
pulsar.getConfiguration().getBacklogQuotaDefaultLimitGB() * 1024 * 1024 * 1024,
pulsar.getConfiguration().getBacklogQuotaDefaultRetentionPolicy());
this.zkCache = pulsar.getConfigurationCache().policiesCache();
this.pulsar = pulsar;
}

public BacklogQuota getDefaultQuota() {
Expand All @@ -70,9 +75,25 @@ public BacklogQuota getBacklogQuota(String namespace, String policyPath) {
}
}

public long getBacklogQuotaLimit(String namespace) {
String policyPath = AdminResource.path(POLICIES, namespace);
return getBacklogQuota(namespace, policyPath).getLimit();
public BacklogQuota getBacklogQuota(TopicName topicName) {
String policyPath = AdminResource.path(POLICIES, topicName.getNamespace());
if (!isTopicLevelPoliciesEnable) {
return getBacklogQuota(topicName.getNamespace(),policyPath);
}

try {
return Optional.ofNullable(pulsar.getTopicPoliciesService().getTopicPolicies(topicName))
.map(TopicPolicies::getBackLogQuotaMap)
.map(map -> map.get(BacklogQuotaType.destination_storage.name()))
.orElseGet(() -> getBacklogQuota(topicName.getNamespace(),policyPath));
} catch (Exception e) {
log.error("Failed to read policies data, will apply the default backlog quota: topicName={}", topicName, e);
}
return getBacklogQuota(topicName.getNamespace(),policyPath);
}

public long getBacklogQuotaLimit(TopicName topicName) {
return getBacklogQuota(topicName).getLimit();
}

/**
Expand All @@ -83,10 +104,7 @@ public long getBacklogQuotaLimit(String namespace) {
*/
public void handleExceededBacklogQuota(PersistentTopic persistentTopic) {
TopicName topicName = TopicName.get(persistentTopic.getName());
String namespace = topicName.getNamespace();
String policyPath = AdminResource.path(POLICIES, namespace);

BacklogQuota quota = getBacklogQuota(namespace, policyPath);
BacklogQuota quota = getBacklogQuota(topicName);
log.info("Backlog quota exceeded for topic [{}]. Applying [{}] policy", persistentTopic.getName(),
quota.getPolicy());
switch (quota.getPolicy()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1831,11 +1831,7 @@ public CompletableFuture<Void> onPoliciesUpdate(Policies data) {
@Override
public BacklogQuota getBacklogQuota() {
TopicName topicName = TopicName.get(this.getName());
String namespace = topicName.getNamespace();
String policyPath = AdminResource.path(POLICIES, namespace);

BacklogQuota backlogQuota = brokerService.getBacklogQuotaManager().getBacklogQuota(namespace, policyPath);
return backlogQuota;
return brokerService.getBacklogQuotaManager().getBacklogQuota(topicName);
}

/**
Expand Down Expand Up @@ -1866,7 +1862,7 @@ && isBacklogExceeded()) {
*/
public boolean isBacklogExceeded() {
TopicName topicName = TopicName.get(getName());
long backlogQuotaLimitInBytes = brokerService.getBacklogQuotaManager().getBacklogQuotaLimit(topicName.getNamespace());
long backlogQuotaLimitInBytes = brokerService.getBacklogQuotaManager().getBacklogQuotaLimit(topicName);
if (backlogQuotaLimitInBytes < 0) {
return false;
}
Expand Down
Loading