|
|
@ -15,6 +15,7 @@ |
|
|
*/ |
|
|
*/ |
|
|
package org.thingsboard.server.cf; |
|
|
package org.thingsboard.server.cf; |
|
|
|
|
|
|
|
|
|
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
import org.assertj.core.api.Assertions; |
|
|
import org.assertj.core.api.Assertions; |
|
|
import org.junit.Before; |
|
|
import org.junit.Before; |
|
|
import org.junit.Test; |
|
|
import org.junit.Test; |
|
|
@ -23,11 +24,15 @@ import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; |
|
|
import org.thingsboard.common.util.JacksonUtil; |
|
|
import org.thingsboard.common.util.JacksonUtil; |
|
|
import org.thingsboard.rule.engine.action.TbAlarmResult; |
|
|
import org.thingsboard.rule.engine.action.TbAlarmResult; |
|
|
import org.thingsboard.server.actors.ActorSystemContext; |
|
|
import org.thingsboard.server.actors.ActorSystemContext; |
|
|
|
|
|
import org.thingsboard.server.common.data.AttributeScope; |
|
|
import org.thingsboard.server.common.data.Device; |
|
|
import org.thingsboard.server.common.data.Device; |
|
|
import org.thingsboard.server.common.data.alarm.Alarm; |
|
|
import org.thingsboard.server.common.data.alarm.Alarm; |
|
|
import org.thingsboard.server.common.data.alarm.AlarmSeverity; |
|
|
import org.thingsboard.server.common.data.alarm.AlarmSeverity; |
|
|
import org.thingsboard.server.common.data.alarm.AlarmStatus; |
|
|
import org.thingsboard.server.common.data.alarm.AlarmStatus; |
|
|
import org.thingsboard.server.common.data.alarm.rule.AlarmRule; |
|
|
import org.thingsboard.server.common.data.alarm.rule.AlarmRule; |
|
|
|
|
|
import org.thingsboard.server.common.data.alarm.rule.condition.AlarmConditionValue; |
|
|
|
|
|
import org.thingsboard.server.common.data.alarm.rule.condition.DurationAlarmCondition; |
|
|
|
|
|
import org.thingsboard.server.common.data.alarm.rule.condition.RepeatingAlarmCondition; |
|
|
import org.thingsboard.server.common.data.alarm.rule.condition.SimpleAlarmCondition; |
|
|
import org.thingsboard.server.common.data.alarm.rule.condition.SimpleAlarmCondition; |
|
|
import org.thingsboard.server.common.data.alarm.rule.condition.expression.TbelAlarmConditionExpression; |
|
|
import org.thingsboard.server.common.data.alarm.rule.condition.expression.TbelAlarmConditionExpression; |
|
|
import org.thingsboard.server.common.data.cf.CalculatedField; |
|
|
import org.thingsboard.server.common.data.cf.CalculatedField; |
|
|
@ -56,6 +61,7 @@ import java.util.function.Consumer; |
|
|
import static org.assertj.core.api.Assertions.assertThat; |
|
|
import static org.assertj.core.api.Assertions.assertThat; |
|
|
import static org.testcontainers.shaded.org.awaitility.Awaitility.await; |
|
|
import static org.testcontainers.shaded.org.awaitility.Awaitility.await; |
|
|
|
|
|
|
|
|
|
|
|
@Slf4j |
|
|
@DaoSqlTest |
|
|
@DaoSqlTest |
|
|
public class AlarmRulesTest extends AbstractControllerTest { |
|
|
public class AlarmRulesTest extends AbstractControllerTest { |
|
|
|
|
|
|
|
|
@ -79,15 +85,17 @@ public class AlarmRulesTest extends AbstractControllerTest { |
|
|
public void testCreateAndSeverityUpdateAndClear() throws Exception { |
|
|
public void testCreateAndSeverityUpdateAndClear() throws Exception { |
|
|
Argument temperatureArgument = new Argument(); |
|
|
Argument temperatureArgument = new Argument(); |
|
|
temperatureArgument.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null)); |
|
|
temperatureArgument.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null)); |
|
|
|
|
|
temperatureArgument.setDefaultValue("0"); |
|
|
Map<String, Argument> arguments = Map.of( |
|
|
Map<String, Argument> arguments = Map.of( |
|
|
"temperature", temperatureArgument |
|
|
"temperature", temperatureArgument |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
Map<AlarmSeverity, String> createRules = Map.of( |
|
|
Map<AlarmSeverity, Condition> createRules = Map.of( |
|
|
AlarmSeverity.MAJOR, "return temperature >= 50;", |
|
|
AlarmSeverity.MAJOR, new Condition("return temperature >= 50;", null, null), |
|
|
AlarmSeverity.CRITICAL, "return temperature >= 100;" |
|
|
AlarmSeverity.CRITICAL, new Condition("return temperature >= 100;", null, null) |
|
|
); |
|
|
); |
|
|
String clearRule = "return temperature <= 25;"; |
|
|
|
|
|
|
|
|
Condition clearRule = new Condition("return temperature <= 25;", null, null); |
|
|
CalculatedField calculatedField = createAlarmCf(deviceId, "High Temperature Alarm", |
|
|
CalculatedField calculatedField = createAlarmCf(deviceId, "High Temperature Alarm", |
|
|
arguments, createRules, clearRule); |
|
|
arguments, createRules, clearRule); |
|
|
|
|
|
|
|
|
@ -120,6 +128,71 @@ public class AlarmRulesTest extends AbstractControllerTest { |
|
|
}); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
* todo: state restore (event count) |
|
|
|
|
|
* */ |
|
|
|
|
|
@Test |
|
|
|
|
|
public void testCreateAlarmForRepeatingConditionOnTs() throws Exception { |
|
|
|
|
|
Argument temperatureArgument = new Argument(); |
|
|
|
|
|
temperatureArgument.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null)); |
|
|
|
|
|
temperatureArgument.setDefaultValue("0"); |
|
|
|
|
|
Map<String, Argument> arguments = Map.of( |
|
|
|
|
|
"temperature", temperatureArgument |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
int eventsCountMajor = 5; |
|
|
|
|
|
int eventsCountCritical = 10; |
|
|
|
|
|
Map<AlarmSeverity, Condition> createRules = Map.of( |
|
|
|
|
|
AlarmSeverity.MAJOR, new Condition("return temperature >= 50;", eventsCountMajor, null), |
|
|
|
|
|
AlarmSeverity.CRITICAL, new Condition("return temperature >= 50;", eventsCountCritical, null) |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
CalculatedField calculatedField = createAlarmCf(deviceId, "High Temperature Alarm", |
|
|
|
|
|
arguments, createRules, null); |
|
|
|
|
|
for (int i = 0; i < 4; i++) { |
|
|
|
|
|
postTelemetry(deviceId, "{\"temperature\":50}"); |
|
|
|
|
|
Thread.sleep(10); |
|
|
|
|
|
} |
|
|
|
|
|
assertThat(getLatestAlarmResult(calculatedField.getId())).isNull(); |
|
|
|
|
|
postTelemetry(deviceId, "{\"temperature\":50}"); |
|
|
|
|
|
checkAlarmResult(calculatedField, alarmResult -> { |
|
|
|
|
|
assertThat(alarmResult.isCreated()).isTrue(); |
|
|
|
|
|
assertThat(alarmResult.getAlarm().getSeverity()).isEqualTo(AlarmSeverity.MAJOR); |
|
|
|
|
|
assertThat(alarmResult.getAlarm().getStatus()).isEqualTo(AlarmStatus.ACTIVE_UNACK); |
|
|
|
|
|
assertThat(alarmResult.getConditionRepeats()).isEqualTo(5); |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
public void testCreateAlarmForRepeatingConditionOnAttribute() { |
|
|
|
|
|
Argument temperatureArgument = new Argument(); |
|
|
|
|
|
temperatureArgument.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.ATTRIBUTE, AttributeScope.SHARED_SCOPE)); |
|
|
|
|
|
Map<String, Argument> arguments = Map.of( |
|
|
|
|
|
"temperature", temperatureArgument |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
Map<AlarmSeverity, String> createRules = Map.of( |
|
|
|
|
|
AlarmSeverity.MAJOR, "return temperature >= 50;", |
|
|
|
|
|
AlarmSeverity.CRITICAL, "return temperature >= 100;" |
|
|
|
|
|
); |
|
|
|
|
|
String clearRule = "return temperature <= 25;"; |
|
|
|
|
|
// CalculatedField calculatedField = createAlarmCf(deviceId, "High Temperature Alarm",
|
|
|
|
|
|
// arguments, createRules, clearRule);
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
public void testCreateAlarmForDurationCondition() { |
|
|
|
|
|
Argument temperatureArgument = new Argument(); |
|
|
|
|
|
temperatureArgument.setRefEntityKey(new ReferencedEntityKey("powerConsumption", ArgumentType.TS_LATEST, null)); |
|
|
|
|
|
Map<String, Argument> arguments = Map.of( |
|
|
|
|
|
"powerConsumption", temperatureArgument |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// CalculatedField calculatedField = createAlarmCf(deviceId, "High power consumption during 5 seconds",
|
|
|
|
|
|
// arguments, createRules, nu);
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
private void checkAlarmResult(CalculatedField calculatedField, Consumer<TbAlarmResult> assertion) { |
|
|
private void checkAlarmResult(CalculatedField calculatedField, Consumer<TbAlarmResult> assertion) { |
|
|
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> { |
|
|
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> { |
|
|
TbAlarmResult alarmResult = getLatestAlarmResult(calculatedField.getId()); |
|
|
TbAlarmResult alarmResult = getLatestAlarmResult(calculatedField.getId()); |
|
|
@ -152,34 +225,61 @@ public class AlarmRulesTest extends AbstractControllerTest { |
|
|
private CalculatedField createAlarmCf(EntityId entityId, |
|
|
private CalculatedField createAlarmCf(EntityId entityId, |
|
|
String alarmType, |
|
|
String alarmType, |
|
|
Map<String, Argument> arguments, |
|
|
Map<String, Argument> arguments, |
|
|
Map<AlarmSeverity, String> createConditions, |
|
|
Map<AlarmSeverity, Condition> createConditions, |
|
|
String clearCondition) { |
|
|
Condition clearCondition) { |
|
|
|
|
|
Map<AlarmSeverity, AlarmRule> createRules = new HashMap<>(); |
|
|
|
|
|
createConditions.forEach((severity, condition) -> { |
|
|
|
|
|
createRules.put(severity, toAlarmRule(condition)); |
|
|
|
|
|
}); |
|
|
|
|
|
AlarmRule clearRule = clearCondition != null ? toAlarmRule(clearCondition) : null; |
|
|
|
|
|
CalculatedField calculatedField = createAlarmCf(entityId, alarmType, arguments, createRules, clearRule); |
|
|
|
|
|
|
|
|
|
|
|
CalculatedFieldDebugEvent debugEvent = await().atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> getDebugEvents(calculatedField.getId(), 1), events -> !events.isEmpty()).get(0); |
|
|
|
|
|
latestEventId = debugEvent.getId(); |
|
|
|
|
|
return calculatedField; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private CalculatedField createAlarmCf(EntityId entityId, |
|
|
|
|
|
String alarmType, |
|
|
|
|
|
Map<String, Argument> arguments, |
|
|
|
|
|
Map<AlarmSeverity, AlarmRule> createRules, |
|
|
|
|
|
AlarmRule clearRule) { |
|
|
CalculatedField calculatedField = new CalculatedField(); |
|
|
CalculatedField calculatedField = new CalculatedField(); |
|
|
calculatedField.setEntityId(entityId); |
|
|
calculatedField.setEntityId(entityId); |
|
|
calculatedField.setName(alarmType); |
|
|
calculatedField.setName(alarmType); |
|
|
calculatedField.setType(CalculatedFieldType.ALARM); |
|
|
calculatedField.setType(CalculatedFieldType.ALARM); |
|
|
AlarmCalculatedFieldConfiguration configuration = new AlarmCalculatedFieldConfiguration(); |
|
|
AlarmCalculatedFieldConfiguration configuration = new AlarmCalculatedFieldConfiguration(); |
|
|
configuration.setArguments(arguments); |
|
|
configuration.setArguments(arguments); |
|
|
configuration.setCreateRules(new HashMap<>()); |
|
|
configuration.setCreateRules(createRules); |
|
|
createConditions.forEach((severity, expression) -> { |
|
|
configuration.setClearRule(clearRule); |
|
|
configuration.getCreateRules().put(severity, toAlarmRule(expression)); |
|
|
|
|
|
}); |
|
|
|
|
|
configuration.setClearRule(toAlarmRule(clearCondition)); |
|
|
|
|
|
calculatedField.setConfiguration(configuration); |
|
|
calculatedField.setConfiguration(configuration); |
|
|
calculatedField.setDebugSettings(DebugSettings.all()); |
|
|
calculatedField.setDebugSettings(DebugSettings.all()); |
|
|
return saveCalculatedField(calculatedField); |
|
|
return saveCalculatedField(calculatedField); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
private AlarmRule toAlarmRule(String conditionExpression) { |
|
|
private AlarmRule toAlarmRule(Condition condition) { |
|
|
if (conditionExpression == null) { |
|
|
|
|
|
return null; |
|
|
|
|
|
} |
|
|
|
|
|
AlarmRule rule = new AlarmRule(); |
|
|
AlarmRule rule = new AlarmRule(); |
|
|
SimpleAlarmCondition condition = new SimpleAlarmCondition(); |
|
|
|
|
|
TbelAlarmConditionExpression expression = new TbelAlarmConditionExpression(); |
|
|
TbelAlarmConditionExpression expression = new TbelAlarmConditionExpression(); |
|
|
expression.setExpression(conditionExpression); |
|
|
expression.setExpression(condition.expression()); |
|
|
condition.setExpression(expression); |
|
|
if (condition.eventsCount() != null) { |
|
|
rule.setCondition(condition); |
|
|
RepeatingAlarmCondition alarmCondition = new RepeatingAlarmCondition(); |
|
|
|
|
|
alarmCondition.setExpression(expression); |
|
|
|
|
|
AlarmConditionValue<Integer> count = new AlarmConditionValue<>(); |
|
|
|
|
|
count.setStaticValue(condition.eventsCount()); |
|
|
|
|
|
alarmCondition.setCount(count); |
|
|
|
|
|
rule.setCondition(alarmCondition); |
|
|
|
|
|
} else if (condition.durationMs() != null) { |
|
|
|
|
|
DurationAlarmCondition alarmCondition = new DurationAlarmCondition(); |
|
|
|
|
|
alarmCondition.setExpression(expression); |
|
|
|
|
|
AlarmConditionValue<Long> duration = new AlarmConditionValue<>(); |
|
|
|
|
|
duration.setStaticValue(condition.durationMs()); |
|
|
|
|
|
alarmCondition.setValue(duration); |
|
|
|
|
|
rule.setCondition(alarmCondition); |
|
|
|
|
|
} else { |
|
|
|
|
|
SimpleAlarmCondition alarmCondition = new SimpleAlarmCondition(); |
|
|
|
|
|
alarmCondition.setExpression(expression); |
|
|
|
|
|
rule.setCondition(alarmCondition); |
|
|
|
|
|
} |
|
|
return rule; |
|
|
return rule; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@ -188,4 +288,6 @@ public class AlarmRulesTest extends AbstractControllerTest { |
|
|
.map(e -> (CalculatedFieldDebugEvent) e).toList(); |
|
|
.map(e -> (CalculatedFieldDebugEvent) e).toList(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
private record Condition(String expression, Integer eventsCount, Long durationMs) {} |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|