committed by
GitHub
18 changed files with 339 additions and 219 deletions
@ -1,37 +0,0 @@ |
|||||
/** |
|
||||
* Copyright © 2016-2023 The Thingsboard Authors |
|
||||
* |
|
||||
* 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 org.thingsboard.server.service.apiusage.limits; |
|
||||
|
|
||||
import lombok.RequiredArgsConstructor; |
|
||||
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; |
|
||||
|
|
||||
import java.util.function.Function; |
|
||||
|
|
||||
@RequiredArgsConstructor |
|
||||
public enum LimitedApi { |
|
||||
|
|
||||
ENTITY_EXPORT(DefaultTenantProfileConfiguration::getTenantEntityExportRateLimit), |
|
||||
ENTITY_IMPORT(DefaultTenantProfileConfiguration::getTenantEntityImportRateLimit), |
|
||||
NOTIFICATION_REQUESTS(DefaultTenantProfileConfiguration::getTenantNotificationRequestsRateLimit), |
|
||||
NOTIFICATION_REQUESTS_PER_RULE(DefaultTenantProfileConfiguration::getTenantNotificationRequestsPerRuleRateLimit); |
|
||||
|
|
||||
private final Function<DefaultTenantProfileConfiguration, String> configExtractor; |
|
||||
|
|
||||
public String getLimitConfig(DefaultTenantProfileConfiguration profileConfiguration) { |
|
||||
return configExtractor.apply(profileConfiguration); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -0,0 +1,110 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2023 The Thingsboard Authors |
||||
|
* |
||||
|
* 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 org.thingsboard.server.service.limits; |
||||
|
|
||||
|
import org.junit.Before; |
||||
|
import org.junit.Test; |
||||
|
import org.junit.runner.RunWith; |
||||
|
import org.mockito.Mockito; |
||||
|
import org.mockito.junit.MockitoJUnitRunner; |
||||
|
import org.thingsboard.server.common.data.TenantProfile; |
||||
|
import org.thingsboard.server.common.data.id.CustomerId; |
||||
|
import org.thingsboard.server.common.data.id.NotificationRuleId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; |
||||
|
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; |
||||
|
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; |
||||
|
import org.thingsboard.server.dao.util.limits.DefaultRateLimitService; |
||||
|
import org.thingsboard.server.dao.util.limits.LimitedApi; |
||||
|
import org.thingsboard.server.dao.util.limits.RateLimitService; |
||||
|
|
||||
|
import java.util.List; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
import static org.junit.Assert.assertFalse; |
||||
|
import static org.junit.Assert.assertTrue; |
||||
|
import static org.mockito.ArgumentMatchers.eq; |
||||
|
import static org.mockito.Mockito.reset; |
||||
|
import static org.mockito.Mockito.when; |
||||
|
|
||||
|
@RunWith(MockitoJUnitRunner.class) |
||||
|
public class RateLimitServiceTest { |
||||
|
|
||||
|
private RateLimitService rateLimitService; |
||||
|
private TbTenantProfileCache tenantProfileCache; |
||||
|
private TenantId tenantId; |
||||
|
|
||||
|
@Before |
||||
|
public void beforeEach() { |
||||
|
tenantProfileCache = Mockito.mock(TbTenantProfileCache.class); |
||||
|
rateLimitService = new DefaultRateLimitService(tenantProfileCache, 60, 100); |
||||
|
tenantId = new TenantId(UUID.randomUUID()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
public void testRateLimits() { |
||||
|
int max = 2; |
||||
|
String rateLimit = max + ":600"; |
||||
|
DefaultTenantProfileConfiguration profileConfiguration = new DefaultTenantProfileConfiguration(); |
||||
|
profileConfiguration.setTenantEntityExportRateLimit(rateLimit); |
||||
|
profileConfiguration.setTenantEntityImportRateLimit(rateLimit); |
||||
|
profileConfiguration.setTenantNotificationRequestsRateLimit(rateLimit); |
||||
|
profileConfiguration.setTenantNotificationRequestsPerRuleRateLimit(rateLimit); |
||||
|
profileConfiguration.setTenantServerRestLimitsConfiguration(rateLimit); |
||||
|
profileConfiguration.setCustomerServerRestLimitsConfiguration(rateLimit); |
||||
|
profileConfiguration.setWsUpdatesPerSessionRateLimit(rateLimit); |
||||
|
profileConfiguration.setCassandraQueryTenantRateLimitsConfiguration(rateLimit); |
||||
|
updateTenantProfileConfiguration(profileConfiguration); |
||||
|
|
||||
|
for (LimitedApi limitedApi : List.of( |
||||
|
LimitedApi.ENTITY_EXPORT, |
||||
|
LimitedApi.ENTITY_IMPORT, |
||||
|
LimitedApi.NOTIFICATION_REQUESTS, |
||||
|
LimitedApi.REST_REQUESTS, |
||||
|
LimitedApi.CASSANDRA_QUERIES |
||||
|
)) { |
||||
|
testRateLimits(limitedApi, max, tenantId); |
||||
|
} |
||||
|
|
||||
|
CustomerId customerId = new CustomerId(UUID.randomUUID()); |
||||
|
testRateLimits(LimitedApi.REST_REQUESTS, max, customerId); |
||||
|
|
||||
|
NotificationRuleId notificationRuleId = new NotificationRuleId(UUID.randomUUID()); |
||||
|
testRateLimits(LimitedApi.NOTIFICATION_REQUESTS_PER_RULE, max, notificationRuleId); |
||||
|
|
||||
|
String wsSessionId = UUID.randomUUID().toString(); |
||||
|
testRateLimits(LimitedApi.WS_UPDATES_PER_SESSION, max, wsSessionId); |
||||
|
} |
||||
|
|
||||
|
private void testRateLimits(LimitedApi limitedApi, int max, Object level) { |
||||
|
for (int i = 1; i <= max; i++) { |
||||
|
boolean success = rateLimitService.checkRateLimit(limitedApi, tenantId, level); |
||||
|
assertTrue(success); |
||||
|
} |
||||
|
boolean success = rateLimitService.checkRateLimit(limitedApi, tenantId, level); |
||||
|
assertFalse(success); |
||||
|
} |
||||
|
|
||||
|
private void updateTenantProfileConfiguration(DefaultTenantProfileConfiguration profileConfiguration) { |
||||
|
reset(tenantProfileCache); |
||||
|
TenantProfile tenantProfile = new TenantProfile(); |
||||
|
TenantProfileData profileData = new TenantProfileData(); |
||||
|
profileData.setConfiguration(profileConfiguration); |
||||
|
tenantProfile.setProfileData(profileData); |
||||
|
when(tenantProfileCache.get(eq(tenantId))).thenReturn(tenantProfile); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2023 The Thingsboard Authors |
||||
|
* |
||||
|
* 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 org.thingsboard.server.common.data.exception; |
||||
|
|
||||
|
import lombok.Getter; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
|
||||
|
public class TenantProfileNotFoundException extends RuntimeException { |
||||
|
|
||||
|
@Getter |
||||
|
private final TenantId tenantId; |
||||
|
|
||||
|
public TenantProfileNotFoundException(TenantId tenantId) { |
||||
|
super("Profile for tenant with id " + tenantId + " not found"); |
||||
|
this.tenantId = tenantId; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2023 The Thingsboard Authors |
||||
|
* |
||||
|
* 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 org.thingsboard.server.dao.util.limits; |
||||
|
|
||||
|
import lombok.Getter; |
||||
|
import org.thingsboard.server.common.data.EntityType; |
||||
|
import org.thingsboard.server.common.data.id.EntityId; |
||||
|
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; |
||||
|
|
||||
|
import java.util.function.BiFunction; |
||||
|
import java.util.function.Function; |
||||
|
|
||||
|
public enum LimitedApi { |
||||
|
|
||||
|
ENTITY_EXPORT(DefaultTenantProfileConfiguration::getTenantEntityExportRateLimit), |
||||
|
ENTITY_IMPORT(DefaultTenantProfileConfiguration::getTenantEntityImportRateLimit), |
||||
|
NOTIFICATION_REQUESTS(DefaultTenantProfileConfiguration::getTenantNotificationRequestsRateLimit), |
||||
|
NOTIFICATION_REQUESTS_PER_RULE(DefaultTenantProfileConfiguration::getTenantNotificationRequestsPerRuleRateLimit), |
||||
|
REST_REQUESTS((profileConfiguration, level) -> ((EntityId) level).getEntityType() == EntityType.TENANT ? |
||||
|
profileConfiguration.getTenantServerRestLimitsConfiguration() : |
||||
|
profileConfiguration.getCustomerServerRestLimitsConfiguration()), |
||||
|
WS_UPDATES_PER_SESSION(DefaultTenantProfileConfiguration::getWsUpdatesPerSessionRateLimit), |
||||
|
CASSANDRA_QUERIES(DefaultTenantProfileConfiguration::getCassandraQueryTenantRateLimitsConfiguration), |
||||
|
PASSWORD_RESET(true), |
||||
|
TWO_FA_VERIFICATION_CODE_SEND(true), |
||||
|
TWO_FA_VERIFICATION_CODE_CHECK(true); |
||||
|
|
||||
|
private final BiFunction<DefaultTenantProfileConfiguration, Object, String> configExtractor; |
||||
|
@Getter |
||||
|
private final boolean refillRateLimitIntervally; |
||||
|
|
||||
|
LimitedApi(Function<DefaultTenantProfileConfiguration, String> configExtractor) { |
||||
|
this((profileConfiguration, level) -> configExtractor.apply(profileConfiguration)); |
||||
|
} |
||||
|
|
||||
|
LimitedApi(BiFunction<DefaultTenantProfileConfiguration, Object, String> configExtractor) { |
||||
|
this.configExtractor = configExtractor; |
||||
|
this.refillRateLimitIntervally = false; |
||||
|
} |
||||
|
|
||||
|
LimitedApi(boolean refillRateLimitIntervally) { |
||||
|
this.configExtractor = null; |
||||
|
this.refillRateLimitIntervally = refillRateLimitIntervally; |
||||
|
} |
||||
|
|
||||
|
public String getLimitConfig(DefaultTenantProfileConfiguration profileConfiguration, Object level) { |
||||
|
if (configExtractor != null) { |
||||
|
return configExtractor.apply(profileConfiguration, level); |
||||
|
} else { |
||||
|
throw new IllegalArgumentException("No tenant profile config for " + name() + " rate limits"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue