Browse Source

Merge branch 'master' into fix_bug_lwm2m_fota

pull/12876/head
nick 2 years ago
parent
commit
b8f6df2740
  1. 6
      application/src/main/java/org/thingsboard/server/actors/calculatedField/CalculatedFieldEntityMessageProcessor.java
  2. 7
      application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldResult.java
  3. 2
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java
  4. 21
      application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java
  5. 55
      application/src/test/java/org/thingsboard/server/controller/BaseQueueControllerTest.java
  6. 1
      application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java
  7. 4
      dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
  8. 73
      docker/compose-utils.sh
  9. 11
      docker/docker-check-log-folders.sh
  10. 2
      docker/docker-compose.yml
  11. 2
      docker/docker-create-log-folders.sh
  12. 2
      docker/docker-install-tb.sh
  13. 2
      docker/docker-start-services.sh
  14. 2
      docker/docker-upgrade-tb.sh
  15. 8
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-layer.ts
  16. 5
      ui-ngx/src/assets/map/openstreetmap_hybrid_reference_style.json
  17. 5
      ui-ngx/src/assets/map/world_edition_hybrid_reference_style.json

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

@ -287,7 +287,11 @@ public class CalculatedFieldEntityMessageProcessor extends AbstractContextAwareM
state.checkStateSize(ctxId, ctx.getMaxStateSize()); state.checkStateSize(ctxId, ctx.getMaxStateSize());
stateSizeChecked = true; stateSizeChecked = true;
if (state.isSizeOk()) { if (state.isSizeOk()) {
cfService.pushMsgToRuleEngine(tenantId, entityId, calculationResult, cfIdList, callback); if (!calculationResult.isEmpty()) {
cfService.pushMsgToRuleEngine(tenantId, entityId, calculationResult, cfIdList, callback);
} else {
callback.onSuccess();
}
if (DebugModeUtil.isDebugAllAvailable(ctx.getCalculatedField())) { if (DebugModeUtil.isDebugAllAvailable(ctx.getCalculatedField())) {
systemContext.persistCalculatedFieldDebugEvent(tenantId, ctx.getCfId(), entityId, state.getArguments(), tbMsgId, tbMsgType, JacksonUtil.writeValueAsString(calculationResult.getResult()), null); systemContext.persistCalculatedFieldDebugEvent(tenantId, ctx.getCfId(), entityId, state.getArguments(), tbMsgId, tbMsgType, JacksonUtil.writeValueAsString(calculationResult.getResult()), null);
} }

7
application/src/main/java/org/thingsboard/server/service/cf/CalculatedFieldResult.java

@ -27,4 +27,11 @@ public final class CalculatedFieldResult {
private final AttributeScope scope; private final AttributeScope scope;
private final JsonNode result; private final JsonNode result;
public boolean isEmpty() {
return result == null || result.isMissingNode() || result.isNull() ||
(result.isObject() && result.isEmpty()) ||
(result.isArray() && result.isEmpty()) ||
(result.isTextual() && result.asText().isEmpty());
}
} }

2
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldCtx.java

@ -83,7 +83,7 @@ public class CalculatedFieldCtx {
for (Map.Entry<String, Argument> entry : arguments.entrySet()) { for (Map.Entry<String, Argument> entry : arguments.entrySet()) {
var refId = entry.getValue().getRefEntityId(); var refId = entry.getValue().getRefEntityId();
var refKey = entry.getValue().getRefEntityKey(); var refKey = entry.getValue().getRefEntityKey();
if (refId == null) { if (refId == null || refId.equals(calculatedField.getEntityId())) {
mainEntityArguments.put(refKey, entry.getKey()); mainEntityArguments.put(refKey, entry.getKey());
} else { } else {
linkedEntityArguments.computeIfAbsent(refId, key -> new HashMap<>()).put(refKey, entry.getKey()); linkedEntityArguments.computeIfAbsent(refId, key -> new HashMap<>()).put(refKey, entry.getKey());

21
application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java

@ -48,6 +48,9 @@ import static org.awaitility.Awaitility.await;
@DaoSqlTest @DaoSqlTest
public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTest { public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTest {
public static final int TIMEOUT = 60;
public static final int POLL_INTERVAL = 1;
@BeforeEach @BeforeEach
void setUp() throws Exception { void setUp() throws Exception {
loginTenantAdmin(); loginTenantAdmin();
@ -86,6 +89,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
CalculatedField savedCalculatedField = doPost("/api/calculatedField", calculatedField, CalculatedField.class); CalculatedField savedCalculatedField = doPost("/api/calculatedField", calculatedField, CalculatedField.class);
await().alias("create CF -> perform initial calculation").atMost(TIMEOUT, TimeUnit.SECONDS) await().alias("create CF -> perform initial calculation").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "fahrenheitTemp"); ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "fahrenheitTemp");
assertThat(fahrenheitTemp).isNotNull(); assertThat(fahrenheitTemp).isNotNull();
@ -95,6 +99,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"temperature\":30}")); doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"temperature\":30}"));
await().alias("update telemetry -> recalculate state").atMost(TIMEOUT, TimeUnit.SECONDS) await().alias("update telemetry -> recalculate state").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "fahrenheitTemp"); ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "fahrenheitTemp");
assertThat(fahrenheitTemp).isNotNull(); assertThat(fahrenheitTemp).isNotNull();
@ -108,6 +113,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
savedCalculatedField = doPost("/api/calculatedField", savedCalculatedField, CalculatedField.class); savedCalculatedField = doPost("/api/calculatedField", savedCalculatedField, CalculatedField.class);
await().alias("update CF output -> perform calculation with updated output").atMost(TIMEOUT, TimeUnit.SECONDS) await().alias("update CF output -> perform calculation with updated output").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
ArrayNode temperatureF = getServerAttributes(testDevice.getId(), "temperatureF"); ArrayNode temperatureF = getServerAttributes(testDevice.getId(), "temperatureF");
assertThat(temperatureF).isNotNull(); assertThat(temperatureF).isNotNull();
@ -119,6 +125,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
savedCalculatedField = doPost("/api/calculatedField", savedCalculatedField, CalculatedField.class); savedCalculatedField = doPost("/api/calculatedField", savedCalculatedField, CalculatedField.class);
await().alias("update CF argument -> perform calculation with new argument").atMost(TIMEOUT, TimeUnit.SECONDS) await().alias("update CF argument -> perform calculation with new argument").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
ArrayNode temperatureF = getServerAttributes(testDevice.getId(), "temperatureF"); ArrayNode temperatureF = getServerAttributes(testDevice.getId(), "temperatureF");
assertThat(temperatureF).isNotNull(); assertThat(temperatureF).isNotNull();
@ -129,6 +136,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
savedCalculatedField = doPost("/api/calculatedField", savedCalculatedField, CalculatedField.class); savedCalculatedField = doPost("/api/calculatedField", savedCalculatedField, CalculatedField.class);
await().alias("update CF expression -> perform calculation with new expression").atMost(TIMEOUT, TimeUnit.SECONDS) await().alias("update CF expression -> perform calculation with new expression").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
ArrayNode temperatureF = getServerAttributes(testDevice.getId(), "temperatureF"); ArrayNode temperatureF = getServerAttributes(testDevice.getId(), "temperatureF");
assertThat(temperatureF).isNotNull(); assertThat(temperatureF).isNotNull();
@ -166,6 +174,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
CalculatedField savedCalculatedField = doPost("/api/calculatedField", calculatedField, CalculatedField.class); CalculatedField savedCalculatedField = doPost("/api/calculatedField", calculatedField, CalculatedField.class);
await().alias("create CF -> state is not ready -> no calculation performed").atMost(TIMEOUT, TimeUnit.SECONDS) await().alias("create CF -> state is not ready -> no calculation performed").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "fahrenheitTemp"); ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "fahrenheitTemp");
assertThat(fahrenheitTemp).isNotNull(); assertThat(fahrenheitTemp).isNotNull();
@ -175,6 +184,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"temperature\":30}")); doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"temperature\":30}"));
await().alias("update telemetry -> perform calculation").atMost(TIMEOUT, TimeUnit.SECONDS) await().alias("update telemetry -> perform calculation").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "fahrenheitTemp"); ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "fahrenheitTemp");
assertThat(fahrenheitTemp).isNotNull(); assertThat(fahrenheitTemp).isNotNull();
@ -213,6 +223,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
CalculatedField savedCalculatedField = doPost("/api/calculatedField", calculatedField, CalculatedField.class); CalculatedField savedCalculatedField = doPost("/api/calculatedField", calculatedField, CalculatedField.class);
await().alias("create CF -> perform initial calculation with default value").atMost(TIMEOUT, TimeUnit.SECONDS) await().alias("create CF -> perform initial calculation with default value").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "fahrenheitTemp"); ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "fahrenheitTemp");
assertThat(fahrenheitTemp).isNotNull(); assertThat(fahrenheitTemp).isNotNull();
@ -222,6 +233,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"temperature\":30}")); doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"temperature\":30}"));
await().alias("update telemetry -> recalculate state").atMost(TIMEOUT, TimeUnit.SECONDS) await().alias("update telemetry -> recalculate state").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "fahrenheitTemp"); ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "fahrenheitTemp");
assertThat(fahrenheitTemp).isNotNull(); assertThat(fahrenheitTemp).isNotNull();
@ -277,6 +289,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
doPost("/api/calculatedField", calculatedField, CalculatedField.class); doPost("/api/calculatedField", calculatedField, CalculatedField.class);
await().alias("create CF and perform initial calculation").atMost(TIMEOUT, TimeUnit.SECONDS) await().alias("create CF and perform initial calculation").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
// result of asset 1 // result of asset 1
ArrayNode z1 = getServerAttributes(asset1.getId(), "z"); ArrayNode z1 = getServerAttributes(asset1.getId(), "z");
@ -292,6 +305,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"x\":25}")); doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"x\":25}"));
await().alias("update device telemetry -> recalculate state for all assets").atMost(TIMEOUT, TimeUnit.SECONDS) await().alias("update device telemetry -> recalculate state for all assets").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
// result of asset 1 // result of asset 1
ArrayNode z1 = getServerAttributes(asset1.getId(), "z"); ArrayNode z1 = getServerAttributes(asset1.getId(), "z");
@ -307,6 +321,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
doPost("/api/plugins/telemetry/ASSET/" + asset1.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"y\":15}")); doPost("/api/plugins/telemetry/ASSET/" + asset1.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"y\":15}"));
await().alias("update asset 1 telemetry -> recalculate state only for asset 1").atMost(TIMEOUT, TimeUnit.SECONDS) await().alias("update asset 1 telemetry -> recalculate state only for asset 1").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
// result of asset 1 // result of asset 1
ArrayNode z1 = getServerAttributes(asset1.getId(), "z"); ArrayNode z1 = getServerAttributes(asset1.getId(), "z");
@ -322,6 +337,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
doPost("/api/plugins/telemetry/ASSET/" + asset2.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"y\":5}")); doPost("/api/plugins/telemetry/ASSET/" + asset2.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"y\":5}"));
await().alias("update asset 2 telemetry -> recalculate state only for asset 2").atMost(TIMEOUT, TimeUnit.SECONDS) await().alias("update asset 2 telemetry -> recalculate state only for asset 2").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
// result of asset 1 (no changes) // result of asset 1 (no changes)
ArrayNode z1 = getServerAttributes(asset1.getId(), "z"); ArrayNode z1 = getServerAttributes(asset1.getId(), "z");
@ -339,6 +355,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
Asset finalAsset3 = asset3; Asset finalAsset3 = asset3;
await().alias("add new entity to profile -> calculate state for new entity").atMost(TIMEOUT, TimeUnit.SECONDS) await().alias("add new entity to profile -> calculate state for new entity").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
// result of asset 3 // result of asset 3
ArrayNode z3 = getServerAttributes(finalAsset3.getId(), "z"); ArrayNode z3 = getServerAttributes(finalAsset3.getId(), "z");
@ -349,6 +366,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"x\":20}")); doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/attributes/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"x\":20}"));
await().alias("update device telemetry -> recalculate state for all assets").atMost(TIMEOUT, TimeUnit.SECONDS) await().alias("update device telemetry -> recalculate state for all assets").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
// result of asset 1 // result of asset 1
ArrayNode z1 = getServerAttributes(asset1.getId(), "z"); ArrayNode z1 = getServerAttributes(asset1.getId(), "z");
@ -375,6 +393,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
Asset updatedAsset3 = asset3; Asset updatedAsset3 = asset3;
await().alias("update device telemetry -> recalculate state for asset 1 and asset 2").atMost(TIMEOUT, TimeUnit.SECONDS) await().alias("update device telemetry -> recalculate state for asset 1 and asset 2").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
// result of asset 1 // result of asset 1
ArrayNode z1 = getServerAttributes(asset1.getId(), "z"); ArrayNode z1 = getServerAttributes(asset1.getId(), "z");
@ -425,6 +444,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
CalculatedField savedCalculatedField = doPost("/api/calculatedField", calculatedField, CalculatedField.class); CalculatedField savedCalculatedField = doPost("/api/calculatedField", calculatedField, CalculatedField.class);
await().alias("create CF -> ctx is not initialized -> no calculation perform").atMost(TIMEOUT, TimeUnit.SECONDS) await().alias("create CF -> ctx is not initialized -> no calculation perform").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "fahrenheitTemp"); ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "fahrenheitTemp");
assertThat(fahrenheitTemp).isNotNull(); assertThat(fahrenheitTemp).isNotNull();
@ -434,6 +454,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"temperature\":30}")); doPost("/api/plugins/telemetry/DEVICE/" + testDevice.getUuidId() + "/timeseries/" + DataConstants.SERVER_SCOPE, JacksonUtil.toJsonNode("{\"temperature\":30}"));
await().alias("update telemetry -> ctx is not initialized -> no calculation perform").atMost(TIMEOUT, TimeUnit.SECONDS) await().alias("update telemetry -> ctx is not initialized -> no calculation perform").atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> { .untilAsserted(() -> {
ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "fahrenheitTemp"); ObjectNode fahrenheitTemp = getLatestTelemetry(testDevice.getId(), "fahrenheitTemp");
assertThat(fahrenheitTemp).isNotNull(); assertThat(fahrenheitTemp).isNotNull();

55
application/src/test/java/org/thingsboard/server/controller/BaseQueueControllerTest.java

@ -28,6 +28,7 @@ import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
@ -73,6 +74,7 @@ import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await; import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.argThat;
@ -325,6 +327,59 @@ public class BaseQueueControllerTest extends AbstractControllerTest {
doDelete("/api/queues/" + queue.getUuidId()).andExpect(status().isOk()); doDelete("/api/queues/" + queue.getUuidId()).andExpect(status().isOk());
} }
@Test
public void testQueueWithReservedName() throws Exception {
loginSysAdmin();
// create queue
Queue queue = new Queue();
queue.setName(DataConstants.CF_QUEUE_NAME);
queue.setTopic("tb_rule_engine.calculated_fields");
queue.setPollInterval(25);
queue.setPartitions(10);
queue.setTenantId(TenantId.SYS_TENANT_ID);
queue.setConsumerPerPartition(false);
queue.setPackProcessingTimeout(2000);
SubmitStrategy submitStrategy = new SubmitStrategy();
submitStrategy.setType(SubmitStrategyType.SEQUENTIAL_BY_ORIGINATOR);
queue.setSubmitStrategy(submitStrategy);
ProcessingStrategy processingStrategy = new ProcessingStrategy();
processingStrategy.setType(ProcessingStrategyType.RETRY_ALL);
processingStrategy.setRetries(3);
processingStrategy.setFailurePercentage(0.7);
processingStrategy.setPauseBetweenRetries(3);
processingStrategy.setMaxPauseBetweenRetries(5);
queue.setProcessingStrategy(processingStrategy);
doPost("/api/queues?serviceType=" + "TB-RULE-ENGINE", queue)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(String.format("The queue name '%s' is not allowed. This name is reserved for internal use. Please choose a different name.", DataConstants.CF_QUEUE_NAME))));
// create queue
Queue queue2 = new Queue();
queue2.setName(DataConstants.CF_STATES_QUEUE_NAME);
queue2.setTopic("tb_rule_engine.calculated_fields");
queue2.setPollInterval(25);
queue2.setPartitions(10);
queue2.setTenantId(TenantId.SYS_TENANT_ID);
queue2.setConsumerPerPartition(false);
queue2.setPackProcessingTimeout(2000);
SubmitStrategy submitStrategy2 = new SubmitStrategy();
submitStrategy2.setType(SubmitStrategyType.SEQUENTIAL_BY_ORIGINATOR);
queue2.setSubmitStrategy(submitStrategy);
ProcessingStrategy processingStrategy2 = new ProcessingStrategy();
processingStrategy2.setType(ProcessingStrategyType.RETRY_ALL);
processingStrategy2.setRetries(3);
processingStrategy2.setFailurePercentage(0.7);
processingStrategy2.setPauseBetweenRetries(3);
processingStrategy2.setMaxPauseBetweenRetries(5);
queue2.setProcessingStrategy(processingStrategy);
doPost("/api/queues?serviceType=" + "TB-RULE-ENGINE", queue2)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(String.format("The queue name '%s' is not allowed. This name is reserved for internal use. Please choose a different name.", DataConstants.CF_STATES_QUEUE_NAME))));
}
private Queue saveQueue(Queue queue) { private Queue saveQueue(Queue queue) {
return doPost("/api/queues?serviceType=TB_RULE_ENGINE", queue, Queue.class); return doPost("/api/queues?serviceType=TB_RULE_ENGINE", queue, Queue.class);
} }

1
application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java

@ -428,7 +428,6 @@ public class HashPartitionServiceTest {
ReflectionTestUtils.setField(partitionService, "corePartitions", 10); ReflectionTestUtils.setField(partitionService, "corePartitions", 10);
ReflectionTestUtils.setField(partitionService, "cfEventTopic", "tb_cf_event"); ReflectionTestUtils.setField(partitionService, "cfEventTopic", "tb_cf_event");
ReflectionTestUtils.setField(partitionService, "cfStateTopic", "tb_cf_state"); ReflectionTestUtils.setField(partitionService, "cfStateTopic", "tb_cf_state");
ReflectionTestUtils.setField(partitionService, "cfPartitions", 10);
ReflectionTestUtils.setField(partitionService, "vcTopic", "tb.vc"); ReflectionTestUtils.setField(partitionService, "vcTopic", "tb.vc");
ReflectionTestUtils.setField(partitionService, "vcPartitions", 10); ReflectionTestUtils.setField(partitionService, "vcPartitions", 10);
ReflectionTestUtils.setField(partitionService, "hashFunctionName", hashFunctionName); ReflectionTestUtils.setField(partitionService, "hashFunctionName", hashFunctionName);

4
dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java

@ -21,6 +21,7 @@ import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -158,6 +159,9 @@ public abstract class DataValidator<D extends BaseData<?>> {
protected static void validateQueueName(String name) { protected static void validateQueueName(String name) {
validateQueueNameOrTopic(name, NAME); validateQueueNameOrTopic(name, NAME);
if (DataConstants.CF_QUEUE_NAME.equals(name) || DataConstants.CF_STATES_QUEUE_NAME.equals(name)) {
throw new DataValidationException(String.format("The queue name '%s' is not allowed. This name is reserved for internal use. Please choose a different name.", name));
}
} }
protected static void validateQueueTopic(String topic) { protected static void validateQueueTopic(String topic) {

73
docker/compose-utils.sh

@ -143,7 +143,6 @@ function additionalComposeEdqsArgs() {
function permissionList() { function permissionList() {
PERMISSION_LIST=" PERMISSION_LIST="
799 799 tb-node/log 799 799 tb-node/log
799 799 tb-transports/coap/log
799 799 tb-transports/lwm2m/log 799 799 tb-transports/lwm2m/log
799 799 tb-transports/http/log 799 799 tb-transports/http/log
799 799 tb-transports/mqtt/log 799 799 tb-transports/mqtt/log
@ -200,29 +199,77 @@ function permissionList() {
} }
function checkFolders() { function checkFolders() {
CREATE=false
SKIP_CHOWN=false
for i in "$@"
do
case $i in
--create)
CREATE=true
shift
;;
--skipChown)
SKIP_CHOWN=true
shift
;;
*)
# unknown option
;;
esac
done
EXIT_CODE=0 EXIT_CODE=0
PERMISSION_LIST=$(permissionList) || exit $? PERMISSION_LIST=$(permissionList) || exit $?
set -e set -e
while read -r USR GRP DIR while read -r USR GRP DIR
do do
if [ -z "$DIR" ]; then # skip empty lines IS_EXIST_CHECK_PASSED=false
IS_OWNER_CHECK_PASSED=false
# skip empty lines
if [ -z "$DIR" ]; then
continue continue
fi fi
MESSAGE="Checking user ${USR} group ${GRP} dir ${DIR}"
if [[ -d "$DIR" ]] && # checks section
[[ $(ls -ldn "$DIR" | awk '{print $3}') -eq "$USR" ]] && echo "Checking if dir ${DIR} exists..."
[[ $(ls -ldn "$DIR" | awk '{print $4}') -eq "$GRP" ]] if [[ -d "$DIR" ]]; then
then echo "> OK"
MESSAGE="$MESSAGE OK" IS_EXIST_CHECK_PASSED=true
if [ "$SKIP_CHOWN" = false ]; then
echo "Checking user ${USR} group ${GRP} ownership for dir ${DIR}..."
if [[ $(ls -ldn "$DIR" | awk '{print $3}') -eq "$USR" ]] && [[ $(ls -ldn "$DIR" | awk '{print $4}') -eq "$GRP" ]]; then
echo "> OK"
IS_OWNER_CHECK_PASSED=true
else
echo "...ownership check failed"
if [ "$CREATE" = false ]; then
EXIT_CODE=1
fi
fi
fi
else else
if [ "$1" = "--create" ]; then echo "...does not exist"
echo "Create and chown: user ${USR} group ${GRP} dir ${DIR}" if [ "$CREATE" = false ]; then
mkdir -p "$DIR" && sudo chown -R "$USR":"$GRP" "$DIR"
else
echo "$MESSAGE FAILED"
EXIT_CODE=1 EXIT_CODE=1
fi fi
fi fi
# create/chown section
if [ "$CREATE" = true ]; then
if [ "$IS_EXIST_CHECK_PASSED" = false ]; then
echo "...will create dir ${DIR}"
if [ "$SKIP_CHOWN" = false ]; then
echo "...will change ownership to user ${USR} group ${GRP} for dir ${DIR}"
mkdir -p "$DIR" && sudo chown -R "$USR":"$GRP" "$DIR" && echo "> OK"
else
mkdir -p "$DIR" && echo "> OK"
fi
elif [ "$IS_OWNER_CHECK_PASSED" = false ] && [ "$SKIP_CHOWN" = false ]; then
echo "...will change ownership to user ${USR} group ${GRP} for dir ${DIR}"
sudo chown -R "$USR":"$GRP" "$DIR" && echo "> OK"
fi
fi
done < <(echo "$PERMISSION_LIST") done < <(echo "$PERMISSION_LIST")
return $EXIT_CODE return $EXIT_CODE
} }

11
docker/docker-check-log-folders.sh

@ -17,5 +17,12 @@
set -e set -e
source compose-utils.sh source compose-utils.sh
checkFolders || exit $? if checkFolders "$@" ; then
echo "OK" echo "------"
echo "All checks have passed"
else
CHECK_EXIT_CODE=$?
echo "------"
echo "Some checks did not pass - check the output"
exit $CHECK_EXIT_CODE
fi

2
docker/docker-compose.yml

@ -20,7 +20,7 @@ version: '3.0'
services: services:
zookeeper: zookeeper:
restart: always restart: always
image: "zookeeper:3.8.0" image: "zookeeper:3.8.1"
ports: ports:
- "2181" - "2181"
environment: environment:

2
docker/docker-create-log-folders.sh

@ -17,4 +17,4 @@
set -e set -e
source compose-utils.sh source compose-utils.sh
checkFolders --create checkFolders --create "$@"

2
docker/docker-install-tb.sh

@ -53,8 +53,6 @@ ADDITIONAL_COMPOSE_EDQS_ARGS=$(additionalComposeEdqsArgs) || exit $?
ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $? ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $?
checkFolders --create || exit $?
if [ ! -z "${ADDITIONAL_STARTUP_SERVICES// }" ]; then if [ ! -z "${ADDITIONAL_STARTUP_SERVICES// }" ]; then
COMPOSE_ARGS="\ COMPOSE_ARGS="\

2
docker/docker-start-services.sh

@ -31,8 +31,6 @@ ADDITIONAL_COMPOSE_MONITORING_ARGS=$(additionalComposeMonitoringArgs) || exit $?
ADDITIONAL_COMPOSE_EDQS_ARGS=$(additionalComposeEdqsArgs) || exit $? ADDITIONAL_COMPOSE_EDQS_ARGS=$(additionalComposeEdqsArgs) || exit $?
checkFolders --create || exit $?
COMPOSE_ARGS="\ COMPOSE_ARGS="\
-f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} ${ADDITIONAL_COMPOSE_MONITORING_ARGS} ${ADDITIONAL_COMPOSE_EDQS_ARGS} \ -f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} ${ADDITIONAL_COMPOSE_MONITORING_ARGS} ${ADDITIONAL_COMPOSE_EDQS_ARGS} \
up -d" up -d"

2
docker/docker-upgrade-tb.sh

@ -46,8 +46,6 @@ ADDITIONAL_COMPOSE_EDQS_ARGS=$(additionalComposeEdqsArgs) || exit $?
ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $? ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $?
checkFolders --create || exit $?
COMPOSE_ARGS_PULL="\ COMPOSE_ARGS_PULL="\
-f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} -f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS}
${ADDITIONAL_COMPOSE_EDQS_ARGS} \ ${ADDITIONAL_COMPOSE_EDQS_ARGS} \

8
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-layer.ts

@ -165,17 +165,9 @@ export abstract class TbMapLayer<S extends MapLayerSettings> {
return spec$.pipe( return spec$.pipe(
map(spec => { map(spec => {
const sourceSpec = (spec.sources['esri'] as VectorSourceSpecification); const sourceSpec = (spec.sources['esri'] as VectorSourceSpecification);
const tileUrl = sourceSpec.url;
const attribution = sourceSpec.attribution; const attribution = sourceSpec.attribution;
const transformRequest = (url: string, resourceType: ResourceType) => {
if (resourceType === 'Tile') {
url = tileUrl + '/' + url;
}
return {url}
}
const gl = L.maplibreGL({ const gl = L.maplibreGL({
style: spec, style: spec,
transformRequest
}); });
gl.options.attribution = attribution; gl.options.attribution = attribution;
return gl; return gl;

5
ui-ngx/src/assets/map/openstreetmap_hybrid_reference_style.json

@ -6,7 +6,10 @@
"esri": { "esri": {
"type": "vector", "type": "vector",
"url": "https://basemaps.arcgis.com/arcgis/rest/services/OpenStreetMap_v2/VectorTileServer", "url": "https://basemaps.arcgis.com/arcgis/rest/services/OpenStreetMap_v2/VectorTileServer",
"attribution": "Map data (c) OpenStreetMap contributors, Microsoft, Facebook, Google, Esri Community Maps contributors, Map layer by Esri" "attribution": "Map data (c) OpenStreetMap contributors, Microsoft, Facebook, Google, Esri Community Maps contributors, Map layer by Esri",
"tiles": [
"https://basemaps.arcgis.com/arcgis/rest/services/OpenStreetMap_v2/VectorTileServer/tile/{z}/{y}/{x}.pbf"
]
} }
}, },
"layers": [ "layers": [

5
ui-ngx/src/assets/map/world_edition_hybrid_reference_style.json

@ -6,7 +6,10 @@
"esri": { "esri": {
"type": "vector", "type": "vector",
"url": "https://basemaps.arcgis.com/arcgis/rest/services/World_Basemap_v2/VectorTileServer", "url": "https://basemaps.arcgis.com/arcgis/rest/services/World_Basemap_v2/VectorTileServer",
"attribution": "Sources: Esri, TomTom, Garmin, FAO, NOAA, USGS, © OpenStreetMap contributors, and the GIS User Community" "attribution": "Sources: Esri, TomTom, Garmin, FAO, NOAA, USGS, © OpenStreetMap contributors, and the GIS User Community",
"tiles": [
"https://basemaps.arcgis.com/arcgis/rest/services/World_Basemap_v2/VectorTileServer/tile/{z}/{y}/{x}.pbf"
]
} }
}, },
"layers": [ "layers": [

Loading…
Cancel
Save