Browse Source

Merge pull request #14961 from thingsboard/lts-4.3

LTS to RC
pull/14967/head
Viacheslav Klimov 3 days ago
committed by GitHub
parent
commit
b44d4b50af
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      application/pom.xml
  2. 3
      application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java
  3. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/cf/CalculatedFieldEdgeProcessor.java
  4. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java
  5. 11
      application/src/test/java/org/thingsboard/server/cf/EntityAggregationCalculatedFieldTest.java
  6. 41
      application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java
  7. 5
      common/edge-api/pom.xml
  8. 11
      common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java
  9. 86
      common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeVersionComparator.java
  10. 2
      common/edge-api/src/main/proto/edge.proto
  11. 102
      common/edge-api/src/test/java/org/thingsboard/edge/rpc/EdgeVersionComparatorTest.java
  12. 24
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java

1
application/pom.xml

@ -458,6 +458,7 @@
<configuration>
<systemPropertyVariables>
<spring.config.name>thingsboard</spring.config.name>
<project.version>${project.version}</project.version>
</systemPropertyVariables>
<excludes>
<exclude>**/nosql/*Test.java</exclude>

3
application/src/main/java/org/thingsboard/server/service/edge/EdgeMsgConstructorUtils.java

@ -26,6 +26,7 @@ import com.google.gson.JsonPrimitive;
import com.google.gson.reflect.TypeToken;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.edge.rpc.EdgeVersionComparator;
import org.thingsboard.rule.engine.action.TbSaveToCustomCassandraTableNode;
import org.thingsboard.rule.engine.ai.TbAiNode;
import org.thingsboard.rule.engine.aws.lambda.TbAwsLambdaNode;
@ -281,7 +282,7 @@ public class EdgeMsgConstructorUtils {
public static String getEntityAndFixLwm2mBootstrapShortServerId(DeviceProfile deviceProfile, EdgeVersion edgeVersion) {
DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration();
if (!(transportConfiguration instanceof Lwm2mDeviceProfileTransportConfiguration) || edgeVersion.getNumber() >= EdgeVersion.V_4_3_0.getNumber()) {
if (!(transportConfiguration instanceof Lwm2mDeviceProfileTransportConfiguration) || EdgeVersionComparator.INSTANCE.compare(edgeVersion, EdgeVersion.V_4_3_0) >= 0) {
return JacksonUtil.toString(deviceProfile);
}
JsonNode jsonNode = JacksonUtil.valueToTree(deviceProfile);

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/cf/CalculatedFieldEdgeProcessor.java

@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.data.util.Pair;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.edge.rpc.EdgeVersionComparator;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.cf.CalculatedField;
@ -116,7 +117,7 @@ public class CalculatedFieldEdgeProcessor extends BaseCalculatedFieldProcessor i
}
private boolean isValidCfToSend(CalculatedFieldType type, EdgeVersion edgeVersion) {
return edgeVersion.getNumber() >= EdgeVersion.V_4_3_0.getNumber() || (type == SIMPLE || type == SCRIPT);
return EdgeVersionComparator.INSTANCE.compare(edgeVersion, EdgeVersion.V_4_3_0) >= 0 || (type == SIMPLE || type == SCRIPT);
}
@Override

3
application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java

@ -16,13 +16,14 @@
package org.thingsboard.server.service.edge.rpc.utils;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.edge.rpc.EdgeVersionComparator;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
@Slf4j
public final class EdgeVersionUtils {
public static boolean isEdgeVersionOlderThan(EdgeVersion currentVersion, EdgeVersion requiredVersion) {
return currentVersion.ordinal() < requiredVersion.ordinal();
return EdgeVersionComparator.INSTANCE.compare(currentVersion, requiredVersion) < 0;
}
}

11
application/src/test/java/org/thingsboard/server/cf/EntityAggregationCalculatedFieldTest.java

@ -205,6 +205,15 @@ public class EntityAggregationCalculatedFieldTest extends AbstractControllerTest
CustomInterval customInterval = new CustomInterval(TZ, 0L, 2L);
createConsumptionCF(device.getId(), customInterval, null);
long interval = customInterval.getCurrentIntervalDurationMillis();
// Wait for a fresh interval
long initialIntervalStart = customInterval.getCurrentIntervalStartTs();
await().alias("wait for fresh interval")
.atMost(interval + 100, TimeUnit.MILLISECONDS)
.pollInterval(100, TimeUnit.MILLISECONDS)
.until(() -> customInterval.getCurrentIntervalStartTs() != initialIntervalStart);
long currentIntervalStartTs = customInterval.getCurrentIntervalStartTs();
long tsBeforeInterval = currentIntervalStartTs - 1000;
@ -216,8 +225,6 @@ public class EntityAggregationCalculatedFieldTest extends AbstractControllerTest
postTelemetry(device.getId(), String.format("{\"ts\": \"%s\", \"values\": {\"energy\":180}}", tsInInterval_2));
postTelemetry(device.getId(), String.format("{\"ts\": \"%s\", \"values\": {\"energy\":120}}", tsInInterval_3));
long interval = customInterval.getCurrentIntervalDurationMillis();
await().alias("create CF -> perform aggregation after interval end")
.atMost(2 * interval, TimeUnit.MILLISECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)

41
application/src/test/java/org/thingsboard/server/edge/EdgeLatestVersionTest.java

@ -0,0 +1,41 @@
/**
* Copyright © 2016-2026 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.edge;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.edge.rpc.EdgeVersionComparator;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
public class EdgeLatestVersionTest {
@Test
public void edgeLatestVersionIsSynchronizedTest() {
EdgeVersion currentHighestEdgeVersion = EdgeVersionComparator.getNewestEdgeVersion();
String projectVersion = EdgeLatestVersionTest.class.getPackage().getImplementationVersion();
if (projectVersion == null || projectVersion.isBlank()) {
projectVersion = System.getProperty("project.version", "UNKNOWN");
}
String projectVersionDigits = projectVersion.replaceAll("\\D", "");
String currentHighestEdgeVersionDigits = currentHighestEdgeVersion.name().replaceAll("\\D", "");
String msg = "EdgeVersion enum in edge.proto is out of sync. Please add respective " + projectVersionDigits + " to EdgeVersion";
Assert.assertEquals(msg, projectVersionDigits, currentHighestEdgeVersionDigits);
}
}

5
common/edge-api/pom.xml

@ -106,6 +106,11 @@
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

11
common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java

@ -143,16 +143,7 @@ public class EdgeGrpcClient implements EdgeRpcClient {
}
public static EdgeVersion getNewestEdgeVersion() {
EdgeVersion newest = null;
for (EdgeVersion v : EdgeVersion.values()) {
if (v == EdgeVersion.V_LATEST || v == EdgeVersion.UNRECOGNIZED) {
continue;
}
if (newest == null || v.getNumber() > newest.getNumber()) {
newest = v;
}
}
return newest;
return EdgeVersionComparator.getNewestEdgeVersion();
}
private StreamObserver<ResponseMsg> initOutputStream(String edgeKey,

86
common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeVersionComparator.java

@ -0,0 +1,86 @@
/**
* Copyright © 2016-2026 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.edge.rpc;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import java.util.Comparator;
public class EdgeVersionComparator implements Comparator<EdgeVersion> {
public static final EdgeVersionComparator INSTANCE = new EdgeVersionComparator();
@Override
public int compare(EdgeVersion v1, EdgeVersion v2) {
if (v1 == v2) {
return 0;
}
// UNRECOGNIZED is less than any other version
if (v1 == EdgeVersion.UNRECOGNIZED) {
return -1;
}
if (v2 == EdgeVersion.UNRECOGNIZED) {
return 1;
}
// V_LATEST is treated as the newest version
if (v1 == EdgeVersion.V_LATEST) {
v1 = getNewestEdgeVersion();
}
if (v2 == EdgeVersion.V_LATEST) {
v2 = getNewestEdgeVersion();
}
return compareVersionParts(parseVersionParts(v1), parseVersionParts(v2));
}
public static EdgeVersion getNewestEdgeVersion() {
EdgeVersion newest = null;
for (EdgeVersion v : EdgeVersion.values()) {
if (v == EdgeVersion.V_LATEST || v == EdgeVersion.UNRECOGNIZED) {
continue;
}
if (newest == null || INSTANCE.compare(v, newest) > 0) {
newest = v;
}
}
return newest;
}
private static int[] parseVersionParts(EdgeVersion version) {
String name = version.name();
if (name.startsWith("V_")) {
name = name.substring(2);
}
String[] parts = name.split("_");
int[] result = new int[parts.length];
for (int i = 0; i < parts.length; i++) {
result[i] = Integer.parseInt(parts[i]);
}
return result;
}
private static int compareVersionParts(int[] a, int[] b) {
int maxLen = Math.max(a.length, b.length);
for (int i = 0; i < maxLen; i++) {
int partA = i < a.length ? a[i] : 0;
int partB = i < b.length ? b[i] : 0;
if (partA != partB) {
return Integer.compare(partA, partB);
}
}
return 0;
}
}

2
common/edge-api/src/main/proto/edge.proto

@ -45,6 +45,8 @@ enum EdgeVersion {
V_4_1_0 = 11;
V_4_2_0 = 12;
V_4_3_0 = 13;
V_4_2_1_2 = 14;
V_4_3_0_1 = 15;
V_LATEST = 999;
}

102
common/edge-api/src/test/java/org/thingsboard/edge/rpc/EdgeVersionComparatorTest.java

@ -0,0 +1,102 @@
/**
* Copyright © 2016-2026 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.edge.rpc;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.gen.edge.v1.EdgeVersion;
import static org.assertj.core.api.Assertions.assertThat;
class EdgeVersionComparatorTest {
@Test
void compare_sameVersion_returnsZero() {
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_3_0, EdgeVersion.V_3_3_0)).isEqualTo(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_0_0, EdgeVersion.V_4_0_0)).isEqualTo(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_2_1_2, EdgeVersion.V_4_2_1_2)).isEqualTo(0);
}
@Test
void compare_majorVersionDifference_returnsCorrectOrder() {
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_3_0, EdgeVersion.V_4_0_0)).isLessThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_0_0, EdgeVersion.V_3_3_0)).isGreaterThan(0);
}
@Test
void compare_minorVersionDifference_returnsCorrectOrder() {
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_3_0, EdgeVersion.V_3_6_0)).isLessThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_6_0, EdgeVersion.V_3_3_0)).isGreaterThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_0_0, EdgeVersion.V_4_1_0)).isLessThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_1_0, EdgeVersion.V_4_0_0)).isGreaterThan(0);
}
@Test
void compare_patchVersionDifference_returnsCorrectOrder() {
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_6_0, EdgeVersion.V_3_6_1)).isLessThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_6_1, EdgeVersion.V_3_6_0)).isGreaterThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_6_1, EdgeVersion.V_3_6_2)).isLessThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_6_2, EdgeVersion.V_3_6_4)).isLessThan(0);
}
@Test
void compare_fourPartVersion_returnsCorrectOrder() {
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_2_0, EdgeVersion.V_4_2_1_2)).isLessThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_2_1_2, EdgeVersion.V_4_2_0)).isGreaterThan(0);
}
@Test
void compare_threePartVsFourPart_treatsImplicitZero() {
// V_4_2_0 should be less than V_4_2_1_2 (4.2.0.0 < 4.2.1.2)
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_4_2_0, EdgeVersion.V_4_2_1_2)).isLessThan(0);
}
@Test
void getNewestEdgeVersion_excludesLatestAndUnrecognized() {
EdgeVersion newest = EdgeVersionComparator.getNewestEdgeVersion();
assertThat(newest).isNotNull();
assertThat(newest).isNotEqualTo(EdgeVersion.V_LATEST);
assertThat(newest).isNotEqualTo(EdgeVersion.UNRECOGNIZED);
}
@Test
void compare_vLatest_treatedAsNewestVersion() {
EdgeVersion newest = EdgeVersionComparator.getNewestEdgeVersion();
// V_LATEST equals the newest version
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_LATEST, newest)).isEqualTo(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(newest, EdgeVersion.V_LATEST)).isEqualTo(0);
// V_LATEST is greater than older versions
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_LATEST, EdgeVersion.V_3_3_0)).isGreaterThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_3_0, EdgeVersion.V_LATEST)).isLessThan(0);
}
@Test
void compare_vLatest_withItself_returnsZero() {
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_LATEST, EdgeVersion.V_LATEST)).isEqualTo(0);
}
@Test
void compare_unrecognized_isLessThanAnyVersion() {
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.UNRECOGNIZED, EdgeVersion.V_3_3_0)).isLessThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.UNRECOGNIZED, EdgeVersion.V_LATEST)).isLessThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_3_3_0, EdgeVersion.UNRECOGNIZED)).isGreaterThan(0);
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.V_LATEST, EdgeVersion.UNRECOGNIZED)).isGreaterThan(0);
}
@Test
void compare_unrecognized_withItself_returnsZero() {
assertThat(EdgeVersionComparator.INSTANCE.compare(EdgeVersion.UNRECOGNIZED, EdgeVersion.UNRECOGNIZED)).isEqualTo(0);
}
}

24
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java

@ -810,18 +810,27 @@ public class CalculatedFieldTest extends AbstractContainerTest {
// --- Build CF: PROPAGATION ---
CalculatedField saved = createPropagationCF(device.getId());
// Wait for INITIALIZED event
waitForDebugEvent(saved, CalculatedFieldEventType.INITIALIZED.name(), 1);
// Create relations FROM asset 2 TO device
Asset asset2 = testRestClient.postAsset(createAsset("Propagated Asset 2", null));
EntityRelation rel2 = new EntityRelation(asset2.getId(), device.getId(), EntityRelation.CONTAINS_TYPE);
testRestClient.postEntityRelation(rel2);
// Wait for RELATION_ADD_OR_UPDATE event
waitForDebugEvent(saved, CalculatedFieldEventType.RELATION_ADD_OR_UPDATE.name(), 2);
// Telemetry on device
testRestClient.postTelemetry(deviceToken, JacksonUtil.toJsonNode("{\"temperature\":25.1}"));
// Wait for POST_TELEMETRY_REQUEST event
waitForDebugEvent(saved, TbMsgType.POST_TELEMETRY_REQUEST.name(), 3);
// Delete relation between asset 1 and device
testRestClient.deleteEntityRelation(asset1.getId(), EntityRelation.CONTAINS_TYPE, device.getId());
// --- Assert propagated calculation (arguments-only mode) ---
// --- Assert all debug events in correct sequence ---
await().alias("check debug events")
.atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
@ -845,6 +854,19 @@ public class CalculatedFieldTest extends AbstractContainerTest {
testRestClient.deleteAsset(asset2.getId());
}
private void waitForDebugEvent(CalculatedField cf, String expectedEventType, int expectedTotalEvents) {
await().alias("wait for debug event: " + expectedEventType)
.atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
List<String> eventTypes = testRestClient.getEvents(cf.getId(), EventType.DEBUG_CALCULATED_FIELD, tenantId, new TimePageLink(expectedTotalEvents, 0, null, SortOrder.BY_CREATED_TIME_DESC)).getData().stream()
.map(e -> e.getBody().get("msgType").asText())
.toList();
assertThat(eventTypes).hasSize(expectedTotalEvents);
assertThat(eventTypes.get(0)).isEqualTo(expectedEventType);
});
}
private CalculatedField createPropagationCF(EntityId entityId) {
CalculatedField cf = new CalculatedField();
cf.setEntityId(entityId);

Loading…
Cancel
Save