From 857aa2c648ace00741d3ee53c313f662287ce2f3 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Mon, 22 Sep 2025 15:18:45 +0300 Subject: [PATCH] fixed nextCycleTs calculation for api usages --- .../DefaultTbApiUsageStateService.java | 4 +- .../DefaultTbApiUsageStateServiceTest.java | 50 ++++++++++++++++++- .../common/msg/tools/SchedulerUtils.java | 12 ----- 3 files changed, 51 insertions(+), 15 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 3dedea999c..84025b3cee 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -442,13 +442,13 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService boolean check(long threshold, long warnThreshold, long value); } - private void checkStartOfNextCycle() { + public void checkStartOfNextCycle() { updateLock.lock(); try { long now = System.currentTimeMillis(); myUsageStates.values().forEach(state -> { if ((state.getNextCycleTs() < now) && (now - state.getNextCycleTs() < TimeUnit.HOURS.toMillis(1))) { - state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth()); + state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextMonth()); if (log.isTraceEnabled()) { log.trace("[{}][{}] Updating state cycles (currentCycleTs={},nextCycleTs={})", state.getTenantId(), state.getEntityId(), state.getCurrentCycleTs(), state.getNextCycleTs()); } diff --git a/application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java b/application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java index 20f8aaf881..969f6de9f5 100644 --- a/application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java @@ -20,9 +20,12 @@ import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.ApiUsageStateId; +import org.thingsboard.server.common.data.id.EntityId; 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; @@ -33,8 +36,17 @@ import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import java.lang.reflect.Field; +import java.time.LocalDate; +import java.util.HashMap; +import java.util.Map; import java.util.UUID; +import java.util.concurrent.TimeUnit; +import static java.time.ZoneOffset.UTC; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoUnit.MONTHS; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; @DaoSqlTest @@ -48,6 +60,7 @@ public class DefaultTbApiUsageStateServiceTest extends AbstractControllerTest { private TenantId tenantId; private Tenant savedTenant; + private TenantProfile savedTenantProfile; private static final int MAX_ENABLE_VALUE = 5000; private static final long VALUE_WARNING = 4500L; @@ -59,7 +72,7 @@ public class DefaultTbApiUsageStateServiceTest extends AbstractControllerTest { loginSysAdmin(); TenantProfile tenantProfile = createTenantProfile(); - TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); + savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); Assert.assertNotNull(savedTenantProfile); Tenant tenant = new Tenant(); @@ -109,6 +122,41 @@ public class DefaultTbApiUsageStateServiceTest extends AbstractControllerTest { assertEquals(ApiUsageStateValue.DISABLED, apiUsageStateService.findTenantApiUsageState(tenantId).getDbStorageState()); } + @Test + public void checkStartOfNextCycle_setsNextCycleToNextMonth() throws Exception { + ApiUsageState apiUsageState = new ApiUsageState(new ApiUsageStateId(UUID.randomUUID())); + apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED); + apiUsageState.setAlarmExecState(ApiUsageStateValue.ENABLED); + apiUsageState.setSmsExecState(ApiUsageStateValue.ENABLED); + apiUsageState.setTbelExecState(ApiUsageStateValue.ENABLED); + apiUsageState.setReExecState(ApiUsageStateValue.ENABLED); + apiUsageState.setTransportState(ApiUsageStateValue.ENABLED); + apiUsageState.setEmailExecState(ApiUsageStateValue.ENABLED); + apiUsageState.setJsExecState(ApiUsageStateValue.ENABLED); + apiUsageState.setTenantId(tenantId); + apiUsageState.setEntityId(tenantId); + + long now = System.currentTimeMillis(); + long currentCycleTs = now - TimeUnit.DAYS.toMillis(30); + long nextCycleTs = now - TimeUnit.MINUTES.toMillis(5); // < 1h ago + TenantApiUsageState tenantApiUsageState = new TenantApiUsageState(savedTenantProfile, apiUsageState); + tenantApiUsageState.setCycles(currentCycleTs, nextCycleTs); + Map map = new HashMap<>(); + map.put(tenantId, tenantApiUsageState); + + Field fieldToSet = DefaultTbApiUsageStateService.class.getDeclaredField("myUsageStates"); + fieldToSet.setAccessible(true); + fieldToSet.set(service, map); + + service.checkStartOfNextCycle(); + + long firstOfNextMonth = LocalDate.now() + .with((temporal) -> temporal.with(DAY_OF_MONTH, 1) + .plus(1, MONTHS)) + .atStartOfDay(UTC).toInstant().toEpochMilli(); + assertThat(tenantApiUsageState.getNextCycleTs()).isEqualTo(firstOfNextMonth); + } + private TenantProfile createTenantProfile() { TenantProfile tenantProfile = new TenantProfile(); tenantProfile.setName("Tenant Profile"); diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java index e0805c27aa..5b8a241a1a 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java @@ -60,16 +60,4 @@ public class SchedulerUtils { return LocalDate.now(UTC).with(TemporalAdjusters.firstDayOfNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli(); } - public static long getStartOfNextNextMonth() { - return getStartOfNextNextMonth(UTC); - } - - public static long getStartOfNextNextMonth(ZoneId zoneId) { - return LocalDate.now(UTC).with(firstDayOfNextNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli(); - } - - public static TemporalAdjuster firstDayOfNextNextMonth() { - return (temporal) -> temporal.with(DAY_OF_MONTH, 1).plus(2, MONTHS); - } - }