Browse Source

Added Readiness Status for CF state

pull/14208/head
dshvaika 11 months ago
parent
commit
78c4892ad4
  1. 6
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java
  2. 18
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java
  3. 31
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java
  4. 3
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SingleValueArgumentEntry.java
  5. 3
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationArgumentEntry.java
  6. 13
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationCalculatedFieldState.java
  7. 6
      application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldStateTest.java
  8. 16
      application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationArgumentEntryTest.java
  9. 10
      application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationCalculatedFieldStateTest.java
  10. 6
      application/src/test/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldStateTest.java
  11. 6
      application/src/test/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldStateTest.java

6
application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java

@ -399,7 +399,7 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
CalculatedFieldEntityCtxId ctxId = new CalculatedFieldEntityCtxId(tenantId, ctx.getCfId(), entityId); CalculatedFieldEntityCtxId ctxId = new CalculatedFieldEntityCtxId(tenantId, ctx.getCfId(), entityId);
boolean stateSizeChecked = false; boolean stateSizeChecked = false;
try { try {
if (ctx.isInitialized() && state.isReady()) { if (ctx.isInitialized() && state.getReadinessStatus().status()) {
log.trace("[{}][{}] Performing calculation. Updated args: {}", entityId, ctx.getCfId(), updatedArgs); log.trace("[{}][{}] Performing calculation. Updated args: {}", entityId, ctx.getCfId(), updatedArgs);
CalculatedFieldResult calculationResult = state.performCalculation(updatedArgs, ctx).get(systemContext.getCfCalculationResultTimeout(), TimeUnit.SECONDS); CalculatedFieldResult calculationResult = state.performCalculation(updatedArgs, ctx).get(systemContext.getCfCalculationResultTimeout(), TimeUnit.SECONDS);
state.checkStateSize(ctxId, ctx.getMaxStateSize()); state.checkStateSize(ctxId, ctx.getMaxStateSize());
@ -415,6 +415,10 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
} }
} }
} else { } else {
if (DebugModeUtil.isDebugFailuresAvailable(ctx.getCalculatedField())) {
String errorMsg = ctx.isInitialized() ? state.getReadinessStatus().reason() : "Calculated field state is not initialized!";
systemContext.persistCalculatedFieldDebugEvent(tenantId, ctx.getCfId(), entityId, state.getArguments(), tbMsgId, tbMsgType, null, errorMsg);
}
callback.onSuccess(); callback.onSuccess();
} }
} catch (Exception e) { } catch (Exception e) {

18
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java

@ -26,6 +26,7 @@ import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
import org.thingsboard.server.utils.CalculatedFieldUtils; import org.thingsboard.server.utils.CalculatedFieldUtils;
import java.io.Closeable; import java.io.Closeable;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -107,9 +108,20 @@ public abstract class BaseCalculatedFieldState implements CalculatedFieldState,
} }
@Override @Override
public boolean isReady() { public ReadinessStatus getReadinessStatus() {
return arguments.keySet().containsAll(requiredArguments) && List<String> missing = new ArrayList<>(requiredArguments);
arguments.values().stream().noneMatch(ArgumentEntry::isEmpty); missing.removeAll(arguments.keySet());
if (!missing.isEmpty()) {
return ReadinessStatus.missingRequiredArguments(missing);
}
List<String> emptyArgs = arguments.entrySet().stream()
.filter(e -> e.getValue() == null || e.getValue().isEmpty())
.map(Map.Entry::getKey)
.toList();
if (!emptyArgs.isEmpty()) {
return ReadinessStatus.emptyArguments(emptyArgs);
}
return ReadinessStatus.ready();
} }
@Override @Override

31
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java

@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type; import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import jakarta.annotation.Nullable;
import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -32,6 +33,7 @@ import org.thingsboard.server.service.cf.ctx.state.geofencing.GeofencingCalculat
import org.thingsboard.server.service.cf.ctx.state.propagation.PropagationCalculatedFieldState; import org.thingsboard.server.service.cf.ctx.state.propagation.PropagationCalculatedFieldState;
import java.io.Closeable; import java.io.Closeable;
import java.util.List;
import java.util.Map; import java.util.Map;
import static org.thingsboard.server.utils.CalculatedFieldUtils.toSingleValueArgumentProto; import static org.thingsboard.server.utils.CalculatedFieldUtils.toSingleValueArgumentProto;
@ -66,7 +68,7 @@ public interface CalculatedFieldState extends Closeable {
ListenableFuture<CalculatedFieldResult> performCalculation(Map<String, ArgumentEntry> updatedArgs, CalculatedFieldCtx ctx); ListenableFuture<CalculatedFieldResult> performCalculation(Map<String, ArgumentEntry> updatedArgs, CalculatedFieldCtx ctx);
@JsonIgnore @JsonIgnore
boolean isReady(); ReadinessStatus getReadinessStatus();
boolean isSizeExceedsLimit(); boolean isSizeExceedsLimit();
@ -92,4 +94,31 @@ public interface CalculatedFieldState extends Closeable {
} }
} }
record ReadinessStatus(boolean status, @Nullable String reason) {
private static final String MISSING_REQUIRED_ARGUMENTS = "Missing required arguments: ";
private static final String EMPTY_ARGUMENTS = "Empty arguments: ";
public static ReadinessStatus ready() {
return new ReadinessStatus(true, null);
}
public static ReadinessStatus notReady(String reason) {
return new ReadinessStatus(false, reason);
}
public static ReadinessStatus missingRequiredArguments(List<String> missingArgument) {
return notReady(MISSING_REQUIRED_ARGUMENTS + stringValue(missingArgument));
}
private static String stringValue(List<String> missingArgument) {
return String.join(", ", missingArgument);
}
public static ReadinessStatus emptyArguments(List<String> emptyArguments) {
return notReady(EMPTY_ARGUMENTS + stringValue(emptyArguments));
}
}
} }

3
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/SingleValueArgumentEntry.java

@ -95,6 +95,9 @@ public class SingleValueArgumentEntry implements ArgumentEntry {
@Override @Override
public TbelCfArg toTbelCfArg() { public TbelCfArg toTbelCfArg() {
if (isEmpty()) {
return new TbelCfSingleValueArg(ts, null);
}
Object value = kvEntryValue.getValue(); Object value = kvEntryValue.getValue();
if (kvEntryValue instanceof JsonDataEntry) { if (kvEntryValue instanceof JsonDataEntry) {
try { try {

3
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationArgumentEntry.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.util.CollectionsUtil;
import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry; import org.thingsboard.server.service.cf.ctx.state.ArgumentEntry;
import org.thingsboard.server.service.cf.ctx.state.ArgumentEntryType; import org.thingsboard.server.service.cf.ctx.state.ArgumentEntryType;
import java.util.ArrayList;
import java.util.List; import java.util.List;
@Data @Data
@ -33,7 +34,7 @@ public class PropagationArgumentEntry implements ArgumentEntry {
private boolean forceResetPrevious; private boolean forceResetPrevious;
public PropagationArgumentEntry(List<EntityId> propagationEntityIds) { public PropagationArgumentEntry(List<EntityId> propagationEntityIds) {
this.propagationEntityIds = propagationEntityIds; this.propagationEntityIds = new ArrayList<>(propagationEntityIds);
} }
@Override @Override

13
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationCalculatedFieldState.java

@ -33,6 +33,7 @@ import org.thingsboard.server.service.cf.ctx.state.CalculatedFieldCtx;
import org.thingsboard.server.service.cf.ctx.state.ScriptCalculatedFieldState; import org.thingsboard.server.service.cf.ctx.state.ScriptCalculatedFieldState;
import org.thingsboard.server.service.cf.ctx.state.SingleValueArgumentEntry; import org.thingsboard.server.service.cf.ctx.state.SingleValueArgumentEntry;
import java.util.ArrayList;
import java.util.Map; import java.util.Map;
import static org.thingsboard.server.common.data.cf.configuration.PropagationCalculatedFieldConfiguration.PROPAGATION_CONFIG_ARGUMENT; import static org.thingsboard.server.common.data.cf.configuration.PropagationCalculatedFieldConfiguration.PROPAGATION_CONFIG_ARGUMENT;
@ -47,21 +48,13 @@ public class PropagationCalculatedFieldState extends ScriptCalculatedFieldState
public void setCtx(CalculatedFieldCtx ctx, TbActorRef actorCtx) { public void setCtx(CalculatedFieldCtx ctx, TbActorRef actorCtx) {
this.ctx = ctx; this.ctx = ctx;
this.actorCtx = actorCtx; this.actorCtx = actorCtx;
this.requiredArguments = ctx.getArgNames(); this.requiredArguments = new ArrayList<>(ctx.getArgNames());
requiredArguments.add(PROPAGATION_CONFIG_ARGUMENT);
if (ctx.isApplyExpressionForResolvedArguments()) { if (ctx.isApplyExpressionForResolvedArguments()) {
this.tbelExpression = ctx.getTbelExpressions().get(ctx.getExpression()); this.tbelExpression = ctx.getTbelExpressions().get(ctx.getExpression());
} }
} }
@Override
public boolean isReady() {
if (!super.isReady()) {
return false;
}
ArgumentEntry propagationArg = arguments.get(PROPAGATION_CONFIG_ARGUMENT);
return propagationArg != null && !propagationArg.isEmpty();
}
@Override @Override
public CalculatedFieldType getType() { public CalculatedFieldType getType() {
return CalculatedFieldType.PROPAGATION; return CalculatedFieldType.PROPAGATION;

6
application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldStateTest.java

@ -199,7 +199,7 @@ public class GeofencingCalculatedFieldStateTest {
@Test @Test
void testIsReadyWhenNotAllArgPresent() { void testIsReadyWhenNotAllArgPresent() {
assertThat(state.isReady()).isFalse(); assertThat(state.getReadinessStatus().status()).isFalse();
} }
@Test @Test
@ -210,7 +210,7 @@ public class GeofencingCalculatedFieldStateTest {
"allowedZones", geofencingAllowedZoneArgEntry, "allowedZones", geofencingAllowedZoneArgEntry,
"restrictedZones", geofencingRestrictedZoneArgEntry "restrictedZones", geofencingRestrictedZoneArgEntry
)); ));
assertThat(state.isReady()).isTrue(); assertThat(state.getReadinessStatus().status()).isTrue();
} }
@Test @Test
@ -224,7 +224,7 @@ public class GeofencingCalculatedFieldStateTest {
state.getArguments().put("noParkingZones", new GeofencingArgumentEntry()); state.getArguments().put("noParkingZones", new GeofencingArgumentEntry());
assertThat(state.isReady()).isFalse(); assertThat(state.getReadinessStatus().status()).isFalse();
} }
@Test @Test

16
application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationArgumentEntryTest.java

@ -57,12 +57,6 @@ public class PropagationArgumentEntryTest {
assertThat(emptyEntry.isEmpty()).isTrue(); assertThat(emptyEntry.isEmpty()).isTrue();
} }
@Test
void testIsEmptyWhenNullList() {
PropagationArgumentEntry nullListEntry = new PropagationArgumentEntry(null);
assertThat(nullListEntry.isEmpty()).isTrue();
}
@Test @Test
void testGetValueReturnsPropagationIds() { void testGetValueReturnsPropagationIds() {
assertThat(entry.getValue()).isInstanceOf(List.class); assertThat(entry.getValue()).isInstanceOf(List.class);
@ -106,16 +100,6 @@ public class PropagationArgumentEntryTest {
assertThat(entry.getPropagationEntityIds()).isEmpty(); assertThat(entry.getPropagationEntityIds()).isEmpty();
} }
@Test
void testUpdateEntryClearsWhenNewEntryIsNullList() {
var updatedNull = new PropagationArgumentEntry(null);
boolean changed = entry.updateEntry(updatedNull);
assertThat(changed).isTrue();
assertThat(entry.getPropagationEntityIds()).isEmpty();
}
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
void testToTbelCfArgWithValues() { void testToTbelCfArgWithValues() {

10
application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationCalculatedFieldStateTest.java

@ -116,20 +116,20 @@ public class PropagationCalculatedFieldStateTest {
@Test @Test
void testInitAddsRequiredArgument() { void testInitAddsRequiredArgument() {
initCtxAndState(false); initCtxAndState(false);
assertThat(state.getRequiredArguments()).containsExactlyInAnyOrder(TEMPERATURE_ARGUMENT_NAME); assertThat(state.getRequiredArguments()).containsExactlyInAnyOrder(TEMPERATURE_ARGUMENT_NAME, PROPAGATION_CONFIG_ARGUMENT);
} }
@Test @Test
void testIsReadyReturnFalseWhenNoArgumentsSet() { void testIsReadyReturnFalseWhenNoArgumentsSet() {
initCtxAndState(false); initCtxAndState(false);
assertThat(state.isReady()).isFalse(); assertThat(state.getReadinessStatus().status()).isFalse();
} }
@Test @Test
void testIsReadyWhenPropagationArgIsNull() { void testIsReadyWhenPropagationArgIsNull() {
initCtxAndState(false); initCtxAndState(false);
state.getArguments().put(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry); state.getArguments().put(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry);
assertThat(state.isReady()).isFalse(); assertThat(state.getReadinessStatus().status()).isFalse();
} }
@Test @Test
@ -137,7 +137,7 @@ public class PropagationCalculatedFieldStateTest {
initCtxAndState(false); initCtxAndState(false);
state.getArguments().put(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry); state.getArguments().put(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry);
state.getArguments().put(PROPAGATION_CONFIG_ARGUMENT, new PropagationArgumentEntry(Collections.emptyList())); state.getArguments().put(PROPAGATION_CONFIG_ARGUMENT, new PropagationArgumentEntry(Collections.emptyList()));
assertThat(state.isReady()).isFalse(); assertThat(state.getReadinessStatus().status()).isFalse();
} }
@Test @Test
@ -145,7 +145,7 @@ public class PropagationCalculatedFieldStateTest {
initCtxAndState(false); initCtxAndState(false);
state.getArguments().put(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry); state.getArguments().put(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry);
state.getArguments().put(PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry); state.getArguments().put(PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry);
assertThat(state.isReady()).isTrue(); assertThat(state.getReadinessStatus().status()).isTrue();
} }

6
application/src/test/java/org/thingsboard/server/service/cf/ctx/state/ScriptCalculatedFieldStateTest.java

@ -160,21 +160,21 @@ public class ScriptCalculatedFieldStateTest {
@Test @Test
void testIsReadyWhenNotAllArgPresent() { void testIsReadyWhenNotAllArgPresent() {
assertThat(state.isReady()).isFalse(); assertThat(state.getReadinessStatus().status()).isFalse();
} }
@Test @Test
void testIsReadyWhenAllArgPresent() { void testIsReadyWhenAllArgPresent() {
state.arguments = new HashMap<>(Map.of("deviceTemperature", deviceTemperatureArgEntry, "assetHumidity", assetHumidityArgEntry)); state.arguments = new HashMap<>(Map.of("deviceTemperature", deviceTemperatureArgEntry, "assetHumidity", assetHumidityArgEntry));
assertThat(state.isReady()).isTrue(); assertThat(state.getReadinessStatus().status()).isTrue();
} }
@Test @Test
void testIsReadyWhenEmptyEntryPresents() { void testIsReadyWhenEmptyEntryPresents() {
state.arguments = new HashMap<>(Map.of("deviceTemperature", new TsRollingArgumentEntry(5, 30000L), "assetHumidity", assetHumidityArgEntry)); state.arguments = new HashMap<>(Map.of("deviceTemperature", new TsRollingArgumentEntry(5, 30000L), "assetHumidity", assetHumidityArgEntry));
assertThat(state.isReady()).isFalse(); assertThat(state.getReadinessStatus().status()).isFalse();
} }
private TsRollingArgumentEntry createRollingArgEntry() { private TsRollingArgumentEntry createRollingArgEntry() {

6
application/src/test/java/org/thingsboard/server/service/cf/ctx/state/SimpleCalculatedFieldStateTest.java

@ -203,7 +203,7 @@ public class SimpleCalculatedFieldStateTest {
@Test @Test
void testIsReadyWhenNotAllArgPresent() { void testIsReadyWhenNotAllArgPresent() {
assertThat(state.isReady()).isFalse(); assertThat(state.getReadinessStatus().status()).isFalse();
} }
@Test @Test
@ -214,7 +214,7 @@ public class SimpleCalculatedFieldStateTest {
"key3", key3ArgEntry "key3", key3ArgEntry
)); ));
assertThat(state.isReady()).isTrue(); assertThat(state.getReadinessStatus().status()).isTrue();
} }
@Test @Test
@ -225,7 +225,7 @@ public class SimpleCalculatedFieldStateTest {
)); ));
state.getArguments().put("key3", new SingleValueArgumentEntry()); state.getArguments().put("key3", new SingleValueArgumentEntry());
assertThat(state.isReady()).isFalse(); assertThat(state.getReadinessStatus().status()).isFalse();
} }
private CalculatedField getCalculatedField() { private CalculatedField getCalculatedField() {

Loading…
Cancel
Save