Rate limits to tenant profile BE refactoring

This commit is contained in:
Viacheslav Klimov 2022-01-20 14:26:23 +02:00
parent cb071ac0a2
commit 487d7165cc
13 changed files with 153 additions and 177 deletions

View File

@ -48,22 +48,20 @@ public class RateLimitProcessingFilter extends GenericFilterBean {
@Autowired @Autowired
private TbTenantProfileCache tenantProfileCache; private TbTenantProfileCache tenantProfileCache;
private ConcurrentMap<TenantId, TbRateLimits> perTenantLimits = new ConcurrentHashMap<>(); private final ConcurrentMap<TenantId, TbRateLimits> perTenantLimits = new ConcurrentHashMap<>();
private ConcurrentMap<CustomerId, TbRateLimits> perCustomerLimits = new ConcurrentHashMap<>(); private final ConcurrentMap<CustomerId, TbRateLimits> perCustomerLimits = new ConcurrentHashMap<>();
@Override @Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
SecurityUser user = getCurrentUser(); SecurityUser user = getCurrentUser();
if (user != null && !user.isSystemAdmin()) { if (user != null && !user.isSystemAdmin()) {
var profileConfiguration = tenantProfileCache.get(user.getTenantId()).getDefaultTenantProfileConfiguration(); var profileConfiguration = tenantProfileCache.get(user.getTenantId()).getDefaultTenantProfileConfiguration();
if (profileConfiguration != null) {
if(profileConfiguration != null) { if (user.isTenantAdmin() && StringUtils.isNotEmpty(profileConfiguration.getTenantServerRestLimitsConfiguration())) {
if (user.isTenantAdmin() && StringUtils.isNotEmpty(profileConfiguration.getRateLimitsTenantConfiguration())) {
TbRateLimits rateLimits = perTenantLimits.get(user.getTenantId()); TbRateLimits rateLimits = perTenantLimits.get(user.getTenantId());
if(rateLimits == null || !rateLimits.getCurrentConfig().equals(profileConfiguration.getRateLimitsTenantConfiguration())) { if (rateLimits == null || !rateLimits.getConfiguration().equals(profileConfiguration.getTenantServerRestLimitsConfiguration())) {
rateLimits = new TbRateLimits(profileConfiguration.getRateLimitsTenantConfiguration()); // fixme: or maybe handle component lifecycle event ?
rateLimits = new TbRateLimits(profileConfiguration.getTenantServerRestLimitsConfiguration());
perTenantLimits.put(user.getTenantId(), rateLimits); perTenantLimits.put(user.getTenantId(), rateLimits);
} }
@ -71,12 +69,10 @@ public class RateLimitProcessingFilter extends GenericFilterBean {
errorResponseHandler.handle(new TbRateLimitsException(EntityType.TENANT), (HttpServletResponse) response); errorResponseHandler.handle(new TbRateLimitsException(EntityType.TENANT), (HttpServletResponse) response);
return; return;
} }
} } else if (user.isCustomerUser() && StringUtils.isNotEmpty(profileConfiguration.getCustomerServerRestLimitsConfiguration())) {
if (user.isCustomerUser() && StringUtils.isNotEmpty(profileConfiguration.getRateLimitsCustomerConfiguration())) {
TbRateLimits rateLimits = perCustomerLimits.get(user.getCustomerId()); TbRateLimits rateLimits = perCustomerLimits.get(user.getCustomerId());
if(rateLimits == null || !rateLimits.getCurrentConfig().equals(profileConfiguration.getRateLimitsCustomerConfiguration())) { if (rateLimits == null || !rateLimits.getConfiguration().equals(profileConfiguration.getCustomerServerRestLimitsConfiguration())) {
rateLimits = new TbRateLimits(profileConfiguration.getRateLimitsCustomerConfiguration()); rateLimits = new TbRateLimits(profileConfiguration.getCustomerServerRestLimitsConfiguration());
perCustomerLimits.put(user.getCustomerId(), rateLimits); perCustomerLimits.put(user.getCustomerId(), rateLimits);
} }

View File

@ -16,12 +16,12 @@
package org.thingsboard.server.controller.plugin; package org.thingsboard.server.controller.plugin;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.BeanCreationNotAllowedException; import org.springframework.beans.factory.BeanCreationNotAllowedException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.PongMessage; import org.springframework.web.socket.PongMessage;
import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.TextMessage;
@ -139,8 +139,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
return; return;
} }
var tenantProfileConfiguration = tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultTenantProfileConfiguration(); var tenantProfileConfiguration = tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultTenantProfileConfiguration();
internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef, tenantProfileConfiguration.getWsMsgQueueLimitPerSession()));
internalSessionMap.put(internalSessionId, new SessionMetaData(session, sessionRef, tenantProfileConfiguration.getWsLimitQueuePerWsSession()));
externalSessionMap.put(externalSessionId, internalSessionId); externalSessionMap.put(externalSessionId, internalSessionId);
processInWebSocketService(sessionRef, SessionEvent.onEstablished()); processInWebSocketService(sessionRef, SessionEvent.onEstablished());
@ -298,14 +297,13 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
String externalId = sessionRef.getSessionId(); String externalId = sessionRef.getSessionId();
log.debug("[{}] Processing {}", externalId, msg); log.debug("[{}] Processing {}", externalId, msg);
String internalId = externalSessionMap.get(externalId); String internalId = externalSessionMap.get(externalId);
var tenantProfileConfiguration = tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultTenantProfileConfiguration();
if (internalId != null) { if (internalId != null) {
SessionMetaData sessionMd = internalSessionMap.get(internalId); SessionMetaData sessionMd = internalSessionMap.get(internalId);
if (sessionMd != null) { if (sessionMd != null) {
if (!StringUtils.isEmpty(tenantProfileConfiguration.getWsLimitUpdatesPerSession())) { var tenantProfileConfiguration = tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultTenantProfileConfiguration();
TbRateLimits rateLimits = perSessionUpdateLimits.computeIfAbsent(sessionRef.getSessionId(), sid -> new TbRateLimits(tenantProfileConfiguration.getWsLimitUpdatesPerSession())); if (StringUtils.isNotEmpty(tenantProfileConfiguration.getWsUpdatesPerSessionRateLimit())) {
// fixme: is this ok not to update rate limits config if it was change in profile?
TbRateLimits rateLimits = perSessionUpdateLimits.computeIfAbsent(sessionRef.getSessionId(), sid -> new TbRateLimits(tenantProfileConfiguration.getWsUpdatesPerSessionRateLimit()));
if (!rateLimits.tryConsume()) { if (!rateLimits.tryConsume()) {
if (blacklistedSessions.putIfAbsent(externalId, sessionRef) == null) { if (blacklistedSessions.putIfAbsent(externalId, sessionRef) == null) {
log.info("[{}][{}][{}] Failed to process session update. Max session updates limit reached" log.info("[{}][{}][{}] Failed to process session update. Max session updates limit reached"
@ -364,15 +362,15 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
var tenantProfileConfiguration = var tenantProfileConfiguration =
tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultTenantProfileConfiguration(); tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultTenantProfileConfiguration();
if(tenantProfileConfiguration == null) { if (tenantProfileConfiguration == null) {
return true; return true;
} }
String sessionId = session.getId(); String sessionId = session.getId();
if (tenantProfileConfiguration.getWsLimitMaxSessionsPerTenant() > 0) { if (tenantProfileConfiguration.getMaxWsSessionsPerTenant() > 0) {
Set<String> tenantSessions = tenantSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getTenantId(), id -> ConcurrentHashMap.newKeySet()); Set<String> tenantSessions = tenantSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getTenantId(), id -> ConcurrentHashMap.newKeySet());
synchronized (tenantSessions) { synchronized (tenantSessions) {
if (tenantSessions.size() < tenantProfileConfiguration.getWsLimitMaxSessionsPerTenant()) { if (tenantSessions.size() < tenantProfileConfiguration.getMaxWsSessionsPerTenant()) {
tenantSessions.add(sessionId); tenantSessions.add(sessionId);
} else { } else {
log.info("[{}][{}][{}] Failed to start session. Max tenant sessions limit reached" log.info("[{}][{}][{}] Failed to start session. Max tenant sessions limit reached"
@ -384,10 +382,10 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
} }
if (sessionRef.getSecurityCtx().isCustomerUser()) { if (sessionRef.getSecurityCtx().isCustomerUser()) {
if (tenantProfileConfiguration.getWsLimitMaxSessionsPerCustomer() > 0) { if (tenantProfileConfiguration.getMaxWsSessionsPerCustomer() > 0) {
Set<String> customerSessions = customerSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getCustomerId(), id -> ConcurrentHashMap.newKeySet()); Set<String> customerSessions = customerSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getCustomerId(), id -> ConcurrentHashMap.newKeySet());
synchronized (customerSessions) { synchronized (customerSessions) {
if (customerSessions.size() < tenantProfileConfiguration.getWsLimitMaxSessionsPerCustomer()) { if (customerSessions.size() < tenantProfileConfiguration.getMaxWsSessionsPerCustomer()) {
customerSessions.add(sessionId); customerSessions.add(sessionId);
} else { } else {
log.info("[{}][{}][{}] Failed to start session. Max customer sessions limit reached" log.info("[{}][{}][{}] Failed to start session. Max customer sessions limit reached"
@ -397,11 +395,11 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
} }
} }
} }
if (tenantProfileConfiguration.getWsLimitMaxSessionsPerRegularUser() > 0 if (tenantProfileConfiguration.getMaxWsSessionsPerRegularUser() > 0
&& UserPrincipal.Type.USER_NAME.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { && UserPrincipal.Type.USER_NAME.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) {
Set<String> regularUserSessions = regularUserSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet()); Set<String> regularUserSessions = regularUserSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet());
synchronized (regularUserSessions) { synchronized (regularUserSessions) {
if (regularUserSessions.size() < tenantProfileConfiguration.getWsLimitMaxSessionsPerRegularUser()) { if (regularUserSessions.size() < tenantProfileConfiguration.getMaxWsSessionsPerRegularUser()) {
regularUserSessions.add(sessionId); regularUserSessions.add(sessionId);
} else { } else {
log.info("[{}][{}][{}] Failed to start session. Max regular user sessions limit reached" log.info("[{}][{}][{}] Failed to start session. Max regular user sessions limit reached"
@ -411,11 +409,11 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
} }
} }
} }
if (tenantProfileConfiguration.getWsLimitMaxSessionsPerPublicUser() > 0 if (tenantProfileConfiguration.getMaxWsSessionsPerPublicUser() > 0
&& UserPrincipal.Type.PUBLIC_ID.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { && UserPrincipal.Type.PUBLIC_ID.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) {
Set<String> publicUserSessions = publicUserSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet()); Set<String> publicUserSessions = publicUserSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet());
synchronized (publicUserSessions) { synchronized (publicUserSessions) {
if (publicUserSessions.size() < tenantProfileConfiguration.getWsLimitMaxSessionsPerPublicUser()) { if (publicUserSessions.size() < tenantProfileConfiguration.getMaxWsSessionsPerPublicUser()) {
publicUserSessions.add(sessionId); publicUserSessions.add(sessionId);
} else { } else {
log.info("[{}][{}][{}] Failed to start session. Max public user sessions limit reached" log.info("[{}][{}][{}] Failed to start session. Max public user sessions limit reached"
@ -435,26 +433,26 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
String sessionId = session.getId(); String sessionId = session.getId();
perSessionUpdateLimits.remove(sessionRef.getSessionId()); perSessionUpdateLimits.remove(sessionRef.getSessionId());
blacklistedSessions.remove(sessionRef.getSessionId()); blacklistedSessions.remove(sessionRef.getSessionId());
if (tenantProfileConfiguration.getWsLimitMaxSessionsPerTenant() > 0) { if (tenantProfileConfiguration.getMaxWsSessionsPerTenant() > 0) {
Set<String> tenantSessions = tenantSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getTenantId(), id -> ConcurrentHashMap.newKeySet()); Set<String> tenantSessions = tenantSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getTenantId(), id -> ConcurrentHashMap.newKeySet());
synchronized (tenantSessions) { synchronized (tenantSessions) {
tenantSessions.remove(sessionId); tenantSessions.remove(sessionId);
} }
} }
if (sessionRef.getSecurityCtx().isCustomerUser()) { if (sessionRef.getSecurityCtx().isCustomerUser()) {
if (tenantProfileConfiguration.getWsLimitMaxSessionsPerCustomer() > 0) { if (tenantProfileConfiguration.getMaxWsSessionsPerCustomer() > 0) {
Set<String> customerSessions = customerSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getCustomerId(), id -> ConcurrentHashMap.newKeySet()); Set<String> customerSessions = customerSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getCustomerId(), id -> ConcurrentHashMap.newKeySet());
synchronized (customerSessions) { synchronized (customerSessions) {
customerSessions.remove(sessionId); customerSessions.remove(sessionId);
} }
} }
if (tenantProfileConfiguration.getWsLimitMaxSessionsPerRegularUser() > 0 && UserPrincipal.Type.USER_NAME.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { if (tenantProfileConfiguration.getMaxWsSessionsPerRegularUser() > 0 && UserPrincipal.Type.USER_NAME.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) {
Set<String> regularUserSessions = regularUserSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet()); Set<String> regularUserSessions = regularUserSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet());
synchronized (regularUserSessions) { synchronized (regularUserSessions) {
regularUserSessions.remove(sessionId); regularUserSessions.remove(sessionId);
} }
} }
if (tenantProfileConfiguration.getWsLimitMaxSessionsPerPublicUser() > 0 && UserPrincipal.Type.PUBLIC_ID.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { if (tenantProfileConfiguration.getMaxWsSessionsPerPublicUser() > 0 && UserPrincipal.Type.PUBLIC_ID.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) {
Set<String> publicUserSessions = publicUserSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet()); Set<String> publicUserSessions = publicUserSessionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet());
synchronized (publicUserSessions) { synchronized (publicUserSessions) {
publicUserSessions.remove(sessionId); publicUserSessions.remove(sessionId);

View File

@ -216,6 +216,9 @@ public class ThingsboardInstallService {
dataUpdateService.updateData("3.3.2"); dataUpdateService.updateData("3.3.2");
log.info("Updating system data..."); log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets(); systemDataLoaderService.updateSystemWidgets();
case "3.3.3":
log.info("Upgrading ThingsBoard from version 3.3.3 to 3.4 ...");
dataUpdateService.updateData("3.3.3");
break; break;
//TODO update CacheCleanupService on the next version upgrade //TODO update CacheCleanupService on the next version upgrade

View File

@ -25,7 +25,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNode; import org.thingsboard.rule.engine.flow.TbRuleChainInputNode;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration; import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.rule.engine.profile.TbDeviceProfileNode; import org.thingsboard.rule.engine.profile.TbDeviceProfileNode;
@ -56,12 +55,9 @@ import org.thingsboard.server.common.data.rule.RuleChainType;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.alarm.AlarmDao;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.model.sql.DeviceProfileEntity; import org.thingsboard.server.dao.model.sql.DeviceProfileEntity;
import org.thingsboard.server.dao.model.sql.RelationEntity;
import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.sql.device.DeviceProfileRepository; import org.thingsboard.server.dao.sql.device.DeviceProfileRepository;
@ -101,9 +97,6 @@ public class DefaultDataUpdateService implements DataUpdateService {
@Autowired @Autowired
private TimeseriesService tsService; private TimeseriesService tsService;
@Autowired
private AlarmService alarmService;
@Autowired @Autowired
private EntityService entityService; private EntityService entityService;
@ -113,9 +106,6 @@ public class DefaultDataUpdateService implements DataUpdateService {
@Autowired @Autowired
private DeviceProfileRepository deviceProfileRepository; private DeviceProfileRepository deviceProfileRepository;
@Autowired
private OAuth2Service oAuth2Service;
@Autowired @Autowired
private RateLimitsUpdater rateLimitsUpdater; private RateLimitsUpdater rateLimitsUpdater;
@ -140,12 +130,15 @@ public class DefaultDataUpdateService implements DataUpdateService {
tenantsAlarmsCustomerUpdater.updateEntities(null); tenantsAlarmsCustomerUpdater.updateEntities(null);
deviceProfileEntityDynamicConditionsUpdater.updateEntities(null); deviceProfileEntityDynamicConditionsUpdater.updateEntities(null);
updateOAuth2Params(); updateOAuth2Params();
rateLimitsUpdater.updateEntities(null);
break; break;
case "3.3.2": case "3.3.2":
log.info("Updating data from version 3.3.2 to 3.3.3 ..."); log.info("Updating data from version 3.3.2 to 3.3.3 ...");
updateNestedRuleChains(); updateNestedRuleChains();
break; break;
case "3.3.3":
log.info("Updating data from version 3.3.3 to 3.4 ...");
rateLimitsUpdater.updateEntities();
break;
default: default:
throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion);
} }

View File

@ -50,6 +50,10 @@ public abstract class PaginatedUpdater<I, D> {
} }
} }
public void updateEntities() {
updateEntities(null);
}
protected boolean forceReportTotal() { protected boolean forceReportTotal() {
return false; return false;
} }

View File

@ -15,53 +15,53 @@
*/ */
package org.thingsboard.server.service.install.update; package org.thingsboard.server.service.install.update;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantProfileService;
@Component @Component
class RateLimitsUpdater extends PaginatedUpdater<String, TenantProfile> { class RateLimitsUpdater extends PaginatedUpdater<String, TenantProfile> {
@Value("${server.rest.limits.tenant.enabled}") @Value("${server.rest.limits.tenant.enabled}")
boolean tenantServerRateLimitsEnabled; boolean tenantServerRestLimitsEnabled;
@Value("${server.rest.limits.customer.enabled}")
boolean customerServerRateLimitsEnabled;
@Value("${server.rest.limits.tenant.configuration}") @Value("${server.rest.limits.tenant.configuration}")
String tenantServerRateLimitsConfiguration; String tenantServerRestLimitsConfiguration;
@Value("${server.rest.limits.customer.enabled}")
boolean customerServerRestLimitsEnabled;
@Value("${server.rest.limits.customer.configuration}") @Value("${server.rest.limits.customer.configuration}")
String customerServerRateLimitsConfiguration; String customerServerRestLimitsConfiguration;
@Value("${server.ws.limits.max_sessions_per_tenant}") @Value("${server.ws.limits.max_sessions_per_tenant}")
private int wsLimitMaxSessionsPerTenant; private int maxWsSessionsPerTenant;
@Value("${server.ws.limits.max_sessions_per_customer}") @Value("${server.ws.limits.max_sessions_per_customer}")
private int wsLimitMaxSessionsPerCustomer; private int maxWsSessionsPerCustomer;
@Value("${server.ws.limits.max_sessions_per_regular_user}") @Value("${server.ws.limits.max_sessions_per_regular_user}")
private int wsLimitMaxSessionsPerRegularUser; private int maxWsSessionsPerRegularUser;
@Value("${server.ws.limits.max_sessions_per_public_user}") @Value("${server.ws.limits.max_sessions_per_public_user}")
private int wsLimitMaxSessionsPerPublicUser; private int maxWsSessionsPerPublicUser;
@Value("${server.ws.limits.max_queue_per_ws_session}") @Value("${server.ws.limits.max_queue_per_ws_session}")
private int wsLimitQueuePerWsSession; private int wsMsgQueueLimitPerSession;
@Value("${server.ws.limits.max_subscriptions_per_tenant}") @Value("${server.ws.limits.max_subscriptions_per_tenant}")
private long wsLimitMaxSubscriptionsPerTenant; private long maxWsSubscriptionsPerTenant;
@Value("${server.ws.limits.max_subscriptions_per_customer}") @Value("${server.ws.limits.max_subscriptions_per_customer}")
private long wsLimitMaxSubscriptionsPerCustomer; private long maxWsSubscriptionsPerCustomer;
@Value("${server.ws.limits.max_subscriptions_per_regular_user}") @Value("${server.ws.limits.max_subscriptions_per_regular_user}")
private long wsLimitMaxSubscriptionsPerRegularUser; private long maxWsSubscriptionsPerRegularUser;
@Value("${server.ws.limits.max_subscriptions_per_public_user}") @Value("${server.ws.limits.max_subscriptions_per_public_user}")
private long wsLimitMaxSubscriptionsPerPublicUser; private long maxWsSubscriptionsPerPublicUser;
@Value("${server.ws.limits.max_updates_per_session}") @Value("${server.ws.limits.max_updates_per_session}")
private String wsLimitUpdatesPerSession; private String wsUpdatesPerSessionRateLimit;
@Value("${cassandra.query.tenant_rate_limits.enabled}") @Value("${cassandra.query.tenant_rate_limits.enabled}")
private boolean cassandraLimitsIsEnabled; private boolean cassandraQueryTenantRateLimitsEnabled;
@Value("${cassandra.query.tenant_rate_limits.configuration}") @Value("${cassandra.query.tenant_rate_limits.configuration}")
private String cassandraTenantLimitsConfiguration; private String cassandraQueryTenantRateLimitsConfiguration;
@Value("${cassandra.query.tenant_rate_limits.print_tenant_names}")
private boolean printTenantNames;
@Autowired @Autowired
private TenantProfileService tenantProfileService; private TenantProfileService tenantProfileService;
@ -78,40 +78,38 @@ class RateLimitsUpdater extends PaginatedUpdater<String, TenantProfile> {
@Override @Override
protected PageData<TenantProfile> findEntities(String id, PageLink pageLink) { protected PageData<TenantProfile> findEntities(String id, PageLink pageLink) {
return tenantProfileService.findTenantProfiles(null, pageLink); return tenantProfileService.findTenantProfiles(TenantId.SYS_TENANT_ID, pageLink);
} }
@Override @Override
protected void updateEntity(TenantProfile entity) { protected void updateEntity(TenantProfile tenantProfile) {
var profileConfiguration = entity.getDefaultTenantProfileConfiguration(); var profileConfiguration = tenantProfile.getDefaultTenantProfileConfiguration();
if (profileConfiguration != null) {
if (tenantServerRateLimitsEnabled && StringUtils.isNotEmpty(tenantServerRateLimitsConfiguration)) {
profileConfiguration.setRateLimitsTenantConfiguration(tenantServerRateLimitsConfiguration);
}
if (customerServerRateLimitsEnabled && StringUtils.isNotEmpty(customerServerRateLimitsConfiguration)) {
profileConfiguration.setRateLimitsCustomerConfiguration(customerServerRateLimitsConfiguration);
}
profileConfiguration.setWsLimitMaxSessionsPerTenant(wsLimitMaxSessionsPerTenant); if (tenantServerRestLimitsEnabled && StringUtils.isNotEmpty(tenantServerRestLimitsConfiguration)) {
profileConfiguration.setWsLimitMaxSessionsPerCustomer(wsLimitMaxSessionsPerCustomer); profileConfiguration.setTenantServerRestLimitsConfiguration(tenantServerRestLimitsConfiguration);
profileConfiguration.setWsLimitMaxSessionsPerPublicUser(wsLimitMaxSessionsPerPublicUser);
profileConfiguration.setWsLimitMaxSessionsPerRegularUser(wsLimitMaxSessionsPerRegularUser);
profileConfiguration.setWsLimitMaxSubscriptionsPerTenant(wsLimitMaxSubscriptionsPerTenant);
profileConfiguration.setWsLimitMaxSubscriptionsPerCustomer(wsLimitMaxSubscriptionsPerCustomer);
profileConfiguration.setWsLimitMaxSubscriptionsPerPublicUser(wsLimitMaxSubscriptionsPerPublicUser);
profileConfiguration.setWsLimitMaxSubscriptionsPerRegularUser(wsLimitMaxSubscriptionsPerRegularUser);
profileConfiguration.setWsLimitQueuePerWsSession(wsLimitQueuePerWsSession);
if (StringUtils.isNotEmpty(wsLimitUpdatesPerSession)) {
profileConfiguration.setWsLimitUpdatesPerSession(wsLimitUpdatesPerSession);
}
if (cassandraLimitsIsEnabled) {
profileConfiguration.setCassandraTenantLimitsConfiguration(cassandraTenantLimitsConfiguration);
profileConfiguration.setPrintTenantNames(printTenantNames);
}
tenantProfileService.saveTenantProfile(null, entity);
} }
if (customerServerRestLimitsEnabled && StringUtils.isNotEmpty(customerServerRestLimitsConfiguration)) {
profileConfiguration.setCustomerServerRestLimitsConfiguration(customerServerRestLimitsConfiguration);
}
profileConfiguration.setMaxWsSessionsPerTenant(maxWsSessionsPerTenant);
profileConfiguration.setMaxWsSessionsPerCustomer(maxWsSessionsPerCustomer);
profileConfiguration.setMaxWsSessionsPerPublicUser(maxWsSessionsPerPublicUser);
profileConfiguration.setMaxWsSessionsPerRegularUser(maxWsSessionsPerRegularUser);
profileConfiguration.setMaxWsSubscriptionsPerTenant(maxWsSubscriptionsPerTenant);
profileConfiguration.setMaxWsSubscriptionsPerCustomer(maxWsSubscriptionsPerCustomer);
profileConfiguration.setMaxWsSubscriptionsPerPublicUser(maxWsSubscriptionsPerPublicUser);
profileConfiguration.setMaxWsSubscriptionsPerRegularUser(maxWsSubscriptionsPerRegularUser);
profileConfiguration.setWsMsgQueueLimitPerSession(wsMsgQueueLimitPerSession);
if (StringUtils.isNotEmpty(wsUpdatesPerSessionRateLimit)) {
profileConfiguration.setWsUpdatesPerSessionRateLimit(wsUpdatesPerSessionRateLimit);
}
if (cassandraQueryTenantRateLimitsEnabled && StringUtils.isNotEmpty(cassandraQueryTenantRateLimitsConfiguration)) {
profileConfiguration.setCassandraQueryTenantRateLimitsConfiguration(cassandraQueryTenantRateLimitsConfiguration);
}
tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, tenantProfile);
} }
} }

View File

@ -24,6 +24,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.CloseStatus;
@ -304,29 +305,29 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
private void processSessionClose(TelemetryWebSocketSessionRef sessionRef) { private void processSessionClose(TelemetryWebSocketSessionRef sessionRef) {
var tenantProfileConfiguration = (DefaultTenantProfileConfiguration) tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultTenantProfileConfiguration(); var tenantProfileConfiguration = (DefaultTenantProfileConfiguration) tenantProfileCache.get(sessionRef.getSecurityCtx().getTenantId()).getDefaultTenantProfileConfiguration();
if(tenantProfileConfiguration != null) { if (tenantProfileConfiguration != null) {
String sessionId = "[" + sessionRef.getSessionId() + "]"; String sessionId = "[" + sessionRef.getSessionId() + "]";
if (tenantProfileConfiguration.getWsLimitMaxSubscriptionsPerTenant() > 0) { if (tenantProfileConfiguration.getMaxWsSubscriptionsPerTenant() > 0) {
Set<String> tenantSubscriptions = tenantSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getTenantId(), id -> ConcurrentHashMap.newKeySet()); Set<String> tenantSubscriptions = tenantSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getTenantId(), id -> ConcurrentHashMap.newKeySet());
synchronized (tenantSubscriptions) { synchronized (tenantSubscriptions) {
tenantSubscriptions.removeIf(subId -> subId.startsWith(sessionId)); tenantSubscriptions.removeIf(subId -> subId.startsWith(sessionId));
} }
} }
if (sessionRef.getSecurityCtx().isCustomerUser()) { if (sessionRef.getSecurityCtx().isCustomerUser()) {
if (tenantProfileConfiguration.getWsLimitMaxSubscriptionsPerCustomer() > 0) { if (tenantProfileConfiguration.getMaxWsSubscriptionsPerCustomer() > 0) {
Set<String> customerSessions = customerSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getCustomerId(), id -> ConcurrentHashMap.newKeySet()); Set<String> customerSessions = customerSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getCustomerId(), id -> ConcurrentHashMap.newKeySet());
synchronized (customerSessions) { synchronized (customerSessions) {
customerSessions.removeIf(subId -> subId.startsWith(sessionId)); customerSessions.removeIf(subId -> subId.startsWith(sessionId));
} }
} }
if (tenantProfileConfiguration.getWsLimitMaxSubscriptionsPerRegularUser() > 0 && UserPrincipal.Type.USER_NAME.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { if (tenantProfileConfiguration.getMaxWsSubscriptionsPerRegularUser() > 0 && UserPrincipal.Type.USER_NAME.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) {
Set<String> regularUserSessions = regularUserSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet()); Set<String> regularUserSessions = regularUserSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet());
synchronized (regularUserSessions) { synchronized (regularUserSessions) {
regularUserSessions.removeIf(subId -> subId.startsWith(sessionId)); regularUserSessions.removeIf(subId -> subId.startsWith(sessionId));
} }
} }
if (tenantProfileConfiguration.getWsLimitMaxSubscriptionsPerPublicUser() > 0 && UserPrincipal.Type.PUBLIC_ID.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { if (tenantProfileConfiguration.getMaxWsSubscriptionsPerPublicUser() > 0 && UserPrincipal.Type.PUBLIC_ID.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) {
Set<String> publicUserSessions = publicUserSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet()); Set<String> publicUserSessions = publicUserSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet());
synchronized (publicUserSessions) { synchronized (publicUserSessions) {
publicUserSessions.removeIf(subId -> subId.startsWith(sessionId)); publicUserSessions.removeIf(subId -> subId.startsWith(sessionId));
@ -341,12 +342,12 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
String subId = "[" + sessionRef.getSessionId() + "]:[" + cmd.getCmdId() + "]"; String subId = "[" + sessionRef.getSessionId() + "]:[" + cmd.getCmdId() + "]";
try { try {
if (tenantProfileConfiguration.getWsLimitMaxSubscriptionsPerTenant() > 0) { if (tenantProfileConfiguration.getMaxWsSubscriptionsPerTenant() > 0) {
Set<String> tenantSubscriptions = tenantSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getTenantId(), id -> ConcurrentHashMap.newKeySet()); Set<String> tenantSubscriptions = tenantSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getTenantId(), id -> ConcurrentHashMap.newKeySet());
synchronized (tenantSubscriptions) { synchronized (tenantSubscriptions) {
if (cmd.isUnsubscribe()) { if (cmd.isUnsubscribe()) {
tenantSubscriptions.remove(subId); tenantSubscriptions.remove(subId);
} else if (tenantSubscriptions.size() < tenantProfileConfiguration.getWsLimitMaxSubscriptionsPerTenant()) { } else if (tenantSubscriptions.size() < tenantProfileConfiguration.getMaxWsSubscriptionsPerTenant()) {
tenantSubscriptions.add(subId); tenantSubscriptions.add(subId);
} else { } else {
log.info("[{}][{}][{}] Failed to start subscription. Max tenant subscriptions limit reached" log.info("[{}][{}][{}] Failed to start subscription. Max tenant subscriptions limit reached"
@ -358,12 +359,12 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
} }
if (sessionRef.getSecurityCtx().isCustomerUser()) { if (sessionRef.getSecurityCtx().isCustomerUser()) {
if (tenantProfileConfiguration.getWsLimitMaxSubscriptionsPerCustomer() > 0) { if (tenantProfileConfiguration.getMaxWsSubscriptionsPerCustomer() > 0) {
Set<String> customerSessions = customerSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getCustomerId(), id -> ConcurrentHashMap.newKeySet()); Set<String> customerSessions = customerSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getCustomerId(), id -> ConcurrentHashMap.newKeySet());
synchronized (customerSessions) { synchronized (customerSessions) {
if (cmd.isUnsubscribe()) { if (cmd.isUnsubscribe()) {
customerSessions.remove(subId); customerSessions.remove(subId);
} else if (customerSessions.size() < tenantProfileConfiguration.getWsLimitMaxSubscriptionsPerCustomer()) { } else if (customerSessions.size() < tenantProfileConfiguration.getMaxWsSubscriptionsPerCustomer()) {
customerSessions.add(subId); customerSessions.add(subId);
} else { } else {
log.info("[{}][{}][{}] Failed to start subscription. Max customer subscriptions limit reached" log.info("[{}][{}][{}] Failed to start subscription. Max customer subscriptions limit reached"
@ -373,10 +374,10 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
} }
} }
} }
if (tenantProfileConfiguration.getWsLimitMaxSubscriptionsPerRegularUser() > 0 && UserPrincipal.Type.USER_NAME.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { if (tenantProfileConfiguration.getMaxWsSubscriptionsPerRegularUser() > 0 && UserPrincipal.Type.USER_NAME.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) {
Set<String> regularUserSessions = regularUserSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet()); Set<String> regularUserSessions = regularUserSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet());
synchronized (regularUserSessions) { synchronized (regularUserSessions) {
if (regularUserSessions.size() < tenantProfileConfiguration.getWsLimitMaxSubscriptionsPerRegularUser()) { if (regularUserSessions.size() < tenantProfileConfiguration.getMaxWsSubscriptionsPerRegularUser()) {
regularUserSessions.add(subId); regularUserSessions.add(subId);
} else { } else {
log.info("[{}][{}][{}] Failed to start subscription. Max regular user subscriptions limit reached" log.info("[{}][{}][{}] Failed to start subscription. Max regular user subscriptions limit reached"
@ -386,10 +387,10 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
} }
} }
} }
if (tenantProfileConfiguration.getWsLimitMaxSubscriptionsPerPublicUser() > 0 && UserPrincipal.Type.PUBLIC_ID.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) { if (tenantProfileConfiguration.getMaxWsSubscriptionsPerPublicUser() > 0 && UserPrincipal.Type.PUBLIC_ID.equals(sessionRef.getSecurityCtx().getUserPrincipal().getType())) {
Set<String> publicUserSessions = publicUserSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet()); Set<String> publicUserSessions = publicUserSubscriptionsMap.computeIfAbsent(sessionRef.getSecurityCtx().getId(), id -> ConcurrentHashMap.newKeySet());
synchronized (publicUserSessions) { synchronized (publicUserSessions) {
if (publicUserSessions.size() < tenantProfileConfiguration.getWsLimitMaxSubscriptionsPerPublicUser()) { if (publicUserSessions.size() < tenantProfileConfiguration.getMaxWsSubscriptionsPerPublicUser()) {
publicUserSessions.add(subId); publicUserSessions.add(subId);
} else { } else {
log.info("[{}][{}][{}] Failed to start subscription. Max public user subscriptions limit reached" log.info("[{}][{}][{}] Failed to start subscription. Max public user subscriptions limit reached"

View File

@ -83,7 +83,7 @@ public class TenantProfile extends SearchTextBased<TenantProfileId> implements H
@ApiModelProperty(position = 1, value = "JSON object with the tenant profile Id. " + @ApiModelProperty(position = 1, value = "JSON object with the tenant profile Id. " +
"Specify this field to update the tenant profile. " + "Specify this field to update the tenant profile. " +
"Referencing non-existing tenant profile Id will cause error. " + "Referencing non-existing tenant profile Id will cause error. " +
"Omit this field to create new tenant profile." ) "Omit this field to create new tenant profile.")
@Override @Override
public TenantProfileId getId() { public TenantProfileId getId() {
return super.getId(); return super.getId();
@ -133,6 +133,7 @@ public class TenantProfile extends SearchTextBased<TenantProfileId> implements H
public TenantProfileData createDefaultTenantProfileData() { public TenantProfileData createDefaultTenantProfileData() {
TenantProfileData tpd = new TenantProfileData(); TenantProfileData tpd = new TenantProfileData();
tpd.setConfiguration(new DefaultTenantProfileConfiguration()); tpd.setConfiguration(new DefaultTenantProfileConfiguration());
this.profileData = tpd;
return tpd; return tpd;
} }
@ -147,11 +148,7 @@ public class TenantProfile extends SearchTextBased<TenantProfileId> implements H
@JsonIgnore @JsonIgnore
public DefaultTenantProfileConfiguration getDefaultTenantProfileConfiguration() { public DefaultTenantProfileConfiguration getDefaultTenantProfileConfiguration() {
if(getProfileData().getConfiguration() != null && return getProfileConfiguration().orElse(null);
getProfileData().getConfiguration().getType().equals(TenantProfileType.DEFAULT)) {
return (DefaultTenantProfileConfiguration) this.profileData.getConfiguration();
}
return null;
} }
} }

View File

@ -46,22 +46,21 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
private String transportDeviceTelemetryMsgRateLimit; private String transportDeviceTelemetryMsgRateLimit;
private String transportDeviceTelemetryDataPointsRateLimit; private String transportDeviceTelemetryDataPointsRateLimit;
private String rateLimitsTenantConfiguration; private String tenantServerRestLimitsConfiguration;
private String rateLimitsCustomerConfiguration; private String customerServerRestLimitsConfiguration;
private int wsLimitMaxSessionsPerTenant; private int maxWsSessionsPerTenant;
private int wsLimitMaxSessionsPerCustomer; private int maxWsSessionsPerCustomer;
private int wsLimitMaxSessionsPerRegularUser; private int maxWsSessionsPerRegularUser;
private int wsLimitMaxSessionsPerPublicUser; private int maxWsSessionsPerPublicUser;
private int wsLimitQueuePerWsSession; private int wsMsgQueueLimitPerSession;
private long wsLimitMaxSubscriptionsPerTenant; private long maxWsSubscriptionsPerTenant;
private long wsLimitMaxSubscriptionsPerCustomer; private long maxWsSubscriptionsPerCustomer;
private long wsLimitMaxSubscriptionsPerRegularUser; private long maxWsSubscriptionsPerRegularUser;
private long wsLimitMaxSubscriptionsPerPublicUser; private long maxWsSubscriptionsPerPublicUser;
private String wsLimitUpdatesPerSession; private String wsUpdatesPerSessionRateLimit;
private String cassandraTenantLimitsConfiguration; private String cassandraQueryTenantRateLimitsConfiguration;
private boolean printTenantNames;
private long maxTransportMessages; private long maxTransportMessages;
private long maxTransportDataPoints; private long maxTransportDataPoints;

View File

@ -29,11 +29,10 @@ import java.time.Duration;
public class TbRateLimits { public class TbRateLimits {
private final LocalBucket bucket; private final LocalBucket bucket;
@Getter @Getter
private final String currentConfig; private final String configuration;
public TbRateLimits(String limitsConfiguration) { public TbRateLimits(String limitsConfiguration) {
LocalBucketBuilder builder = Bucket4j.builder(); LocalBucketBuilder builder = Bucket4j.builder();
currentConfig = limitsConfiguration;
boolean initialized = false; boolean initialized = false;
for (String limitSrc : limitsConfiguration.split(",")) { for (String limitSrc : limitsConfiguration.split(",")) {
long capacity = Long.parseLong(limitSrc.split(":")[0]); long capacity = Long.parseLong(limitSrc.split(":")[0]);
@ -46,8 +45,7 @@ public class TbRateLimits {
} else { } else {
throw new IllegalArgumentException("Failed to parse rate limits configuration: " + limitsConfiguration); throw new IllegalArgumentException("Failed to parse rate limits configuration: " + limitsConfiguration);
} }
this.configuration = limitsConfiguration;
} }
public boolean tryConsume() { public boolean tryConsume() {

View File

@ -22,9 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.common.stats.DefaultCounter; import org.thingsboard.server.common.stats.DefaultCounter;
import org.thingsboard.server.common.stats.StatsCounter; import org.thingsboard.server.common.stats.StatsCounter;
import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsFactory;
@ -33,7 +31,6 @@ import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor; import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor;
import org.thingsboard.server.dao.util.AsyncTaskContext; import org.thingsboard.server.dao.util.AsyncTaskContext;
import org.thingsboard.server.dao.util.NoSqlAnyDao; import org.thingsboard.server.dao.util.NoSqlAnyDao;
import org.thingsboard.server.dao.util.TenantRateLimitException;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@ -45,13 +42,6 @@ import javax.annotation.PreDestroy;
@NoSqlAnyDao @NoSqlAnyDao
public class CassandraBufferedRateReadExecutor extends AbstractBufferedRateExecutor<CassandraStatementTask, TbResultSetFuture, TbResultSet> { public class CassandraBufferedRateReadExecutor extends AbstractBufferedRateExecutor<CassandraStatementTask, TbResultSetFuture, TbResultSet> {
@Autowired
private EntityService entityService;
@Autowired
private TbTenantProfileCache tenantProfileCache;
private Map<TenantId, String> tenantNamesCache = new HashMap<>();
static final String BUFFER_NAME = "Read"; static final String BUFFER_NAME = "Read";
public CassandraBufferedRateReadExecutor( public CassandraBufferedRateReadExecutor(
@ -64,9 +54,10 @@ public class CassandraBufferedRateReadExecutor extends AbstractBufferedRateExecu
@Value("${cassandra.query.tenant_rate_limits.print_tenant_names}") boolean printTenantNames, @Value("${cassandra.query.tenant_rate_limits.print_tenant_names}") boolean printTenantNames,
@Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq, @Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq,
@Autowired StatsFactory statsFactory, @Autowired StatsFactory statsFactory,
@Autowired EntityService entityService) { @Autowired EntityService entityService,
@Autowired TbTenantProfileCache tenantProfileCache) {
super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, printQueriesFreq, statsFactory, super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, printQueriesFreq, statsFactory,
entityService, printTenantNames); entityService, tenantProfileCache, printTenantNames);
} }
@Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}") @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}")
@ -104,24 +95,4 @@ public class CassandraBufferedRateReadExecutor extends AbstractBufferedRateExecu
); );
} }
@Override
protected boolean checkRateLimits(CassandraStatementTask task, SettableFuture<TbResultSet> future) {
var tenantProfileConfiguration = tenantProfileCache.get(task.getTenantId()).getDefaultTenantProfileConfiguration();
if (StringUtils.isNotEmpty(tenantProfileConfiguration.getCassandraTenantLimitsConfiguration())) {
if (task.getTenantId() == null) {
log.info("Invalid task received: {}", task);
} else if (!task.getTenantId().isNullUid()) {
TbRateLimits rateLimits = perTenantLimits.computeIfAbsent(
task.getTenantId(), id -> new TbRateLimits(tenantProfileConfiguration.getCassandraTenantLimitsConfiguration())
);
if (!rateLimits.tryConsume()) {
stats.incrementRateLimitedTenant(task.getTenantId());
stats.getTotalRateLimited().increment();
future.setException(new TenantRateLimitException());
return true;
}
}
}
return false;
}
} }

View File

@ -24,6 +24,7 @@ import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor; import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor;
import org.thingsboard.server.dao.util.AsyncTaskContext; import org.thingsboard.server.dao.util.AsyncTaskContext;
import org.thingsboard.server.dao.util.NoSqlAnyDao; import org.thingsboard.server.dao.util.NoSqlAnyDao;
@ -47,14 +48,13 @@ public class CassandraBufferedRateWriteExecutor extends AbstractBufferedRateExec
@Value("${cassandra.query.dispatcher_threads:2}") int dispatcherThreads, @Value("${cassandra.query.dispatcher_threads:2}") int dispatcherThreads,
@Value("${cassandra.query.callback_threads:4}") int callbackThreads, @Value("${cassandra.query.callback_threads:4}") int callbackThreads,
@Value("${cassandra.query.poll_ms:50}") long pollMs, @Value("${cassandra.query.poll_ms:50}") long pollMs,
@Value("${cassandra.query.tenant_rate_limits.enabled}") boolean tenantRateLimitsEnabled,
@Value("${cassandra.query.tenant_rate_limits.configuration}") String tenantRateLimitsConfiguration,
@Value("${cassandra.query.tenant_rate_limits.print_tenant_names}") boolean printTenantNames, @Value("${cassandra.query.tenant_rate_limits.print_tenant_names}") boolean printTenantNames,
@Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq, @Value("${cassandra.query.print_queries_freq:0}") int printQueriesFreq,
@Autowired StatsFactory statsFactory, @Autowired StatsFactory statsFactory,
@Autowired EntityService entityService) { @Autowired EntityService entityService,
super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, tenantRateLimitsEnabled, tenantRateLimitsConfiguration, printQueriesFreq, statsFactory, @Autowired TbTenantProfileCache tenantProfileCache) {
entityService, printTenantNames); super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs, printQueriesFreq, statsFactory,
entityService, tenantProfileCache, printTenantNames);
} }
@Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}") @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}")

View File

@ -30,6 +30,7 @@ import com.google.common.util.concurrent.SettableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.common.stats.DefaultCounter; import org.thingsboard.server.common.stats.DefaultCounter;
@ -38,6 +39,7 @@ import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsType; import org.thingsboard.server.common.stats.StatsType;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.nosql.CassandraStatementTask; import org.thingsboard.server.dao.nosql.CassandraStatementTask;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.HashMap; import java.util.HashMap;
@ -71,22 +73,22 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
private final ScheduledExecutorService timeoutExecutor; private final ScheduledExecutorService timeoutExecutor;
private final int concurrencyLimit; private final int concurrencyLimit;
private final int printQueriesFreq; private final int printQueriesFreq;
protected final ConcurrentMap<TenantId, TbRateLimits> perTenantLimits = new ConcurrentHashMap<>(); private final ConcurrentMap<TenantId, TbRateLimits> perTenantLimits = new ConcurrentHashMap<>();
private final AtomicInteger printQueriesIdx = new AtomicInteger(0); private final AtomicInteger printQueriesIdx = new AtomicInteger(0);
protected final AtomicInteger concurrencyLevel; protected final AtomicInteger concurrencyLevel;
protected final BufferedRateExecutorStats stats; protected final BufferedRateExecutorStats stats;
private final EntityService entityService; private final EntityService entityService;
private final Map<TenantId, String> tenantNamesCache = new HashMap<>(); private final TbTenantProfileCache tenantProfileCache;
private final boolean printTenantNames; private final boolean printTenantNames;
private final Map<TenantId, String> tenantNamesCache = new HashMap<>();
public AbstractBufferedRateExecutor(int queueLimit, int concurrencyLimit, long maxWaitTime, int dispatcherThreads, public AbstractBufferedRateExecutor(int queueLimit, int concurrencyLimit, long maxWaitTime, int dispatcherThreads,
int callbackThreads, long pollMs, int printQueriesFreq, StatsFactory statsFactory, int callbackThreads, long pollMs, int printQueriesFreq, StatsFactory statsFactory,
EntityService entityService, boolean printTenantNames) { EntityService entityService, TbTenantProfileCache tenantProfileCache, boolean printTenantNames) {
this.maxWaitTime = maxWaitTime; this.maxWaitTime = maxWaitTime;
this.pollMs = pollMs; this.pollMs = pollMs;
this.concurrencyLimit = concurrencyLimit; this.concurrencyLimit = concurrencyLimit;
@ -100,6 +102,7 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
this.concurrencyLevel = statsFactory.createGauge(concurrencyLevelKey, new AtomicInteger(0)); this.concurrencyLevel = statsFactory.createGauge(concurrencyLevelKey, new AtomicInteger(0));
this.entityService = entityService; this.entityService = entityService;
this.tenantProfileCache = tenantProfileCache;
this.printTenantNames = printTenantNames; this.printTenantNames = printTenantNames;
for (int i = 0; i < dispatcherThreads; i++) { for (int i = 0; i < dispatcherThreads; i++) {
@ -107,13 +110,28 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
} }
} }
protected abstract boolean checkRateLimits(T task, SettableFuture<V> future);
@Override @Override
public F submit(T task) { public F submit(T task) {
SettableFuture<V> settableFuture = create(); SettableFuture<V> settableFuture = create();
F result = wrap(task, settableFuture); F result = wrap(task, settableFuture);
boolean perTenantLimitReached = checkRateLimits(task, settableFuture);
boolean perTenantLimitReached = false;
var tenantProfileConfiguration = tenantProfileCache.get(task.getTenantId()).getDefaultTenantProfileConfiguration();
if (StringUtils.isNotEmpty(tenantProfileConfiguration.getCassandraQueryTenantRateLimitsConfiguration())) {
if (task.getTenantId() == null) {
log.info("Invalid task received: {}", task);
} else if (!task.getTenantId().isNullUid()) {
TbRateLimits rateLimits = perTenantLimits.computeIfAbsent(
task.getTenantId(), id -> new TbRateLimits(tenantProfileConfiguration.getCassandraQueryTenantRateLimitsConfiguration())
);
if (!rateLimits.tryConsume()) {
stats.incrementRateLimitedTenant(task.getTenantId());
stats.getTotalRateLimited().increment();
settableFuture.setException(new TenantRateLimitException());
perTenantLimitReached = true;
}
}
}
if (!perTenantLimitReached) { if (!perTenantLimitReached) {
try { try {