|
|
|
@ -16,6 +16,9 @@ |
|
|
|
package org.thingsboard.server.service.state; |
|
|
|
|
|
|
|
import com.google.common.util.concurrent.Futures; |
|
|
|
import com.google.common.util.concurrent.ListeningExecutorService; |
|
|
|
import com.google.common.util.concurrent.MoreExecutors; |
|
|
|
import org.junit.jupiter.api.AfterEach; |
|
|
|
import org.junit.jupiter.api.BeforeEach; |
|
|
|
import org.junit.jupiter.api.Test; |
|
|
|
import org.junit.jupiter.api.extension.ExtendWith; |
|
|
|
@ -31,13 +34,13 @@ import org.thingsboard.rule.engine.api.AttributesSaveRequest; |
|
|
|
import org.thingsboard.server.cluster.TbClusterService; |
|
|
|
import org.thingsboard.server.common.data.AttributeScope; |
|
|
|
import org.thingsboard.server.common.data.Device; |
|
|
|
import org.thingsboard.server.common.data.DeviceIdInfo; |
|
|
|
import org.thingsboard.server.common.data.id.DeviceId; |
|
|
|
import org.thingsboard.server.common.data.id.EntityId; |
|
|
|
import org.thingsboard.server.common.data.id.TenantId; |
|
|
|
import org.thingsboard.server.common.data.kv.AttributeKvEntry; |
|
|
|
import org.thingsboard.server.common.data.kv.AttributesSaveResult; |
|
|
|
import org.thingsboard.server.common.data.msg.TbMsgType; |
|
|
|
import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityTrigger; |
|
|
|
import org.thingsboard.server.common.data.page.PageData; |
|
|
|
import org.thingsboard.server.common.msg.TbMsg; |
|
|
|
import org.thingsboard.server.common.msg.TbMsgMetaData; |
|
|
|
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor; |
|
|
|
@ -50,20 +53,22 @@ import org.thingsboard.server.dao.sql.query.EntityQueryRepository; |
|
|
|
import org.thingsboard.server.dao.timeseries.TimeseriesService; |
|
|
|
import org.thingsboard.server.gen.transport.TransportProtos; |
|
|
|
import org.thingsboard.server.queue.discovery.PartitionService; |
|
|
|
import org.thingsboard.server.queue.discovery.QueueKey; |
|
|
|
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; |
|
|
|
import org.thingsboard.server.queue.usagestats.DefaultTbApiUsageReportClient; |
|
|
|
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; |
|
|
|
|
|
|
|
import java.time.Duration; |
|
|
|
import java.util.Collections; |
|
|
|
import java.util.HashSet; |
|
|
|
import java.util.List; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Set; |
|
|
|
import java.util.UUID; |
|
|
|
import java.util.concurrent.ConcurrentHashMap; |
|
|
|
import java.util.concurrent.ConcurrentMap; |
|
|
|
import java.util.concurrent.CountDownLatch; |
|
|
|
import java.util.concurrent.ExecutorService; |
|
|
|
import java.util.concurrent.Executors; |
|
|
|
import java.util.concurrent.ThreadLocalRandom; |
|
|
|
import java.util.concurrent.TimeUnit; |
|
|
|
import java.util.concurrent.atomic.AtomicLong; |
|
|
|
import java.util.stream.Stream; |
|
|
|
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat; |
|
|
|
@ -77,8 +82,8 @@ import static org.mockito.BDDMockito.then; |
|
|
|
import static org.mockito.BDDMockito.willReturn; |
|
|
|
import static org.mockito.Mockito.doAnswer; |
|
|
|
import static org.mockito.Mockito.doReturn; |
|
|
|
import static org.mockito.Mockito.lenient; |
|
|
|
import static org.mockito.Mockito.never; |
|
|
|
import static org.mockito.Mockito.reset; |
|
|
|
import static org.mockito.Mockito.spy; |
|
|
|
import static org.mockito.Mockito.times; |
|
|
|
import static org.mockito.Mockito.verify; |
|
|
|
@ -90,7 +95,10 @@ import static org.thingsboard.server.service.state.DefaultDeviceStateService.LAS |
|
|
|
import static org.thingsboard.server.service.state.DefaultDeviceStateService.LAST_DISCONNECT_TIME; |
|
|
|
|
|
|
|
@ExtendWith(MockitoExtension.class) |
|
|
|
public class DefaultDeviceStateServiceTest { |
|
|
|
class DefaultDeviceStateServiceTest { |
|
|
|
|
|
|
|
ListeningExecutorService deviceStateExecutor; |
|
|
|
ListeningExecutorService deviceStateCallbackExecutor; |
|
|
|
|
|
|
|
@Mock |
|
|
|
DeviceService deviceService; |
|
|
|
@ -113,25 +121,48 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
@Mock |
|
|
|
DefaultTbApiUsageReportClient defaultTbApiUsageReportClient; |
|
|
|
|
|
|
|
TenantId tenantId = new TenantId(UUID.fromString("00797a3b-7aeb-4b5b-b57a-c2a810d0f112")); |
|
|
|
DeviceId deviceId = DeviceId.fromString("00797a3b-7aeb-4b5b-b57a-c2a810d0f112"); |
|
|
|
TopicPartitionInfo tpi; |
|
|
|
long defaultInactivityTimeoutMs = Duration.ofMinutes(10L).toMillis(); |
|
|
|
|
|
|
|
TenantId tenantId = TenantId.fromUUID(UUID.fromString("00797a3b-7aeb-4b5b-b57a-c2a810d0f112")); |
|
|
|
DeviceId deviceId = DeviceId.fromString("c209f718-42e5-11f0-9fe2-0242ac120002"); |
|
|
|
TopicPartitionInfo tpi = TopicPartitionInfo.builder() |
|
|
|
.topic("tb_core") |
|
|
|
.partition(0) |
|
|
|
.myPartition(true) |
|
|
|
.build(); |
|
|
|
|
|
|
|
DefaultDeviceStateService service; |
|
|
|
|
|
|
|
@BeforeEach |
|
|
|
public void setUp() { |
|
|
|
void setUp() { |
|
|
|
service = spy(new DefaultDeviceStateService(deviceService, attributesService, tsService, clusterService, partitionService, entityQueryRepository, null, defaultTbApiUsageReportClient, notificationRuleProcessor)); |
|
|
|
ReflectionTestUtils.setField(service, "tsSubService", telemetrySubscriptionService); |
|
|
|
ReflectionTestUtils.setField(service, "defaultInactivityTimeoutMs", defaultInactivityTimeoutMs); |
|
|
|
ReflectionTestUtils.setField(service, "defaultStateCheckIntervalInSec", 60); |
|
|
|
ReflectionTestUtils.setField(service, "defaultActivityStatsIntervalInSec", 60); |
|
|
|
ReflectionTestUtils.setField(service, "initFetchPackSize", 10); |
|
|
|
ReflectionTestUtils.setField(service, "initFetchPackSize", 50000); |
|
|
|
|
|
|
|
deviceStateExecutor = MoreExecutors.newDirectExecutorService(); |
|
|
|
ReflectionTestUtils.setField(service, "deviceStateExecutor", deviceStateExecutor); |
|
|
|
|
|
|
|
deviceStateCallbackExecutor = MoreExecutors.newDirectExecutorService(); |
|
|
|
ReflectionTestUtils.setField(service, "deviceStateCallbackExecutor", deviceStateCallbackExecutor); |
|
|
|
|
|
|
|
lenient().when(partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId)).thenReturn(tpi); |
|
|
|
|
|
|
|
ConcurrentMap<TopicPartitionInfo, Set<DeviceId>> partitionedEntities = new ConcurrentHashMap<>(); |
|
|
|
partitionedEntities.put(tpi, new HashSet<>()); |
|
|
|
ReflectionTestUtils.setField(service, "partitionedEntities", partitionedEntities); |
|
|
|
} |
|
|
|
|
|
|
|
tpi = TopicPartitionInfo.builder().myPartition(true).build(); |
|
|
|
@AfterEach |
|
|
|
void cleanup() { |
|
|
|
deviceStateExecutor.shutdownNow(); |
|
|
|
deviceStateCallbackExecutor.shutdownNow(); |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void givenDeviceBelongsToExternalPartition_whenOnDeviceConnect_thenCleansStateAndDoesNotReportConnect() { |
|
|
|
void givenDeviceBelongsToExternalPartition_whenOnDeviceConnect_thenCleansStateAndDoesNotReportConnect() { |
|
|
|
// GIVEN
|
|
|
|
doReturn(true).when(service).cleanDeviceStateIfBelongsToExternalPartition(tenantId, deviceId); |
|
|
|
|
|
|
|
@ -149,7 +180,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
|
|
|
|
@ParameterizedTest |
|
|
|
@ValueSource(longs = {Long.MIN_VALUE, -100, -1}) |
|
|
|
public void givenNegativeLastConnectTime_whenOnDeviceConnect_thenSkipsThisEvent(long negativeLastConnectTime) { |
|
|
|
void givenNegativeLastConnectTime_whenOnDeviceConnect_thenSkipsThisEvent(long negativeLastConnectTime) { |
|
|
|
// GIVEN
|
|
|
|
doReturn(false).when(service).cleanDeviceStateIfBelongsToExternalPartition(tenantId, deviceId); |
|
|
|
|
|
|
|
@ -166,7 +197,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
|
|
|
|
@ParameterizedTest |
|
|
|
@MethodSource("provideOutdatedTimestamps") |
|
|
|
public void givenOutdatedLastConnectTime_whenOnDeviceDisconnect_thenSkipsThisEvent(long outdatedLastConnectTime, long currentLastConnectTime) { |
|
|
|
void givenOutdatedLastConnectTime_whenOnDeviceDisconnect_thenSkipsThisEvent(long outdatedLastConnectTime, long currentLastConnectTime) { |
|
|
|
// GIVEN
|
|
|
|
doReturn(false).when(service).cleanDeviceStateIfBelongsToExternalPartition(tenantId, deviceId); |
|
|
|
|
|
|
|
@ -188,7 +219,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void givenDeviceBelongsToMyPartition_whenOnDeviceConnect_thenReportsConnect() { |
|
|
|
void givenDeviceBelongsToMyPartition_whenOnDeviceConnect_thenReportsConnect() { |
|
|
|
// GIVEN
|
|
|
|
var deviceStateData = DeviceStateData.builder() |
|
|
|
.tenantId(tenantId) |
|
|
|
@ -202,11 +233,13 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
service.deviceStates.put(deviceId, deviceStateData); |
|
|
|
long lastConnectTime = System.currentTimeMillis(); |
|
|
|
|
|
|
|
mockSuccessfulSaveAttributes(); |
|
|
|
|
|
|
|
// WHEN
|
|
|
|
service.onDeviceConnect(tenantId, deviceId, lastConnectTime); |
|
|
|
|
|
|
|
// THEN
|
|
|
|
then(telemetrySubscriptionService).should().saveAttributes(argThat(request -> |
|
|
|
then(telemetrySubscriptionService).should().saveAttributesInternal(argThat(request -> |
|
|
|
request.getTenantId().equals(tenantId) && request.getEntityId().equals(deviceId) && |
|
|
|
request.getScope().equals(AttributeScope.SERVER_SCOPE) && |
|
|
|
request.getEntries().get(0).getKey().equals(LAST_CONNECT_TIME) && |
|
|
|
@ -221,7 +254,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void givenDeviceBelongsToExternalPartition_whenOnDeviceDisconnect_thenCleansStateAndDoesNotReportDisconnect() { |
|
|
|
void givenDeviceBelongsToExternalPartition_whenOnDeviceDisconnect_thenCleansStateAndDoesNotReportDisconnect() { |
|
|
|
// GIVEN
|
|
|
|
doReturn(true).when(service).cleanDeviceStateIfBelongsToExternalPartition(tenantId, deviceId); |
|
|
|
|
|
|
|
@ -238,7 +271,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
|
|
|
|
@ParameterizedTest |
|
|
|
@ValueSource(longs = {Long.MIN_VALUE, -100, -1}) |
|
|
|
public void givenNegativeLastDisconnectTime_whenOnDeviceDisconnect_thenSkipsThisEvent(long negativeLastDisconnectTime) { |
|
|
|
void givenNegativeLastDisconnectTime_whenOnDeviceDisconnect_thenSkipsThisEvent(long negativeLastDisconnectTime) { |
|
|
|
// GIVEN
|
|
|
|
doReturn(false).when(service).cleanDeviceStateIfBelongsToExternalPartition(tenantId, deviceId); |
|
|
|
|
|
|
|
@ -254,7 +287,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
|
|
|
|
@ParameterizedTest |
|
|
|
@MethodSource("provideOutdatedTimestamps") |
|
|
|
public void givenOutdatedLastDisconnectTime_whenOnDeviceDisconnect_thenSkipsThisEvent(long outdatedLastDisconnectTime, long currentLastDisconnectTime) { |
|
|
|
void givenOutdatedLastDisconnectTime_whenOnDeviceDisconnect_thenSkipsThisEvent(long outdatedLastDisconnectTime, long currentLastDisconnectTime) { |
|
|
|
// GIVEN
|
|
|
|
doReturn(false).when(service).cleanDeviceStateIfBelongsToExternalPartition(tenantId, deviceId); |
|
|
|
|
|
|
|
@ -275,7 +308,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void givenDeviceBelongsToMyPartition_whenOnDeviceDisconnect_thenReportsDisconnect() { |
|
|
|
void givenDeviceBelongsToMyPartition_whenOnDeviceDisconnect_thenReportsDisconnect() { |
|
|
|
// GIVEN
|
|
|
|
var deviceStateData = DeviceStateData.builder() |
|
|
|
.tenantId(tenantId) |
|
|
|
@ -289,11 +322,13 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
service.deviceStates.put(deviceId, deviceStateData); |
|
|
|
long lastDisconnectTime = System.currentTimeMillis(); |
|
|
|
|
|
|
|
mockSuccessfulSaveAttributes(); |
|
|
|
|
|
|
|
// WHEN
|
|
|
|
service.onDeviceDisconnect(tenantId, deviceId, lastDisconnectTime); |
|
|
|
|
|
|
|
// THEN
|
|
|
|
then(telemetrySubscriptionService).should().saveAttributes(argThat(request -> |
|
|
|
then(telemetrySubscriptionService).should().saveAttributesInternal(argThat(request -> |
|
|
|
request.getTenantId().equals(tenantId) && request.getEntityId().equals(deviceId) && |
|
|
|
request.getScope().equals(AttributeScope.SERVER_SCOPE) && |
|
|
|
request.getEntries().get(0).getKey().equals(LAST_DISCONNECT_TIME) && |
|
|
|
@ -308,7 +343,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void givenDeviceBelongsToExternalPartition_whenOnDeviceInactivity_thenCleansStateAndDoesNotReportInactivity() { |
|
|
|
void givenDeviceBelongsToExternalPartition_whenOnDeviceInactivity_thenCleansStateAndDoesNotReportInactivity() { |
|
|
|
// GIVEN
|
|
|
|
doReturn(true).when(service).cleanDeviceStateIfBelongsToExternalPartition(tenantId, deviceId); |
|
|
|
|
|
|
|
@ -325,7 +360,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
|
|
|
|
@ParameterizedTest |
|
|
|
@ValueSource(longs = {Long.MIN_VALUE, -100, -1}) |
|
|
|
public void givenNegativeLastInactivityTime_whenOnDeviceInactivity_thenSkipsThisEvent(long negativeLastInactivityTime) { |
|
|
|
void givenNegativeLastInactivityTime_whenOnDeviceInactivity_thenSkipsThisEvent(long negativeLastInactivityTime) { |
|
|
|
// GIVEN
|
|
|
|
doReturn(false).when(service).cleanDeviceStateIfBelongsToExternalPartition(tenantId, deviceId); |
|
|
|
|
|
|
|
@ -341,7 +376,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
|
|
|
|
@ParameterizedTest |
|
|
|
@MethodSource("provideOutdatedTimestamps") |
|
|
|
public void givenReceivedInactivityTimeIsLessThanOrEqualToCurrentInactivityTime_whenOnDeviceInactivity_thenSkipsThisEvent( |
|
|
|
void givenReceivedInactivityTimeIsLessThanOrEqualToCurrentInactivityTime_whenOnDeviceInactivity_thenSkipsThisEvent( |
|
|
|
long outdatedLastInactivityTime, long currentLastInactivityTime |
|
|
|
) { |
|
|
|
// GIVEN
|
|
|
|
@ -365,7 +400,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
|
|
|
|
@ParameterizedTest |
|
|
|
@MethodSource("provideOutdatedTimestamps") |
|
|
|
public void givenReceivedInactivityTimeIsLessThanOrEqualToCurrentActivityTime_whenOnDeviceInactivity_thenSkipsThisEvent( |
|
|
|
void givenReceivedInactivityTimeIsLessThanOrEqualToCurrentActivityTime_whenOnDeviceInactivity_thenSkipsThisEvent( |
|
|
|
long outdatedLastInactivityTime, long currentLastActivityTime |
|
|
|
) { |
|
|
|
// GIVEN
|
|
|
|
@ -398,7 +433,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void givenDeviceBelongsToMyPartition_whenOnDeviceInactivity_thenReportsInactivity() { |
|
|
|
void givenDeviceBelongsToMyPartition_whenOnDeviceInactivity_thenReportsInactivity() { |
|
|
|
// GIVEN
|
|
|
|
var deviceStateData = DeviceStateData.builder() |
|
|
|
.tenantId(tenantId) |
|
|
|
@ -412,17 +447,19 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
service.deviceStates.put(deviceId, deviceStateData); |
|
|
|
long lastInactivityTime = System.currentTimeMillis(); |
|
|
|
|
|
|
|
mockSuccessfulSaveAttributes(); |
|
|
|
|
|
|
|
// WHEN
|
|
|
|
service.onDeviceInactivity(tenantId, deviceId, lastInactivityTime); |
|
|
|
|
|
|
|
// THEN
|
|
|
|
then(telemetrySubscriptionService).should().saveAttributes(argThat(request -> |
|
|
|
then(telemetrySubscriptionService).should().saveAttributesInternal(argThat(request -> |
|
|
|
request.getTenantId().equals(tenantId) && request.getEntityId().equals(deviceId) && |
|
|
|
request.getScope().equals(AttributeScope.SERVER_SCOPE) && |
|
|
|
request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME) && |
|
|
|
request.getEntries().get(0).getValue().equals(lastInactivityTime) |
|
|
|
)); |
|
|
|
then(telemetrySubscriptionService).should().saveAttributes(argThat(request -> |
|
|
|
then(telemetrySubscriptionService).should().saveAttributesInternal(argThat(request -> |
|
|
|
request.getTenantId().equals(tenantId) && request.getEntityId().equals(deviceId) && |
|
|
|
request.getScope().equals(AttributeScope.SERVER_SCOPE) && |
|
|
|
request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) && |
|
|
|
@ -445,7 +482,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void givenInactivityTimeoutReached_whenUpdateInactivityStateIfExpired_thenReportsInactivity() { |
|
|
|
void givenInactivityTimeoutReached_whenUpdateInactivityStateIfExpired_thenReportsInactivity() { |
|
|
|
// GIVEN
|
|
|
|
var deviceStateData = DeviceStateData.builder() |
|
|
|
.tenantId(tenantId) |
|
|
|
@ -456,16 +493,18 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
|
|
|
|
given(partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId)).willReturn(tpi); |
|
|
|
|
|
|
|
mockSuccessfulSaveAttributes(); |
|
|
|
|
|
|
|
// WHEN
|
|
|
|
service.updateInactivityStateIfExpired(System.currentTimeMillis(), deviceId, deviceStateData); |
|
|
|
|
|
|
|
// THEN
|
|
|
|
then(telemetrySubscriptionService).should().saveAttributes(argThat(request -> |
|
|
|
then(telemetrySubscriptionService).should().saveAttributesInternal(argThat(request -> |
|
|
|
request.getTenantId().equals(tenantId) && request.getEntityId().equals(deviceId) && |
|
|
|
request.getScope().equals(AttributeScope.SERVER_SCOPE) && |
|
|
|
request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME) |
|
|
|
)); |
|
|
|
then(telemetrySubscriptionService).should().saveAttributes(argThat(request -> |
|
|
|
then(telemetrySubscriptionService).should().saveAttributesInternal(argThat(request -> |
|
|
|
request.getTenantId().equals(tenantId) && request.getEntityId().equals(deviceId) && |
|
|
|
request.getScope().equals(AttributeScope.SERVER_SCOPE) && |
|
|
|
request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) && |
|
|
|
@ -488,7 +527,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void givenDeviceIdFromDeviceStatesMap_whenGetOrFetchDeviceStateData_thenNoStackOverflow() { |
|
|
|
void givenDeviceIdFromDeviceStatesMap_whenGetOrFetchDeviceStateData_thenNoStackOverflow() { |
|
|
|
service.deviceStates.put(deviceId, deviceStateDataMock); |
|
|
|
DeviceStateData deviceStateData = service.getOrFetchDeviceStateData(deviceId); |
|
|
|
assertThat(deviceStateData).isEqualTo(deviceStateDataMock); |
|
|
|
@ -496,7 +535,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void givenDeviceIdWithoutDeviceStateInMap_whenGetOrFetchDeviceStateData_thenFetchDeviceStateData() { |
|
|
|
void givenDeviceIdWithoutDeviceStateInMap_whenGetOrFetchDeviceStateData_thenFetchDeviceStateData() { |
|
|
|
service.deviceStates.clear(); |
|
|
|
willReturn(deviceStateDataMock).given(service).fetchDeviceStateDataUsingSeparateRequests(deviceId); |
|
|
|
DeviceStateData deviceStateData = service.getOrFetchDeviceStateData(deviceId); |
|
|
|
@ -504,172 +543,18 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
verify(service).fetchDeviceStateDataUsingSeparateRequests(deviceId); |
|
|
|
} |
|
|
|
|
|
|
|
private void initStateService(long timeout) throws InterruptedException { |
|
|
|
service.stop(); |
|
|
|
reset(service, telemetrySubscriptionService); |
|
|
|
service.setDefaultInactivityTimeoutMs(timeout); |
|
|
|
service.init(); |
|
|
|
when(partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId)).thenReturn(tpi); |
|
|
|
when(entityQueryRepository.findEntityDataByQueryInternal(any())).thenReturn(new PageData<>()); |
|
|
|
var deviceIdInfo = new DeviceIdInfo(tenantId.getId(), null, deviceId.getId()); |
|
|
|
when(deviceService.findDeviceIdInfos(any())) |
|
|
|
.thenReturn(new PageData<>(List.of(deviceIdInfo), 0, 1, false)); |
|
|
|
PartitionChangeEvent event = new PartitionChangeEvent(this, ServiceType.TB_CORE, Map.of( |
|
|
|
new QueueKey(ServiceType.TB_CORE), Collections.singleton(tpi) |
|
|
|
), Collections.emptyMap()); |
|
|
|
service.onApplicationEvent(event); |
|
|
|
Thread.sleep(100); |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void increaseInactivityForInactiveDeviceTest() throws Exception { |
|
|
|
final long defaultTimeout = 1; |
|
|
|
initStateService(defaultTimeout); |
|
|
|
DeviceState deviceState = DeviceState.builder().build(); |
|
|
|
DeviceStateData deviceStateData = DeviceStateData.builder() |
|
|
|
.tenantId(tenantId) |
|
|
|
.deviceId(deviceId) |
|
|
|
.state(deviceState) |
|
|
|
.metaData(new TbMsgMetaData()) |
|
|
|
.build(); |
|
|
|
|
|
|
|
service.deviceStates.put(deviceId, deviceStateData); |
|
|
|
service.getPartitionedEntities(tpi).add(deviceId); |
|
|
|
|
|
|
|
service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); |
|
|
|
activityVerify(true); |
|
|
|
Thread.sleep(defaultTimeout); |
|
|
|
service.checkStates(); |
|
|
|
activityVerify(false); |
|
|
|
|
|
|
|
reset(telemetrySubscriptionService); |
|
|
|
|
|
|
|
long increase = 100; |
|
|
|
long newTimeout = System.currentTimeMillis() - deviceState.getLastActivityTime() + increase; |
|
|
|
|
|
|
|
service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout); |
|
|
|
activityVerify(true); |
|
|
|
Thread.sleep(increase); |
|
|
|
service.checkStates(); |
|
|
|
activityVerify(false); |
|
|
|
|
|
|
|
reset(telemetrySubscriptionService); |
|
|
|
|
|
|
|
service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); |
|
|
|
activityVerify(true); |
|
|
|
Thread.sleep(newTimeout + 5); |
|
|
|
service.checkStates(); |
|
|
|
activityVerify(false); |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void increaseInactivityForActiveDeviceTest() throws Exception { |
|
|
|
final long defaultTimeout = 1000; |
|
|
|
initStateService(defaultTimeout); |
|
|
|
DeviceState deviceState = DeviceState.builder().build(); |
|
|
|
DeviceStateData deviceStateData = DeviceStateData.builder() |
|
|
|
.tenantId(tenantId) |
|
|
|
.deviceId(deviceId) |
|
|
|
.state(deviceState) |
|
|
|
.metaData(new TbMsgMetaData()) |
|
|
|
.build(); |
|
|
|
|
|
|
|
service.deviceStates.put(deviceId, deviceStateData); |
|
|
|
service.getPartitionedEntities(tpi).add(deviceId); |
|
|
|
|
|
|
|
service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); |
|
|
|
activityVerify(true); |
|
|
|
|
|
|
|
reset(telemetrySubscriptionService); |
|
|
|
|
|
|
|
long increase = 100; |
|
|
|
long newTimeout = System.currentTimeMillis() - deviceState.getLastActivityTime() + increase; |
|
|
|
|
|
|
|
service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout); |
|
|
|
verify(telemetrySubscriptionService, never()).saveAttributes(argThat(request -> |
|
|
|
request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) |
|
|
|
)); |
|
|
|
Thread.sleep(defaultTimeout + increase); |
|
|
|
service.checkStates(); |
|
|
|
activityVerify(false); |
|
|
|
|
|
|
|
reset(telemetrySubscriptionService); |
|
|
|
|
|
|
|
service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); |
|
|
|
activityVerify(true); |
|
|
|
Thread.sleep(newTimeout); |
|
|
|
service.checkStates(); |
|
|
|
activityVerify(false); |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void increaseSmallInactivityForInactiveDeviceTest() throws Exception { |
|
|
|
final long defaultTimeout = 1; |
|
|
|
initStateService(defaultTimeout); |
|
|
|
DeviceState deviceState = DeviceState.builder().build(); |
|
|
|
DeviceStateData deviceStateData = DeviceStateData.builder() |
|
|
|
.tenantId(tenantId) |
|
|
|
.deviceId(deviceId) |
|
|
|
.state(deviceState) |
|
|
|
.metaData(new TbMsgMetaData()) |
|
|
|
.build(); |
|
|
|
|
|
|
|
service.deviceStates.put(deviceId, deviceStateData); |
|
|
|
service.getPartitionedEntities(tpi).add(deviceId); |
|
|
|
|
|
|
|
service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); |
|
|
|
activityVerify(true); |
|
|
|
Thread.sleep(defaultTimeout); |
|
|
|
service.checkStates(); |
|
|
|
activityVerify(false); |
|
|
|
|
|
|
|
reset(telemetrySubscriptionService); |
|
|
|
|
|
|
|
long newTimeout = 1; |
|
|
|
Thread.sleep(newTimeout); |
|
|
|
verify(telemetrySubscriptionService, never()).saveAttributes(argThat(request -> |
|
|
|
request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) |
|
|
|
)); |
|
|
|
} |
|
|
|
@MethodSource |
|
|
|
@ParameterizedTest |
|
|
|
void testOnDeviceInactivityTimeoutUpdate(boolean initialActivityStatus, long newInactivityTimeout, boolean expectedActivityStatus) { |
|
|
|
// GIVEN
|
|
|
|
doReturn(200L).when(service).getCurrentTimeMillis(); |
|
|
|
|
|
|
|
@Test |
|
|
|
public void decreaseInactivityForActiveDeviceTest() throws Exception { |
|
|
|
final long defaultTimeout = 1000; |
|
|
|
initStateService(defaultTimeout); |
|
|
|
DeviceState deviceState = DeviceState.builder().build(); |
|
|
|
DeviceStateData deviceStateData = DeviceStateData.builder() |
|
|
|
.tenantId(tenantId) |
|
|
|
.deviceId(deviceId) |
|
|
|
.state(deviceState) |
|
|
|
.metaData(new TbMsgMetaData()) |
|
|
|
var deviceState = DeviceState.builder() |
|
|
|
.active(initialActivityStatus) |
|
|
|
.lastActivityTime(100L) |
|
|
|
.build(); |
|
|
|
|
|
|
|
service.deviceStates.put(deviceId, deviceStateData); |
|
|
|
service.getPartitionedEntities(tpi).add(deviceId); |
|
|
|
|
|
|
|
service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); |
|
|
|
activityVerify(true); |
|
|
|
|
|
|
|
long newTimeout = 1; |
|
|
|
Thread.sleep(newTimeout); |
|
|
|
|
|
|
|
service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout); |
|
|
|
activityVerify(false); |
|
|
|
reset(telemetrySubscriptionService); |
|
|
|
|
|
|
|
service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, defaultTimeout); |
|
|
|
activityVerify(true); |
|
|
|
Thread.sleep(defaultTimeout); |
|
|
|
service.checkStates(); |
|
|
|
activityVerify(false); |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void decreaseInactivityForInactiveDeviceTest() throws Exception { |
|
|
|
final long defaultTimeout = 1000; |
|
|
|
initStateService(defaultTimeout); |
|
|
|
DeviceState deviceState = DeviceState.builder().build(); |
|
|
|
DeviceStateData deviceStateData = DeviceStateData.builder() |
|
|
|
var deviceStateData = DeviceStateData.builder() |
|
|
|
.tenantId(tenantId) |
|
|
|
.deviceId(deviceId) |
|
|
|
.state(deviceState) |
|
|
|
@ -679,31 +564,44 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
service.deviceStates.put(deviceId, deviceStateData); |
|
|
|
service.getPartitionedEntities(tpi).add(deviceId); |
|
|
|
|
|
|
|
service.onDeviceActivity(tenantId, deviceId, System.currentTimeMillis()); |
|
|
|
activityVerify(true); |
|
|
|
Thread.sleep(defaultTimeout); |
|
|
|
service.checkStates(); |
|
|
|
activityVerify(false); |
|
|
|
reset(telemetrySubscriptionService); |
|
|
|
mockSuccessfulSaveAttributes(); |
|
|
|
|
|
|
|
long newTimeout = 1; |
|
|
|
// WHEN
|
|
|
|
service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newInactivityTimeout); |
|
|
|
|
|
|
|
service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newTimeout); |
|
|
|
verify(telemetrySubscriptionService, never()).saveAttributes(argThat(request -> |
|
|
|
request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) |
|
|
|
)); |
|
|
|
// THEN
|
|
|
|
long expectedInactivityTimeout = newInactivityTimeout != 0 ? newInactivityTimeout : defaultInactivityTimeoutMs; |
|
|
|
assertThat(deviceState.getInactivityTimeout()).isEqualTo(expectedInactivityTimeout); |
|
|
|
|
|
|
|
assertThat(deviceState.isActive()).isEqualTo(expectedActivityStatus); |
|
|
|
if (initialActivityStatus != expectedActivityStatus) { |
|
|
|
then(telemetrySubscriptionService).should().saveAttributesInternal(argThat(request -> { |
|
|
|
AttributeKvEntry entry = request.getEntries().get(0); |
|
|
|
return request.getEntityId().equals(deviceId) && entry.getKey().equals(ACTIVITY_STATE) && entry.getValue().equals(expectedActivityStatus); |
|
|
|
})); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private void activityVerify(boolean isActive) { |
|
|
|
verify(telemetrySubscriptionService).saveAttributes(argThat(request -> |
|
|
|
request.getEntityId().equals(deviceId) && |
|
|
|
request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) && |
|
|
|
request.getEntries().get(0).getValue().equals(isActive) |
|
|
|
)); |
|
|
|
// to simplify test, these arguments assume that the current time is 200 and the last activity time is 100
|
|
|
|
private static Stream<Arguments> testOnDeviceInactivityTimeoutUpdate() { |
|
|
|
return Stream.of( |
|
|
|
Arguments.of(true, 1L, false), |
|
|
|
Arguments.of(true, 50L, false), |
|
|
|
Arguments.of(true, 99L, false), |
|
|
|
Arguments.of(true, 100L, false), |
|
|
|
Arguments.of(true, 101L, true), |
|
|
|
Arguments.of(true, 0L, true), // should use default inactivity timeout of 10 minutes
|
|
|
|
Arguments.of(false, 1L, false), |
|
|
|
Arguments.of(false, 50L, false), |
|
|
|
Arguments.of(false, 99L, false), |
|
|
|
Arguments.of(false, 100L, false), |
|
|
|
Arguments.of(false, 101L, true), |
|
|
|
Arguments.of(false, 0L, true) // should use default inactivity timeout of 10 minutes
|
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void givenStateDataIsNull_whenUpdateActivityState_thenShouldCleanupDevice() { |
|
|
|
void givenStateDataIsNull_whenUpdateActivityState_thenShouldCleanupDevice() { |
|
|
|
// GIVEN
|
|
|
|
service.deviceStates.put(deviceId, deviceStateDataMock); |
|
|
|
|
|
|
|
@ -719,7 +617,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
|
|
|
|
@ParameterizedTest |
|
|
|
@MethodSource("provideParametersForUpdateActivityState") |
|
|
|
public void givenTestParameters_whenUpdateActivityState_thenShouldBeInTheExpectedStateAndPerformExpectedActions( |
|
|
|
void givenTestParameters_whenUpdateActivityState_thenShouldBeInTheExpectedStateAndPerformExpectedActions( |
|
|
|
boolean activityState, long previousActivityTime, long lastReportedActivity, long inactivityAlarmTime, |
|
|
|
long expectedInactivityAlarmTime, boolean shouldSetInactivityAlarmTimeToZero, |
|
|
|
boolean shouldUpdateActivityStateToActive |
|
|
|
@ -739,13 +637,15 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
.metaData(new TbMsgMetaData()) |
|
|
|
.build(); |
|
|
|
|
|
|
|
mockSuccessfulSaveAttributes(); |
|
|
|
|
|
|
|
// WHEN
|
|
|
|
service.updateActivityState(deviceId, deviceStateData, lastReportedActivity); |
|
|
|
|
|
|
|
// THEN
|
|
|
|
assertThat(deviceState.isActive()).isEqualTo(true); |
|
|
|
assertThat(deviceState.getLastActivityTime()).isEqualTo(lastReportedActivity); |
|
|
|
then(telemetrySubscriptionService).should().saveAttributes(argThat(request -> |
|
|
|
then(telemetrySubscriptionService).should().saveAttributesInternal(argThat(request -> |
|
|
|
request.getEntityId().equals(deviceId) && |
|
|
|
request.getEntries().get(0).getKey().equals(LAST_ACTIVITY_TIME) && |
|
|
|
request.getEntries().get(0).getValue().equals(lastReportedActivity) |
|
|
|
@ -753,7 +653,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
|
|
|
|
assertThat(deviceState.getLastInactivityAlarmTime()).isEqualTo(expectedInactivityAlarmTime); |
|
|
|
if (shouldSetInactivityAlarmTimeToZero) { |
|
|
|
then(telemetrySubscriptionService).should().saveAttributes(argThat(request -> |
|
|
|
then(telemetrySubscriptionService).should().saveAttributesInternal(argThat(request -> |
|
|
|
request.getEntityId().equals(deviceId) && |
|
|
|
request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME) && |
|
|
|
request.getEntries().get(0).getValue().equals(0L) |
|
|
|
@ -761,7 +661,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
} |
|
|
|
|
|
|
|
if (shouldUpdateActivityStateToActive) { |
|
|
|
then(telemetrySubscriptionService).should().saveAttributes(argThat(request -> |
|
|
|
then(telemetrySubscriptionService).should().saveAttributesInternal(argThat(request -> |
|
|
|
request.getEntityId().equals(deviceId) && |
|
|
|
request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) && |
|
|
|
request.getEntries().get(0).getValue().equals(true) |
|
|
|
@ -809,59 +709,8 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
@ParameterizedTest |
|
|
|
@MethodSource("provideParametersForDecreaseInactivityTimeout") |
|
|
|
public void givenTestParameters_whenOnDeviceInactivityTimeout_thenShouldBeInTheExpectedStateAndPerformExpectedActions( |
|
|
|
boolean activityState, long newInactivityTimeout, long timeIncrement, boolean expectedActivityState |
|
|
|
) throws Exception { |
|
|
|
// GIVEN
|
|
|
|
long defaultInactivityTimeout = 10000; |
|
|
|
initStateService(defaultInactivityTimeout); |
|
|
|
|
|
|
|
var currentTime = new AtomicLong(System.currentTimeMillis()); |
|
|
|
|
|
|
|
DeviceState deviceState = DeviceState.builder() |
|
|
|
.active(activityState) |
|
|
|
.lastActivityTime(currentTime.get()) |
|
|
|
.inactivityTimeout(defaultInactivityTimeout) |
|
|
|
.build(); |
|
|
|
|
|
|
|
DeviceStateData deviceStateData = DeviceStateData.builder() |
|
|
|
.tenantId(tenantId) |
|
|
|
.deviceId(deviceId) |
|
|
|
.state(deviceState) |
|
|
|
.metaData(new TbMsgMetaData()) |
|
|
|
.build(); |
|
|
|
|
|
|
|
service.deviceStates.put(deviceId, deviceStateData); |
|
|
|
service.getPartitionedEntities(tpi).add(deviceId); |
|
|
|
|
|
|
|
given(service.getCurrentTimeMillis()).willReturn(currentTime.addAndGet(timeIncrement)); |
|
|
|
|
|
|
|
// WHEN
|
|
|
|
service.onDeviceInactivityTimeoutUpdate(tenantId, deviceId, newInactivityTimeout); |
|
|
|
|
|
|
|
// THEN
|
|
|
|
assertThat(deviceState.getInactivityTimeout()).isEqualTo(newInactivityTimeout); |
|
|
|
assertThat(deviceState.isActive()).isEqualTo(expectedActivityState); |
|
|
|
if (activityState && !expectedActivityState) { |
|
|
|
then(telemetrySubscriptionService).should().saveAttributes(argThat(request -> |
|
|
|
request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) && |
|
|
|
request.getEntries().get(0).getValue().equals(false) |
|
|
|
)); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private static Stream<Arguments> provideParametersForDecreaseInactivityTimeout() { |
|
|
|
return Stream.of( |
|
|
|
Arguments.of(true, 1, 0, true), |
|
|
|
|
|
|
|
Arguments.of(true, 1, 1, false) |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void givenStateDataIsNull_whenUpdateInactivityTimeoutIfExpired_thenShouldCleanupDevice() { |
|
|
|
void givenStateDataIsNull_whenUpdateInactivityTimeoutIfExpired_thenShouldCleanupDevice() { |
|
|
|
// GIVEN
|
|
|
|
service.deviceStates.put(deviceId, deviceStateDataMock); |
|
|
|
|
|
|
|
@ -875,7 +724,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void givenNotMyPartition_whenUpdateInactivityTimeoutIfExpired_thenShouldCleanupDevice() { |
|
|
|
void givenNotMyPartition_whenUpdateInactivityTimeoutIfExpired_thenShouldCleanupDevice() { |
|
|
|
// GIVEN
|
|
|
|
long currentTime = System.currentTimeMillis(); |
|
|
|
|
|
|
|
@ -911,7 +760,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
|
|
|
|
@ParameterizedTest |
|
|
|
@MethodSource("provideParametersForUpdateInactivityStateIfExpired") |
|
|
|
public void givenTestParameters_whenUpdateInactivityStateIfExpired_thenShouldBeInTheExpectedStateAndPerformExpectedActions( |
|
|
|
void givenTestParameters_whenUpdateInactivityStateIfExpired_thenShouldBeInTheExpectedStateAndPerformExpectedActions( |
|
|
|
boolean activityState, long ts, long lastActivityTime, long lastInactivityAlarmTime, long inactivityTimeout, long deviceCreationTime, |
|
|
|
boolean expectedActivityState, long expectedLastInactivityAlarmTime, boolean shouldUpdateActivityStateToInactive |
|
|
|
) { |
|
|
|
@ -933,6 +782,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
|
|
|
|
if (shouldUpdateActivityStateToInactive) { |
|
|
|
given(partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId)).willReturn(tpi); |
|
|
|
mockSuccessfulSaveAttributes(); |
|
|
|
} |
|
|
|
|
|
|
|
// WHEN
|
|
|
|
@ -943,7 +793,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
assertThat(state.getLastInactivityAlarmTime()).isEqualTo(expectedLastInactivityAlarmTime); |
|
|
|
|
|
|
|
if (shouldUpdateActivityStateToInactive) { |
|
|
|
then(telemetrySubscriptionService).should().saveAttributes(argThat(request -> |
|
|
|
then(telemetrySubscriptionService).should().saveAttributesInternal(argThat(request -> |
|
|
|
request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) && |
|
|
|
request.getEntries().get(0).getValue().equals(false) |
|
|
|
)); |
|
|
|
@ -961,7 +811,7 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
assertThat(actualNotification.getDeviceId()).isEqualTo(deviceId); |
|
|
|
assertThat(actualNotification.isActive()).isFalse(); |
|
|
|
|
|
|
|
then(telemetrySubscriptionService).should().saveAttributes(argThat(request -> |
|
|
|
then(telemetrySubscriptionService).should().saveAttributesInternal(argThat(request -> |
|
|
|
request.getTenantId().equals(tenantId) && request.getEntityId().equals(deviceId) && |
|
|
|
request.getScope().equals(AttributeScope.SERVER_SCOPE) && |
|
|
|
request.getEntries().get(0).getKey().equals(INACTIVITY_ALARM_TIME) && |
|
|
|
@ -1033,7 +883,79 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void givenConcurrentAccess_whenGetOrFetchDeviceStateData_thenFetchDeviceStateDataInvokedOnce() { |
|
|
|
void givenInactiveDevice_whenActivityStatusChangesToActiveButFailedToSaveUpdatedActivityStatus_thenShouldNotUpdateCache2() { |
|
|
|
// GIVEN
|
|
|
|
var deviceState = DeviceState.builder() |
|
|
|
.active(false) |
|
|
|
.lastActivityTime(100L) |
|
|
|
.inactivityTimeout(50L) |
|
|
|
.build(); |
|
|
|
|
|
|
|
var deviceStateData = DeviceStateData.builder() |
|
|
|
.tenantId(tenantId) |
|
|
|
.deviceId(deviceId) |
|
|
|
.state(deviceState) |
|
|
|
.metaData(TbMsgMetaData.EMPTY) |
|
|
|
.build(); |
|
|
|
|
|
|
|
service.deviceStates.put(deviceId, deviceStateData); |
|
|
|
service.getPartitionedEntities(tpi).add(deviceId); |
|
|
|
|
|
|
|
// WHEN-THEN
|
|
|
|
|
|
|
|
// simulating short DB outage
|
|
|
|
given(telemetrySubscriptionService.saveAttributesInternal(any())).willReturn(Futures.immediateFailedFuture(new RuntimeException("failed to save"))); |
|
|
|
doReturn(200L).when(service).getCurrentTimeMillis(); |
|
|
|
service.onDeviceActivity(tenantId, deviceId, 180L); |
|
|
|
assertThat(deviceState.isActive()).isFalse(); // still inactive
|
|
|
|
|
|
|
|
// 10 millis pass... and new activity message it received
|
|
|
|
|
|
|
|
// this time DB save is successful
|
|
|
|
when(telemetrySubscriptionService.saveAttributesInternal(any())).thenReturn(Futures.immediateFuture(AttributesSaveResult.of(generateRandomVersions(1)))); |
|
|
|
doReturn(210L).when(service).getCurrentTimeMillis(); |
|
|
|
service.onDeviceActivity(tenantId, deviceId, 190L); |
|
|
|
assertThat(deviceState.isActive()).isTrue(); |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
void givenActiveDevice_whenActivityStatusChangesToInactiveButFailedToSaveUpdatedActivityStatus_thenShouldNotUpdateCache() { |
|
|
|
// GIVEN
|
|
|
|
var deviceState = DeviceState.builder() |
|
|
|
.active(true) |
|
|
|
.lastActivityTime(100L) |
|
|
|
.inactivityTimeout(50L) |
|
|
|
.build(); |
|
|
|
|
|
|
|
var deviceStateData = DeviceStateData.builder() |
|
|
|
.tenantId(tenantId) |
|
|
|
.deviceId(deviceId) |
|
|
|
.state(deviceState) |
|
|
|
.metaData(TbMsgMetaData.EMPTY) |
|
|
|
.build(); |
|
|
|
|
|
|
|
service.deviceStates.put(deviceId, deviceStateData); |
|
|
|
service.getPartitionedEntities(tpi).add(deviceId); |
|
|
|
|
|
|
|
// WHEN-THEN (assuming periodic activity states check is done every 100 millis)
|
|
|
|
|
|
|
|
// simulating short DB outage
|
|
|
|
given(telemetrySubscriptionService.saveAttributesInternal(any())).willReturn(Futures.immediateFailedFuture(new RuntimeException("failed to save"))); |
|
|
|
doReturn(200L).when(service).getCurrentTimeMillis(); |
|
|
|
service.checkStates(); |
|
|
|
assertThat(deviceState.isActive()).isTrue(); // still active
|
|
|
|
|
|
|
|
// waiting 100 millis... periodic activity states check is triggered again
|
|
|
|
|
|
|
|
// this time DB save is successful
|
|
|
|
when(telemetrySubscriptionService.saveAttributesInternal(any())).thenReturn(Futures.immediateFuture(AttributesSaveResult.of(generateRandomVersions(1)))); |
|
|
|
doReturn(300L).when(service).getCurrentTimeMillis(); |
|
|
|
service.checkStates(); |
|
|
|
assertThat(deviceState.isActive()).isFalse(); |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
void givenConcurrentAccess_whenGetOrFetchDeviceStateData_thenFetchDeviceStateDataInvokedOnce() { |
|
|
|
doAnswer(invocation -> { |
|
|
|
Thread.sleep(100); |
|
|
|
return deviceStateDataMock; |
|
|
|
@ -1069,10 +991,8 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void givenDeviceAdded_whenOnQueueMsg_thenShouldCacheAndSaveActivityToFalse() throws InterruptedException { |
|
|
|
void givenDeviceAdded_whenOnQueueMsg_thenShouldCacheAndSaveActivityToFalse() { |
|
|
|
// GIVEN
|
|
|
|
final long defaultTimeout = 1000; |
|
|
|
initStateService(defaultTimeout); |
|
|
|
given(deviceService.findDeviceById(any(TenantId.class), any(DeviceId.class))).willReturn(new Device(deviceId)); |
|
|
|
given(attributesService.find(any(TenantId.class), any(EntityId.class), any(AttributeScope.class), anyCollection())).willReturn(Futures.immediateFuture(Collections.emptyList())); |
|
|
|
|
|
|
|
@ -1086,13 +1006,15 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
.setDeleted(false) |
|
|
|
.build(); |
|
|
|
|
|
|
|
mockSuccessfulSaveAttributes(); |
|
|
|
|
|
|
|
// WHEN
|
|
|
|
service.onQueueMsg(proto, TbCallback.EMPTY); |
|
|
|
|
|
|
|
// THEN
|
|
|
|
await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { |
|
|
|
assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(false); |
|
|
|
then(telemetrySubscriptionService).should().saveAttributes(argThat(request -> |
|
|
|
then(telemetrySubscriptionService).should().saveAttributesInternal(argThat(request -> |
|
|
|
request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) && |
|
|
|
request.getEntries().get(0).getValue().equals(false) |
|
|
|
)); |
|
|
|
@ -1100,14 +1022,12 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void givenDeviceActivityEventHappenedAfterAdded_whenOnDeviceActivity_thenShouldCacheAndSaveActivityToTrue() throws InterruptedException { |
|
|
|
void givenDeviceActivityEventHappenedAfterAdded_whenOnDeviceActivity_thenShouldCacheAndSaveActivityToTrue() { |
|
|
|
// GIVEN
|
|
|
|
final long defaultTimeout = 1000; |
|
|
|
initStateService(defaultTimeout); |
|
|
|
long currentTime = System.currentTimeMillis(); |
|
|
|
DeviceState deviceState = DeviceState.builder() |
|
|
|
.active(false) |
|
|
|
.inactivityTimeout(service.getDefaultInactivityTimeoutInSec()) |
|
|
|
.inactivityTimeout(defaultInactivityTimeoutMs) |
|
|
|
.build(); |
|
|
|
DeviceStateData stateData = DeviceStateData.builder() |
|
|
|
.tenantId(tenantId) |
|
|
|
@ -1118,12 +1038,14 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
.build(); |
|
|
|
service.deviceStates.put(deviceId, stateData); |
|
|
|
|
|
|
|
mockSuccessfulSaveAttributes(); |
|
|
|
|
|
|
|
// WHEN
|
|
|
|
service.onDeviceActivity(tenantId, deviceId, currentTime); |
|
|
|
|
|
|
|
// THEN
|
|
|
|
ArgumentCaptor<AttributesSaveRequest> attributeRequestCaptor = ArgumentCaptor.forClass(AttributesSaveRequest.class); |
|
|
|
then(telemetrySubscriptionService).should(times(2)).saveAttributes(attributeRequestCaptor.capture()); |
|
|
|
then(telemetrySubscriptionService).should(times(2)).saveAttributesInternal(attributeRequestCaptor.capture()); |
|
|
|
|
|
|
|
await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { |
|
|
|
assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(true); |
|
|
|
@ -1151,15 +1073,14 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void givenDeviceActivityEventHappenedBeforeAdded_whenOnQueueMsg_thenShouldSaveActivityStateUsingValueFromCache() throws InterruptedException { |
|
|
|
void givenDeviceActivityEventHappenedBeforeAdded_whenOnQueueMsg_thenShouldSaveActivityStateUsingValueFromCache() { |
|
|
|
// GIVEN
|
|
|
|
final long defaultTimeout = 1000; |
|
|
|
initStateService(defaultTimeout); |
|
|
|
given(deviceService.findDeviceById(any(TenantId.class), any(DeviceId.class))).willReturn(new Device(deviceId)); |
|
|
|
given(attributesService.find(any(TenantId.class), any(EntityId.class), any(AttributeScope.class), anyCollection())).willReturn(Futures.immediateFuture(Collections.emptyList())); |
|
|
|
|
|
|
|
long currentTime = System.currentTimeMillis(); |
|
|
|
DeviceState deviceState = DeviceState.builder() |
|
|
|
|
|
|
|
var deviceState = DeviceState.builder() |
|
|
|
.active(true) |
|
|
|
.lastConnectTime(currentTime - 8000) |
|
|
|
.lastActivityTime(currentTime - 4000) |
|
|
|
@ -1167,16 +1088,20 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
.lastInactivityAlarmTime(0) |
|
|
|
.inactivityTimeout(3000) |
|
|
|
.build(); |
|
|
|
DeviceStateData stateData = DeviceStateData.builder() |
|
|
|
|
|
|
|
var stateData = DeviceStateData.builder() |
|
|
|
.tenantId(tenantId) |
|
|
|
.deviceId(deviceId) |
|
|
|
.deviceCreationTime(currentTime - 10000) |
|
|
|
.state(deviceState) |
|
|
|
.build(); |
|
|
|
|
|
|
|
service.deviceStates.put(deviceId, stateData); |
|
|
|
|
|
|
|
mockSuccessfulSaveAttributes(); |
|
|
|
|
|
|
|
// WHEN
|
|
|
|
TransportProtos.DeviceStateServiceMsgProto proto = TransportProtos.DeviceStateServiceMsgProto.newBuilder() |
|
|
|
var proto = TransportProtos.DeviceStateServiceMsgProto.newBuilder() |
|
|
|
.setTenantIdMSB(tenantId.getId().getMostSignificantBits()) |
|
|
|
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) |
|
|
|
.setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) |
|
|
|
@ -1190,11 +1115,25 @@ public class DefaultDeviceStateServiceTest { |
|
|
|
// THEN
|
|
|
|
await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> { |
|
|
|
assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(true); |
|
|
|
then(telemetrySubscriptionService).should().saveAttributes(argThat(request -> |
|
|
|
then(telemetrySubscriptionService).should().saveAttributesInternal(argThat(request -> |
|
|
|
request.getEntityId().equals(deviceId) && request.getEntries().get(0).getKey().equals(ACTIVITY_STATE) && |
|
|
|
request.getEntries().get(0).getValue().equals(true) |
|
|
|
)); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
private void mockSuccessfulSaveAttributes() { |
|
|
|
lenient().when(telemetrySubscriptionService.saveAttributesInternal(any())).thenAnswer(invocation -> { |
|
|
|
AttributesSaveRequest request = invocation.getArgument(0); |
|
|
|
return Futures.immediateFuture(generateRandomVersions(request.getEntries().size())); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
private static List<Long> generateRandomVersions(int n) { |
|
|
|
return ThreadLocalRandom.current() |
|
|
|
.longs(n) |
|
|
|
.boxed() |
|
|
|
.toList(); |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|