Browse Source

Use min value from tenant profile for scheduled update interval when not provided

pull/14827/head
Dmytro Skarzhynets 9 months ago
parent
commit
49c34ebe91
No known key found for this signature in database GPG Key ID: 2B51652F224037DF
  1. 8
      common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/ScheduledUpdateSupportedCalculatedFieldConfiguration.java
  2. 2
      common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/RelatedEntitiesAggregationCalculatedFieldConfiguration.java
  3. 5
      common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/geofencing/GeofencingCalculatedFieldConfiguration.java
  4. 14
      common/data/src/test/java/org/thingsboard/server/common/data/cf/configuration/geofencing/GeofencingCalculatedFieldConfigurationTest.java
  5. 14
      dao/src/main/java/org/thingsboard/server/dao/cf/BaseCalculatedFieldService.java
  6. 169
      dao/src/test/java/org/thingsboard/server/dao/service/CalculatedFieldServiceTest.java

8
common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/ScheduledUpdateSupportedCalculatedFieldConfiguration.java

@ -22,14 +22,14 @@ public interface ScheduledUpdateSupportedCalculatedFieldConfiguration extends Ca
boolean isScheduledUpdateEnabled();
@PositiveOrZero
int getScheduledUpdateInterval();
Integer getScheduledUpdateInterval();
void setScheduledUpdateInterval(int interval);
void setScheduledUpdateInterval(Integer interval);
default void validate(long minAllowedScheduledUpdateInterval) {
if (getScheduledUpdateInterval() < minAllowedScheduledUpdateInterval) {
throw new IllegalArgumentException("Scheduled update interval is less than configured " +
"minimum allowed interval in tenant profile: " + minAllowedScheduledUpdateInterval);
throw new IllegalArgumentException("Scheduled update interval (" + getScheduledUpdateInterval() +
" seconds) is less than minimum allowed interval in tenant profile: " + minAllowedScheduledUpdateInterval + " seconds");
}
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/RelatedEntitiesAggregationCalculatedFieldConfiguration.java

@ -44,7 +44,7 @@ public class RelatedEntitiesAggregationCalculatedFieldConfiguration implements A
private Output output;
private boolean useLatestTs;
private int scheduledUpdateInterval;
private Integer scheduledUpdateInterval;
@Override
public CalculatedFieldType getType() {

5
common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/geofencing/GeofencingCalculatedFieldConfiguration.java

@ -46,7 +46,7 @@ public class GeofencingCalculatedFieldConfiguration implements ArgumentsBasedCal
private Map<String, ZoneGroupConfiguration> zoneGroups;
private boolean scheduledUpdateEnabled;
private int scheduledUpdateInterval;
private Integer scheduledUpdateInterval;
private Output output;
@ -79,6 +79,9 @@ public class GeofencingCalculatedFieldConfiguration implements ArgumentsBasedCal
@Override
public void validate() {
if (scheduledUpdateEnabled && scheduledUpdateInterval == null) {
throw new IllegalArgumentException("Refresh interval is required when periodic zone group refresh is enabled.");
}
zoneGroups.forEach((key, value) -> value.validate(key));
}

14
common/data/src/test/java/org/thingsboard/server/common/data/cf/configuration/geofencing/GeofencingCalculatedFieldConfigurationTest.java

@ -28,6 +28,7 @@ import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.thingsboard.server.common.data.cf.configuration.geofencing.EntityCoordinates.ENTITY_ID_LATITUDE_ARGUMENT_KEY;
@ -103,4 +104,17 @@ public class GeofencingCalculatedFieldConfigurationTest {
assertThat(allowedZonesArgument.getRefEntityKey()).isEqualTo(new ReferencedEntityKey("perimeter", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE));
}
@Test
void validateShouldThrowWhenScheduledUpdateEnabledButIntervalNotSet() {
var cfg = new GeofencingCalculatedFieldConfiguration();
cfg.setEntityCoordinates(mock(EntityCoordinates.class));
cfg.setZoneGroups(Map.of("zone", mock(ZoneGroupConfiguration.class)));
cfg.setScheduledUpdateEnabled(true);
cfg.setScheduledUpdateInterval(null);
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Refresh interval is required when periodic zone group refresh is enabled.");
}
}

14
dao/src/main/java/org/thingsboard/server/dao/cf/BaseCalculatedFieldService.java

@ -26,18 +26,21 @@ import org.thingsboard.server.common.data.cf.CalculatedFieldFilter;
import org.thingsboard.server.common.data.cf.CalculatedFieldInfo;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.aggregation.RelatedEntitiesAggregationCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.service.validator.CalculatedFieldDataValidator;
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import java.util.EnumSet;
import java.util.List;
@ -62,6 +65,7 @@ public class BaseCalculatedFieldService extends AbstractEntityService implements
private final EntityService entityService;
private final CalculatedFieldDao calculatedFieldDao;
private final CalculatedFieldDataValidator calculatedFieldDataValidator;
private final ApiLimitService apiLimitService;
@Override
public CalculatedField save(CalculatedField calculatedField) {
@ -70,6 +74,7 @@ public class BaseCalculatedFieldService extends AbstractEntityService implements
@Override
public CalculatedField save(CalculatedField calculatedField, boolean doValidate) {
setConfigurationDefaults(calculatedField);
CalculatedField oldCalculatedField = null;
if (doValidate) {
oldCalculatedField = calculatedFieldDataValidator.validate(calculatedField, CalculatedField::getTenantId);
@ -79,6 +84,15 @@ public class BaseCalculatedFieldService extends AbstractEntityService implements
return doSave(calculatedField, oldCalculatedField);
}
private void setConfigurationDefaults(CalculatedField calculatedField) {
if (calculatedField.getConfiguration() instanceof RelatedEntitiesAggregationCalculatedFieldConfiguration config
&& config.getScheduledUpdateInterval() == null) {
int minScheduledUpdateInterval = (int) apiLimitService.getLimit(
calculatedField.getTenantId(), DefaultTenantProfileConfiguration::getMinAllowedScheduledUpdateIntervalInSecForCF
);
config.setScheduledUpdateInterval(minScheduledUpdateInterval);
}
}
private CalculatedField doSave(CalculatedField calculatedField, CalculatedField oldCalculatedField) {
try {

169
dao/src/test/java/org/thingsboard/server/dao/service/CalculatedFieldServiceTest.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.service;
import org.apache.commons.lang3.RandomUtils;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.Device;
@ -27,6 +28,10 @@ import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
import org.thingsboard.server.common.data.cf.configuration.RelationPathQueryDynamicSourceConfiguration;
import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.TimeSeriesOutput;
import org.thingsboard.server.common.data.cf.configuration.aggregation.AggFunction;
import org.thingsboard.server.common.data.cf.configuration.aggregation.AggKeyInput;
import org.thingsboard.server.common.data.cf.configuration.aggregation.AggMetric;
import org.thingsboard.server.common.data.cf.configuration.aggregation.RelatedEntitiesAggregationCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.geofencing.EntityCoordinates;
import org.thingsboard.server.common.data.cf.configuration.geofencing.GeofencingCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.geofencing.ZoneGroupConfiguration;
@ -36,8 +41,8 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationPathLevel;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.exception.DataValidationException;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.exception.DataValidationException;
import java.util.ArrayList;
import java.util.List;
@ -107,11 +112,11 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
int min = tbTenantProfileCache.get(tenantId)
.getDefaultProfileConfiguration()
.getMinAllowedScheduledUpdateIntervalInSecForCF();
int valueFromConfig = min - 10;
// Enable scheduling with an interval below tenant min
cfg.setScheduledUpdateEnabled(true);
cfg.setScheduledUpdateInterval(valueFromConfig);
int invalidInterval = RandomUtils.insecure().randomInt(1, min);
cfg.setScheduledUpdateInterval(invalidInterval);
// Create & save Calculated Field
CalculatedField cf = new CalculatedField();
@ -125,8 +130,8 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
assertThatThrownBy(() -> calculatedFieldService.save(cf))
.isInstanceOf(DataValidationException.class)
.hasCauseInstanceOf(IllegalArgumentException.class)
.hasMessageStartingWith("Scheduled update interval is less than configured " +
"minimum allowed interval in tenant profile: ");
.hasMessage("Scheduled update interval (" + invalidInterval +
" seconds) is less than minimum allowed interval in tenant profile: " + min + " seconds");
}
@Test
@ -253,6 +258,160 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
assertThat(calculatedFieldService.findById(tenantId, savedCalculatedField.getId())).isNull();
}
@Test
public void testSaveRelatedEntitiesAggregationCF_shouldUseMinScheduledUpdateIntervalFromTenantProfileWhenNotSet() {
// GIVEN
var device = createTestDevice();
var cfg = new RelatedEntitiesAggregationCalculatedFieldConfiguration();
cfg.setRelation(new RelationPathLevel(EntitySearchDirection.FROM, EntityRelation.CONTAINS_TYPE));
var argument = new Argument();
argument.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null));
cfg.setArguments(Map.of("temp", argument));
var metric = new AggMetric();
metric.setFunction(AggFunction.AVG);
metric.setInput(new AggKeyInput("temp"));
cfg.setMetrics(Map.of("avgTemp", metric));
var output = new TimeSeriesOutput();
output.setName("avgTemperature");
cfg.setOutput(output);
int minDeduplicationInterval = (int) tbTenantProfileCache.get(tenantId)
.getDefaultProfileConfiguration()
.getMinAllowedDeduplicationIntervalInSecForCF();
cfg.setDeduplicationIntervalInSec(minDeduplicationInterval);
// Do NOT set scheduledUpdateInterval - it should default to tenant profile min value
var cf = new CalculatedField();
cf.setTenantId(tenantId);
cf.setEntityId(device.getId());
cf.setType(CalculatedFieldType.RELATED_ENTITIES_AGGREGATION);
cf.setName("Related Entities Aggregation CF - default scheduled interval test");
cf.setConfigurationVersion(0);
cf.setConfiguration(cfg);
// WHEN
CalculatedField saved = calculatedFieldService.save(cf);
// THEN
assertThat(saved).isNotNull();
assertThat(saved.getConfiguration()).isInstanceOf(RelatedEntitiesAggregationCalculatedFieldConfiguration.class);
var savedConfig = (RelatedEntitiesAggregationCalculatedFieldConfiguration) saved.getConfiguration();
int expectedMinScheduledUpdateInterval = tbTenantProfileCache.get(tenantId)
.getDefaultProfileConfiguration()
.getMinAllowedScheduledUpdateIntervalInSecForCF();
assertThat(savedConfig.getScheduledUpdateInterval()).isEqualTo(expectedMinScheduledUpdateInterval);
calculatedFieldService.deleteCalculatedField(tenantId, saved.getId());
}
@Test
public void testSaveRelatedEntitiesAggregationCF_shouldThrowWhenScheduledUpdateIntervalLessThanMinAllowed() {
// GIVEN
var device = createTestDevice();
var cfg = new RelatedEntitiesAggregationCalculatedFieldConfiguration();
cfg.setRelation(new RelationPathLevel(EntitySearchDirection.FROM, EntityRelation.CONTAINS_TYPE));
var argument = new Argument();
argument.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null));
cfg.setArguments(Map.of("temp", argument));
var metric = new AggMetric();
metric.setFunction(AggFunction.AVG);
metric.setInput(new AggKeyInput("temp"));
cfg.setMetrics(Map.of("avgTemp", metric));
var output = new TimeSeriesOutput();
output.setName("avgTemperature");
cfg.setOutput(output);
int minDeduplicationInterval = (int) tbTenantProfileCache.get(tenantId)
.getDefaultProfileConfiguration()
.getMinAllowedDeduplicationIntervalInSecForCF();
cfg.setDeduplicationIntervalInSec(minDeduplicationInterval);
int minScheduledUpdateInterval = tbTenantProfileCache.get(tenantId)
.getDefaultProfileConfiguration()
.getMinAllowedScheduledUpdateIntervalInSecForCF();
int invalidInterval = RandomUtils.insecure().randomInt(1, minScheduledUpdateInterval);
cfg.setScheduledUpdateInterval(invalidInterval);
var cf = new CalculatedField();
cf.setTenantId(tenantId);
cf.setEntityId(device.getId());
cf.setType(CalculatedFieldType.RELATED_ENTITIES_AGGREGATION);
cf.setName("Related Entities Aggregation CF - invalid scheduled interval test");
cf.setConfigurationVersion(0);
cf.setConfiguration(cfg);
// WHEN-THEN
assertThatThrownBy(() -> calculatedFieldService.save(cf))
.isInstanceOf(DataValidationException.class)
.hasCauseInstanceOf(IllegalArgumentException.class)
.hasMessage("Scheduled update interval (" + invalidInterval +
" seconds) is less than minimum allowed interval in tenant profile: " + minScheduledUpdateInterval + " seconds");
}
@Test
public void testSaveRelatedEntitiesAggregationCF_shouldAcceptValidScheduledUpdateInterval() {
// GIVEN
var device = createTestDevice();
var cfg = new RelatedEntitiesAggregationCalculatedFieldConfiguration();
cfg.setRelation(new RelationPathLevel(EntitySearchDirection.FROM, EntityRelation.CONTAINS_TYPE));
var argument = new Argument();
argument.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null));
cfg.setArguments(Map.of("temp", argument));
var metric = new AggMetric();
metric.setFunction(AggFunction.AVG);
metric.setInput(new AggKeyInput("temp"));
cfg.setMetrics(Map.of("avgTemp", metric));
var output = new TimeSeriesOutput();
output.setName("avgTemperature");
cfg.setOutput(output);
int minDeduplicationInterval = (int) tbTenantProfileCache.get(tenantId)
.getDefaultProfileConfiguration()
.getMinAllowedDeduplicationIntervalInSecForCF();
cfg.setDeduplicationIntervalInSec(minDeduplicationInterval);
int minScheduledUpdateInterval = tbTenantProfileCache.get(tenantId)
.getDefaultProfileConfiguration()
.getMinAllowedScheduledUpdateIntervalInSecForCF();
int customScheduledUpdateInterval = minScheduledUpdateInterval + 100;
cfg.setScheduledUpdateInterval(customScheduledUpdateInterval);
var cf = new CalculatedField();
cf.setTenantId(tenantId);
cf.setEntityId(device.getId());
cf.setType(CalculatedFieldType.RELATED_ENTITIES_AGGREGATION);
cf.setName("Related Entities Aggregation CF - valid scheduled interval test");
cf.setConfigurationVersion(0);
cf.setConfiguration(cfg);
// WHEN
CalculatedField saved = calculatedFieldService.save(cf);
// THEN
assertThat(saved).isNotNull();
assertThat(saved.getConfiguration()).isInstanceOf(RelatedEntitiesAggregationCalculatedFieldConfiguration.class);
var savedConfig = (RelatedEntitiesAggregationCalculatedFieldConfiguration) saved.getConfiguration();
assertThat(savedConfig.getScheduledUpdateInterval()).isEqualTo(customScheduledUpdateInterval);
calculatedFieldService.deleteCalculatedField(tenantId, saved.getId());
}
private CalculatedField saveValidCalculatedField() {
Device device = createTestDevice();
CalculatedField calculatedField = getCalculatedField(device.getId(), device.getId());

Loading…
Cancel
Save