From 1631f80da953aef05d9c1cea55c022a03bc13d16 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 8 Nov 2023 15:53:51 +0100 Subject: [PATCH 01/24] fixed query timeout exception --- .../server/controller/AuditLogController.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java b/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java index 387f0d014b..642b744d80 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuditLogController.java @@ -99,7 +99,7 @@ public class AuditLogController extends BaseController { @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { checkParameter("CustomerId", strCustomerId); TenantId tenantId = getCurrentUser().getTenantId(); - TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, getStartTime(startTime), getEndTime(endTime)); List actionTypes = parseActionTypesStr(actionTypesStr); return checkNotNull(auditLogService.findAuditLogsByTenantIdAndCustomerId(tenantId, new CustomerId(UUID.fromString(strCustomerId)), actionTypes, pageLink)); } @@ -133,7 +133,7 @@ public class AuditLogController extends BaseController { @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { checkParameter("UserId", strUserId); TenantId tenantId = getCurrentUser().getTenantId(); - TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, getStartTime(startTime), getEndTime(endTime)); List actionTypes = parseActionTypesStr(actionTypesStr); return checkNotNull(auditLogService.findAuditLogsByTenantIdAndUserId(tenantId, new UserId(UUID.fromString(strUserId)), actionTypes, pageLink)); } @@ -171,7 +171,7 @@ public class AuditLogController extends BaseController { checkParameter("EntityId", strEntityId); checkParameter("EntityType", strEntityType); TenantId tenantId = getCurrentUser().getTenantId(); - TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, getStartTime(startTime), getEndTime(endTime)); List actionTypes = parseActionTypesStr(actionTypesStr); return checkNotNull(auditLogService.findAuditLogsByTenantIdAndEntityId(tenantId, EntityIdFactory.getByTypeAndId(strEntityType, strEntityId), actionTypes, pageLink)); } @@ -202,7 +202,7 @@ public class AuditLogController extends BaseController { @RequestParam(name = "actionTypes", required = false) String actionTypesStr) throws ThingsboardException { TenantId tenantId = getCurrentUser().getTenantId(); List actionTypes = parseActionTypesStr(actionTypesStr); - TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, getStartTime(startTime), getEndTime(endTime)); return checkNotNull(auditLogService.findAuditLogsByTenantId(tenantId, actionTypes, pageLink)); } @@ -214,4 +214,18 @@ public class AuditLogController extends BaseController { } return result; } + + private Long getStartTime(Long startTime) { + if (startTime == null) { + return 1L; + } + return startTime; + } + + private Long getEndTime(Long endTime) { + if (endTime == null) { + return System.currentTimeMillis(); + } + return endTime; + } } From 7b84cfb9e83a0e0b46a19281436f79b1a4c6cce9 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 9 Nov 2023 13:04:08 +0200 Subject: [PATCH 02/24] added "--network=host " for docker command if host equals localhost --- .../controller/DeviceConnectivityControllerTest.java | 12 ++++++------ .../server/dao/util/DeviceConnectivityUtil.java | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java index d280c5770c..015334296b 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -248,10 +248,10 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { "-t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"", credentials.getCredentialsId())); JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); - assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" + + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" + " -p 1883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " + + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients " + "/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + "mosquitto_pub -d -q 1 --cafile ca-root.pem -h localhost -p 8883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"\"", credentials.getCredentialsId())); @@ -286,10 +286,10 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { "-t %s -u \"%s\" -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); - assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" + + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" + " -p 1883 -t %s -u \"%s\" -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); - assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " + + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients " + "/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + "mosquitto_pub -d -q 1 --cafile ca-root.pem -h localhost -p 8883 -t %s -u \"%s\" -m \"{temperature:25}\"\"", DEVICE_TELEMETRY_TOPIC, credentials.getCredentialsId())); @@ -330,10 +330,10 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { "-t %s -i \"%s\" -u \"%s\" -P \"%s\" -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); - assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" + + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h localhost" + " -p 1883 -t %s -i \"%s\" -u \"%s\" -P \"%s\" -m \"{temperature:25}\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); - assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " + + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it --network=host thingsboard/mosquitto-clients " + "/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + "mosquitto_pub -d -q 1 --cafile ca-root.pem -h localhost -p 8883 -t %s -i \"%s\" -u \"%s\" -P \"%s\" -m \"{temperature:25}\"\"", DEVICE_TELEMETRY_TOPIC, clientId, userName, password)); diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java b/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java index db516388a4..8cb0897a98 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java @@ -135,7 +135,7 @@ public class DeviceConnectivityUtil { } StringBuilder mqttDockerCommand = new StringBuilder(); - mqttDockerCommand.append(DOCKER_RUN).append(MQTT_IMAGE); + mqttDockerCommand.append(DOCKER_RUN).append("localhost".equals(host) ? "--network=host ":"").append(MQTT_IMAGE); if (MQTTS.equals(protocol)) { mqttDockerCommand.append("/bin/sh -c \"") @@ -167,6 +167,6 @@ public class DeviceConnectivityUtil { public static String getDockerCoapPublishCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) { String coapCommand = getCoapPublishCommand(protocol, host, port, deviceCredentials); - return coapCommand != null ? String.format("%s%s%s", DOCKER_RUN, COAP_IMAGE, coapCommand) : null; + return coapCommand != null ? String.format("%s%s%s", DOCKER_RUN + ("localhost".equals(host) ? "--network=host ":""), COAP_IMAGE, coapCommand) : null; } } From 0a9c769afe1ac64fd64585b91af562b3aa3d228b Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Thu, 9 Nov 2023 15:14:35 +0200 Subject: [PATCH 03/24] UI: added default value to colorSettings type and default case for colorProcessor --- .../lib/settings/common/color-settings-panel.component.ts | 2 +- ui-ngx/src/app/shared/models/widget-settings.models.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts index 26d838abc0..6ae04ec94e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/common/color-settings-panel.component.ts @@ -65,7 +65,7 @@ export class ColorSettingsPanelComponent extends PageComponent implements OnInit ngOnInit(): void { this.colorSettingsFormGroup = this.fb.group( { - type: [this.colorSettings?.type, []], + type: [this.colorSettings?.type || ColorType.constant, []], color: [this.colorSettings?.color, []], rangeList: [this.colorSettings?.rangeList, []], colorFunction: [this.colorSettings?.colorFunction, []] diff --git a/ui-ngx/src/app/shared/models/widget-settings.models.ts b/ui-ngx/src/app/shared/models/widget-settings.models.ts index 165a4369bc..27b32621aa 100644 --- a/ui-ngx/src/app/shared/models/widget-settings.models.ts +++ b/ui-ngx/src/app/shared/models/widget-settings.models.ts @@ -161,6 +161,8 @@ export abstract class ColorProcessor { return new RangeColorProcessor(settings); case ColorType.function: return new FunctionColorProcessor(settings); + default: + return new ConstantColorProcessor(settings); } } From e8bcf292eecf3fe99082bdb992fb249c29ae084e Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 9 Nov 2023 17:01:14 +0200 Subject: [PATCH 04/24] UI: Fixed updated show value and toltip and liquid level widget --- .../widget/lib/indicator/liquid-level-widget.component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts index 9bb7053c3e..baf9f018c0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts @@ -541,13 +541,13 @@ export class LiquidLevelWidgetComponent implements OnInit { `; } - private convertInputData(value: number): number { + private convertInputData(value: any): number { if (this.settings.datasourceUnits !== CapacityUnits.percent) { - return (convertLiters(value, this.settings.datasourceUnits, ConversionType.to) / + return (convertLiters(Number(value), this.settings.datasourceUnits, ConversionType.to) / convertLiters(this.volume, this.settings.volumeUnits, ConversionType.to)) * 100; } - return value; + return Number(value); } private convertOutputData(value: number): number { From 8b034308de320d4ddb1f96544cc17508a0e6af0a Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 9 Nov 2023 17:13:16 +0200 Subject: [PATCH 05/24] updated docker connectivity command localhost check, updated test --- .../DeviceConnectivityControllerTest.java | 181 +++++++++++++++++- ...ectivityControllerWithDefaultPortTest.java | 159 --------------- .../dao/util/DeviceConnectivityUtil.java | 8 +- 3 files changed, 180 insertions(+), 168 deletions(-) delete mode 100644 application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerWithDefaultPortTest.java diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java index 015334296b..a4897f6bd9 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -24,7 +24,6 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.junit.jupiter.api.BeforeAll; import org.mockito.AdditionalAnswers; import org.mockito.Mockito; import org.springframework.context.annotation.Bean; @@ -100,8 +99,6 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { "C3swkUdrR3pezg==\n" + "-----END PRIVATE KEY-----\n"; - ListeningExecutorService executor; - private Tenant savedTenant; private User tenantAdmin; private DeviceProfileId mqttDeviceProfileId; @@ -117,8 +114,6 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { @Before public void beforeTest() throws Exception { - executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass())); - loginSysAdmin(); ObjectNode config = JacksonUtil.newObjectNode(); @@ -208,8 +203,6 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { @After public void afterTest() throws Exception { - executor.shutdownNow(); - loginSysAdmin(); doDelete("/api/tenant/" + savedTenant.getId().getId()) @@ -261,6 +254,12 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { "-t json -e \"{temperature:25}\"", credentials.getCredentialsId())); assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry" + " -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); + + JsonNode dockerCoapCommands = commands.get(COAP).get(DOCKER); + assertThat(dockerCoapCommands.get(COAP).asText()).isEqualTo(String.format("docker run --rm -it --network=host" + + " thingsboard/coap-clients coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); + assertThat(dockerCoapCommands.get(COAPS).asText()).isEqualTo(String.format("docker run --rm -it --network=host" + + " thingsboard/coap-clients coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); } @Test @@ -445,4 +444,172 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { public void testDownloadCertWithUnknownProtocol() throws Exception { doGet("/api/device-connectivity/unknownProtocol/certificate/download").andExpect(status().isNotFound()); } + + @Test + public void testFetchPublishTelemetryCommandsForDefaultDeviceIfPortsSetToDefault() throws Exception { + loginSysAdmin(); + + ObjectNode config = JacksonUtil.newObjectNode(); + + ObjectNode http = JacksonUtil.newObjectNode(); + http.put("enabled", true); + http.put("host", ""); + http.put("port", 80); + config.set("http", http); + + ObjectNode https = JacksonUtil.newObjectNode(); + https.put("enabled", true); + https.put("host", ""); + https.put("port", 443); + config.set("https", https); + + ObjectNode mqtt = JacksonUtil.newObjectNode(); + mqtt.put("enabled", false); + mqtt.put("host", ""); + mqtt.put("port", 1883); + config.set("mqtt", mqtt); + + ObjectNode mqtts = JacksonUtil.newObjectNode(); + mqtts.put("enabled", false); + mqtts.put("host", ""); + mqtts.put("port", 8883); + config.set("mqtts", mqtts); + + ObjectNode coap = JacksonUtil.newObjectNode(); + coap.put("enabled", false); + coap.put("host", ""); + coap.put("port", 5683); + config.set("coap", coap); + + ObjectNode coaps = JacksonUtil.newObjectNode(); + coaps.put("enabled", false); + coaps.put("host", ""); + coaps.put("port", 5684); + config.set("coaps", coaps); + + AdminSettings adminSettings = doGet("/api/admin/settings/connectivity", AdminSettings.class); + adminSettings.setJsonValue(config); + doPost("/api/admin/settings", adminSettings).andExpect(status().isOk()); + + login("tenant2@thingsboard.org", "testPassword1"); + + Device device = new Device(); + device.setName("My device"); + device.setType("default"); + Device savedDevice = doPost("/api/device", device, Device.class); + JsonNode commands = + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() { + }); + + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + + assertThat(commands).hasSize(1); + JsonNode httpCommands = commands.get(HTTP); + assertThat(httpCommands.get(HTTP).asText()).isEqualTo(String.format("curl -v -X POST http://localhost/api/v1/%s/telemetry " + + "--header Content-Type:application/json --data \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://localhost/api/v1/%s/telemetry " + + "--header Content-Type:application/json --data \"{temperature:25}\"", + credentials.getCredentialsId())); + } + + @Test + public void testFetchPublishTelemetryCommandsForDefaultDeviceIfHostIsNotLocalhost() throws Exception { + loginSysAdmin(); + + ObjectNode config = JacksonUtil.newObjectNode(); + + ObjectNode http = JacksonUtil.newObjectNode(); + http.put("enabled", true); + http.put("host", "test.domain"); + http.put("port", 8080); + config.set("http", http); + + ObjectNode https = JacksonUtil.newObjectNode(); + https.put("enabled", true); + https.put("host", "test.domain"); + https.put("port", 443); + config.set("https", https); + + ObjectNode mqtt = JacksonUtil.newObjectNode(); + mqtt.put("enabled", true); + mqtt.put("host", "test.domain"); + mqtt.put("port", 1883); + config.set("mqtt", mqtt); + + ObjectNode mqtts = JacksonUtil.newObjectNode(); + mqtts.put("enabled", true); + mqtts.put("host", "test.domain"); + mqtts.put("port", 8883); + config.set("mqtts", mqtts); + + ObjectNode coap = JacksonUtil.newObjectNode(); + coap.put("enabled", true); + coap.put("host", "test.domain"); + coap.put("port", 5683); + config.set("coap", coap); + + ObjectNode coaps = JacksonUtil.newObjectNode(); + coaps.put("enabled", true); + coaps.put("host", "test.domain"); + coaps.put("port", 5684); + config.set("coaps", coaps); + + AdminSettings adminSettings = doGet("/api/admin/settings/connectivity", AdminSettings.class); + adminSettings.setJsonValue(config); + doPost("/api/admin/settings", adminSettings).andExpect(status().isOk()); + + login("tenant2@thingsboard.org", "testPassword1"); + + Device device = new Device(); + device.setName("My device"); + device.setType("default"); + Device savedDevice = doPost("/api/device", device, Device.class); + JsonNode commands = + doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() { + }); + + DeviceCredentials credentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + + assertThat(commands).hasSize(3); + JsonNode httpCommands = commands.get(HTTP); + assertThat(httpCommands.get(HTTP).asText()).isEqualTo(String.format("curl -v -X POST http://test.domain:8080/api/v1/%s/telemetry " + + "--header Content-Type:application/json --data \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://test.domain/api/v1/%s/telemetry " + + "--header Content-Type:application/json --data \"{temperature:25}\"", + credentials.getCredentialsId())); + + + JsonNode mqttCommands = commands.get(MQTT); + assertThat(mqttCommands.get(MQTT).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 -h test.domain -p 1883 -t v1/devices/me/telemetry " + + "-u \"%s\" -m \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(mqttCommands.get(MQTTS).get(0).asText()).isEqualTo("curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download"); + assertThat(mqttCommands.get(MQTTS).get(1).asText()).isEqualTo(String.format("mosquitto_pub -d -q 1 --cafile ca-root.pem -h test.domain -p 8883 " + + "-t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"", credentials.getCredentialsId())); + + JsonNode dockerMqttCommands = commands.get(MQTT).get(DOCKER); + assertThat(dockerMqttCommands.get(MQTT).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients mosquitto_pub -d -q 1 -h test.domain" + + " -p 1883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"", + credentials.getCredentialsId())); + assertThat(dockerMqttCommands.get(MQTTS).asText()).isEqualTo(String.format("docker run --rm -it thingsboard/mosquitto-clients " + + "/bin/sh -c \"curl -f -S -o ca-root.pem http://localhost:80/api/device-connectivity/mqtts/certificate/download && " + + "mosquitto_pub -d -q 1 --cafile ca-root.pem -h test.domain -p 8883 -t v1/devices/me/telemetry -u \"%s\" -m \"{temperature:25}\"\"", + credentials.getCredentialsId())); + + JsonNode linuxCoapCommands = commands.get(COAP); + assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -m POST coap://test.domain:5683/api/v1/%s/telemetry " + + "-t json -e \"{temperature:25}\"", credentials.getCredentialsId())); + assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://test.domain:5684/api/v1/%s/telemetry" + + " -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); + + JsonNode dockerCoapCommands = commands.get(COAP).get(DOCKER); + assertThat(dockerCoapCommands.get(COAP).asText()).isEqualTo(String.format("docker run --rm -it " + + "thingsboard/coap-clients coap-client -m POST coap://test.domain:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); + assertThat(dockerCoapCommands.get(COAPS).asText()).isEqualTo(String.format("docker run --rm -it " + + "thingsboard/coap-clients coap-client-openssl -m POST coaps://test.domain:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); + } } diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerWithDefaultPortTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerWithDefaultPortTest.java deleted file mode 100644 index f2f52443c3..0000000000 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerWithDefaultPortTest.java +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Copyright © 2016-2023 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.controller; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.util.concurrent.ListeningExecutorService; -import com.google.common.util.concurrent.MoreExecutors; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.mockito.AdditionalAnswers; -import org.mockito.Mockito; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Primary; -import org.springframework.test.context.ContextConfiguration; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.common.util.ThingsBoardExecutors; -import org.thingsboard.server.common.data.AdminSettings; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.dao.device.DeviceDao; -import org.thingsboard.server.dao.service.DaoSqlTest; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTP; -import static org.thingsboard.server.dao.util.DeviceConnectivityUtil.HTTPS; - -@ContextConfiguration(classes = {DeviceConnectivityControllerWithDefaultPortTest.Config.class}) -@DaoSqlTest -public class DeviceConnectivityControllerWithDefaultPortTest extends AbstractControllerTest { - - ListeningExecutorService executor; - - private Tenant savedTenant; - - static class Config { - @Bean - @Primary - public DeviceDao deviceDao(DeviceDao deviceDao) { - return Mockito.mock(DeviceDao.class, AdditionalAnswers.delegatesTo(deviceDao)); - } - } - - @Before - public void beforeTest() throws Exception { - executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass())); - - loginSysAdmin(); - - ObjectNode config = JacksonUtil.newObjectNode(); - - ObjectNode http = JacksonUtil.newObjectNode(); - http.put("enabled", true); - http.put("host", ""); - http.put("port", 80); - config.set("http", http); - - ObjectNode https = JacksonUtil.newObjectNode(); - https.put("enabled", true); - https.put("host", ""); - https.put("port", 443); - config.set("https", https); - - ObjectNode mqtt = JacksonUtil.newObjectNode(); - mqtt.put("enabled", false); - mqtt.put("host", ""); - mqtt.put("port", 1883); - config.set("mqtt", mqtt); - - ObjectNode mqtts = JacksonUtil.newObjectNode(); - mqtts.put("enabled", false); - mqtts.put("host", ""); - mqtts.put("port", 8883); - config.set("mqtts", mqtts); - - ObjectNode coap = JacksonUtil.newObjectNode(); - coap.put("enabled", false); - coap.put("host", ""); - coap.put("port", 5683); - config.set("coap", coap); - - ObjectNode coaps = JacksonUtil.newObjectNode(); - coaps.put("enabled", false); - coaps.put("host", ""); - coaps.put("port", 5684); - config.set("coaps", coaps); - - AdminSettings adminSettings = doGet("/api/admin/settings/connectivity", AdminSettings.class); - adminSettings.setJsonValue(config); - doPost("/api/admin/settings", adminSettings).andExpect(status().isOk()); - - Tenant tenant = new Tenant(); - tenant.setTitle("My tenant"); - savedTenant = doPost("/api/tenant", tenant, Tenant.class); - Assert.assertNotNull(savedTenant); - - User tenantAdmin = new User(); - tenantAdmin.setAuthority(Authority.TENANT_ADMIN); - tenantAdmin.setTenantId(savedTenant.getId()); - tenantAdmin.setEmail("tenant2@thingsboard.org"); - tenantAdmin.setFirstName("Joe"); - tenantAdmin.setLastName("Downs"); - - createUserAndLogin(tenantAdmin, "testPassword1"); - } - - @After - public void afterTest() throws Exception { - executor.shutdownNow(); - - loginSysAdmin(); - - doDelete("/api/tenant/" + savedTenant.getId().getId()) - .andExpect(status().isOk()); - } - - @Test - public void testFetchPublishTelemetryCommandsForDefaultDevice() throws Exception { - Device device = new Device(); - device.setName("My device"); - device.setType("default"); - Device savedDevice = doPost("/api/device", device, Device.class); - JsonNode commands = - doGetTyped("/api/device-connectivity/" + savedDevice.getId().getId(), new TypeReference<>() { - }); - - DeviceCredentials credentials = - doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); - - assertThat(commands).hasSize(1); - JsonNode httpCommands = commands.get(HTTP); - assertThat(httpCommands.get(HTTP).asText()).isEqualTo(String.format("curl -v -X POST http://localhost/api/v1/%s/telemetry " + - "--header Content-Type:application/json --data \"{temperature:25}\"", - credentials.getCredentialsId())); - assertThat(httpCommands.get(HTTPS).asText()).isEqualTo(String.format("curl -v -X POST https://localhost/api/v1/%s/telemetry " + - "--header Content-Type:application/json --data \"{temperature:25}\"", - credentials.getCredentialsId())); - } -} diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java b/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java index 8cb0897a98..bde018122c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java @@ -20,6 +20,9 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials; +import java.util.Arrays; +import java.util.List; + public class DeviceConnectivityUtil { public static final String HTTP = "http"; @@ -38,6 +41,7 @@ public class DeviceConnectivityUtil { public static final String GATEWAY_DOCKER_RUN = "docker run -it "; public static final String MQTT_IMAGE = "thingsboard/mosquitto-clients "; public static final String COAP_IMAGE = "thingsboard/coap-clients "; + public static final List LOCAL_HOSTS = Arrays.asList("localhost", "127.0.0.1"); public static String getHttpPublishCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) { return String.format("curl -v -X POST %s://%s%s/api/v1/%s/telemetry --header Content-Type:application/json --data " + JSON_EXAMPLE_PAYLOAD, @@ -135,7 +139,7 @@ public class DeviceConnectivityUtil { } StringBuilder mqttDockerCommand = new StringBuilder(); - mqttDockerCommand.append(DOCKER_RUN).append("localhost".equals(host) ? "--network=host ":"").append(MQTT_IMAGE); + mqttDockerCommand.append(DOCKER_RUN).append(LOCAL_HOSTS.contains(host) ? "--network=host ":"").append(MQTT_IMAGE); if (MQTTS.equals(protocol)) { mqttDockerCommand.append("/bin/sh -c \"") @@ -167,6 +171,6 @@ public class DeviceConnectivityUtil { public static String getDockerCoapPublishCommand(String protocol, String host, String port, DeviceCredentials deviceCredentials) { String coapCommand = getCoapPublishCommand(protocol, host, port, deviceCredentials); - return coapCommand != null ? String.format("%s%s%s", DOCKER_RUN + ("localhost".equals(host) ? "--network=host ":""), COAP_IMAGE, coapCommand) : null; + return coapCommand != null ? String.format("%s%s%s", DOCKER_RUN + (LOCAL_HOSTS.contains(host) ? "--network=host ":""), COAP_IMAGE, coapCommand) : null; } } From 6358b0be90c1612c616c903ed1589b9f03b3678e Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Thu, 9 Nov 2023 18:16:40 +0200 Subject: [PATCH 06/24] Admin settings update. @author AndriiLandiak --- .../service/edge/EdgeContextComponent.java | 4 - .../service/edge/rpc/EdgeSyncCursor.java | 2 +- .../fetch/AdminSettingsEdgeEventFetcher.java | 125 ++---------------- .../relation/RelationEdgeProcessor.java | 2 + .../resource/BaseResourceProcessor.java | 22 ++- .../resource/ResourceEdgeProcessor.java | 12 +- .../edge/rpc/utils/EdgeVersionUtils.java | 36 +++++ .../queue/DefaultTbClusterService.java | 8 +- .../server/service/queue/ProtoUtils.java | 21 ++- .../server/controller/EdgeControllerTest.java | 21 ++- .../server/edge/AbstractEdgeTest.java | 38 ++++-- .../server/edge/AssetEdgeTest.java | 4 +- .../server/edge/DeviceEdgeTest.java | 4 +- .../server/edge/ResourceEdgeTest.java | 51 +++++++ common/cluster-api/src/main/proto/queue.proto | 8 +- .../thingsboard/edge/rpc/EdgeGrpcClient.java | 2 +- common/edge-api/src/main/proto/edge.proto | 5 +- 17 files changed, 199 insertions(+), 166 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java index 7850032142..0d2b6675c9 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.edge; -import freemarker.template.Configuration; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; @@ -87,9 +86,6 @@ public class EdgeContextComponent { @Autowired private AdminSettingsService adminSettingsService; - @Autowired - private Configuration freemarkerConfig; - @Autowired private DeviceService deviceService; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java index 5074d580f0..98c93e54ec 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java @@ -57,7 +57,7 @@ public class EdgeSyncCursor { fetchers.add(new TenantEdgeEventFetcher(ctx.getTenantService())); fetchers.add(new QueuesEdgeEventFetcher(ctx.getQueueService())); fetchers.add(new RuleChainsEdgeEventFetcher(ctx.getRuleChainService())); - fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService(), ctx.getFreemarkerConfig())); + fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService())); fetchers.add(new TenantAdminUsersEdgeEventFetcher(ctx.getUserService())); Customer publicCustomer = ctx.getCustomerService().findOrCreatePublicCustomer(edge.getTenantId()); fetchers.add(new CustomerEdgeEventFetcher(publicCustomer.getId())); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java index e3a8c14012..e522529b8e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2023 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 - * + *

+ * 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. @@ -15,146 +15,49 @@ */ package org.thingsboard.server.service.edge.rpc.fetch; -import com.datastax.oss.driver.api.core.uuid.Uuids; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import freemarker.template.Configuration; -import freemarker.template.Template; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.text.WordUtils; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.EdgeUtils; -import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; -import org.thingsboard.server.common.data.id.AdminSettingsId; +import org.thingsboard.server.common.data.id.EdgeId; 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.dao.settings.AdminSettingsService; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; @AllArgsConstructor @Slf4j public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher { private final AdminSettingsService adminSettingsService; - private final Configuration freemarkerConfig; - - private static final Pattern startPattern = Pattern.compile("

"); - private static final Pattern endPattern = Pattern.compile("
"); - - private static final List templatesNames = Arrays.asList( - "account.activated.ftl", - "account.lockout.ftl", - "activation.ftl", - "password.was.reset.ftl", - "reset.password.ftl", - "test.ftl"); - - // TODO: @voba fix format of next templates - // "state.disabled.ftl", - // "state.enabled.ftl", - // "state.warning.ftl", @Override public PageLink getPageLink(int pageSize) { return null; } - @Override - public PageData fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) throws Exception { - List result = new ArrayList<>(); - - AdminSettings systemMailSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail"); - result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, - EdgeEventActionType.UPDATED, null, JacksonUtil.valueToTree(systemMailSettings))); - - AdminSettings tenantMailSettings = convertToTenantAdminSettings(tenantId, systemMailSettings.getKey(), (ObjectNode) systemMailSettings.getJsonValue()); - result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, - EdgeEventActionType.UPDATED, null, JacksonUtil.valueToTree(tenantMailSettings))); - - AdminSettings systemMailTemplates = loadMailTemplates(tenantId); - result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, - EdgeEventActionType.UPDATED, null, JacksonUtil.valueToTree(systemMailTemplates))); - - AdminSettings tenantMailTemplates = convertToTenantAdminSettings(tenantId, systemMailTemplates.getKey(), (ObjectNode) systemMailTemplates.getJsonValue()); - result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, - EdgeEventActionType.UPDATED, null, JacksonUtil.valueToTree(tenantMailTemplates))); + public PageData fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) { + List result = fetchAdminSettingsForKeys(tenantId, edge.getId(), List.of("general", "mail", "connectivity", "jwt")); // return PageData object to be in sync with other fetchers return new PageData<>(result, 1, result.size(), false); } - private AdminSettings loadMailTemplates(TenantId tenantId) throws Exception { - Map mailTemplates = new HashMap<>(); - for (String templatesName : templatesNames) { - Template template = freemarkerConfig.getTemplate(templatesName); - if (template != null) { - String name = validateName(template.getName()); - Map mailTemplate = getMailTemplateFromFile(template.toString()); - if (mailTemplate != null) { - mailTemplates.put(name, mailTemplate); - } else { - log.error("[{}] Can't load mail template from file {}", tenantId, template.getName()); - } - } - } - AdminSettings adminSettings = new AdminSettings(); - adminSettings.setId(new AdminSettingsId(Uuids.timeBased())); - adminSettings.setKey("mailTemplates"); - adminSettings.setJsonValue(JacksonUtil.convertValue(mailTemplates, JsonNode.class)); - return adminSettings; - } - - private Map getMailTemplateFromFile(String stringTemplate) { - Map mailTemplate = new HashMap<>(); - Matcher start = startPattern.matcher(stringTemplate); - Matcher end = endPattern.matcher(stringTemplate); - if (start.find() && end.find()) { - String body = StringUtils.substringBetween(stringTemplate, start.group(), end.group()).replaceAll("\t", ""); - String subject = StringUtils.substringBetween(body, "

", "

"); - mailTemplate.put("subject", subject); - mailTemplate.put("body", body); - } else { - return null; - } - return mailTemplate; - } - - private String validateName(String name) throws Exception { - StringBuilder nameBuilder = new StringBuilder(); - name = name.replace(".ftl", ""); - String[] nameParts = name.split("\\."); - if (nameParts.length >= 1) { - nameBuilder.append(nameParts[0]); - for (int i = 1; i < nameParts.length; i++) { - String word = WordUtils.capitalize(nameParts[i]); - nameBuilder.append(word); - } - return nameBuilder.toString(); - } else { - throw new Exception("Error during filename validation"); + private List fetchAdminSettingsForKeys(TenantId tenantId, EdgeId edgeId, List keys) { + List result = new ArrayList<>(); + for (String key : keys) { + AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key); + result.add(EdgeUtils.constructEdgeEvent(tenantId, edgeId, EdgeEventType.ADMIN_SETTINGS, + EdgeEventActionType.UPDATED, null, JacksonUtil.valueToTree(adminSettings))); } - } - - private AdminSettings convertToTenantAdminSettings(TenantId tenantId, String key, ObjectNode jsonValue) { - AdminSettings tenantMailSettings = new AdminSettings(); - tenantMailSettings.setTenantId(tenantId); - jsonValue.put("useSystemMailSettings", true); - tenantMailSettings.setJsonValue(jsonValue); - tenantMailSettings.setKey(key); - return tenantMailSettings; + return result; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java index fe76fed545..487267e60f 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java @@ -71,10 +71,12 @@ public class RelationEdgeProcessor extends BaseRelationProcessor { if (relation == null || (relation.getFrom().getEntityType().equals(EntityType.EDGE) || relation.getTo().getEntityType().equals(EntityType.EDGE))) { return Futures.immediateFuture(null); } + EdgeId sourceEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB()); Set uniqueEdgeIds = new HashSet<>(); uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getTo())); uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getFrom())); + uniqueEdgeIds.remove(sourceEdgeId); if (uniqueEdgeIds.isEmpty()) { return Futures.immediateFuture(null); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java index 13ca012f3c..a00745d8b7 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/BaseResourceProcessor.java @@ -18,17 +18,20 @@ package org.thingsboard.server.service.edge.rpc.processor.resource; import com.datastax.oss.driver.api.core.uuid.Uuids; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.ResourceType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.gen.edge.v1.ResourceUpdateMsg; import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @Slf4j public abstract class BaseResourceProcessor extends BaseEdgeProcessor { - protected void saveOrUpdateTbResource(TenantId tenantId, TbResourceId tbResourceId, ResourceUpdateMsg resourceUpdateMsg) { + protected boolean saveOrUpdateTbResource(TenantId tenantId, TbResourceId tbResourceId, ResourceUpdateMsg resourceUpdateMsg) { + boolean resourceKeyUpdated = false; try { boolean created = false; TbResource resource = resourceService.findResourceById(tenantId, tbResourceId); @@ -42,9 +45,21 @@ public abstract class BaseResourceProcessor extends BaseEdgeProcessor { resource.setCreatedTime(Uuids.unixTimestamp(tbResourceId.getId())); created = true; } + String resourceKey = resourceUpdateMsg.getResourceKey(); + ResourceType resourceType = ResourceType.valueOf(resourceUpdateMsg.getResourceType()); + PageDataIterable resourcesIterable = new PageDataIterable<>( + link -> resourceService.findTenantResourcesByResourceTypeAndPageLink(tenantId, resourceType, link), 1024); + for (TbResource tbResource : resourcesIterable) { + if (tbResource.getResourceKey().equals(resourceUpdateMsg.getResourceKey()) && !tbResourceId.equals(tbResource.getId())) { + resourceKey = StringUtils.randomAlphabetic(15) + "_" + resourceKey; + log.warn("[{}] Resource with resource type {} and key {} already exists. Renaming resource key to {}", + tenantId, resourceType, resourceUpdateMsg.getResourceKey(), resourceKey); + resourceKeyUpdated = true; + } + } resource.setTitle(resourceUpdateMsg.getTitle()); - resource.setResourceKey(resourceUpdateMsg.getResourceKey()); - resource.setResourceType(ResourceType.valueOf(resourceUpdateMsg.getResourceType())); + resource.setResourceKey(resourceKey); + resource.setResourceType(resourceType); resource.setFileName(resourceUpdateMsg.getFileName()); resource.setData(resourceUpdateMsg.hasData() ? resourceUpdateMsg.getData() : null); resource.setEtag(resourceUpdateMsg.hasEtag() ? resourceUpdateMsg.getEtag() : null); @@ -57,5 +72,6 @@ public abstract class BaseResourceProcessor extends BaseEdgeProcessor { log.error("[{}] Failed to process resource update msg [{}]", tenantId, resourceUpdateMsg, e); throw e; } + return resourceKeyUpdated; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java index e7c5b21dbd..ad283826f8 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/resource/ResourceEdgeProcessor.java @@ -23,6 +23,8 @@ import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.exception.DataValidationException; @@ -46,14 +48,12 @@ public class ResourceEdgeProcessor extends BaseResourceProcessor { switch (resourceUpdateMsg.getMsgType()) { case ENTITY_CREATED_RPC_MESSAGE: case ENTITY_UPDATED_RPC_MESSAGE: - super.saveOrUpdateTbResource(tenantId, tbResourceId, resourceUpdateMsg); - break; - case ENTITY_DELETED_RPC_MESSAGE: - TbResource tbResourceToDelete = resourceService.findResourceById(tenantId, tbResourceId); - if (tbResourceToDelete != null) { - resourceService.deleteResource(tenantId, tbResourceId); + boolean resourceKeyUpdated = super.saveOrUpdateTbResource(tenantId, tbResourceId, resourceUpdateMsg); + if (resourceKeyUpdated) { + saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.TB_RESOURCE, EdgeEventActionType.UPDATED, tbResourceId, null); } break; + case ENTITY_DELETED_RPC_MESSAGE: case UNRECOGNIZED: return handleUnsupportedMsgType(resourceUpdateMsg.getMsgType()); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java new file mode 100644 index 0000000000..75cd430a26 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2023 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.service.edge.rpc.utils; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; + +@Slf4j +public final class EdgeVersionUtils { + + public static boolean isEdgeProtoDeprecated(EdgeVersion edgeVersion) { + switch (edgeVersion) { + case V_3_3_0: + case V_3_3_3: + case V_3_4_0: + case V_3_6_0: + return true; + case V_3_6_1: + default: + return false; + } + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 103d2b68e2..f96e49930c 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -486,7 +486,7 @@ public class DefaultTbClusterService implements TbClusterService { } @Override - public void sendNotificationMsgToEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action, EdgeId sourceEdgeId) { + public void sendNotificationMsgToEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action, EdgeId originatorEdgeId) { if (!edgesEnabled) { return; } @@ -519,9 +519,9 @@ public class DefaultTbClusterService implements TbClusterService { if (body != null) { builder.setBody(body); } - if (sourceEdgeId != null) { - builder.setSourceEdgeIdMSB(sourceEdgeId.getId().getMostSignificantBits()); - builder.setSourceEdgeIdLSB(sourceEdgeId.getId().getLeastSignificantBits()); + if (originatorEdgeId != null) { + builder.setOriginatorEdgeIdMSB(originatorEdgeId.getId().getMostSignificantBits()); + builder.setOriginatorEdgeIdLSB(originatorEdgeId.getId().getLeastSignificantBits()); } TransportProtos.EdgeNotificationMsgProto msg = builder.build(); log.trace("[{}] sending notification to edge service {}", tenantId.getId(), msg); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/ProtoUtils.java b/application/src/main/java/org/thingsboard/server/service/queue/ProtoUtils.java index 76a8e3e89a..36038568bc 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/ProtoUtils.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/ProtoUtils.java @@ -152,22 +152,29 @@ public class ProtoUtils { } private static TransportProtos.DeviceEdgeUpdateMsgProto toProto(DeviceEdgeUpdateMsg msg) { - return TransportProtos.DeviceEdgeUpdateMsgProto.newBuilder() + TransportProtos.DeviceEdgeUpdateMsgProto.Builder builder = TransportProtos.DeviceEdgeUpdateMsgProto.newBuilder() .setTenantIdMSB(msg.getTenantId().getId().getMostSignificantBits()) .setTenantIdLSB(msg.getTenantId().getId().getLeastSignificantBits()) .setDeviceIdMSB(msg.getDeviceId().getId().getMostSignificantBits()) - .setDeviceIdLSB(msg.getDeviceId().getId().getLeastSignificantBits()) - .setEdgeIdMSB(msg.getEdgeId().getId().getMostSignificantBits()) - .setEdgeIdLSB(msg.getEdgeId().getId().getLeastSignificantBits()) - .build(); + .setDeviceIdLSB(msg.getDeviceId().getId().getLeastSignificantBits()); + + if (msg.getEdgeId() != null) { + builder.setEdgeIdMSB(msg.getEdgeId().getId().getMostSignificantBits()) + .setEdgeIdLSB(msg.getEdgeId().getId().getLeastSignificantBits()); + } + + return builder.build(); } private static DeviceEdgeUpdateMsg fromProto(TransportProtos.DeviceEdgeUpdateMsgProto proto) { + EdgeId edgeId = null; + if (proto.hasEdgeIdMSB() && proto.hasEdgeIdLSB()) { + edgeId = new EdgeId(new UUID(proto.getEdgeIdMSB(), proto.getEdgeIdLSB())); + } return new DeviceEdgeUpdateMsg( TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())), new DeviceId(new UUID(proto.getDeviceIdMSB(), proto.getDeviceIdLSB())), - new EdgeId(new UUID(proto.getEdgeIdMSB(), proto.getEdgeIdLSB())) - ); + edgeId); } private static TransportProtos.DeviceNameOrTypeUpdateMsgProto toProto(DeviceNameOrTypeUpdateMsg msg) { diff --git a/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java index b9e0b8cd6c..f0c47bb53a 100644 --- a/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java @@ -54,6 +54,7 @@ import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.security.model.JwtSettings; import org.thingsboard.server.dao.edge.EdgeDao; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.model.ModelConstants; @@ -843,6 +844,13 @@ public class EdgeControllerTest extends AbstractControllerTest { @Test public void testSyncEdge() throws Exception { + loginSysAdmin(); + // get jwt settings from yaml config + JwtSettings settings = doGet("/api/admin/jwtSettings", JwtSettings.class); + // save jwt settings into db + doPost("/api/admin/jwtSettings", settings).andExpect(status().isOk()); + loginTenantAdmin(); + Asset asset = new Asset(); asset.setName("Test Sync Edge Asset 1"); asset.setType("test"); @@ -904,10 +912,10 @@ public class EdgeControllerTest extends AbstractControllerTest { private void verifyFetchersMsgs(EdgeImitator edgeImitator) { Assert.assertTrue(popQueueMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Main")); Assert.assertTrue(popRuleChainMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Edge Root Rule Chain")); - Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mail", true)); - Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mail", false)); - Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mailTemplates", true)); - Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mailTemplates", false)); + Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "general")); + Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "mail")); + Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "connectivity")); + Assert.assertTrue(popAdminSettingsMsg(edgeImitator.getDownlinkMsgs(), "jwt")); Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default")); @@ -953,12 +961,11 @@ public class EdgeControllerTest extends AbstractControllerTest { return false; } - private boolean popAdminSettingsMsg(List messages, String key, boolean isSystem) { + private boolean popAdminSettingsMsg(List messages, String key) { for (AbstractMessage message : messages) { if (message instanceof AdminSettingsUpdateMsg) { AdminSettingsUpdateMsg adminSettingsUpdateMsg = (AdminSettingsUpdateMsg) message; - if (key.equals(adminSettingsUpdateMsg.getKey()) - && isSystem == adminSettingsUpdateMsg.getIsSystem()) { + if (key.equals(adminSettingsUpdateMsg.getKey())) { messages.remove(message); return true; } diff --git a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java index bfe6eb8190..3b63385bc8 100644 --- a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java @@ -71,6 +71,7 @@ import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainType; +import org.thingsboard.server.common.data.security.model.JwtSettings; import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.edge.imitator.EdgeImitator; @@ -127,6 +128,13 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { @Before public void setupEdgeTest() throws Exception { + loginSysAdmin(); + + // get jwt settings from yaml config + JwtSettings settings = doGet("/api/admin/jwtSettings", JwtSettings.class); + // save jwt settings into db + doPost("/api/admin/jwtSettings", settings).andExpect(status().isOk()); + loginTenantAdmin(); installation(); @@ -237,9 +245,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { validateMsgsCnt(RuleChainMetadataUpdateMsg.class, 1); validateRuleChainMetadataUpdates(ruleChainUUID); - // 4 messages - // - 2 from fetcher - system level ('mail', 'mailTemplates') - // - 2 from fetcher - admin level ('mail', 'mailTemplates') + // 4 messages ('general', 'mail', 'connectivity', 'jwt) validateMsgsCnt(AdminSettingsUpdateMsg.class, 4); validateAdminSettings(); @@ -418,15 +424,23 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { Assert.assertEquals(4, adminSettingsUpdateMsgs.size()); for (AdminSettingsUpdateMsg adminSettingsUpdateMsg : adminSettingsUpdateMsgs) { + if (adminSettingsUpdateMsg.getKey().equals("general")) { + validateGeneralAdminSettings(adminSettingsUpdateMsg); + } if (adminSettingsUpdateMsg.getKey().equals("mail")) { validateMailAdminSettings(adminSettingsUpdateMsg); } - if (adminSettingsUpdateMsg.getKey().equals("mailTemplates")) { - validateMailTemplatesAdminSettings(adminSettingsUpdateMsg); + if (adminSettingsUpdateMsg.getKey().equals("connectivity")) { + validateConnectivityAdminSettings(adminSettingsUpdateMsg); } } } + private void validateGeneralAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) { + JsonNode jsonNode = JacksonUtil.toJsonNode(adminSettingsUpdateMsg.getJsonValue()); + Assert.assertNotNull(jsonNode.get("baseUrl")); + } + private void validateMailAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) { JsonNode jsonNode = JacksonUtil.toJsonNode(adminSettingsUpdateMsg.getJsonValue()); Assert.assertNotNull(jsonNode.get("mailFrom")); @@ -436,14 +450,14 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { Assert.assertNotNull(jsonNode.get("timeout")); } - private void validateMailTemplatesAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) { + private void validateConnectivityAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) { JsonNode jsonNode = JacksonUtil.toJsonNode(adminSettingsUpdateMsg.getJsonValue()); - Assert.assertNotNull(jsonNode.get("accountActivated")); - Assert.assertNotNull(jsonNode.get("accountLockout")); - Assert.assertNotNull(jsonNode.get("activation")); - Assert.assertNotNull(jsonNode.get("passwordWasReset")); - Assert.assertNotNull(jsonNode.get("resetPassword")); - Assert.assertNotNull(jsonNode.get("test")); + Assert.assertNotNull(jsonNode.get("http")); + Assert.assertNotNull(jsonNode.get("https")); + Assert.assertNotNull(jsonNode.get("mqtt")); + Assert.assertNotNull(jsonNode.get("mqtts")); + Assert.assertNotNull(jsonNode.get("coap")); + Assert.assertNotNull(jsonNode.get("coaps")); } private void validateAssetProfiles() throws Exception { diff --git a/application/src/test/java/org/thingsboard/server/edge/AssetEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/AssetEdgeTest.java index 08ea322cb8..0eb19a17fb 100644 --- a/application/src/test/java/org/thingsboard/server/edge/AssetEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/AssetEdgeTest.java @@ -173,7 +173,7 @@ public class AssetEdgeTest extends AbstractEdgeTest { assetUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); assetUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); assetUpdateMsgBuilder.setName("Asset Edge 2"); - assetUpdateMsgBuilder.setType("test"); + assetUpdateMsgBuilder.setType("default"); assetUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); testAutoGeneratedCodeByProtobuf(assetUpdateMsgBuilder); uplinkMsgBuilder.addAssetUpdateMsg(assetUpdateMsgBuilder.build()); @@ -205,7 +205,7 @@ public class AssetEdgeTest extends AbstractEdgeTest { assetUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); assetUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); assetUpdateMsgBuilder.setName(assetOnCloudName); - assetUpdateMsgBuilder.setType("test"); + assetUpdateMsgBuilder.setType("default"); assetUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); testAutoGeneratedCodeByProtobuf(assetUpdateMsgBuilder); uplinkMsgBuilder.addAssetUpdateMsg(assetUpdateMsgBuilder.build()); diff --git a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java index de6939444d..e6fee370f1 100644 --- a/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java @@ -492,7 +492,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest { deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); deviceUpdateMsgBuilder.setName(deviceOnCloudName); - deviceUpdateMsgBuilder.setType("test"); + deviceUpdateMsgBuilder.setType("default"); deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); testAutoGeneratedCodeByProtobuf(deviceUpdateMsgBuilder); uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build()); @@ -541,7 +541,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest { deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); deviceUpdateMsgBuilder.setName("Edge Device 2"); - deviceUpdateMsgBuilder.setType("test"); + deviceUpdateMsgBuilder.setType("default"); deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build()); diff --git a/application/src/test/java/org/thingsboard/server/edge/ResourceEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/ResourceEdgeTest.java index 6a3001f292..2ae57f7f19 100644 --- a/application/src/test/java/org/thingsboard/server/edge/ResourceEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/ResourceEdgeTest.java @@ -28,6 +28,7 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UplinkMsg; import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg; +import java.util.Optional; import java.util.UUID; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -120,4 +121,54 @@ public class ResourceEdgeTest extends AbstractEdgeTest { Assert.assertNotNull(tbResource); Assert.assertEquals("Edge Test Resource", tbResource.getName()); } + + @Test + public void testResourceToCloudWithNameThatAlreadyExistsOnCloud() throws Exception { + TbResource resource = new TbResource(); + resource.setResourceType(ResourceType.JKS); + resource.setTitle("Edge Test Resource"); + resource.setFileName(FILE_NAME); + resource.setData(TEST_DATA); + + edgeImitator.expectMessageAmount(1); + TbResource savedResource = doPost("/api/resource", resource, TbResource.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + + UUID uuid = Uuids.timeBased(); + + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + ResourceUpdateMsg.Builder resourceUpdateMsgBuilder = ResourceUpdateMsg.newBuilder(); + resourceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); + resourceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); + resourceUpdateMsgBuilder.setTitle("Edge Test Resource"); + resourceUpdateMsgBuilder.setResourceType(ResourceType.JKS.name()); + resourceUpdateMsgBuilder.setResourceKey(FILE_NAME); + resourceUpdateMsgBuilder.setFileName(FILE_NAME); + resourceUpdateMsgBuilder.setData(TEST_DATA); + resourceUpdateMsgBuilder.setIsSystem(false); + resourceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); + testAutoGeneratedCodeByProtobuf(resourceUpdateMsgBuilder); + uplinkMsgBuilder.addResourceUpdateMsg(resourceUpdateMsgBuilder.build()); + + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.expectMessageAmount(1); + + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); + + Optional resourceUpdateMsgOpt = edgeImitator.findMessageByType(ResourceUpdateMsg.class); + Assert.assertTrue(resourceUpdateMsgOpt.isPresent()); + ResourceUpdateMsg latestResourceUpdateMsg = resourceUpdateMsgOpt.get(); + Assert.assertNotEquals(FILE_NAME, latestResourceUpdateMsg.getResourceKey()); + + Assert.assertNotEquals(savedResource.getUuidId(), uuid); + + TbResource tbResource = doGet("/api/resource/" + uuid, TbResource.class); + Assert.assertNotNull(tbResource); + Assert.assertNotEquals(FILE_NAME, tbResource.getName()); + } } diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 6116411b2e..016a40f5dc 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -910,8 +910,8 @@ message EdgeNotificationMsgProto { string body = 10; PostTelemetryMsg postTelemetryMsg = 11; PostAttributeMsg postAttributesMsg = 12; - int64 sourceEdgeIdMSB = 13; - int64 sourceEdgeIdLSB = 14; + int64 originatorEdgeIdMSB = 13; + int64 originatorEdgeIdLSB = 14; } message EdgeEventUpdateMsgProto { @@ -945,8 +945,8 @@ message DeviceEdgeUpdateMsgProto { int64 tenantIdLSB = 2; int64 deviceIdMSB = 3; int64 deviceIdLSB = 4; - int64 edgeIdMSB = 5; - int64 edgeIdLSB = 6; + optional int64 edgeIdMSB = 5; + optional int64 edgeIdLSB = 6; } message DeviceNameOrTypeUpdateMsgProto { diff --git a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java index 35a4ab9cd5..a9b12865a2 100644 --- a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java +++ b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java @@ -111,7 +111,7 @@ public class EdgeGrpcClient implements EdgeRpcClient { .setConnectRequestMsg(ConnectRequestMsg.newBuilder() .setEdgeRoutingKey(edgeKey) .setEdgeSecret(edgeSecret) - .setEdgeVersion(EdgeVersion.V_3_6_0) + .setEdgeVersion(EdgeVersion.V_3_6_1) .setMaxInboundMessageSize(maxInboundMessageSize) .build()) .build()); diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto index f145ae3b98..71e84304ed 100644 --- a/common/edge-api/src/main/proto/edge.proto +++ b/common/edge-api/src/main/proto/edge.proto @@ -35,6 +35,7 @@ enum EdgeVersion { V_3_3_3 = 1; V_3_4_0 = 2; V_3_6_0 = 3; + V_3_6_1 = 4; } /** @@ -361,8 +362,8 @@ message WidgetsBundleUpdateMsg { optional bytes image = 6; bool isSystem = 7; optional string description = 8; - optional int32 order = 9; - optional string widgets = 10; + optional string widgets = 9; + optional int32 order = 10; } message WidgetTypeUpdateMsg { From 3d7b4d4c6219f5c848002a43ef3d525942e8a8f8 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Thu, 9 Nov 2023 18:21:50 +0200 Subject: [PATCH 07/24] Fixed comp error --- .../service/edge/rpc/processor/BaseEdgeProcessor.java | 10 +++++----- .../edge/rpc/processor/alarm/AlarmEdgeProcessor.java | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java index 276363e5f6..8790b5d1fd 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java @@ -467,9 +467,9 @@ public abstract class BaseEdgeProcessor { EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); - EdgeId sourceEdgeId = safeGetEdgeId(edgeNotificationMsg.getSourceEdgeIdMSB(), edgeNotificationMsg.getSourceEdgeIdLSB()); + EdgeId originatorEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB()); if (type.isAllEdgesRelated()) { - return processEntityNotificationForAllEdges(tenantId, type, actionType, entityId, sourceEdgeId); + return processEntityNotificationForAllEdges(tenantId, type, actionType, entityId, originatorEdgeId); } else { JsonNode body = JacksonUtil.toJsonNode(edgeNotificationMsg.getBody()); EdgeId edgeId = safeGetEdgeId(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()); @@ -481,19 +481,19 @@ public abstract class BaseEdgeProcessor { if (edgeId != null) { return saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, body); } else { - return processNotificationToRelatedEdges(tenantId, entityId, type, actionType, sourceEdgeId); + return processNotificationToRelatedEdges(tenantId, entityId, type, actionType, originatorEdgeId); } case DELETED: EdgeEventActionType deleted = EdgeEventActionType.DELETED; if (edgeId != null) { return saveEdgeEvent(tenantId, edgeId, type, deleted, entityId, body); } else { - return Futures.transform(Futures.allAsList(processActionForAllEdgesByTenantId(tenantId, type, deleted, entityId, body, sourceEdgeId)), + return Futures.transform(Futures.allAsList(processActionForAllEdgesByTenantId(tenantId, type, deleted, entityId, body, originatorEdgeId)), voids -> null, dbCallbackExecutorService); } case ASSIGNED_TO_EDGE: case UNASSIGNED_FROM_EDGE: - if (sourceEdgeId == null) { + if (originatorEdgeId == null) { ListenableFuture future = saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, body); return Futures.transformAsync(future, unused -> { if (type.equals(EdgeEventType.RULE_CHAIN)) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java index f002b5c04e..882fd90c46 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/alarm/AlarmEdgeProcessor.java @@ -71,7 +71,7 @@ public class AlarmEdgeProcessor extends BaseAlarmProcessor { public ListenableFuture processAlarmNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); AlarmId alarmId = new AlarmId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); - EdgeId sourceEdgeId = safeGetEdgeId(edgeNotificationMsg.getSourceEdgeIdMSB(), edgeNotificationMsg.getSourceEdgeIdLSB()); + EdgeId originatorEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB()); switch (actionType) { case DELETED: Alarm deletedAlarm = JacksonUtil.fromString(edgeNotificationMsg.getBody(), Alarm.class); @@ -79,7 +79,7 @@ public class AlarmEdgeProcessor extends BaseAlarmProcessor { return Futures.immediateFuture(null); } List> delFutures = pushEventToAllRelatedEdges(tenantId, deletedAlarm.getOriginator(), - alarmId, actionType, JacksonUtil.valueToTree(deletedAlarm), sourceEdgeId); + alarmId, actionType, JacksonUtil.valueToTree(deletedAlarm), originatorEdgeId); return Futures.transform(Futures.allAsList(delFutures), voids -> null, dbCallbackExecutorService); default: ListenableFuture alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId); @@ -92,7 +92,7 @@ public class AlarmEdgeProcessor extends BaseAlarmProcessor { return Futures.immediateFuture(null); } List> futures = pushEventToAllRelatedEdges(tenantId, alarm.getOriginator(), - alarmId, actionType, null, sourceEdgeId); + alarmId, actionType, null, originatorEdgeId); return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); }, dbCallbackExecutorService); } From d4ed67ff6dbbbe4be488745a2e45abf4dbff30ca Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Thu, 9 Nov 2023 18:28:45 +0200 Subject: [PATCH 08/24] Fixed license header --- .../rpc/fetch/AdminSettingsEdgeEventFetcher.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java index e522529b8e..a292c89e9c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2023 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 - *

+ * + * 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. @@ -55,8 +55,10 @@ public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher { List result = new ArrayList<>(); for (String key : keys) { AdminSettings adminSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key); - result.add(EdgeUtils.constructEdgeEvent(tenantId, edgeId, EdgeEventType.ADMIN_SETTINGS, - EdgeEventActionType.UPDATED, null, JacksonUtil.valueToTree(adminSettings))); + if (adminSettings != null) { + result.add(EdgeUtils.constructEdgeEvent(tenantId, edgeId, EdgeEventType.ADMIN_SETTINGS, + EdgeEventActionType.UPDATED, null, JacksonUtil.valueToTree(adminSettings))); + } } return result; } From 037820c9fae74930912870ea4605920add09fe85 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Fri, 10 Nov 2023 08:45:17 +0200 Subject: [PATCH 09/24] Improve stability of edge tests --- .../server/edge/AbstractEdgeTest.java | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java index 3b63385bc8..8a3dbf6ecb 100644 --- a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java @@ -99,6 +99,7 @@ import java.util.List; import java.util.Optional; import java.util.TreeMap; import java.util.UUID; +import java.util.concurrent.TimeUnit; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -181,7 +182,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { } catch (Exception ignored) {} } - private void installation() { + private void installation() throws Exception { thermostatDeviceProfile = this.createDeviceProfile(THERMOSTAT_DEVICE_PROFILE_NAME, createMqttDeviceProfileTransportConfiguration(new JsonTransportPayloadConfiguration(), false)); extendDeviceProfileData(thermostatDeviceProfile); @@ -197,6 +198,9 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { + "/device/" + savedDevice.getUuidId(), Device.class); doPost("/api/edge/" + edge.getUuidId() + "/asset/" + savedAsset.getUuidId(), Asset.class); + + // wait until assign device and asset events are fully processed by edge notification service + TimeUnit.MILLISECONDS.sleep(500); } protected void extendDeviceProfileData(DeviceProfile deviceProfile) { @@ -247,21 +251,21 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { // 4 messages ('general', 'mail', 'connectivity', 'jwt) validateMsgsCnt(AdminSettingsUpdateMsg.class, 4); - validateAdminSettings(); + validateAdminSettings(4); // 4 messages // - 1 from default profile fetcher // - 2 from device profile fetcher (default and thermostat) // - 1 from device fetcher validateMsgsCnt(DeviceProfileUpdateMsg.class, 4); - validateDeviceProfiles(); + validateDeviceProfiles(4); // 3 messages // - 1 from default profile fetcher // - 1 message from asset profile fetcher // - 1 message from asset fetcher validateMsgsCnt(AssetProfileUpdateMsg.class, 3); - validateAssetProfiles(); + validateAssetProfiles(3); // 1 from device fetcher validateMsgsCnt(DeviceUpdateMsg.class, 1); @@ -332,13 +336,13 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { testAutoGeneratedCodeByProtobuf(tenantProfileUpdateMsg); } - private void validateDeviceProfiles() throws Exception { + private void validateDeviceProfiles(int expectedMsgCnt) throws Exception { List deviceProfileUpdateMsgList = edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class); // default msg default device profile from fetcher // default msg device profile from fetcher // thermostat msg from device profile fetcher // thermostat msg from device fetcher - Assert.assertEquals(4, deviceProfileUpdateMsgList.size()); + Assert.assertEquals(expectedMsgCnt, deviceProfileUpdateMsgList.size()); Optional thermostatProfileUpdateMsgOpt = deviceProfileUpdateMsgList.stream().filter(dfum -> THERMOSTAT_DEVICE_PROFILE_NAME.equals(dfum.getName())).findAny(); Assert.assertTrue(thermostatProfileUpdateMsgOpt.isPresent()); @@ -419,9 +423,9 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { Assert.assertEquals(expectedRuleChainUUID, ruleChainUUID); } - private void validateAdminSettings() { + private void validateAdminSettings(int expectedMsgCnt) { List adminSettingsUpdateMsgs = edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class); - Assert.assertEquals(4, adminSettingsUpdateMsgs.size()); + Assert.assertEquals(expectedMsgCnt, adminSettingsUpdateMsgs.size()); for (AdminSettingsUpdateMsg adminSettingsUpdateMsg : adminSettingsUpdateMsgs) { if (adminSettingsUpdateMsg.getKey().equals("general")) { @@ -460,9 +464,9 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { Assert.assertNotNull(jsonNode.get("coaps")); } - private void validateAssetProfiles() throws Exception { + private void validateAssetProfiles(int expectedMsgCnt) throws Exception { List assetProfileUpdateMsgs = edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class); - Assert.assertEquals(3, assetProfileUpdateMsgs.size()); + Assert.assertEquals(expectedMsgCnt, assetProfileUpdateMsgs.size()); AssetProfileUpdateMsg assetProfileUpdateMsg = assetProfileUpdateMsgs.get(0); Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetProfileUpdateMsg.getMsgType()); UUID assetProfileUUID = new UUID(assetProfileUpdateMsg.getIdMSB(), assetProfileUpdateMsg.getIdLSB()); From 4a1793c47f4f06072c5608c9ae384d53cac38be6 Mon Sep 17 00:00:00 2001 From: Yevhen Popok Date: Fri, 10 Nov 2023 10:14:04 +0200 Subject: [PATCH 10/24] packaging: remove dupplicated "adduser --quiet" option in deb preinst adduser command (from Debian "preinst" file) contains the duplicated option "--quiet" --- packaging/java/scripts/control/deb/preinst | 3 +-- packaging/js/scripts/control/deb/preinst | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packaging/java/scripts/control/deb/preinst b/packaging/java/scripts/control/deb/preinst index eebe378588..10a9b92679 100644 --- a/packaging/java/scripts/control/deb/preinst +++ b/packaging/java/scripts/control/deb/preinst @@ -10,7 +10,6 @@ if ! getent passwd ${pkg.user} >/dev/null; then adduser --quiet \ --system \ --ingroup ${pkg.user} \ - --quiet \ --disabled-login \ --disabled-password \ --home ${pkg.installFolder} \ @@ -19,4 +18,4 @@ if ! getent passwd ${pkg.user} >/dev/null; then ${pkg.user} fi -exit 0 \ No newline at end of file +exit 0 diff --git a/packaging/js/scripts/control/deb/preinst b/packaging/js/scripts/control/deb/preinst index d2ebea46d7..e51297c6c1 100644 --- a/packaging/js/scripts/control/deb/preinst +++ b/packaging/js/scripts/control/deb/preinst @@ -8,7 +8,6 @@ if ! getent passwd ${pkg.user} >/dev/null; then adduser --quiet \ --system \ --ingroup ${pkg.user} \ - --quiet \ --disabled-login \ --disabled-password \ --home ${pkg.installFolder} \ From 2bc9c61c69dc3f3a5855029245e01988328af517 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 10 Nov 2023 11:28:19 +0100 Subject: [PATCH 11/24] fixed removing blackbox volumes --- .../server/msa/ThingsBoardDbInstaller.java | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java index e41bbc3449..fe467ec341 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java @@ -25,6 +25,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.StringJoiner; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -74,8 +75,8 @@ public class ThingsBoardDbInstaller { new File("./../../docker/docker-compose.yml"), new File("./../../docker/docker-compose.volumes.yml"), IS_HYBRID_MODE - ? new File("./../../docker/docker-compose.hybrid.yml") - : new File("./../../docker/docker-compose.postgres.yml"), + ? new File("./../../docker/docker-compose.hybrid.yml") + : new File("./../../docker/docker-compose.postgres.yml"), new File("./../../docker/docker-compose.postgres.volumes.yml"), resolveRedisComposeFile(), resolveRedisComposeVolumesFile() @@ -156,7 +157,7 @@ public class ThingsBoardDbInstaller { return env; } - public void createVolumes() { + public void createVolumes() { try { dockerCompose.withCommand("volume create " + postgresDataVolume); @@ -200,7 +201,7 @@ public class ThingsBoardDbInstaller { } } else if (IS_REDIS_SENTINEL) { additionalServices.append(" redis-master"); - dockerCompose.withCommand("volume create " + redisSentinelDataVolume +"-" + "master"); + dockerCompose.withCommand("volume create " + redisSentinelDataVolume + "-" + "master"); dockerCompose.invokeDocker(); additionalServices.append(" redis-slave"); @@ -226,7 +227,8 @@ public class ThingsBoardDbInstaller { try { dockerCompose.withCommand("down -v"); dockerCompose.invokeCompose(); - } catch (Exception ignored) {} + } catch (Exception ignored) { + } } } @@ -239,15 +241,24 @@ public class ThingsBoardDbInstaller { copyLogs(tbSnmpTransportLogVolume, "./target/tb-snmp-transport-logs/"); copyLogs(tbVcExecutorLogVolume, "./target/tb-vc-executor-logs/"); - dockerCompose.withCommand("volume rm -f " + postgresDataVolume + " " + tbLogVolume + - " " + tbCoapTransportLogVolume + " " + tbLwm2mTransportLogVolume + " " + tbHttpTransportLogVolume + - " " + tbMqttTransportLogVolume + " " + tbSnmpTransportLogVolume + " " + tbVcExecutorLogVolume + resolveRedisComposeVolumeLog()); - dockerCompose.invokeDocker(); + StringJoiner rmVolumesCommand = new StringJoiner(" ") + .add("volume rm -f") + .add(postgresDataVolume) + .add(tbLogVolume) + .add(tbCoapTransportLogVolume) + .add(tbLwm2mTransportLogVolume) + .add(tbHttpTransportLogVolume) + .add(tbMqttTransportLogVolume) + .add(tbSnmpTransportLogVolume) + .add(tbVcExecutorLogVolume) + .add(resolveRedisComposeVolumeLog()); + + dockerCompose.withCommand(rmVolumesCommand.toString()); } private String resolveRedisComposeVolumeLog() { if (IS_REDIS_CLUSTER) { - return IntStream.range(0, 6).mapToObj(i -> " " + redisClusterDataVolume + "-" + i).collect(Collectors.joining()); + return IntStream.range(0, 6).mapToObj(i -> redisClusterDataVolume + "-" + i).collect(Collectors.joining()); } if (IS_REDIS_SENTINEL) { return redisSentinelDataVolume + "-" + "master " + " " + @@ -266,7 +277,7 @@ public class ThingsBoardDbInstaller { dockerCompose.withCommand("run -d --rm --name " + logsContainerName + " -v " + volumeName + ":/root alpine tail -f /dev/null"); dockerCompose.invokeDocker(); - dockerCompose.withCommand("cp " + logsContainerName + ":/root/. "+tbLogsDir.getAbsolutePath()); + dockerCompose.withCommand("cp " + logsContainerName + ":/root/. " + tbLogsDir.getAbsolutePath()); dockerCompose.invokeDocker(); dockerCompose.withCommand("rm -f " + logsContainerName); From 7248f70517d23a117b167b26856c229fe0620443 Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Fri, 10 Nov 2023 14:20:42 +0200 Subject: [PATCH 12/24] added log printing to coap commands --- .../DeviceConnectivityControllerTest.java | 20 +++++++++---------- .../dao/util/DeviceConnectivityUtil.java | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java index a4897f6bd9..227d37f483 100644 --- a/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java @@ -250,16 +250,16 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { credentials.getCredentialsId())); JsonNode linuxCoapCommands = commands.get(COAP); - assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry " + + assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -v 6 -m POST coap://localhost:5683/api/v1/%s/telemetry " + "-t json -e \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry" + + assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -v 6 -m POST coaps://localhost:5684/api/v1/%s/telemetry" + " -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); JsonNode dockerCoapCommands = commands.get(COAP).get(DOCKER); assertThat(dockerCoapCommands.get(COAP).asText()).isEqualTo(String.format("docker run --rm -it --network=host" + - " thingsboard/coap-clients coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); + " thingsboard/coap-clients coap-client -v 6 -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); assertThat(dockerCoapCommands.get(COAPS).asText()).isEqualTo(String.format("docker run --rm -it --network=host" + - " thingsboard/coap-clients coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); + " thingsboard/coap-clients coap-client-openssl -v 6 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); } @Test @@ -377,9 +377,9 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { assertThat(commands).hasSize(1); JsonNode linuxCommands = commands.get(COAP); - assertThat(linuxCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + assertThat(linuxCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -v 6 -m POST coap://localhost:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(linuxCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", + assertThat(linuxCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -v 6 -m POST coaps://localhost:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); } @@ -601,15 +601,15 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest { credentials.getCredentialsId())); JsonNode linuxCoapCommands = commands.get(COAP); - assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -m POST coap://test.domain:5683/api/v1/%s/telemetry " + + assertThat(linuxCoapCommands.get(COAP).asText()).isEqualTo(String.format("coap-client -v 6 -m POST coap://test.domain:5683/api/v1/%s/telemetry " + "-t json -e \"{temperature:25}\"", credentials.getCredentialsId())); - assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -m POST coaps://test.domain:5684/api/v1/%s/telemetry" + + assertThat(linuxCoapCommands.get(COAPS).asText()).isEqualTo(String.format("coap-client-openssl -v 6 -m POST coaps://test.domain:5684/api/v1/%s/telemetry" + " -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); JsonNode dockerCoapCommands = commands.get(COAP).get(DOCKER); assertThat(dockerCoapCommands.get(COAP).asText()).isEqualTo(String.format("docker run --rm -it " + - "thingsboard/coap-clients coap-client -m POST coap://test.domain:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); + "thingsboard/coap-clients coap-client -v 6 -m POST coap://test.domain:5683/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); assertThat(dockerCoapCommands.get(COAPS).asText()).isEqualTo(String.format("docker run --rm -it " + - "thingsboard/coap-clients coap-client-openssl -m POST coaps://test.domain:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); + "thingsboard/coap-clients coap-client-openssl -v 6 -m POST coaps://test.domain:5684/api/v1/%s/telemetry -t json -e \"{temperature:25}\"", credentials.getCredentialsId())); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java b/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java index bde018122c..aa446c12b5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java +++ b/dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java @@ -162,7 +162,7 @@ public class DeviceConnectivityUtil { switch (deviceCredentials.getCredentialsType()) { case ACCESS_TOKEN: String client = COAPS.equals(protocol) ? "coap-client-openssl" : "coap-client"; - return String.format("%s -m POST %s://%s%s/api/v1/%s/telemetry -t json -e %s", + return String.format("%s -v 6 -m POST %s://%s%s/api/v1/%s/telemetry -t json -e %s", client, protocol, host, port, deviceCredentials.getCredentialsId(), JSON_EXAMPLE_PAYLOAD); default: return null; From 4d638213b92860c83841dfbac75d467460cffc3d Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 10 Nov 2023 14:33:29 +0200 Subject: [PATCH 13/24] UI: Removed connectivity command for CoAP in Mac OS --- ...e-check-connectivity-dialog.component.html | 25 ------------------- ...ice-check-connectivity-dialog.component.ts | 5 ++-- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html index 9b0b18e2c2..d36755b711 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.html @@ -226,31 +226,6 @@

device.connectivity.use-following-instructions
- - - - MacOS - - -
-
-
device.connectivity.install-necessary-client-tools
-
- - -
-
- - -
-
-
diff --git a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts index eeb54381df..f2e4229314 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device-check-connectivity-dialog.component.ts @@ -199,20 +199,21 @@ export class DeviceCheckConnectivityDialogComponent extends case 'android': this.httpTabIndex = 2; this.mqttTabIndex = 2; - this.coapTabIndex = 1; break; case 'macos': case 'ios': this.httpTabIndex = 1; this.mqttTabIndex = 1; + this.coapTabIndex = 1; break; case 'windows': this.httpTabIndex = 0; this.mqttTabIndex = 0; + this.coapTabIndex = 1; break; default: this.mqttTabIndex = this.commands.mqtt?.docker ? 3 : 0; - this.coapTabIndex = this.commands.coap?.docker ? 2 : 1; + this.coapTabIndex = this.commands.coap?.docker ? 1 : 0; } } From d761524fe412103d7afa4acb380fe526bfd6079b Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Fri, 10 Nov 2023 14:42:03 +0200 Subject: [PATCH 14/24] Telemetry edge processor: added check for attr/timeseries value size --- .../telemetry/TelemetryEdgeProcessor.java | 6 +++ .../telemetry/TelemetryEdgeProcessorTest.java | 42 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessorTest.java diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessor.java index 861d766638..647994dcd9 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessor.java @@ -37,6 +37,12 @@ public class TelemetryEdgeProcessor extends BaseTelemetryProcessor { } public DownlinkMsg convertTelemetryEventToDownlink(EdgeEvent edgeEvent) throws JsonProcessingException { + if (edgeEvent.getBody() != null && edgeEvent.getBody().toString().length() > 1000) { + log.debug("[{}][{}][{}] Conversion to a DownlinkMsg telemetry event failed due to a size limit violation. " + + "Current size is {}, but the limit is 1000. {}", edgeEvent.getTenantId(), edgeEvent.getEdgeId(), + edgeEvent.getEntityId(), edgeEvent.getBody().toString().length(), edgeEvent.getBody().toString().substring(0, 100)); + return null; + } EntityType entityType = EntityType.valueOf(edgeEvent.getType().name()); EntityDataProto entityDataProto = convertTelemetryEventToEntityDataProto( edgeEvent.getTenantId(), entityType, edgeEvent.getEntityId(), diff --git a/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessorTest.java b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessorTest.java new file mode 100644 index 0000000000..9dbfa6ae45 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessorTest.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2023 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.service.edge.rpc.processor.telemetry; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; + +@Slf4j +@RunWith(MockitoJUnitRunner.class) +public class TelemetryEdgeProcessorTest { + + @Test + public void testConvert_maxSizeLimit() throws Exception { + EdgeEvent edgeEvent = new EdgeEvent(); + ObjectNode body = JacksonUtil.newObjectNode(); + body.put("value", StringUtils.randomAlphanumeric(10000)); + edgeEvent.setBody(body); + DownlinkMsg downlinkMsg = new TelemetryEdgeProcessor().convertTelemetryEventToDownlink(edgeEvent); + Assert.assertNull(downlinkMsg); + } +} From 8dce7dcad239ddb470e9227eec6e4e0a85fef593 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Fri, 10 Nov 2023 14:55:07 +0200 Subject: [PATCH 15/24] UI: liquid level widget added additional checkup for datasources --- .../liquid-level-widget.component.ts | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts index baf9f018c0..847937581b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/indicator/liquid-level-widget.component.ts @@ -160,26 +160,30 @@ export class LiquidLevelWidgetComponent implements OnInit { } private getData(): Observable<{ svg: string; volume: number; units: string }> { - const entityId: EntityId = { - entityType: this.ctx.datasources[0].entityType, - id: this.ctx.datasources[0].entityId - }; + if (this.ctx.datasources?.length) { + const entityId: EntityId = { + entityType: this.ctx.datasources[0].entityType, + id: this.ctx.datasources[0].entityId + }; + + return this.getShape(entityId).pipe( + switchMap(shape => { + this.shape = shape; + this.svgParams = svgMapping.get(shape); + if (this.svgParams) { + return forkJoin([ + this.resourcesService.loadJsonResource(this.svgParams.svg), + this.getTankersParams(entityId) + ]).pipe( + map(params => ({svg: params[0], ...params[1]})) + ); + } + return of(null); + }) + ); + } - return this.getShape(entityId).pipe( - switchMap(shape => { - this.shape = shape; - this.svgParams = svgMapping.get(shape); - if (this.svgParams) { - return forkJoin([ - this.resourcesService.loadJsonResource(this.svgParams.svg), - this.getTankersParams(entityId) - ]).pipe( - map(params => ({svg: params[0], ...params[1]})) - ); - } - return of(null); - }) - ); + return of(null); } public onInit() { From 92178384eaaee61d726649b75af5c8746e391845 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Fri, 10 Nov 2023 15:42:10 +0200 Subject: [PATCH 16/24] added TbNodeUpgradeUtils && fixed upgrade when config is null or NullNode --- .../update/DefaultDataUpdateService.java | 11 +- .../rule/DefaultTbRuleChainService.java | 11 +- .../server/utils/TbNodeUpgradeUtils.java | 39 ++++++ .../server/utils/TbNodeUpgradeUtilsTest.java | 111 ++++++++++++++++++ .../rule/engine/api/util/TbNodeUtils.java | 2 +- 5 files changed, 157 insertions(+), 17 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/utils/TbNodeUpgradeUtils.java create mode 100644 application/src/test/java/org/thingsboard/server/utils/TbNodeUpgradeUtilsTest.java diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index c49578c912..5375590084 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -27,7 +27,6 @@ import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.flow.TbRuleChainInputNode; import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration; import org.thingsboard.rule.engine.profile.TbDeviceProfileNode; @@ -67,7 +66,6 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration; -import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.audit.AuditLogDao; @@ -90,6 +88,7 @@ import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.component.RuleNodeClassInfo; import org.thingsboard.server.service.install.InstallScripts; import org.thingsboard.server.service.install.SystemDataLoaderService; +import org.thingsboard.server.utils.TbNodeUpgradeUtils; import java.util.ArrayList; import java.util.Collections; @@ -291,16 +290,12 @@ public class DefaultDataUpdateService implements DataUpdateService { continue; } var ruleNodeId = ruleNode.getId(); - var oldConfiguration = ruleNode.getConfiguration(); int fromVersion = ruleNode.getConfigurationVersion(); log.debug("Going to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}", ruleNodeId, ruleNodeType, fromVersion, toVersion); try { - var tbVersionedNode = (TbNode) ruleNodeClassInfo.getClazz().getDeclaredConstructor().newInstance(); - TbPair upgradeRuleNodeConfigurationResult = tbVersionedNode.upgrade(fromVersion, oldConfiguration); - if (upgradeRuleNodeConfigurationResult.getFirst()) { - ruleNode.setConfiguration(upgradeRuleNodeConfigurationResult.getSecond()); - } + ruleNode.setConfiguration(TbNodeUpgradeUtils.upgradeRuleNodeConfiguration(ruleNode, ruleNodeClassInfo.getAnnotation(), + ruleNodeClassInfo.getClazz())); ruleNode.setConfigurationVersion(toVersion); saveFutures.add(jpaExecutorService.submit(() -> { ruleChainService.saveRuleNode(TenantId.SYS_TENANT_ID, ruleNode); diff --git a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java index 56df2caa71..50f370fbb9 100644 --- a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java +++ b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java @@ -15,12 +15,10 @@ */ package org.thingsboard.server.service.rule; -import com.fasterxml.jackson.databind.JsonNode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.flow.TbRuleChainInputNode; import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration; @@ -44,13 +42,13 @@ import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleChainUpdateResult; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNodeUpdateResult; -import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.install.InstallScripts; +import org.thingsboard.server.utils.TbNodeUpgradeUtils; import java.util.ArrayList; import java.util.Collections; @@ -402,17 +400,14 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement var ruleNodeClass = componentDiscoveryService.getRuleNodeInfo(ruleNodeType) .orElseThrow(() -> new RuntimeException("Rule node " + ruleNodeType + " is not supported!")); if (ruleNodeClass.isVersioned()) { - TbNode tbVersionedNode = (TbNode) ruleNodeClass.getClazz().getDeclaredConstructor().newInstance(); int fromVersion = node.getConfigurationVersion(); int toVersion = ruleNodeClass.getCurrentVersion(); if (fromVersion < toVersion) { log.debug("Going to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}", ruleNodeId, ruleNodeType, fromVersion, toVersion); try { - TbPair upgradeResult = tbVersionedNode.upgrade(fromVersion, node.getConfiguration()); - if (upgradeResult.getFirst()) { - node.setConfiguration(upgradeResult.getSecond()); - } + node.setConfiguration(TbNodeUpgradeUtils.upgradeRuleNodeConfiguration(node, ruleNodeClass.getAnnotation(), + ruleNodeClass.getClazz())); node.setConfigurationVersion(toVersion); log.debug("Successfully upgrade rule node with id: {} type: {}, rule chain id: {} fromVersion: {} toVersion: {}", ruleNodeId, ruleNodeType, ruleChainId, fromVersion, toVersion); diff --git a/application/src/main/java/org/thingsboard/server/utils/TbNodeUpgradeUtils.java b/application/src/main/java/org/thingsboard/server/utils/TbNodeUpgradeUtils.java new file mode 100644 index 0000000000..98f5e713df --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/utils/TbNodeUpgradeUtils.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2023 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.utils; + +import com.fasterxml.jackson.databind.JsonNode; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.TbNode; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.common.data.util.TbPair; + +public class TbNodeUpgradeUtils { + + public static JsonNode upgradeRuleNodeConfiguration(RuleNode node, + org.thingsboard.rule.engine.api.RuleNode annotation, + Class nodeClass) throws Exception { + JsonNode oldConfiguration = node.getConfiguration(); + if (oldConfiguration == null || !oldConfiguration.isObject()) { + var configClass = annotation.configClazz(); + return JacksonUtil.valueToTree(configClass.getDeclaredConstructor().newInstance().defaultConfiguration()); + } + var tbVersionedNode = (TbNode) nodeClass.getDeclaredConstructor().newInstance(); + TbPair upgradeResult = tbVersionedNode.upgrade(node.getConfigurationVersion(), oldConfiguration); + return upgradeResult.getFirst() ? upgradeResult.getSecond() : oldConfiguration; + } + +} diff --git a/application/src/test/java/org/thingsboard/server/utils/TbNodeUpgradeUtilsTest.java b/application/src/test/java/org/thingsboard/server/utils/TbNodeUpgradeUtilsTest.java new file mode 100644 index 0000000000..1fb2ddcc25 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/utils/TbNodeUpgradeUtilsTest.java @@ -0,0 +1,111 @@ +/** + * Copyright © 2016-2023 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.utils; + +import com.fasterxml.jackson.databind.node.NullNode; +import org.assertj.core.api.Assertions; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.TbNode; +import org.thingsboard.rule.engine.metadata.TbGetAttributesNode; +import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.common.data.util.TbPair; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class TbNodeUpgradeUtilsTest { + + @Test + public void testUpgradeRuleNodeConfigurationWithNullConfig() throws Exception { + // GIVEN + var node = mock(RuleNode.class); + var nodeClass = TbGetAttributesNode.class; + var nodeConfigClazz = TbGetAttributesNodeConfiguration.class; + + var annotation = mock(org.thingsboard.rule.engine.api.RuleNode.class); + + var defaultConfig = JacksonUtil.valueToTree(nodeConfigClazz.getDeclaredConstructor().newInstance().defaultConfiguration()); + + when(node.getConfiguration()).thenReturn(null); + when(node.getConfigurationVersion()).thenReturn(0); + when(annotation.configClazz()).thenReturn((Class) nodeConfigClazz); + // WHEN + var upgradedConfig = TbNodeUpgradeUtils.upgradeRuleNodeConfiguration(node, annotation, nodeClass); + + // THEN + Assertions.assertThat(upgradedConfig).isEqualTo(defaultConfig); + } + + @Test + public void testUpgradeRuleNodeConfigurationWithNullNodeConfig() throws Exception { + // GIVEN + var node = mock(RuleNode.class); + var nodeClass = TbGetAttributesNode.class; + var nodeConfigClazz = TbGetAttributesNodeConfiguration.class; + + var annotation = mock(org.thingsboard.rule.engine.api.RuleNode.class); + + var defaultConfig = JacksonUtil.valueToTree(nodeConfigClazz.getDeclaredConstructor().newInstance().defaultConfiguration()); + + when(node.getConfiguration()).thenReturn(NullNode.instance); + when(node.getConfigurationVersion()).thenReturn(0); + when(annotation.configClazz()).thenReturn((Class) nodeConfigClazz); + // WHEN + var upgradedConfig = TbNodeUpgradeUtils.upgradeRuleNodeConfiguration(node, annotation, nodeClass); + + // THEN + Assertions.assertThat(upgradedConfig).isEqualTo(defaultConfig); + } + + @Test + public void testUpgradeRuleNodeConfigurationWithNonNullConfig() throws Exception { + // GIVEN + var node = mock(RuleNode.class); + var nodeClass = TbGetAttributesNode.class; + var nodeConfigClazz = TbGetAttributesNodeConfiguration.class; + + var annotation = mock(org.thingsboard.rule.engine.api.RuleNode.class); + + String versionZeroDefaultConfigStr = "{\"fetchToData\":false," + + "\"clientAttributeNames\":[]," + + "\"sharedAttributeNames\":[]," + + "\"serverAttributeNames\":[]," + + "\"latestTsKeyNames\":[]," + + "\"tellFailureIfAbsent\":true," + + "\"getLatestValueWithTs\":false}"; + + var existingConfig = JacksonUtil.toJsonNode(versionZeroDefaultConfigStr); + int fromVersion = 0; + var currentDefaultConfig = JacksonUtil.valueToTree(nodeConfigClazz.getDeclaredConstructor().newInstance().defaultConfiguration()); + + when(node.getConfiguration()).thenReturn(existingConfig); + when(node.getConfigurationVersion()).thenReturn(fromVersion); + when(annotation.configClazz()).thenReturn((Class) nodeConfigClazz); + + TbNode tbVersionedNodeMock = mock(nodeClass); + + when(tbVersionedNodeMock.upgrade(fromVersion, existingConfig)).thenReturn(new TbPair<>(true, currentDefaultConfig)); + + // WHEN + var upgradedConfig = TbNodeUpgradeUtils.upgradeRuleNodeConfiguration(node, annotation, nodeClass); + + // THEN + Assertions.assertThat(upgradedConfig).isEqualTo(currentDefaultConfig); + } + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java index bbba1855c2..396d810b00 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java @@ -15,7 +15,6 @@ */ package org.thingsboard.rule.engine.api.util; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import org.springframework.util.CollectionUtils; import org.thingsboard.common.util.JacksonUtil; @@ -85,6 +84,7 @@ public class TbNodeUtils { } } + @Deprecated(since = "3.6.1", forRemoval = true) public static List processPatterns(List patterns, TbMsgMetaData metaData) { if (!CollectionUtils.isEmpty(patterns)) { return patterns.stream().map(p -> processPattern(p, metaData)).collect(Collectors.toList()); From 28b35c9513d6a6bfc7b656bc72e3ffc2a70947b7 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Fri, 10 Nov 2023 15:57:21 +0200 Subject: [PATCH 17/24] Add backward compatibility support for widged types and tenant profile data --- .../server/service/edge/rpc/EdgeGrpcSession.java | 6 +++--- .../constructor/TenantProfileMsgConstructor.java | 8 ++++++-- .../rpc/constructor/WidgetTypeMsgConstructor.java | 11 +++++++++-- .../processor/relation/RelationEdgeProcessor.java | 4 ++-- .../rpc/processor/tenant/TenantEdgeProcessor.java | 5 +++-- .../tenant/TenantProfileEdgeProcessor.java | 5 +++-- .../processor/widget/WidgetTypeEdgeProcessor.java | 5 +++-- .../service/edge/rpc/utils/EdgeVersionUtils.java | 13 ++----------- .../thingsboard/server/edge/AbstractEdgeTest.java | 2 +- 9 files changed, 32 insertions(+), 27 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index f9bd956706..7318b1eb24 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -641,7 +641,7 @@ public final class EdgeGrpcSession implements Closeable { case WIDGETS_BUNDLE: return ctx.getWidgetBundleProcessor().convertWidgetsBundleEventToDownlink(edgeEvent); case WIDGET_TYPE: - return ctx.getWidgetTypeProcessor().convertWidgetTypeEventToDownlink(edgeEvent); + return ctx.getWidgetTypeProcessor().convertWidgetTypeEventToDownlink(edgeEvent, this.edgeVersion); case ADMIN_SETTINGS: return ctx.getAdminSettingsProcessor().convertAdminSettingsEventToDownlink(edgeEvent); case OTA_PACKAGE: @@ -651,9 +651,9 @@ public final class EdgeGrpcSession implements Closeable { case QUEUE: return ctx.getQueueEdgeProcessor().convertQueueEventToDownlink(edgeEvent); case TENANT: - return ctx.getTenantEdgeProcessor().convertTenantEventToDownlink(edgeEvent); + return ctx.getTenantEdgeProcessor().convertTenantEventToDownlink(edgeEvent, this.getEdgeVersion()); case TENANT_PROFILE: - return ctx.getTenantProfileEdgeProcessor().convertTenantProfileEventToDownlink(edgeEvent); + return ctx.getTenantProfileEdgeProcessor().convertTenantProfileEventToDownlink(edgeEvent, this.getEdgeVersion()); default: log.warn("[{}] Unsupported edge event type [{}]", this.tenantId, edgeEvent); return null; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/TenantProfileMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/TenantProfileMsgConstructor.java index 57655c3aeb..9fdb1bf069 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/TenantProfileMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/TenantProfileMsgConstructor.java @@ -19,10 +19,12 @@ import com.google.protobuf.ByteString; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.utils.EdgeVersionUtils; @Component @TbCoreComponent @@ -31,7 +33,9 @@ public class TenantProfileMsgConstructor { @Autowired private DataDecodingEncodingService dataDecodingEncodingService; - public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile) { + public TenantProfileUpdateMsg constructTenantProfileUpdateMsg(UpdateMsgType msgType, TenantProfile tenantProfile, EdgeVersion edgeVersion) { + ByteString profileData = EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_6_1) ? + ByteString.empty() : ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile.getProfileData())); TenantProfileUpdateMsg.Builder builder = TenantProfileUpdateMsg.newBuilder() .setMsgType(msgType) .setIdMSB(tenantProfile.getId().getId().getMostSignificantBits()) @@ -39,7 +43,7 @@ public class TenantProfileMsgConstructor { .setName(tenantProfile.getName()) .setDefault(tenantProfile.isDefault()) .setIsolatedRuleChain(tenantProfile.isIsolatedTbRuleEngine()) - .setProfileDataBytes(ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile.getProfileData()))); + .setProfileDataBytes(profileData); if (tenantProfile.getDescription() != null) { builder.setDescription(tenantProfile.getDescription()); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java index e6f04261fb..17a66a47d4 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java @@ -20,9 +20,11 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.edge.rpc.utils.EdgeVersionUtils; import java.util.Arrays; @@ -30,7 +32,7 @@ import java.util.Arrays; @TbCoreComponent public class WidgetTypeMsgConstructor { - public WidgetTypeUpdateMsg constructWidgetTypeUpdateMsg(UpdateMsgType msgType, WidgetTypeDetails widgetTypeDetails) { + public WidgetTypeUpdateMsg constructWidgetTypeUpdateMsg(UpdateMsgType msgType, WidgetTypeDetails widgetTypeDetails, EdgeVersion edgeVersion) { WidgetTypeUpdateMsg.Builder builder = WidgetTypeUpdateMsg.newBuilder() .setMsgType(msgType) .setIdMSB(widgetTypeDetails.getId().getId().getMostSignificantBits()) @@ -58,7 +60,12 @@ public class WidgetTypeMsgConstructor { builder.setImage(widgetTypeDetails.getImage()); } if (widgetTypeDetails.getDescription() != null) { - builder.setDescription(widgetTypeDetails.getDescription()); + if (EdgeVersionUtils.isEdgeVersionOlderThan(edgeVersion, EdgeVersion.V_3_6_0) && + widgetTypeDetails.getDescription().length() > 255) { + builder.setDescription(widgetTypeDetails.getDescription().substring(0, 254)); + } else { + builder.setDescription(widgetTypeDetails.getDescription()); + } } builder.setDeprecated(widgetTypeDetails.isDeprecated()); if (widgetTypeDetails.getTags() != null) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java index 487267e60f..b3676e8d82 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/relation/RelationEdgeProcessor.java @@ -71,12 +71,12 @@ public class RelationEdgeProcessor extends BaseRelationProcessor { if (relation == null || (relation.getFrom().getEntityType().equals(EntityType.EDGE) || relation.getTo().getEntityType().equals(EntityType.EDGE))) { return Futures.immediateFuture(null); } - EdgeId sourceEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB()); + EdgeId originatorEdgeId = safeGetEdgeId(edgeNotificationMsg.getOriginatorEdgeIdMSB(), edgeNotificationMsg.getOriginatorEdgeIdLSB()); Set uniqueEdgeIds = new HashSet<>(); uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getTo())); uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getFrom())); - uniqueEdgeIds.remove(sourceEdgeId); + uniqueEdgeIds.remove(originatorEdgeId); if (uniqueEdgeIds.isEmpty()) { return Futures.immediateFuture(null); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java index 9bd87660d2..9fd08b4447 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantEdgeProcessor.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.TenantUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; @@ -35,7 +36,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @TbCoreComponent public class TenantEdgeProcessor extends BaseEdgeProcessor { - public DownlinkMsg convertTenantEventToDownlink(EdgeEvent edgeEvent) { + public DownlinkMsg convertTenantEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { TenantId tenantId = new TenantId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; if (EdgeEventActionType.UPDATED.equals(edgeEvent.getAction())) { @@ -44,7 +45,7 @@ public class TenantEdgeProcessor extends BaseEdgeProcessor { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); TenantUpdateMsg tenantUpdateMsg = tenantMsgConstructor.constructTenantUpdateMsg(msgType, tenant); TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(tenantId, tenant.getTenantProfileId()); - TenantProfileUpdateMsg tenantProfileUpdateMsg = tenantProfileMsgConstructor.constructTenantProfileUpdateMsg(msgType, tenantProfile); + TenantProfileUpdateMsg tenantProfileUpdateMsg = tenantProfileMsgConstructor.constructTenantProfileUpdateMsg(msgType, tenantProfile, edgeVersion); downlinkMsg = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addTenantUpdateMsg(tenantUpdateMsg) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java index c9071571c4..970ea3a1f6 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/tenant/TenantProfileEdgeProcessor.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.TenantProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -33,7 +34,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @TbCoreComponent public class TenantProfileEdgeProcessor extends BaseEdgeProcessor { - public DownlinkMsg convertTenantProfileEventToDownlink(EdgeEvent edgeEvent) { + public DownlinkMsg convertTenantProfileEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { TenantProfileId tenantProfileId = new TenantProfileId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; if (EdgeEventActionType.UPDATED.equals(edgeEvent.getAction())) { @@ -41,7 +42,7 @@ public class TenantProfileEdgeProcessor extends BaseEdgeProcessor { if (tenantProfile != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); TenantProfileUpdateMsg tenantProfileUpdateMsg = - tenantProfileMsgConstructor.constructTenantProfileUpdateMsg(msgType, tenantProfile); + tenantProfileMsgConstructor.constructTenantProfileUpdateMsg(msgType, tenantProfile, edgeVersion); downlinkMsg = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addTenantProfileUpdateMsg(tenantProfileUpdateMsg) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java index 5171724439..de9617d5d7 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/widget/WidgetTypeEdgeProcessor.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -32,7 +33,7 @@ import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor; @TbCoreComponent public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor { - public DownlinkMsg convertWidgetTypeEventToDownlink(EdgeEvent edgeEvent) { + public DownlinkMsg convertWidgetTypeEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { WidgetTypeId widgetTypeId = new WidgetTypeId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; switch (edgeEvent.getAction()) { @@ -42,7 +43,7 @@ public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor { if (widgetTypeDetails != null) { UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); WidgetTypeUpdateMsg widgetTypeUpdateMsg = - widgetTypeMsgConstructor.constructWidgetTypeUpdateMsg(msgType, widgetTypeDetails); + widgetTypeMsgConstructor.constructWidgetTypeUpdateMsg(msgType, widgetTypeDetails, edgeVersion); downlinkMsg = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addWidgetTypeUpdateMsg(widgetTypeUpdateMsg) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java index 75cd430a26..c9e6987a23 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/utils/EdgeVersionUtils.java @@ -21,16 +21,7 @@ import org.thingsboard.server.gen.edge.v1.EdgeVersion; @Slf4j public final class EdgeVersionUtils { - public static boolean isEdgeProtoDeprecated(EdgeVersion edgeVersion) { - switch (edgeVersion) { - case V_3_3_0: - case V_3_3_3: - case V_3_4_0: - case V_3_6_0: - return true; - case V_3_6_1: - default: - return false; - } + public static boolean isEdgeVersionOlderThan(EdgeVersion currentVersion, EdgeVersion requiredVersion) { + return currentVersion.ordinal() < requiredVersion.ordinal(); } } diff --git a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java index 8a3dbf6ecb..2771a71eac 100644 --- a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java @@ -249,7 +249,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest { validateMsgsCnt(RuleChainMetadataUpdateMsg.class, 1); validateRuleChainMetadataUpdates(ruleChainUUID); - // 4 messages ('general', 'mail', 'connectivity', 'jwt) + // 4 messages ('general', 'mail', 'connectivity', 'jwt') validateMsgsCnt(AdminSettingsUpdateMsg.class, 4); validateAdminSettings(4); From e5216e8354c62c686baf2f3441d467502142c304 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Fri, 10 Nov 2023 16:49:45 +0200 Subject: [PATCH 18/24] UI: added additional z-index to widget no data container --- .../src/app/modules/home/components/widget/widget.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.scss b/ui-ngx/src/app/modules/home/components/widget/widget.component.scss index ac942225c8..ef3d7007e0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.scss @@ -30,6 +30,7 @@ align-items: center; justify-content: center; background: rgba(255, 255, 255, .75); + z-index: 2; span { color: #000; From 37e8f29c074d98b0ae2874e3ab202dc862f519cc Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 10 Nov 2023 17:06:21 +0200 Subject: [PATCH 19/24] UI: Replacement of capital letters with lowercase letters in the second word in the names of menu items --- .../assets/locale/locale.constant-en_US.json | 66 +++++++++---------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 32d79d5840..ef660731bf 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -97,7 +97,7 @@ "general-settings": "General Settings", "home-settings": "Home Settings", "home": "Home", - "outgoing-mail": "Mail Server", + "outgoing-mail": "Mail server", "outgoing-mail-settings": "Outgoing Mail Server Settings", "system-settings": "System Settings", "test-mail-sent": "Test mail was successfully sent!", @@ -621,7 +621,7 @@ "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", "filter-type-entity-view-search-query": "Entity view search query", "filter-type-entity-view-search-query-description": "Entity views with types {{entityViewTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", - "filter-type-apiUsageState": "Api Usage State", + "filter-type-apiUsageState": "API Usage State", "filter-type-edge-search-query": "Edge search query", "filter-type-edge-search-query-description": "Edges with types {{edgeTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", "entity-filter": "Entity filter", @@ -647,7 +647,7 @@ "assets": "Assets", "management": "Asset management", "view-assets": "View Assets", - "add": "Add Asset", + "add": "Add asset", "asset-type-max-length": "Asset type should be less than 256", "assign-to-customer": "Assign to customer", "assign-asset-to-customer": "Assign Asset(s) To Customer", @@ -772,7 +772,7 @@ }, "api-usage": { "api-features": "API features", - "api-usage": "Api Usage", + "api-usage": "API usage", "alarm": "Alarm", "alarms-created": "Alarms created", "alarms-created-daily-activity": "Alarms created daily activity", @@ -847,10 +847,10 @@ }, "audit-log": { "audit": "Audit", - "audit-logs": "Audit Logs", + "audit-logs": "Audit logs", "timestamp": "Timestamp", - "entity-type": "Entity Type", - "entity-name": "Entity Name", + "entity-type": "Entity type", + "entity-name": "Entity name", "user": "User", "type": "Type", "status": "Status", @@ -951,7 +951,7 @@ "public-assets": "Public Assets", "public-edges": "Public Edges", "public-entity-views": "Public Entity Views", - "add": "Add Customer", + "add": "Add customer", "delete": "Delete customer", "manage-customer-users": "Manage customer users", "manage-customer-devices": "Manage customer devices", @@ -1011,7 +1011,7 @@ "dashboards": "Dashboards", "management": "Dashboard management", "view-dashboards": "View Dashboards", - "add": "Add Dashboard", + "add": "Add dashboard", "assign-dashboard-to-customer": "Assign Dashboard(s) To Customer", "assign-dashboard-to-customer-text": "Please select the dashboards to assign to the customer", "assign-dashboard-to-edge-title": "Assign Dashboard(s) To Edge", @@ -1299,7 +1299,7 @@ "device-list-empty": "No devices selected.", "device-name-filter-required": "Device name filter is required.", "device-name-filter-no-device-matched": "No devices starting with '{{device}}' were found.", - "add": "Add Device", + "add": "Add device", "assign-to-customer": "Assign to customer", "assign-device-to-customer": "Assign Device(s) To Customer", "assign-device-to-customer-text": "Please select the devices to assign to the customer", @@ -1933,7 +1933,7 @@ "type-max-length": "Type should be less than 256", "management": "Edge management", "no-edges-matching": "No edges matching '{{entity}}' were found.", - "add": "Add Edge", + "add": "Add edge", "no-edges-text": "No edges found", "edge-details": "Edge details", "add-edge-text": "Add new edge", @@ -2192,7 +2192,7 @@ "no-entities-prompt": "No entities found", "no-data": "No data to display", "columns-to-display": "Columns to Display", - "type-api-usage-state": "Api Usage State", + "type-api-usage-state": "API Usage State", "type-edge": "Edge", "type-edges": "Edges", "list-of-edges": "{ count, plural, =1 {One edge} other {List of # edges} }", @@ -2231,9 +2231,9 @@ "label": "Label" }, "entity-view": { - "entity-view": "Entity View", + "entity-view": "Entity view", "entity-view-required": "Entity view is required.", - "entity-views": "Entity Views", + "entity-views": "Entity views", "management": "Entity View management", "view-entity-views": "View Entity Views", "entity-view-alias": "Entity View alias", @@ -2259,7 +2259,7 @@ "entity-view-list-empty": "No entity views selected.", "entity-view-name-filter-required": "Entity view name filter is required.", "entity-view-name-filter-no-entity-view-matched": "No entity views starting with '{{entityView}}' were found.", - "add": "Add Entity View", + "add": "Add entity view", "entity-view-public": "Entity view is public", "assign-to-customer": "Assign to customer", "assign-entity-view-to-customer": "Assign Entity View(s) To Customer", @@ -2287,14 +2287,14 @@ "unassign-entity-view": "Unassign entity view", "unassign-entity-views-title": "Are you sure you want to unassign { count, plural, =1 {1 entity view} other {# entity views} }?", "unassign-entity-views-text": "After the confirmation all selected entity views will be unassigned and won't be accessible by the customer.", - "entity-view-type": "Entity View type", - "entity-view-type-required": "Entity View type is required.", + "entity-view-type": "Entity view type", + "entity-view-type-required": "Entity view type is required.", "select-entity-view-type": "Select entity view type", "enter-entity-view-type": "Enter entity view type", "any-entity-view": "Any entity view", "no-entity-view-types-matching": "No entity view types matching '{{entitySubtype}}' were found.", "entity-view-type-list-empty": "No entity view types selected.", - "entity-view-types": "Entity View types", + "entity-view-types": "Entity view types", "created-time": "Created time", "name": "Name", "name-required": "Name is required.", @@ -3457,7 +3457,7 @@ "use-external-url": "Use external URL", "version": "Version", "version-required": "Version is required.", - "version-tag": "Version Tag", + "version-tag": "Version tag", "version-tag-hint": "Custom tag should match the package version reported by your device.", "version-max-length": "Version should be less than 256", "warning-after-save-no-edit": "Once the package is uploaded, you will not be able to modify title, version, device profile and package type." @@ -3608,7 +3608,7 @@ "no-relations-text": "No relations found" }, "resource": { - "add": "Add Resource", + "add": "Add resource", "all-types": "All", "copyId": "Copy resource Id", "delete": "Delete resource", @@ -3653,7 +3653,7 @@ "name-required": "Name is required.", "name-max-length": "Name should be less than 256", "description": "Description", - "add": "Add Rule Chain", + "add": "Add rule chain", "set-root": "Make rule chain root", "set-root-rulechain-title": "Are you sure you want to make the rule chain '{{ruleChainName}}' root?", "set-root-rulechain-text": "After the confirmation the rule chain will become root and will handle all incoming transport messages.", @@ -3685,8 +3685,8 @@ "selected-rulechains": "{ count, plural, =1 {1 rule chain} other {# rule chains} } selected", "open-rulechain": "Open rule chain", "assign-new-rulechain": "Assign new rulechain", - "edge-template-root": "Template Root", - "assign-to-edge": "Assign to Edge", + "edge-template-root": "Template root", + "assign-to-edge": "Assign to edge", "edge-rulechain": "Edge rule chain", "unassign-rulechain-from-edge-text": "After the confirmation the rulechain will be unassigned and won't be accessible by the edge.", "unassign-rulechains-from-edge-title": "Are you sure you want to unassign { count, plural, =1 {1 rulechain} other {# rulechains} }?", @@ -3896,7 +3896,7 @@ "tenant": "Tenant", "tenants": "Tenants", "management": "Tenant management", - "add": "Add Tenant", + "add": "Add tenant", "admins": "Admins", "manage-tenant-admins": "Manage tenant admins", "delete": "Delete tenant", @@ -4623,13 +4623,13 @@ "user": { "user": "User", "users": "Users", - "customer-users": "Customer Users", - "tenant-admins": "Tenant Admins", + "customer-users": "Customer users", + "tenant-admins": "Tenant admins", "sys-admin": "System administrator", "tenant-admin": "Tenant administrator", "customer": "Customer", "anonymous": "Anonymous", - "add": "Add User", + "add": "Add user", "delete": "Delete user", "add-user-text": "Add new user", "no-users-text": "No users found", @@ -4644,8 +4644,8 @@ "email": "Email", "email-required": "Email is required.", "invalid-email-format": "Invalid email format.", - "first-name": "First Name", - "last-name": "Last Name", + "first-name": "First name", + "last-name": "Last name", "description": "Description", "default-dashboard": "Default dashboard", "always-fullscreen": "Always fullscreen", @@ -4764,7 +4764,7 @@ "auto-commit-settings-read-only-hint": "Auto-commit feature doesn't work with enabled read-only option in Repository settings." }, "widget": { - "widget-library": "Widgets Library", + "widget-library": "Widgets library", "widget-bundle": "Widgets Bundle", "all-bundles": "All bundles", "select-widgets-bundle": "Select widgets bundle", @@ -4849,7 +4849,7 @@ "widget-template-load-failed-error": "Failed to load widget template!", "details": "Details", "widget-details": "Widget details", - "add": "Add Widget", + "add": "Add widget", "add-existing-widget": "Add existing widget", "add-new-widget": "Add new widget", "search-widgets": "Search widgets", @@ -4925,9 +4925,9 @@ }, "widgets-bundle": { "current": "Current bundle", - "widgets-bundles": "Widgets Bundles", + "widgets-bundles": "Widgets bundles", "widgets-bundle-widgets": "Widgets Bundle Widgets", - "add": "Add Widgets Bundle", + "add": "Add widgets bundle", "delete": "Delete widgets bundle", "title": "Title", "title-required": "Title is required.", From 3d182a736b44186f19cf44b183ed7fad44598a37 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 10 Nov 2023 17:32:29 +0200 Subject: [PATCH 20/24] Improve load system widgets method. --- .../install/ThingsboardInstallService.java | 4 +- .../DefaultSystemDataLoaderService.java | 51 ------- .../service/install/InstallScripts.java | 124 ++++++++++++------ .../install/SystemDataLoaderService.java | 4 - 4 files changed, 89 insertions(+), 94 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 9f51fcf288..0730bca8f8 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -108,7 +108,7 @@ public class ThingsboardInstallService { log.info("Migrating ThingsBoard entities data from cassandra to SQL database ..."); entitiesMigrateService.migrate(); log.info("Updating system data..."); - systemDataLoaderService.updateSystemWidgets(); + systemDataLoaderService.loadSystemWidgets(); } else if ("3.0.1-cassandra".equals(upgradeFromVersion)) { log.info("Migrating ThingsBoard latest timeseries data from cassandra to SQL database ..."); latestMigrateService.migrate(); @@ -275,7 +275,7 @@ public class ThingsboardInstallService { entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry); log.info("Updating system data..."); dataUpdateService.upgradeRuleNodes(); - systemDataLoaderService.updateSystemWidgets(); + systemDataLoaderService.loadSystemWidgets(); installScripts.loadSystemLwm2mResources(); } log.info("Upgrade finished successfully!"); diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 16704a0856..cf7b779dbe 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -63,7 +63,6 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; -import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; @@ -84,9 +83,6 @@ import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration; -import org.thingsboard.server.common.data.widget.DeprecatedFilter; -import org.thingsboard.server.common.data.widget.WidgetTypeInfo; -import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.device.DeviceCredentialsService; @@ -536,58 +532,11 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { installScripts.loadDashboards(demoTenant.getId(), null); } - @Override - public void deleteSystemWidgetBundle(String bundleAlias) throws Exception { - WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, bundleAlias); - if (widgetsBundle != null) { - PageData widgetTypes; - var pageLink = new PageLink(1024); - do { - widgetTypes = widgetTypeService.findWidgetTypesInfosByWidgetsBundleId(TenantId.SYS_TENANT_ID, widgetsBundle.getId(), false, DeprecatedFilter.ALL, null, pageLink); - for (var widgetType : widgetTypes.getData()) { - widgetTypeService.deleteWidgetType(TenantId.SYS_TENANT_ID, widgetType.getId()); - } - pageLink.nextPageLink(); - } while (widgetTypes.hasNext()); - widgetsBundleService.deleteWidgetsBundle(TenantId.SYS_TENANT_ID, widgetsBundle.getId()); - } - } - @Override public void loadSystemWidgets() throws Exception { installScripts.loadSystemWidgets(); } - @Override - public void updateSystemWidgets() throws Exception { - this.deleteSystemWidgetBundle("charts"); - this.deleteSystemWidgetBundle("cards"); - this.deleteSystemWidgetBundle("maps"); - this.deleteSystemWidgetBundle("analogue_gauges"); - this.deleteSystemWidgetBundle("digital_gauges"); - this.deleteSystemWidgetBundle("gpio_widgets"); - this.deleteSystemWidgetBundle("alarm_widgets"); - this.deleteSystemWidgetBundle("control_widgets"); - this.deleteSystemWidgetBundle("maps_v2"); - this.deleteSystemWidgetBundle("gateway_widgets"); - this.deleteSystemWidgetBundle("input_widgets"); - this.deleteSystemWidgetBundle("date"); - this.deleteSystemWidgetBundle("entity_admin_widgets"); - this.deleteSystemWidgetBundle("navigation_widgets"); - this.deleteSystemWidgetBundle("edge_widgets"); - this.deleteSystemWidgetBundle("home_page_widgets"); - this.deleteSystemWidgetBundle("entity_widgets"); - this.deleteSystemWidgetBundle("html_widgets"); - this.deleteSystemWidgetBundle("tables"); - this.deleteSystemWidgetBundle("count_widgets"); - this.deleteSystemWidgetBundle("status_indicators"); - this.deleteSystemWidgetBundle("outdoor_environment"); - this.deleteSystemWidgetBundle("indoor_environment"); - this.deleteSystemWidgetBundle("air_quality"); - this.deleteSystemWidgetBundle("liquid_level_tanks"); - installScripts.loadSystemWidgets(); - } - private User createUser(Authority authority, TenantId tenantId, CustomerId customerId, diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index 7f0b70cb27..3e23f9af37 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -29,9 +29,13 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.widget.DeprecatedFilter; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; +import org.thingsboard.server.common.data.widget.WidgetTypeInfo; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.exception.DataValidationException; @@ -48,7 +52,9 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Base64; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.Function; @@ -189,6 +195,38 @@ public class InstallScripts { } public void loadSystemWidgets() throws Exception { + Map widgetsBundlesMap = new HashMap<>(); + Path widgetBundlesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR); + try (DirectoryStream dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) { + dirStream.forEach( + path -> { + JsonNode widgetsBundleDescriptorJson; + try { + widgetsBundleDescriptorJson = JacksonUtil.toJsonNode(path.toFile()); + } catch (Exception e) { + log.error("Unable to parse widgets bundle from json: [{}]", path); + throw new RuntimeException("Unable to parse widgets bundle from json", e); + } + if (widgetsBundleDescriptorJson == null || !widgetsBundleDescriptorJson.has("widgetsBundle")) { + log.error("Invalid widgets bundle json: [{}]", path); + throw new RuntimeException("Invalid widgets bundle json: [" + path + "]"); + } + widgetsBundlesMap.put(path, widgetsBundleDescriptorJson); + JsonNode bundleAliasNode = widgetsBundleDescriptorJson.get("widgetsBundle").get("alias"); + if (bundleAliasNode == null || !bundleAliasNode.isTextual()) { + log.error("Invalid widgets bundle json: [{}]", path); + throw new RuntimeException("Invalid widgets bundle json: [" + path + "]"); + } + String bundleAlias = bundleAliasNode.asText(); + try { + this.deleteSystemWidgetBundle(bundleAlias); + } catch (Exception e) { + log.error("Failed to delete system widgets bundle: [{}]", bundleAlias); + throw new RuntimeException("Failed to delete system widgets bundle: [" + bundleAlias + "]", e); + } + } + ); + } Path widgetTypesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_TYPES_DIR); if (Files.exists(widgetTypesDir)) { try (DirectoryStream dirStream = Files.newDirectoryStream(widgetTypesDir, path -> path.toString().endsWith(JSON_EXT))) { @@ -206,44 +244,56 @@ public class InstallScripts { ); } } - Path widgetBundlesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, WIDGET_BUNDLES_DIR); - try (DirectoryStream dirStream = Files.newDirectoryStream(widgetBundlesDir, path -> path.toString().endsWith(JSON_EXT))) { - dirStream.forEach( - path -> { - try { - JsonNode widgetsBundleDescriptorJson = JacksonUtil.toJsonNode(path.toFile()); - JsonNode widgetsBundleJson = widgetsBundleDescriptorJson.get("widgetsBundle"); - WidgetsBundle widgetsBundle = JacksonUtil.treeToValue(widgetsBundleJson, WidgetsBundle.class); - WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); - List widgetTypeFqns = new ArrayList<>(); - if (widgetsBundleDescriptorJson.has("widgetTypes")) { - JsonNode widgetTypesArrayJson = widgetsBundleDescriptorJson.get("widgetTypes"); - widgetTypesArrayJson.forEach( - widgetTypeJson -> { - try { - WidgetTypeDetails widgetTypeDetails = JacksonUtil.treeToValue(widgetTypeJson, WidgetTypeDetails.class); - var savedWidgetType = widgetTypeService.saveWidgetType(widgetTypeDetails); - widgetTypeFqns.add(savedWidgetType.getFqn()); - } catch (Exception e) { - log.error("Unable to load widget type from json: [{}]", path.toString()); - throw new RuntimeException("Unable to load widget type from json", e); - } - } - ); - } - if (widgetsBundleDescriptorJson.has("widgetTypeFqns")) { - JsonNode widgetFqnsArrayJson = widgetsBundleDescriptorJson.get("widgetTypeFqns"); - widgetFqnsArrayJson.forEach(fqnJson -> { - widgetTypeFqns.add(fqnJson.asText()); - }); + for (var widgetsBundleDescriptorEntry : widgetsBundlesMap.entrySet()) { + Path path = widgetsBundleDescriptorEntry.getKey(); + try { + JsonNode widgetsBundleDescriptorJson = widgetsBundleDescriptorEntry.getValue(); + JsonNode widgetsBundleJson = widgetsBundleDescriptorJson.get("widgetsBundle"); + WidgetsBundle widgetsBundle = JacksonUtil.treeToValue(widgetsBundleJson, WidgetsBundle.class); + WidgetsBundle savedWidgetsBundle = widgetsBundleService.saveWidgetsBundle(widgetsBundle); + List widgetTypeFqns = new ArrayList<>(); + if (widgetsBundleDescriptorJson.has("widgetTypes")) { + JsonNode widgetTypesArrayJson = widgetsBundleDescriptorJson.get("widgetTypes"); + widgetTypesArrayJson.forEach( + widgetTypeJson -> { + try { + WidgetTypeDetails widgetTypeDetails = JacksonUtil.treeToValue(widgetTypeJson, WidgetTypeDetails.class); + var savedWidgetType = widgetTypeService.saveWidgetType(widgetTypeDetails); + widgetTypeFqns.add(savedWidgetType.getFqn()); + } catch (Exception e) { + log.error("Unable to load widget type from json: [{}]", path.toString()); + throw new RuntimeException("Unable to load widget type from json", e); + } } - widgetTypeService.updateWidgetsBundleWidgetFqns(TenantId.SYS_TENANT_ID, savedWidgetsBundle.getId(), widgetTypeFqns); - } catch (Exception e) { - log.error("Unable to load widgets bundle from json: [{}]", path.toString()); - throw new RuntimeException("Unable to load widgets bundle from json", e); - } - } - ); + ); + } + if (widgetsBundleDescriptorJson.has("widgetTypeFqns")) { + JsonNode widgetFqnsArrayJson = widgetsBundleDescriptorJson.get("widgetTypeFqns"); + widgetFqnsArrayJson.forEach(fqnJson -> { + widgetTypeFqns.add(fqnJson.asText()); + }); + } + widgetTypeService.updateWidgetsBundleWidgetFqns(TenantId.SYS_TENANT_ID, savedWidgetsBundle.getId(), widgetTypeFqns); + } catch (Exception e) { + log.error("Unable to load widgets bundle from json: [{}]", path.toString()); + throw new RuntimeException("Unable to load widgets bundle from json", e); + } + } + } + + private void deleteSystemWidgetBundle(String bundleAlias) { + WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(TenantId.SYS_TENANT_ID, bundleAlias); + if (widgetsBundle != null) { + PageData widgetTypes; + var pageLink = new PageLink(1024); + do { + widgetTypes = widgetTypeService.findWidgetTypesInfosByWidgetsBundleId(TenantId.SYS_TENANT_ID, widgetsBundle.getId(), false, DeprecatedFilter.ALL, null, pageLink); + for (var widgetType : widgetTypes.getData()) { + widgetTypeService.deleteWidgetType(TenantId.SYS_TENANT_ID, widgetType.getId()); + } + pageLink.nextPageLink(); + } while (widgetTypes.hasNext()); + widgetsBundleService.deleteWidgetsBundle(TenantId.SYS_TENANT_ID, widgetsBundle.getId()); } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java index 08de12da78..344870cf1b 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java @@ -31,12 +31,8 @@ public interface SystemDataLoaderService { void loadSystemWidgets() throws Exception; - void updateSystemWidgets() throws Exception; - void loadDemoData() throws Exception; - void deleteSystemWidgetBundle(String bundleAlias) throws Exception; - void createQueues(); void createDefaultNotificationConfigs(); From ed7b86cf61e7d80d82a75831fdc3ff857650ffef Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Fri, 10 Nov 2023 17:41:16 +0200 Subject: [PATCH 21/24] Code review changes --- .../telemetry/TelemetryEdgeProcessor.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessor.java index 647994dcd9..667d661d2e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/telemetry/TelemetryEdgeProcessor.java @@ -21,6 +21,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EntityDataProto; @@ -37,11 +38,14 @@ public class TelemetryEdgeProcessor extends BaseTelemetryProcessor { } public DownlinkMsg convertTelemetryEventToDownlink(EdgeEvent edgeEvent) throws JsonProcessingException { - if (edgeEvent.getBody() != null && edgeEvent.getBody().toString().length() > 1000) { - log.debug("[{}][{}][{}] Conversion to a DownlinkMsg telemetry event failed due to a size limit violation. " + - "Current size is {}, but the limit is 1000. {}", edgeEvent.getTenantId(), edgeEvent.getEdgeId(), - edgeEvent.getEntityId(), edgeEvent.getBody().toString().length(), edgeEvent.getBody().toString().substring(0, 100)); - return null; + if (edgeEvent.getBody() != null) { + String bodyStr = edgeEvent.getBody().toString(); + if (bodyStr.length() > 1000) { + log.debug("[{}][{}][{}] Conversion to a DownlinkMsg telemetry event failed due to a size limit violation. " + + "Current size is {}, but the limit is 1000. {}", edgeEvent.getTenantId(), edgeEvent.getEdgeId(), + edgeEvent.getEntityId(), bodyStr.length(), StringUtils.truncate(bodyStr, 100)); + return null; + } } EntityType entityType = EntityType.valueOf(edgeEvent.getType().name()); EntityDataProto entityDataProto = convertTelemetryEventToEntityDataProto( From 4041186f40f41f176c79b9a61adf23eb73aafff0 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Fri, 10 Nov 2023 17:59:58 +0200 Subject: [PATCH 22/24] EdgeRepository - fixed typo searchText <> textSearch --- .../thingsboard/server/dao/sql/edge/EdgeRepository.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeRepository.java index f632ef92ef..69795edf11 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/EdgeRepository.java @@ -91,10 +91,10 @@ public interface EdgeRepository extends JpaRepository { "LEFT JOIN CustomerEntity c on c.id = a.customerId " + "WHERE a.tenantId = :tenantId " + "AND a.customerId = :customerId " + - "AND (:searchText IS NULL OR ilike(a.name, CONCAT('%', :textSearch, '%')) = true)") + "AND (:textSearch IS NULL OR ilike(a.name, CONCAT('%', :textSearch, '%')) = true)") Page findEdgeInfosByTenantIdAndCustomerId(@Param("tenantId") UUID tenantId, @Param("customerId") UUID customerId, - @Param("searchText") String searchText, + @Param("textSearch") String textSearch, Pageable pageable); @Query("SELECT new org.thingsboard.server.dao.model.sql.EdgeInfoEntity(a, c.title, c.additionalInfo) " + @@ -113,11 +113,11 @@ public interface EdgeRepository extends JpaRepository { @Query("SELECT ee FROM EdgeEntity ee, RelationEntity re WHERE ee.tenantId = :tenantId " + "AND ee.id = re.fromId AND re.fromType = 'EDGE' AND re.relationTypeGroup = 'EDGE' " + "AND re.relationType = 'Contains' AND re.toId = :entityId AND re.toType = :entityType " + - "AND (:searchText IS NULL OR ilike(ee.name, CONCAT('%', :searchText, '%')) = true)") + "AND (:textSearch IS NULL OR ilike(ee.name, CONCAT('%', :textSearch, '%')) = true)") Page findByTenantIdAndEntityId(@Param("tenantId") UUID tenantId, @Param("entityId") UUID entityId, @Param("entityType") String entityType, - @Param("searchText") String searchText, + @Param("textSearch") String textSearch, Pageable pageable); @Query("SELECT ee FROM EdgeEntity ee, TenantEntity te WHERE ee.tenantId = te.id AND te.tenantProfileId = :tenantProfileId ") From 78a87185b0f7fc011ba08a57863b9e9c95f1831e Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 10 Nov 2023 18:26:02 +0200 Subject: [PATCH 23/24] Node upgrade improvement --- .../update/DefaultDataUpdateService.java | 4 +- .../rule/DefaultTbRuleChainService.java | 4 +- .../server/utils/TbNodeUpgradeUtils.java | 19 ++--- .../server/utils/TbNodeUpgradeUtilsTest.java | 72 +++++++++---------- 4 files changed, 46 insertions(+), 53 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index 5375590084..7d08fdca00 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -294,9 +294,7 @@ public class DefaultDataUpdateService implements DataUpdateService { log.debug("Going to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}", ruleNodeId, ruleNodeType, fromVersion, toVersion); try { - ruleNode.setConfiguration(TbNodeUpgradeUtils.upgradeRuleNodeConfiguration(ruleNode, ruleNodeClassInfo.getAnnotation(), - ruleNodeClassInfo.getClazz())); - ruleNode.setConfigurationVersion(toVersion); + TbNodeUpgradeUtils.upgradeConfigurationAndVersion(ruleNode, ruleNodeClassInfo); saveFutures.add(jpaExecutorService.submit(() -> { ruleChainService.saveRuleNode(TenantId.SYS_TENANT_ID, ruleNode); log.debug("Successfully upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}", diff --git a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java index 50f370fbb9..70c150b5f2 100644 --- a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java +++ b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java @@ -406,9 +406,7 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement log.debug("Going to upgrade rule node with id: {} type: {} fromVersion: {} toVersion: {}", ruleNodeId, ruleNodeType, fromVersion, toVersion); try { - node.setConfiguration(TbNodeUpgradeUtils.upgradeRuleNodeConfiguration(node, ruleNodeClass.getAnnotation(), - ruleNodeClass.getClazz())); - node.setConfigurationVersion(toVersion); + TbNodeUpgradeUtils.upgradeConfigurationAndVersion(node, ruleNodeClass); log.debug("Successfully upgrade rule node with id: {} type: {}, rule chain id: {} fromVersion: {} toVersion: {}", ruleNodeId, ruleNodeType, ruleChainId, fromVersion, toVersion); } catch (TbNodeException e) { diff --git a/application/src/main/java/org/thingsboard/server/utils/TbNodeUpgradeUtils.java b/application/src/main/java/org/thingsboard/server/utils/TbNodeUpgradeUtils.java index 98f5e713df..5aa4176b1f 100644 --- a/application/src/main/java/org/thingsboard/server/utils/TbNodeUpgradeUtils.java +++ b/application/src/main/java/org/thingsboard/server/utils/TbNodeUpgradeUtils.java @@ -20,20 +20,23 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.util.TbPair; +import org.thingsboard.server.service.component.RuleNodeClassInfo; public class TbNodeUpgradeUtils { - public static JsonNode upgradeRuleNodeConfiguration(RuleNode node, - org.thingsboard.rule.engine.api.RuleNode annotation, - Class nodeClass) throws Exception { + public static void upgradeConfigurationAndVersion(RuleNode node, RuleNodeClassInfo nodeInfo) throws Exception { JsonNode oldConfiguration = node.getConfiguration(); if (oldConfiguration == null || !oldConfiguration.isObject()) { - var configClass = annotation.configClazz(); - return JacksonUtil.valueToTree(configClass.getDeclaredConstructor().newInstance().defaultConfiguration()); + var configClass = nodeInfo.getAnnotation().configClazz(); + node.setConfiguration(JacksonUtil.valueToTree(configClass.getDeclaredConstructor().newInstance().defaultConfiguration())); + } else { + var tbVersionedNode = (TbNode) nodeInfo.getClazz().getDeclaredConstructor().newInstance(); + TbPair upgradeResult = tbVersionedNode.upgrade(node.getConfigurationVersion(), oldConfiguration); + if (upgradeResult.getFirst()) { + node.setConfiguration(upgradeResult.getSecond()); + } } - var tbVersionedNode = (TbNode) nodeClass.getDeclaredConstructor().newInstance(); - TbPair upgradeResult = tbVersionedNode.upgrade(node.getConfigurationVersion(), oldConfiguration); - return upgradeResult.getFirst() ? upgradeResult.getSecond() : oldConfiguration; + node.setConfigurationVersion(nodeInfo.getCurrentVersion()); } } diff --git a/application/src/test/java/org/thingsboard/server/utils/TbNodeUpgradeUtilsTest.java b/application/src/test/java/org/thingsboard/server/utils/TbNodeUpgradeUtilsTest.java index 1fb2ddcc25..483772301b 100644 --- a/application/src/test/java/org/thingsboard/server/utils/TbNodeUpgradeUtilsTest.java +++ b/application/src/test/java/org/thingsboard/server/utils/TbNodeUpgradeUtilsTest.java @@ -19,11 +19,10 @@ import com.fasterxml.jackson.databind.node.NullNode; import org.assertj.core.api.Assertions; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.metadata.TbGetAttributesNode; import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; import org.thingsboard.server.common.data.rule.RuleNode; -import org.thingsboard.server.common.data.util.TbPair; +import org.thingsboard.server.service.component.RuleNodeClassInfo; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -33,53 +32,59 @@ public class TbNodeUpgradeUtilsTest { @Test public void testUpgradeRuleNodeConfigurationWithNullConfig() throws Exception { // GIVEN - var node = mock(RuleNode.class); - var nodeClass = TbGetAttributesNode.class; + var node = new RuleNode(); + var nodeInfo = mock(RuleNodeClassInfo.class); var nodeConfigClazz = TbGetAttributesNodeConfiguration.class; - var annotation = mock(org.thingsboard.rule.engine.api.RuleNode.class); - var defaultConfig = JacksonUtil.valueToTree(nodeConfigClazz.getDeclaredConstructor().newInstance().defaultConfiguration()); - when(node.getConfiguration()).thenReturn(null); - when(node.getConfigurationVersion()).thenReturn(0); + when(nodeInfo.getClazz()).thenReturn((Class)TbGetAttributesNode.class); + when(nodeInfo.getCurrentVersion()).thenReturn(1); + when(nodeInfo.getAnnotation()).thenReturn(annotation); when(annotation.configClazz()).thenReturn((Class) nodeConfigClazz); - // WHEN - var upgradedConfig = TbNodeUpgradeUtils.upgradeRuleNodeConfiguration(node, annotation, nodeClass); + // WHEN + TbNodeUpgradeUtils.upgradeConfigurationAndVersion(node, nodeInfo); // THEN - Assertions.assertThat(upgradedConfig).isEqualTo(defaultConfig); + Assertions.assertThat(node.getConfiguration()).isEqualTo(defaultConfig); + Assertions.assertThat(node.getConfigurationVersion()).isEqualTo(1); } @Test public void testUpgradeRuleNodeConfigurationWithNullNodeConfig() throws Exception { // GIVEN - var node = mock(RuleNode.class); - var nodeClass = TbGetAttributesNode.class; + var node = new RuleNode(); + node.setConfiguration(NullNode.instance); + var nodeInfo = mock(RuleNodeClassInfo.class); var nodeConfigClazz = TbGetAttributesNodeConfiguration.class; - var annotation = mock(org.thingsboard.rule.engine.api.RuleNode.class); - var defaultConfig = JacksonUtil.valueToTree(nodeConfigClazz.getDeclaredConstructor().newInstance().defaultConfiguration()); - when(node.getConfiguration()).thenReturn(NullNode.instance); - when(node.getConfigurationVersion()).thenReturn(0); + when(nodeInfo.getClazz()).thenReturn((Class)TbGetAttributesNode.class); + when(nodeInfo.getCurrentVersion()).thenReturn(1); + when(nodeInfo.getAnnotation()).thenReturn(annotation); when(annotation.configClazz()).thenReturn((Class) nodeConfigClazz); - // WHEN - var upgradedConfig = TbNodeUpgradeUtils.upgradeRuleNodeConfiguration(node, annotation, nodeClass); + // WHEN + TbNodeUpgradeUtils.upgradeConfigurationAndVersion(node, nodeInfo); // THEN - Assertions.assertThat(upgradedConfig).isEqualTo(defaultConfig); + Assertions.assertThat(node.getConfiguration()).isEqualTo(defaultConfig); + Assertions.assertThat(node.getConfigurationVersion()).isEqualTo(1); } @Test public void testUpgradeRuleNodeConfigurationWithNonNullConfig() throws Exception { // GIVEN - var node = mock(RuleNode.class); - var nodeClass = TbGetAttributesNode.class; + var node = new RuleNode(); + var nodeInfo = mock(RuleNodeClassInfo.class); var nodeConfigClazz = TbGetAttributesNodeConfiguration.class; - var annotation = mock(org.thingsboard.rule.engine.api.RuleNode.class); + var defaultConfig = JacksonUtil.valueToTree(nodeConfigClazz.getDeclaredConstructor().newInstance().defaultConfiguration()); + + when(nodeInfo.getClazz()).thenReturn((Class)TbGetAttributesNode.class); + when(nodeInfo.getCurrentVersion()).thenReturn(1); + when(nodeInfo.getAnnotation()).thenReturn(annotation); + when(annotation.configClazz()).thenReturn((Class) nodeConfigClazz); String versionZeroDefaultConfigStr = "{\"fetchToData\":false," + "\"clientAttributeNames\":[]," + @@ -88,24 +93,13 @@ public class TbNodeUpgradeUtilsTest { "\"latestTsKeyNames\":[]," + "\"tellFailureIfAbsent\":true," + "\"getLatestValueWithTs\":false}"; - - var existingConfig = JacksonUtil.toJsonNode(versionZeroDefaultConfigStr); - int fromVersion = 0; - var currentDefaultConfig = JacksonUtil.valueToTree(nodeConfigClazz.getDeclaredConstructor().newInstance().defaultConfiguration()); - - when(node.getConfiguration()).thenReturn(existingConfig); - when(node.getConfigurationVersion()).thenReturn(fromVersion); - when(annotation.configClazz()).thenReturn((Class) nodeConfigClazz); - - TbNode tbVersionedNodeMock = mock(nodeClass); - - when(tbVersionedNodeMock.upgrade(fromVersion, existingConfig)).thenReturn(new TbPair<>(true, currentDefaultConfig)); - + node.setConfiguration(JacksonUtil.toJsonNode(versionZeroDefaultConfigStr)); // WHEN - var upgradedConfig = TbNodeUpgradeUtils.upgradeRuleNodeConfiguration(node, annotation, nodeClass); - + TbNodeUpgradeUtils.upgradeConfigurationAndVersion(node, nodeInfo); // THEN - Assertions.assertThat(upgradedConfig).isEqualTo(currentDefaultConfig); + Assertions.assertThat(node.getConfiguration()).isEqualTo(defaultConfig); + Assertions.assertThat(node.getConfigurationVersion()).isEqualTo(1); + } } From afa54ef277c79ca3fa0e2d26c08cc5b77f4ae6f1 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 10 Nov 2023 19:36:39 +0200 Subject: [PATCH 24/24] Update TBEL version to 1.1.4 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d5b517c48e..818bef02f0 100755 --- a/pom.xml +++ b/pom.xml @@ -78,7 +78,7 @@ 3.8.1 3.21.9 1.42.1 - 1.1.3 + 1.1.4 1.18.18 1.2.4 1.2.5