@ -51,12 +51,15 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec ;
import org.thingsboard.server.common.data.id.AlarmId ;
import org.thingsboard.server.common.data.id.DeviceId ;
import org.thingsboard.server.common.data.id.TenantId ;
import org.thingsboard.server.common.data.limit.LimitedApi ;
import org.thingsboard.server.common.data.notification.Notification ;
import org.thingsboard.server.common.data.notification.NotificationDeliveryMethod ;
import org.thingsboard.server.common.data.notification.NotificationRequest ;
import org.thingsboard.server.common.data.notification.NotificationRequestInfo ;
import org.thingsboard.server.common.data.notification.NotificationType ;
import org.thingsboard.server.common.data.notification.info.AlarmNotificationInfo ;
import org.thingsboard.server.common.data.notification.info.RateLimitsNotificationInfo ;
import org.thingsboard.server.common.data.notification.rule.DefaultNotificationRuleRecipientsConfig ;
import org.thingsboard.server.common.data.notification.rule.EscalatedNotificationRuleRecipientsConfig ;
import org.thingsboard.server.common.data.notification.rule.NotificationRule ;
@ -70,7 +73,10 @@ import org.thingsboard.server.common.data.notification.rule.trigger.EntitiesLimi
import org.thingsboard.server.common.data.notification.rule.trigger.EntityActionNotificationRuleTriggerConfig ;
import org.thingsboard.server.common.data.notification.rule.trigger.NewPlatformVersionNotificationRuleTriggerConfig ;
import org.thingsboard.server.common.data.notification.rule.trigger.NotificationRuleTriggerType ;
import org.thingsboard.server.common.data.notification.rule.trigger.RateLimitsNotificationRuleTriggerConfig ;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget ;
import org.thingsboard.server.common.data.notification.targets.platform.AffectedTenantAdministratorsFilter ;
import org.thingsboard.server.common.data.notification.targets.platform.SystemAdministratorsFilter ;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate ;
import org.thingsboard.server.common.data.page.PageData ;
import org.thingsboard.server.common.data.page.PageLink ;
@ -82,12 +88,13 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.security.Authority ;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor ;
import org.thingsboard.server.common.msg.notification.trigger.NewPlatformVersionTrigger ;
import org.thingsboard.server.common.msg.notification.trigger.RateLimitsTrigger ;
import org.thingsboard.server.dao.notification.DefaultNotifications ;
import org.thingsboard.server.dao.notification.NotificationRequestService ;
import org.thingsboard.server.dao.rule.RuleChainService ;
import org.thingsboard.server.dao.service.DaoSqlTest ;
import org.thingsboard.server.dao.util.limits.LimitedApi ;
import org.thingsboard.server.dao.util.limits.RateLimitService ;
import org.thingsboard.server.service.notification.rule.DefaultNotificationRuleProcessor ;
import org.thingsboard.server.service.notification.rule.cache.DefaultNotificationRulesCache ;
import org.thingsboard.server.service.state.DeviceStateService ;
import org.thingsboard.server.service.telemetry.AlarmSubscriptionService ;
@ -103,6 +110,7 @@ import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit ;
import java.util.function.BiConsumer ;
import java.util.function.Consumer ;
import java.util.stream.Collectors ;
import static org.assertj.core.api.Assertions.assertThat ;
import static org.assertj.core.api.Assertions.offset ;
@ -116,7 +124,8 @@ import static org.thingsboard.server.common.data.notification.rule.trigger.Devic
@DaoSqlTest
@TestPropertySource ( properties = {
"transport.http.enabled=true"
"transport.http.enabled=true" ,
"notification_system.rules.trigger_types_configs.RATE_LIMITS.deduplication_duration=10000"
} )
public class NotificationRuleApiTest extends AbstractNotificationApiTest {
@ -400,6 +409,60 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
} ) ;
}
@Test
public void testNotificationRuleProcessing_exceededRateLimits ( ) throws Exception {
loginSysAdmin ( ) ;
NotificationTarget sysadmins = createNotificationTarget ( new SystemAdministratorsFilter ( ) ) ;
NotificationTarget affectedTenantAdmins = createNotificationTarget ( new AffectedTenantAdministratorsFilter ( ) ) ;
defaultNotifications . create ( TenantId . SYS_TENANT_ID , DefaultNotifications . exceededRateLimitsForSysadmin , sysadmins . getId ( ) ) ;
defaultNotifications . create ( TenantId . SYS_TENANT_ID , DefaultNotifications . exceededRateLimits , affectedTenantAdmins . getId ( ) ) ;
defaultNotifications . create ( TenantId . SYS_TENANT_ID , DefaultNotifications . exceededPerEntityRateLimits , affectedTenantAdmins . getId ( ) ) ;
notificationRulesCache . evict ( TenantId . SYS_TENANT_ID ) ;
int n = 10 ;
updateDefaultTenantProfile ( profileConfiguration - > {
profileConfiguration . setTenantEntityExportRateLimit ( n + ":600" ) ;
profileConfiguration . setCustomerServerRestLimitsConfiguration ( n + ":600" ) ;
profileConfiguration . setTenantNotificationRequestsPerRuleRateLimit ( n + ":600" ) ;
profileConfiguration . setTransportDeviceTelemetryMsgRateLimit ( n + ":600" ) ;
} ) ;
loginTenantAdmin ( ) ;
NotificationRule rule = createNotificationRule ( AlarmCommentNotificationRuleTriggerConfig . builder ( )
. alarmTypes ( Set . of ( "weklfjkwefa" ) )
. build ( ) , "Test" , "Test" , createNotificationTarget ( tenantAdminUserId ) . getId ( ) ) ;
for ( int i = 1 ; i < = n * 2 ; i + + ) {
rateLimitService . checkRateLimit ( LimitedApi . ENTITY_EXPORT , tenantId ) ;
rateLimitService . checkRateLimit ( LimitedApi . REST_REQUESTS_PER_CUSTOMER , tenantId , customerId ) ;
rateLimitService . checkRateLimit ( LimitedApi . NOTIFICATION_REQUESTS_PER_RULE , tenantId , rule . getId ( ) ) ;
}
loginTenantAdmin ( ) ;
List < Notification > notifications = await ( ) . atMost ( 30 , TimeUnit . SECONDS )
. until ( ( ) - > getMyNotifications ( true , 10 ) , list - > list . size ( ) = = 3 ) ;
assertThat ( notifications ) . allSatisfy ( notification - > {
assertThat ( notification . getSubject ( ) ) . isEqualTo ( "Rate limits exceeded" ) ;
} ) ;
assertThat ( notifications ) . anySatisfy ( notification - > {
assertThat ( notification . getText ( ) ) . isEqualTo ( "Rate limits for entity version creation exceeded" ) ;
} ) ;
assertThat ( notifications ) . anySatisfy ( notification - > {
assertThat ( notification . getText ( ) ) . isEqualTo ( "Rate limits for REST API requests per customer " +
"exceeded for 'Customer'" ) ;
} ) ;
assertThat ( notifications ) . anySatisfy ( notification - > {
assertThat ( notification . getText ( ) ) . isEqualTo ( "Rate limits for notification requests " +
"per rule exceeded for '" + rule . getName ( ) + "'" ) ;
} ) ;
loginSysAdmin ( ) ;
notifications = await ( ) . atMost ( 30 , TimeUnit . SECONDS )
. until ( ( ) - > getMyNotifications ( true , 10 ) , list - > list . size ( ) = = 1 ) ;
assertThat ( notifications ) . allSatisfy ( notification - > {
assertThat ( notification . getSubject ( ) ) . isEqualTo ( "Rate limits exceeded for tenant " + TEST_TENANT_NAME ) ;
} ) ;
assertThat ( notifications . get ( 0 ) . getText ( ) ) . isEqualTo ( "Rate limits for entity version creation exceeded" ) ;
}
@Test
public void testNotificationRuleProcessing_alarmAssignment ( ) throws Exception {
AlarmAssignmentNotificationRuleTriggerConfig triggerConfig = AlarmAssignmentNotificationRuleTriggerConfig . builder ( )
@ -617,6 +680,68 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
} ) ;
}
@Test
public void testNotificationsDeduplication_exceededRateLimits ( ) throws Exception {
RateLimitsNotificationRuleTriggerConfig triggerConfig = new RateLimitsNotificationRuleTriggerConfig ( ) ;
triggerConfig . setApis ( Set . of ( LimitedApi . ENTITY_EXPORT , LimitedApi . TRANSPORT_MESSAGES_PER_DEVICE ) ) ;
loginSysAdmin ( ) ;
NotificationTarget target = createNotificationTarget ( tenantAdminUserId ) ;
NotificationRule rule = createNotificationRule ( triggerConfig , "Test 1" , "Test" , target . getId ( ) ) ;
int n = 5 ;
updateDefaultTenantProfile ( profileConfiguration - > {
profileConfiguration . setTenantEntityExportRateLimit ( n + ":600" ) ;
profileConfiguration . setTransportDeviceTelemetryMsgRateLimit ( n + ":800" ) ;
} ) ;
RateLimitsTrigger expectedTrigger = RateLimitsTrigger . builder ( )
. tenantId ( tenantId )
. api ( LimitedApi . ENTITY_EXPORT )
. limitLevel ( tenantId )
. build ( ) ;
assertThat ( DefaultNotificationRuleProcessor . getDeduplicationKey ( expectedTrigger , rule ) )
. isEqualTo ( "RATE_LIMITS:TENANT:" + tenantId + ":ENTITY_EXPORT_" +
target . getId ( ) + ":ENTITY_EXPORT,TRANSPORT_MESSAGES_PER_DEVICE" ) ;
loginTenantAdmin ( ) ;
getWsClient ( ) . subscribeForUnreadNotifications ( 10 ) . waitForReply ( ) ;
getWsClient ( ) . registerWaitForUpdate ( 2 ) ;
Device device = createDevice ( "Test" , "Test" ) ;
for ( int i = 1 ; i < = n + 1 ; i + + ) {
rateLimitService . checkRateLimit ( LimitedApi . ENTITY_EXPORT , tenantId ) ;
doPost ( "/api/v1/" + device . getName ( ) + "/telemetry" , "{\"dp1\":123}" , String . class ) ;
}
int expectedNotificationsCount1 = 2 ;
getWsClient ( ) . waitForUpdate ( true ) ;
List < Notification > notifications1 = getMyNotifications ( true , 10 ) ;
assertThat ( notifications1 ) . size ( ) . isEqualTo ( expectedNotificationsCount1 ) ;
assertThat ( notifications1 )
. anyMatch ( notification - > ( ( RateLimitsNotificationInfo ) notification . getInfo ( ) ) . getApi ( ) = = LimitedApi . ENTITY_EXPORT )
. anyMatch ( notification - > ( ( RateLimitsNotificationInfo ) notification . getInfo ( ) ) . getApi ( ) = = LimitedApi . TRANSPORT_MESSAGES_PER_DEVICE ) ;
getWsClient ( ) . registerWaitForUpdate ( 2 ) ;
for ( int i = 0 ; i < 10 ; i + + ) {
rateLimitService . checkRateLimit ( LimitedApi . ENTITY_EXPORT , tenantId ) ;
doPost ( "/api/v1/" + device . getName ( ) + "/telemetry" , "{\"dp1\":123}" , String . class ) ;
}
assertThat ( getWsClient ( ) . waitForUpdate ( 5000 ) ) . isNull ( ) ;
int deduplicationDuration = 10000 ; // configured in TestPropertySource above
await ( ) . atLeast ( 2 , TimeUnit . SECONDS )
. atMost ( deduplicationDuration , TimeUnit . MILLISECONDS )
. untilAsserted ( ( ) - > {
rateLimitService . checkRateLimit ( LimitedApi . ENTITY_EXPORT , tenantId ) ;
doPost ( "/api/v1/" + device . getName ( ) + "/telemetry" , "{\"dp1\":123}" , String . class ) ;
Map < LimitedApi , Long > notifications2 = getMyNotifications ( true , 10 ) . stream ( )
. map ( notification - > ( RateLimitsNotificationInfo ) notification . getInfo ( ) )
. collect ( Collectors . groupingBy ( RateLimitsNotificationInfo : : getApi , Collectors . counting ( ) ) ) ;
assertThat ( notifications2 . get ( LimitedApi . ENTITY_EXPORT ) ) . isEqualTo ( 2 ) ;
assertThat ( notifications2 . get ( LimitedApi . TRANSPORT_MESSAGES_PER_DEVICE ) ) . isEqualTo ( 2 ) ;
} ) ;
}
@Test
public void testNotificationRuleDisabling ( ) throws Exception {
EntityActionNotificationRuleTriggerConfig triggerConfig = new EntityActionNotificationRuleTriggerConfig ( ) ;