Merge pull request #11322 from YevhenBondarenko/feature/re-strategy-test
added TbRuleEngineStrategyTest
This commit is contained in:
commit
ee00e5c8d2
@ -0,0 +1,286 @@
|
||||
/**
|
||||
* Copyright © 2016-2024 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.queue.ruleengine;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoSettings;
|
||||
import org.mockito.quality.Strictness;
|
||||
import org.thingsboard.server.actors.ActorSystemContext;
|
||||
import org.thingsboard.server.common.data.id.DeviceId;
|
||||
import org.thingsboard.server.common.data.queue.ProcessingStrategy;
|
||||
import org.thingsboard.server.common.data.queue.ProcessingStrategyType;
|
||||
import org.thingsboard.server.common.data.queue.Queue;
|
||||
import org.thingsboard.server.common.data.queue.SubmitStrategy;
|
||||
import org.thingsboard.server.common.data.queue.SubmitStrategyType;
|
||||
import org.thingsboard.server.common.msg.TbMsg;
|
||||
import org.thingsboard.server.common.msg.TbMsgDataType;
|
||||
import org.thingsboard.server.common.msg.TbMsgProcessingCtx;
|
||||
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
|
||||
import org.thingsboard.server.common.msg.queue.RuleEngineException;
|
||||
import org.thingsboard.server.common.msg.queue.ServiceType;
|
||||
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
|
||||
import org.thingsboard.server.queue.TbQueueConsumer;
|
||||
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
|
||||
import org.thingsboard.server.queue.discovery.QueueKey;
|
||||
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingStrategyFactory;
|
||||
import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategyFactory;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.thingsboard.server.common.data.queue.ProcessingStrategyType.RETRY_ALL;
|
||||
import static org.thingsboard.server.common.data.queue.ProcessingStrategyType.RETRY_FAILED;
|
||||
import static org.thingsboard.server.common.data.queue.ProcessingStrategyType.RETRY_FAILED_AND_TIMED_OUT;
|
||||
import static org.thingsboard.server.common.data.queue.ProcessingStrategyType.RETRY_TIMED_OUT;
|
||||
import static org.thingsboard.server.common.data.queue.ProcessingStrategyType.SKIP_ALL_FAILURES;
|
||||
import static org.thingsboard.server.common.data.queue.ProcessingStrategyType.SKIP_ALL_FAILURES_AND_TIMED_OUT;
|
||||
import static org.thingsboard.server.common.data.queue.SubmitStrategyType.BATCH;
|
||||
import static org.thingsboard.server.common.data.queue.SubmitStrategyType.BURST;
|
||||
import static org.thingsboard.server.common.data.queue.SubmitStrategyType.SEQUENTIAL_BY_ORIGINATOR;
|
||||
|
||||
@Slf4j
|
||||
@MockitoSettings(strictness = Strictness.LENIENT)
|
||||
public class TbRuleEngineStrategyTest {
|
||||
|
||||
@Mock
|
||||
private ActorSystemContext actorContext;
|
||||
@Mock
|
||||
private TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>> consumer;
|
||||
private TbRuleEngineConsumerContext ruleEngineConsumerContext;
|
||||
|
||||
private static UUID tenantId = UUID.randomUUID();
|
||||
private static DeviceId deviceId = new DeviceId(UUID.randomUUID());
|
||||
|
||||
@BeforeEach
|
||||
public void beforeEach() {
|
||||
ruleEngineConsumerContext = new TbRuleEngineConsumerContext(
|
||||
actorContext, mock(), new TbRuleEngineSubmitStrategyFactory(),
|
||||
new TbRuleEngineProcessingStrategyFactory(), mock(), mock(),
|
||||
mock(), mock(), mock(), mock()
|
||||
);
|
||||
when(consumer.isStopped()).thenReturn(false);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> testData() {
|
||||
return Stream.of(
|
||||
//BURST
|
||||
Arguments.of(BURST, SKIP_ALL_FAILURES, 3, List.of(new ProcessingData(1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BURST, SKIP_ALL_FAILURES, 3, List.of(new ProcessingData(true, false, 1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BURST, SKIP_ALL_FAILURES, 3, List.of(new ProcessingData(false, true, 1), new ProcessingData(1), new ProcessingData(1))),
|
||||
|
||||
Arguments.of(BURST, SKIP_ALL_FAILURES_AND_TIMED_OUT, 3, List.of(new ProcessingData(1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BURST, SKIP_ALL_FAILURES_AND_TIMED_OUT, 3, List.of(new ProcessingData(true, false, 1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BURST, SKIP_ALL_FAILURES_AND_TIMED_OUT, 3, List.of(new ProcessingData(false, true, 1), new ProcessingData(1), new ProcessingData(1))),
|
||||
|
||||
Arguments.of(BURST, RETRY_ALL, 3, List.of(new ProcessingData(1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BURST, RETRY_ALL, 3, List.of(new ProcessingData(true, false, 2), new ProcessingData(2), new ProcessingData(2))),
|
||||
Arguments.of(BURST, RETRY_ALL, 3, List.of(new ProcessingData(false, true, 2), new ProcessingData(2), new ProcessingData(2))),
|
||||
|
||||
Arguments.of(BURST, RETRY_FAILED, 3, List.of(new ProcessingData(1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BURST, RETRY_FAILED, 3, List.of(new ProcessingData(true, false, 2), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BURST, RETRY_FAILED, 3, List.of(new ProcessingData(false, true, 1), new ProcessingData(1), new ProcessingData(1))),
|
||||
|
||||
Arguments.of(BURST, RETRY_TIMED_OUT, 3, List.of(new ProcessingData(1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BURST, RETRY_TIMED_OUT, 3, List.of(new ProcessingData(true, false, 1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BURST, RETRY_TIMED_OUT, 3, List.of(new ProcessingData(false, true, 2), new ProcessingData(1), new ProcessingData(1))),
|
||||
|
||||
Arguments.of(BURST, RETRY_FAILED_AND_TIMED_OUT, 3, List.of(new ProcessingData(1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BURST, RETRY_FAILED_AND_TIMED_OUT, 3, List.of(new ProcessingData(true, false, 2), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BURST, RETRY_FAILED_AND_TIMED_OUT, 3, List.of(new ProcessingData(false, true, 2), new ProcessingData(1), new ProcessingData(1))),
|
||||
|
||||
//BATCH
|
||||
Arguments.of(BATCH, SKIP_ALL_FAILURES, 3, List.of(new ProcessingData(1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BATCH, SKIP_ALL_FAILURES, 3, List.of(new ProcessingData(true, false, 1), new ProcessingData(1), new ProcessingData(0))),
|
||||
Arguments.of(BATCH, SKIP_ALL_FAILURES, 3, List.of(new ProcessingData(false, true, 1), new ProcessingData(1), new ProcessingData(0))),
|
||||
|
||||
Arguments.of(BATCH, SKIP_ALL_FAILURES_AND_TIMED_OUT, 3, List.of(new ProcessingData(1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BATCH, SKIP_ALL_FAILURES_AND_TIMED_OUT, 3, List.of(new ProcessingData(true, false, 1), new ProcessingData(1), new ProcessingData(0))),
|
||||
Arguments.of(BATCH, SKIP_ALL_FAILURES_AND_TIMED_OUT, 3, List.of(new ProcessingData(false, true, 1), new ProcessingData(1), new ProcessingData(0))),
|
||||
|
||||
Arguments.of(BATCH, RETRY_ALL, 3, List.of(new ProcessingData(1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BATCH, RETRY_ALL, 3, List.of(new ProcessingData(true, false, 2), new ProcessingData(2), new ProcessingData(1))),
|
||||
Arguments.of(BATCH, RETRY_ALL, 3, List.of(new ProcessingData(false, true, 2), new ProcessingData(2), new ProcessingData(1))),
|
||||
|
||||
Arguments.of(BATCH, RETRY_FAILED, 3, List.of(new ProcessingData(1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BATCH, RETRY_FAILED, 3, List.of(new ProcessingData(true, false, 2), new ProcessingData(1), new ProcessingData(0))),
|
||||
Arguments.of(BATCH, RETRY_FAILED, 3, List.of(new ProcessingData(false, true, 1), new ProcessingData(1), new ProcessingData(0))),
|
||||
|
||||
Arguments.of(BATCH, RETRY_TIMED_OUT, 3, List.of(new ProcessingData(1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BATCH, RETRY_TIMED_OUT, 3, List.of(new ProcessingData(true, false, 1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BATCH, RETRY_TIMED_OUT, 3, List.of(new ProcessingData(false, true, 2), new ProcessingData(1), new ProcessingData(1))),
|
||||
|
||||
Arguments.of(BATCH, RETRY_FAILED_AND_TIMED_OUT, 3, List.of(new ProcessingData(1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BATCH, RETRY_FAILED_AND_TIMED_OUT, 3, List.of(new ProcessingData(true, false, 2), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(BATCH, RETRY_FAILED_AND_TIMED_OUT, 3, List.of(new ProcessingData(false, true, 2), new ProcessingData(1), new ProcessingData(1))),
|
||||
|
||||
//SEQUENTIAL_BY_ORIGINATOR
|
||||
Arguments.of(SEQUENTIAL_BY_ORIGINATOR, SKIP_ALL_FAILURES, 3, List.of(new ProcessingData(1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(SEQUENTIAL_BY_ORIGINATOR, SKIP_ALL_FAILURES, 3, List.of(new ProcessingData(true, false, 1), new ProcessingData(0), new ProcessingData(0))),
|
||||
Arguments.of(SEQUENTIAL_BY_ORIGINATOR, SKIP_ALL_FAILURES, 3, List.of(new ProcessingData(false, true, 1), new ProcessingData(0), new ProcessingData(0))),
|
||||
|
||||
Arguments.of(SEQUENTIAL_BY_ORIGINATOR, SKIP_ALL_FAILURES_AND_TIMED_OUT, 3, List.of(new ProcessingData(1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(SEQUENTIAL_BY_ORIGINATOR, SKIP_ALL_FAILURES_AND_TIMED_OUT, 3, List.of(new ProcessingData(true, false, 1), new ProcessingData(0), new ProcessingData(0))),
|
||||
Arguments.of(SEQUENTIAL_BY_ORIGINATOR, SKIP_ALL_FAILURES_AND_TIMED_OUT, 3, List.of(new ProcessingData(false, true, 1), new ProcessingData(0), new ProcessingData(0))),
|
||||
|
||||
Arguments.of(SEQUENTIAL_BY_ORIGINATOR, RETRY_ALL, 3, List.of(new ProcessingData(1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(SEQUENTIAL_BY_ORIGINATOR, RETRY_ALL, 3, List.of(new ProcessingData(true, false, 2), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(SEQUENTIAL_BY_ORIGINATOR, RETRY_ALL, 3, List.of(new ProcessingData(false, true, 2), new ProcessingData(1), new ProcessingData(1))),
|
||||
|
||||
Arguments.of(SEQUENTIAL_BY_ORIGINATOR, RETRY_FAILED, 3, List.of(new ProcessingData(1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(SEQUENTIAL_BY_ORIGINATOR, RETRY_FAILED, 3, List.of(new ProcessingData(true, false, 2), new ProcessingData(0), new ProcessingData(0))),
|
||||
Arguments.of(SEQUENTIAL_BY_ORIGINATOR, RETRY_FAILED, 3, List.of(new ProcessingData(false, true, 1), new ProcessingData(0), new ProcessingData(0))),
|
||||
|
||||
Arguments.of(SEQUENTIAL_BY_ORIGINATOR, RETRY_TIMED_OUT, 3, List.of(new ProcessingData(1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(SEQUENTIAL_BY_ORIGINATOR, RETRY_TIMED_OUT, 3, List.of(new ProcessingData(true, false, 1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(SEQUENTIAL_BY_ORIGINATOR, RETRY_TIMED_OUT, 3, List.of(new ProcessingData(false, true, 2), new ProcessingData(1), new ProcessingData(1))),
|
||||
|
||||
Arguments.of(SEQUENTIAL_BY_ORIGINATOR, RETRY_FAILED_AND_TIMED_OUT, 3, List.of(new ProcessingData(1), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(SEQUENTIAL_BY_ORIGINATOR, RETRY_FAILED_AND_TIMED_OUT, 3, List.of(new ProcessingData(true, false, 2), new ProcessingData(1), new ProcessingData(1))),
|
||||
Arguments.of(SEQUENTIAL_BY_ORIGINATOR, RETRY_FAILED_AND_TIMED_OUT, 3, List.of(new ProcessingData(false, true, 2), new ProcessingData(1), new ProcessingData(1)))
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("testData")
|
||||
public void processMsgsTest(SubmitStrategyType submitStrategyType, ProcessingStrategyType processingStrategyType, int retries, List<ProcessingData> processingData) throws Exception {
|
||||
var queue = new Queue();
|
||||
queue.setName("Test");
|
||||
queue.setPackProcessingTimeout(100);
|
||||
SubmitStrategy submitStrategy = new SubmitStrategy();
|
||||
submitStrategy.setType(submitStrategyType);
|
||||
submitStrategy.setBatchSize(2);
|
||||
queue.setSubmitStrategy(submitStrategy);
|
||||
ProcessingStrategy processingStrategy = new ProcessingStrategy();
|
||||
processingStrategy.setType(processingStrategyType);
|
||||
processingStrategy.setRetries(retries);
|
||||
queue.setProcessingStrategy(processingStrategy);
|
||||
|
||||
QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, queue);
|
||||
var consumerManager = TbRuleEngineQueueConsumerManager.create()
|
||||
.ctx(ruleEngineConsumerContext)
|
||||
.queueKey(queueKey)
|
||||
.build();
|
||||
|
||||
consumerManager.init(queue);
|
||||
|
||||
Map<UUID, ProcessingData> msgsMap = processingData.stream().collect(Collectors.toMap(data -> data.tbMsg.getId(), data -> data));
|
||||
Map<UUID, AtomicInteger> attemptsMap = new HashMap<>();
|
||||
Map<UUID, AtomicBoolean> failedMap = new HashMap<>();
|
||||
Map<UUID, AtomicBoolean> timeoutMap = new HashMap<>();
|
||||
|
||||
doAnswer(inv -> {
|
||||
QueueToRuleEngineMsg msg = inv.getArgument(0);
|
||||
|
||||
UUID msgId = msg.getMsg().getId();
|
||||
var data = msgsMap.get(msgId);
|
||||
|
||||
var attempts = attemptsMap.computeIfAbsent(msgId, key -> new AtomicInteger(0));
|
||||
Assertions.assertTrue(attempts.getAndIncrement() <= data.attempts);
|
||||
|
||||
var callback = msg.getMsg().getCallback();
|
||||
|
||||
if (data.shouldFailed) {
|
||||
var alreadyFailed = failedMap.computeIfAbsent(msgId, key -> new AtomicBoolean(false));
|
||||
if (!alreadyFailed.getAndSet(true)) {
|
||||
callback.onFailure(new RuleEngineException("Failed to process test msg!"));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.shouldTimeout) {
|
||||
var alreadyTimedOuted = timeoutMap.computeIfAbsent(msgId, key -> new AtomicBoolean(false));
|
||||
if (!alreadyTimedOuted.getAndSet(true)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
callback.onSuccess();
|
||||
return null;
|
||||
}).when(actorContext).tell(any());
|
||||
|
||||
List<TbProtoQueueMsg<ToRuleEngineMsg>> protoMsgs = processingData.stream()
|
||||
.map(data -> data.tbMsg)
|
||||
.map(this::toProto)
|
||||
.toList();
|
||||
|
||||
consumerManager.processMsgs(protoMsgs, consumer, queue);
|
||||
|
||||
processingData.forEach(data -> {
|
||||
verify(actorContext, times(data.attempts)).tell(argThat(msg ->
|
||||
((QueueToRuleEngineMsg) msg).getMsg().getId().equals(data.tbMsg.getId())
|
||||
));
|
||||
});
|
||||
}
|
||||
|
||||
private static TbMsg createRandomMsg() {
|
||||
return TbMsg.builder()
|
||||
.id(UUID.randomUUID())
|
||||
.type("test type")
|
||||
.originator(deviceId)
|
||||
.dataType(TbMsgDataType.TEXT)
|
||||
.data("test data")
|
||||
.ctx(new TbMsgProcessingCtx())
|
||||
.build();
|
||||
}
|
||||
|
||||
private TbProtoQueueMsg<ToRuleEngineMsg> toProto(TbMsg tbMsg) {
|
||||
return new TbProtoQueueMsg<>(UUID.randomUUID(), ToRuleEngineMsg.newBuilder()
|
||||
.setTenantIdMSB(tenantId.getMostSignificantBits())
|
||||
.setTenantIdLSB(tenantId.getLeastSignificantBits())
|
||||
.addRelationTypes("Success")
|
||||
.setTbMsg(TbMsg.toByteString(tbMsg))
|
||||
.build());
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@EqualsAndHashCode
|
||||
@ToString(exclude = "tbMsg")
|
||||
private static class ProcessingData {
|
||||
private final TbMsg tbMsg = createRandomMsg();
|
||||
private final boolean shouldFailed;
|
||||
private final boolean shouldTimeout;
|
||||
private final int attempts;
|
||||
|
||||
public ProcessingData(int attempts) {
|
||||
shouldFailed = false;
|
||||
shouldTimeout = false;
|
||||
this.attempts = attempts;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user