Resolved review comments

This commit is contained in:
Artem Barysh 2025-02-14 19:21:03 +02:00
parent ec8e7a0726
commit 61f77f301d
9 changed files with 189 additions and 18 deletions

View File

@ -40,7 +40,8 @@ public abstract class BaseApiUsageState {
private final Map<ApiUsageRecordKey, Long> gaugesReportCycles = new HashMap<>(); private final Map<ApiUsageRecordKey, Long> gaugesReportCycles = new HashMap<>();
@Getter @Getter
private final ApiUsageState apiUsageState; @Setter
private ApiUsageState apiUsageState;
@Getter @Getter
private volatile long currentCycleTs; private volatile long currentCycleTs;
@Getter @Getter

View File

@ -355,7 +355,8 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService
private void persistAndNotify(BaseApiUsageState state, Map<ApiFeature, ApiUsageStateValue> result) { private void persistAndNotify(BaseApiUsageState state, Map<ApiFeature, ApiUsageStateValue> result) {
log.info("[{}] Detected update of the API state for {}: {}", state.getEntityId(), state.getEntityType(), result); log.info("[{}] Detected update of the API state for {}: {}", state.getEntityId(), state.getEntityType(), result);
apiUsageStateService.update(state.getApiUsageState()); ApiUsageState updatedState = apiUsageStateService.update(state.getApiUsageState());
state.setApiUsageState(updatedState);
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
List<TsKvEntry> stateTelemetry = new ArrayList<>(); List<TsKvEntry> stateTelemetry = new ArrayList<>();
result.forEach((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name())))); result.forEach((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name()))));

View File

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.service.apiusage; package org.thingsboard.server.service.apiusage;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
@ -23,14 +24,43 @@ import org.mockito.Mock;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.Spy; import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.ApiUsageStateValue;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.notification.rule.trigger.ApiUsageLimitTrigger;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.telemetry.InternalTelemetryService;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
public class DefaultTbApiUsageStateServiceTest { public class DefaultTbApiUsageStateServiceTest {
@ -38,6 +68,21 @@ public class DefaultTbApiUsageStateServiceTest {
@Mock @Mock
TenantApiUsageState tenantUsageStateMock; TenantApiUsageState tenantUsageStateMock;
@Mock
private NotificationRuleProcessor notificationRuleProcessor;
@Mock
private ApiUsageStateService apiUsageStateService;
@Mock
private TenantService tenantService;
@Mock
private InternalTelemetryService tsWsService;
@Mock
private MailExecutorService mailExecutor;
TenantId tenantId = TenantId.fromUUID(UUID.fromString("00797a3b-7aeb-4b5b-b57a-c2a810d0f112")); TenantId tenantId = TenantId.fromUUID(UUID.fromString("00797a3b-7aeb-4b5b-b57a-c2a810d0f112"));
@Spy @Spy
@ -46,6 +91,7 @@ public class DefaultTbApiUsageStateServiceTest {
@BeforeEach @BeforeEach
public void setUp() { public void setUp() {
ReflectionTestUtils.setField(service, "tsWsService", tsWsService);
} }
@Test @Test
@ -56,4 +102,100 @@ public class DefaultTbApiUsageStateServiceTest {
Mockito.verify(service, never()).getOrFetchState(tenantId, tenantId); Mockito.verify(service, never()).getOrFetchState(tenantId, tenantId);
} }
@Test
public void testAllApiFeaturesDisabledWhenLimitReached() {
doReturn(null).when(tsWsService).saveTimeseriesInternal(any());
TenantApiUsageState tenantUsageStateMock = mock(TenantApiUsageState.class);
ApiUsageState apiUsageState = getApiUsageState();
when(tenantUsageStateMock.getApiUsageState()).thenReturn(apiUsageState);
doReturn(BaseApiUsageState.StatsCalculationResult.builder()
.newValue(200L)
.valueChanged(true)
.newHourlyValue(200L)
.hourlyValueChanged(true)
.build())
.when(tenantUsageStateMock).calculate(any(ApiUsageRecordKey.class), anyLong(), anyString());
doReturn(200L).when(tenantUsageStateMock).getProfileThreshold(any(ApiUsageRecordKey.class));
doReturn(50L).when(tenantUsageStateMock).getProfileWarnThreshold(any(ApiUsageRecordKey.class));
doReturn(300L).when(tenantUsageStateMock).get(any(ApiUsageRecordKey.class));
when(tenantUsageStateMock.getEntityType()).thenReturn(EntityType.TENANT);
when(tenantUsageStateMock.getEntityId()).thenReturn(tenantId);
Map<ApiFeature, ApiUsageStateValue> expectedResult = getExpectedResult();
when(tenantUsageStateMock.checkStateUpdatedDueToThreshold(any())).thenReturn(expectedResult);
service.myUsageStates.put(tenantId, tenantUsageStateMock);
when(apiUsageStateService.update(apiUsageState)).thenReturn(apiUsageState);
Tenant dummyTenant = new Tenant();
dummyTenant.setEmail("test@example.com");
when(tenantService.findTenantById(any())).thenReturn(dummyTenant);
TransportProtos.ToUsageStatsServiceMsg.Builder msgBuilder = TransportProtos.ToUsageStatsServiceMsg.newBuilder();
UUID uuid = tenantId.getId();
msgBuilder.setTenantIdMSB(uuid.getMostSignificantBits())
.setTenantIdLSB(uuid.getLeastSignificantBits());
msgBuilder.setCustomerIdMSB(0)
.setCustomerIdLSB(0);
msgBuilder.setServiceId("TEST_SERVICE");
List<TransportProtos.UsageStatsKVProto> usageStats = new ArrayList<>();
for (ApiUsageRecordKey recordKey : ApiUsageRecordKey.values()) {
if (recordKey.getApiFeature() != null) {
TransportProtos.UsageStatsKVProto stat = TransportProtos.UsageStatsKVProto.newBuilder()
.setKey(recordKey.name())
.setValue(1000L)
.build();
usageStats.add(stat);
msgBuilder.addValues(stat);
}
}
TransportProtos.ToUsageStatsServiceMsg statsMsg = msgBuilder.build();
TbProtoQueueMsg<TransportProtos.ToUsageStatsServiceMsg> queueMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), statsMsg);
TbCallback callback = mock(TbCallback.class);
service.process(queueMsg, callback);
verify(callback).onSuccess();
for (ApiFeature feature : expectedResult.keySet()) {
verify(notificationRuleProcessor, atLeastOnce()).process(argThat(trigger ->
trigger instanceof ApiUsageLimitTrigger &&
((ApiUsageLimitTrigger) trigger).getStatus() == ApiUsageStateValue.DISABLED &&
((ApiUsageLimitTrigger) trigger).getState().getApiFeature().getApiStateKey().equals(feature.getApiStateKey())
));
}
}
@NotNull
private ApiUsageState getApiUsageState() {
ApiUsageState apiUsageState = new ApiUsageState();
apiUsageState.setTenantId(tenantId);
apiUsageState.setTransportState(ApiUsageStateValue.ENABLED);
apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED);
apiUsageState.setReExecState(ApiUsageStateValue.ENABLED);
apiUsageState.setJsExecState(ApiUsageStateValue.ENABLED);
apiUsageState.setTbelExecState(ApiUsageStateValue.ENABLED);
apiUsageState.setEmailExecState(ApiUsageStateValue.ENABLED);
apiUsageState.setSmsExecState(ApiUsageStateValue.ENABLED);
apiUsageState.setAlarmExecState(ApiUsageStateValue.ENABLED);
return apiUsageState;
}
private Map<ApiFeature, ApiUsageStateValue> getExpectedResult() {
Map<ApiFeature, ApiUsageStateValue> expectedResult = new HashMap<>();
for (ApiUsageRecordKey recordKey : ApiUsageRecordKey.values()) {
if (recordKey.getApiFeature() != null) {
expectedResult.put(recordKey.getApiFeature(), ApiUsageStateValue.DISABLED);
}
}
return expectedResult;
}
} }

View File

@ -34,4 +34,5 @@ public interface ApiUsageStateService extends EntityDaoService {
void deleteApiUsageStateByEntityId(EntityId entityId); void deleteApiUsageStateByEntityId(EntityId entityId);
ApiUsageState findApiUsageStateById(TenantId tenantId, ApiUsageStateId id); ApiUsageState findApiUsageStateById(TenantId tenantId, ApiUsageStateId id);
} }

View File

@ -1019,7 +1019,9 @@ public class ProtoUtils {
.setTbelExecState(apiUsageState.getTbelExecState().name()) .setTbelExecState(apiUsageState.getTbelExecState().name())
.setEmailExecState(apiUsageState.getEmailExecState().name()) .setEmailExecState(apiUsageState.getEmailExecState().name())
.setSmsExecState(apiUsageState.getSmsExecState().name()) .setSmsExecState(apiUsageState.getSmsExecState().name())
.setAlarmExecState(apiUsageState.getAlarmExecState().name()).build(); .setAlarmExecState(apiUsageState.getAlarmExecState().name())
.setVersion(apiUsageState.getVersion())
.build();
} }
public static ApiUsageState fromProto(TransportProtos.ApiUsageStateProto proto) { public static ApiUsageState fromProto(TransportProtos.ApiUsageStateProto proto) {
@ -1035,6 +1037,7 @@ public class ProtoUtils {
apiUsageState.setEmailExecState(ApiUsageStateValue.valueOf(proto.getEmailExecState())); apiUsageState.setEmailExecState(ApiUsageStateValue.valueOf(proto.getEmailExecState()));
apiUsageState.setSmsExecState(ApiUsageStateValue.valueOf(proto.getSmsExecState())); apiUsageState.setSmsExecState(ApiUsageStateValue.valueOf(proto.getSmsExecState()));
apiUsageState.setAlarmExecState(ApiUsageStateValue.valueOf(proto.getAlarmExecState())); apiUsageState.setAlarmExecState(ApiUsageStateValue.valueOf(proto.getAlarmExecState()));
apiUsageState.setVersion(proto.getVersion());
return apiUsageState; return apiUsageState;
} }

View File

@ -325,6 +325,7 @@ message ApiUsageStateProto {
string emailExecState = 14; string emailExecState = 14;
string smsExecState = 15; string smsExecState = 15;
string alarmExecState = 16; string alarmExecState = 16;
int64 version = 17;
} }
message RepositorySettingsProto { message RepositorySettingsProto {

View File

@ -154,6 +154,7 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A
log.trace("Executing save [{}]", apiUsageState.getTenantId()); log.trace("Executing save [{}]", apiUsageState.getTenantId());
validateId(apiUsageState.getTenantId(), id -> INCORRECT_TENANT_ID + id); validateId(apiUsageState.getTenantId(), id -> INCORRECT_TENANT_ID + id);
validateId(apiUsageState.getId(), "Can't save new usage state. Only update is allowed!"); validateId(apiUsageState.getId(), "Can't save new usage state. Only update is allowed!");
apiUsageState.setVersion(null);
ApiUsageState savedState = apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); ApiUsageState savedState = apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedState.getTenantId()).entityId(savedState.getId()) eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(savedState.getTenantId()).entityId(savedState.getId())
.entity(savedState).build()); .entity(savedState).build());

View File

@ -20,6 +20,7 @@ import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.ApiUsageStateValue;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
@ -30,22 +31,42 @@ public class ApiUsageStateServiceTest extends AbstractServiceTest {
ApiUsageStateService apiUsageStateService; ApiUsageStateService apiUsageStateService;
@Test @Test
public void testFindApiUsageStateByTenantId() { public void testFindTenantApiUsageState() {
ApiUsageState apiUsageState = apiUsageStateService.findTenantApiUsageState(tenantId); ApiUsageState state = apiUsageStateService.findTenantApiUsageState(tenantId);
Assert.assertNotNull(apiUsageState); Assert.assertNotNull(state);
} }
@Test @Test
public void testUpdateApiUsageState(){ public void testUpdate() {
ApiUsageState apiUsageState = apiUsageStateService.findTenantApiUsageState(tenantId); ApiUsageState state = apiUsageStateService.findTenantApiUsageState(tenantId);
Assert.assertNotNull(apiUsageState);
Assert.assertTrue(apiUsageState.isTransportEnabled()); state.setTransportState(ApiUsageStateValue.DISABLED);
apiUsageState.setTransportState(ApiUsageStateValue.DISABLED); ApiUsageState updated = apiUsageStateService.update(state);
apiUsageState = apiUsageStateService.update(apiUsageState); Assert.assertEquals(ApiUsageStateValue.DISABLED, updated.getTransportState());
Assert.assertNotNull(apiUsageState); }
apiUsageState = apiUsageStateService.findTenantApiUsageState(tenantId);
Assert.assertNotNull(apiUsageState); @Test
Assert.assertFalse(apiUsageState.isTransportEnabled()); public void testUpdateWithNullId() {
ApiUsageState newState = new ApiUsageState();
newState.setTenantId(tenantId);
newState.setTransportState(ApiUsageStateValue.ENABLED);
Assert.assertThrows(IncorrectParameterException.class, () -> apiUsageStateService.update(newState));
}
@Test
public void testFindApiUsageStateByEntityId() {
ApiUsageState state = apiUsageStateService.findApiUsageStateByEntityId(tenantId);
Assert.assertNotNull(state);
}
@Test
public void testDeleteByTenantId() {
ApiUsageState state = apiUsageStateService.findTenantApiUsageState(tenantId);
Assert.assertNotNull(state);
apiUsageStateService.deleteByTenantId(tenantId);
state = apiUsageStateService.findTenantApiUsageState(tenantId);
Assert.assertNull(state);
} }
} }