Browse Source

UI: merge with master and resolve conflict

pull/11532/head
Artem Dzhereleiko 2 years ago
parent
commit
1f7ff3cca8
  1. 158
      application/src/main/data/json/system/scada_symbols/leak-sensor.svg
  2. 16
      application/src/main/data/json/system/scada_symbols/stand-filter.svg
  3. 276
      application/src/main/data/json/system/scada_symbols/waterstop.svg
  4. 2
      application/src/main/data/json/system/widget_bundles/scada_water_system_symbols.json
  5. 2
      application/src/main/data/json/tenant/dashboards/gateways.json
  6. 21
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  7. 7
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  8. 25
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  9. 78
      application/src/main/java/org/thingsboard/server/config/RequestSizeFilter.java
  10. 14
      application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
  11. 3
      application/src/main/java/org/thingsboard/server/controller/EdgeController.java
  12. 2
      application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java
  13. 222
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
  14. 14
      application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java
  15. 3
      application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java
  16. 1
      application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallInstructionsService.java
  17. 3
      application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeUpgradeInstructionsService.java
  18. 1
      application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeUpgradeInstructionsService.java
  19. 157
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java
  20. 62
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  21. 3
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeRpcService.java
  22. 1
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java
  23. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BasePageableEdgeEventFetcher.java
  24. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java
  25. 9
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  26. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java
  27. 6
      application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java
  28. 18
      application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java
  29. 7
      application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java
  30. 2
      application/src/main/java/org/thingsboard/server/service/entitiy/edge/TbEdgeService.java
  31. 4
      application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java
  32. 123
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  33. 37
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  34. 332
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbEdgeConsumerService.java
  35. 2
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  36. 99
      application/src/main/java/org/thingsboard/server/service/queue/EdgeConsumerStats.java
  37. 31
      application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java
  38. 19
      application/src/main/java/org/thingsboard/server/service/queue/TbEdgeConsumerService.java
  39. 3
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  40. 63
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  41. 8
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  42. 65
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java
  43. 2
      application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java
  44. 2
      application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java
  45. 2
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
  46. 2
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java
  47. 4
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfo.java
  48. 6
      application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java
  49. 4
      application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java
  50. 2
      application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java
  51. 22
      application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java
  52. 3
      application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java
  53. 5
      application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionRef.java
  54. 2
      application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java
  55. 47
      application/src/main/resources/thingsboard.yml
  56. 6
      application/src/test/java/org/thingsboard/server/cache/CaffeineCacheDefaultConfigurationTest.java
  57. 26
      application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
  58. 3
      application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java
  59. 57
      application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java
  60. 2
      application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java
  61. 13
      application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java
  62. 2
      application/src/test/java/org/thingsboard/server/controller/RpcControllerTest.java
  63. 43
      application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java
  64. 14
      application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
  65. 28
      application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java
  66. 67
      application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java
  67. 33
      application/src/test/java/org/thingsboard/server/edge/TelemetryEdgeTest.java
  68. 15
      application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java
  69. 2
      application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java
  70. 4
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java
  71. 4
      application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java
  72. 7
      application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java
  73. 4
      application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java
  74. 108
      application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java
  75. 2
      application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java
  76. 22
      application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java
  77. 4
      application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java
  78. 5
      common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java
  79. 4
      common/cache/src/main/java/org/thingsboard/server/cache/TbCaffeineCacheConfiguration.java
  80. 10
      common/cache/src/main/java/org/thingsboard/server/cache/edge/RelatedEdgesCacheValue.java
  81. 20
      common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java
  82. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java
  83. 1
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  84. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  85. 4
      common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java
  86. 6
      common/data/src/main/java/org/thingsboard/server/common/data/exception/EntityVersionMismatchException.java
  87. 6
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java
  88. 2
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/RuleChainExportData.java
  89. 5
      common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
  90. 1
      common/message/src/main/java/org/thingsboard/server/common/msg/edge/EdgeEventUpdateMsg.java
  91. 21
      common/message/src/main/java/org/thingsboard/server/common/msg/edge/EdgeHighPriorityMsg.java
  92. 11
      common/message/src/main/java/org/thingsboard/server/common/msg/edge/EdgeSessionMsg.java
  93. 1
      common/message/src/main/java/org/thingsboard/server/common/msg/edge/FromEdgeSyncResponse.java
  94. 2
      common/message/src/main/java/org/thingsboard/server/common/msg/edge/ToEdgeSyncRequest.java
  95. 10
      common/message/src/main/java/org/thingsboard/server/common/msg/tools/MaxPayloadSizeExceededException.java
  96. 59
      common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java
  97. 45
      common/proto/src/main/proto/queue.proto
  98. 13
      common/proto/src/test/java/org/thingsboard/server/common/util/ProtoUtilsTest.java
  99. 7
      common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusQueueConfigs.java
  100. 2
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java

158
application/src/main/data/json/system/scada_symbols/leak-sensor.svg

@ -0,0 +1,158 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="100" height="100" fill="none" version="1.1" viewBox="0 0 100 100">
<tb:metadata xmlns=""><![CDATA[{
"title": "Leak sensor",
"description": "Leak sensor",
"searchTags": [
"leak"
],
"widgetSizeX": 1,
"widgetSizeY": 1,
"tags": [
{
"tag": "background",
"stateRenderFunction": "var fill = ctx.properties.backgroundColor;\nelement.attr({fill: fill});",
"actions": null
},
{
"tag": "clickArea",
"stateRenderFunction": null,
"actions": {
"click": {
"actionFunction": "ctx.api.callAction(event, 'click');"
}
}
},
{
"tag": "icon",
"stateRenderFunction": "var leak = ctx.values.leak;\nif (leak) {\n element.attr({stroke: ctx.properties.leakColor});\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n} else {\n element.attr({stroke: ctx.properties.defaultColor});\n ctx.api.resetAnimation(element);\n}\n",
"actions": null
}
],
"behavior": [
{
"id": "leak",
"name": "{i18n:scada.symbol.leak}",
"hint": "{i18n:scada.symbol.water-leak-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": {
"action": "GET_TIME_SERIES",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": null,
"key": "state"
},
"getTimeSeries": {
"key": "leak"
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "click",
"name": "{i18n:scada.symbol.on-click}",
"hint": "{i18n:scada.symbol.on-click-hint}",
"group": null,
"type": "widgetAction",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": {
"type": "doNothing",
"targetDashboardStateId": null,
"openRightLayout": false,
"setEntityId": false,
"stateEntityParamName": null
}
}
],
"properties": [
{
"id": "backgroundColor",
"name": "{i18n:scada.symbol.background-color}",
"type": "color",
"default": "#FFFFFF",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "defaultColor",
"name": "{i18n:scada.symbol.default-color}",
"type": "color",
"default": "#1C943E",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "leakColor",
"name": "{i18n:scada.symbol.leak-color}",
"type": "color",
"default": "#D12730",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}
]
}]]></tb:metadata><rect x=".5" y=".5" width="99" height="99" rx="23.5" fill="#fff" tb:tag="background"/><rect x=".5" y=".5" width="99" height="99" rx="23.5" fill="url(#paint0_linear_2089_217648)"/><rect x=".5" y=".5" width="99" height="99" rx="23.5" fill="url(#paint1_linear_2089_217648)"/><rect x=".5" y=".5" width="99" height="99" rx="23.5" stroke="#fff"/><path d="m56.668 52.309c0 2.04-0.7902 3.9966-2.1967 5.4391-0.7985 0.819-1.7586 1.4347-2.8035 1.8135m10-6.7401c0-10.962-11.667-19.487-11.667-19.487s-11.667 8.5256-11.667 19.487c0 3.2301 1.2292 6.3283 3.4171 8.6125 2.1879 2.284 5.1556 3.5659 8.2497 3.5659 3.0942 0 6.0615-1.2826 8.2493-3.5666 2.188-2.2842 3.4172-5.3817 3.4172-8.6118z" stroke="#1C943E" stroke-linecap="round" stroke-linejoin="round" stroke-width="3" tb:tag="icon"/><path d="m33.631 0s-33.631 0-33.631 16.75v82.09c0 0.6628 0.89543 1.1601 2 1.1601h96c1.1045 0 2-0.4973 2-1.1601v-82.09c0-16.75-33.035-16.75-33.035-16.75h-16.965zm33.535 20.3c-0.64434 0-1.1666 0.3134-1.1666 0.7v75.1c0 0.3866 0.52234 0.7 1.1666 0.7h7.3336c0.64434 0 1.1666-0.3134 1.1666-0.7v-75.1c0-0.3866-0.52233-0.7-1.1666-0.7z" fill="#000" fill-opacity="0" style="stroke-width:.059761" tb:tag="clickArea"/><defs>
<linearGradient id="paint0_linear_2089_217648" x1="100" x2="1.5002" y1="50.119" y2="49.881" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".2" offset="0"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".05"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".15"/>
<stop stop-color="#fff" stop-opacity=".2" offset=".51114"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".85"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".95"/>
<stop stop-color="#020202" stop-opacity=".2" offset=".99341"/>
</linearGradient>
<linearGradient id="paint1_linear_2089_217648" x1="50" x2="50" y2="100" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".2" offset=".01"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".05"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".15343"/>
<stop stop-color="#fff" stop-opacity=".2" offset=".5"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".85"/>
<stop stop-opacity=".12" offset=".95"/>
<stop stop-opacity=".2" offset="1"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

16
application/src/main/data/json/system/scada_symbols/stand-filter.svg

@ -7,7 +7,7 @@
],
"widgetSizeX": 3,
"widgetSizeY": 5,
"stateRenderFunction": "",
"stateRenderFunction": "var running = ctx.values.running;\nif (running) {\n ctx.api.enable(svg.children());\n} else {\n ctx.api.disable(svg.children());\n}",
"tags": [
{
"tag": "background",
@ -25,7 +25,7 @@
},
{
"tag": "filterMode",
"stateRenderFunction": "var defaultBorderColor = ctx.properties.defaultBorderColor;\nvar activeBorderColor = ctx.properties.activeBorderColor;\nvar defaultLabelColor = ctx.properties.defaultLabelColor;\nvar activeLabelColor = ctx.properties.activeLabelColor;\nvar boxBackground = ctx.properties.modeBoxBackground;\n\nvar running = ctx.values.running;\nvar filtrationMode = ctx.values.filtrationMode;\n\nvar filtrationMap = {};\nvar bottomShift = 104;\nvar rightShift = 226;\nvar middleShift = 121;\n\nvar filterModeSet = element.remember('filterModeSet');\n\nif (!filterModeSet) {\n element.remember('filterModeSet', true);\n var clone = element.children()[0];\n setFilterModeColors(clone);\n element.clear();\n \n var i = 0;\n if (ctx.properties.filtrationMode) {\n i++;\n filtrationMap[i] = 'filter';\n }\n if (ctx.properties.wasteMode) {\n i++;\n filtrationMap[i] = 'waste';\n }\n if (ctx.properties.backwashMode) {\n i++;\n filtrationMap[i] = 'backwash';\n }\n if (ctx.properties.recirculateMode) {\n i++;\n filtrationMap[i] = 'recirculate';\n }\n if (ctx.properties.rinseMode) {\n i++;\n filtrationMap[i] = 'rinse';\n }\n if (ctx.properties.closedMode) {\n i++;\n filtrationMap[i] = 'closed';\n }\n \n var filterMode = Object.values(filtrationMap);\n var lastToMiddle = filterMode.length % 2;\n \n filterMode.forEach((mode, index, arr) => {\n var template = clone.clone();\n var x = (index % 2) * rightShift;\n var y = Math.floor((index % filterMode.length) / 2) * bottomShift;\n if (index === filterMode.length-1 && lastToMiddle) {\n x = middleShift;\n }\n template.attr({'class': mode}).css('cursor', 'pointer').translate(x, y);\n ctx.api.text(template.findOne('text'), capitalizeFirstLetter(mode));\n template.click((event) => click(event, getFilterModeKey(mode)));\n element.add(template);\n })\n if (isFinite(filtrationMode)) {\n setFilterModeColorsByMap(filtrationMode, running);\n }\n}\n\nif (running) {\n ctx.api.enable(element.children());\n} else {\n ctx.api.disable(element.children());\n}\n\nfunction click(event, index) {\n var filtrationMode = ctx.values.filtrationMode;\n if (ctx.values.running && isFinite(filtrationMode)) {\n ctx.api.disable(element.children());\n var newValue = +index;\n if (newValue === filtrationMode) {\n newValue = 0;\n } else {\n setFilterModeColorsByMap(filtrationMode);\n }\n ctx.api.setValue('filtrationMode', newValue);\n ctx.api.callAction(event, 'filtrationModeUpdateState', newValue, {\n next: () => {\n setFilterModeColorsByMap(newValue ? newValue: filtrationMode, newValue);\n ctx.api.enable(element.children());\n },\n error: () => {\n setFilterModeColorsByMap(newValue);\n ctx.api.setValue('filtrationMode', filtrationMode);\n }\n });\n }\n}\n\nfunction getFilterModeKey(value) {\n return Object.keys(filtrationMap).find(key => filtrationMap[key] === value);\n}\n\nfunction setFilterModeColorsByMap(mode, active = false) {\n var filterBox = element.findOne('g.'+filtrationMap[mode])\n if (filterBox) {\n return setFilterModeColors(filterBox, active);\n }\n}\n\nfunction setFilterModeColors(filterBox, active = false) {\n if (filterBox) {\n var borderColor = active ? activeBorderColor : defaultBorderColor;\n var labelColor = active ? activeLabelColor : defaultLabelColor;\n filterBox.children()[0].fill(boxBackground);\n filterBox.children()[1].stroke(borderColor);\n filterBox.children()[2].fill(borderColor);\n ctx.api.font(filterBox.findOne('text'), null, labelColor);\n }\n}\n\nfunction capitalizeFirstLetter(string) {\n return string.charAt(0).toUpperCase() + string.slice(1);\n}",
"stateRenderFunction": "var defaultBorderColor = ctx.properties.defaultBorderColor;\nvar activeBorderColor = ctx.properties.activeBorderColor;\nvar defaultLabelColor = ctx.properties.defaultLabelColor;\nvar activeLabelColor = ctx.properties.activeLabelColor;\nvar boxBackground = ctx.properties.modeBoxBackground;\n\nvar running = ctx.values.running;\nvar filtrationMode = ctx.values.filtrationMode;\n\nvar filtrationMap = {};\nvar bottomShift = 104;\nvar rightShift = 226;\nvar middleShift = 121;\n\nvar filterModeSet = element.remember('filterModeSet');\n\nvar i = 0;\nif (ctx.properties.filtrationMode) {\n i++;\n filtrationMap[i] = 'filter';\n}\nif (ctx.properties.wasteMode) {\n i++;\n filtrationMap[i] = 'waste';\n}\nif (ctx.properties.backwashMode) {\n i++;\n filtrationMap[i] = 'backwash';\n}\nif (ctx.properties.recirculateMode) {\n i++;\n filtrationMap[i] = 'recirculate';\n}\nif (ctx.properties.rinseMode) {\n i++;\n filtrationMap[i] = 'rinse';\n}\nif (ctx.properties.closedMode) {\n i++;\n filtrationMap[i] = 'closed';\n}\n\nif (!filterModeSet) {\n element.remember('filterModeSet', true);\n var clone = element.children()[0];\n setFilterModeColors(clone);\n element.clear();\n \n var filterMode = Object.values(filtrationMap);\n var lastToMiddle = filterMode.length % 2;\n \n filterMode.forEach((mode, index, arr) => {\n var template = clone.clone();\n var x = (index % 2) * rightShift;\n var y = Math.floor((index % filterMode.length) / 2) * bottomShift;\n if (index === filterMode.length-1 && lastToMiddle) {\n x = middleShift;\n }\n template.attr({'class': mode}).css('cursor', 'pointer').translate(x, y);\n ctx.api.text(template.findOne('text'), capitalizeFirstLetter(mode));\n template.click((event) => click(event, getFilterModeKey(mode)));\n element.add(template);\n })\n}\n\nif (isFinite(filtrationMode)) {\n if (element.findOne('.active')) {\n setFilterModeColors(element.findOne('.active'));\n }\n setFilterModeColorsByMap(filtrationMode, running);\n}\n\nfunction click(event, index) {\n var filtrationMode = ctx.values.filtrationMode;\n if (ctx.values.running && isFinite(filtrationMode)) {\n ctx.api.disable(element.children());\n var newValue = +index;\n if (newValue === filtrationMode) {\n newValue = 0;\n } else {\n setFilterModeColorsByMap(filtrationMode);\n }\n ctx.api.setValue('filtrationMode', newValue);\n ctx.api.callAction(event, 'filtrationModeUpdateState', newValue, {\n next: () => {\n setFilterModeColorsByMap(newValue ? newValue: filtrationMode, newValue);\n ctx.api.enable(element.children());\n },\n error: () => {\n setFilterModeColorsByMap(newValue);\n ctx.api.setValue('filtrationMode', filtrationMode);\n ctx.api.enable(element.children());\n }\n });\n }\n}\n\nfunction getFilterModeKey(value) {\n return Object.keys(filtrationMap).find(key => filtrationMap[key] === value);\n}\n\nfunction setFilterModeColorsByMap(mode, active = false) {\n var filterBox = element.findOne('g.'+filtrationMap[mode])\n if (filterBox) {\n return setFilterModeColors(filterBox, active);\n }\n}\n\nfunction setFilterModeColors(filterBox, active = false) {\n if (filterBox) {\n if (active) {\n filterBox.addClass('active');\n } else {\n filterBox.removeClass('active');\n }\n var borderColor = active ? activeBorderColor : defaultBorderColor;\n var labelColor = active ? activeLabelColor : defaultLabelColor;\n filterBox.children()[0].fill(boxBackground);\n filterBox.children()[1].stroke(borderColor);\n filterBox.children()[2].fill(borderColor);\n ctx.api.font(filterBox.findOne('text'), null, labelColor);\n }\n}\n\nfunction capitalizeFirstLetter(string) {\n return string.charAt(0).toUpperCase() + string.slice(1);\n}",
"actions": null
}
],
@ -159,7 +159,7 @@
"id": "filtrationMode",
"name": "{i18n:scada.symbol.filter-mode}",
"type": "switch",
"default": false,
"default": true,
"required": null,
"subLabel": null,
"divider": null,
@ -175,7 +175,7 @@
"id": "wasteMode",
"name": "{i18n:scada.symbol.waste-mode}",
"type": "switch",
"default": false,
"default": true,
"required": null,
"subLabel": null,
"divider": null,
@ -191,7 +191,7 @@
"id": "backwashMode",
"name": "{i18n:scada.symbol.backwash-mode}",
"type": "switch",
"default": false,
"default": true,
"required": null,
"subLabel": null,
"divider": null,
@ -207,7 +207,7 @@
"id": "recirculateMode",
"name": "{i18n:scada.symbol.recirculate-mode}",
"type": "switch",
"default": false,
"default": true,
"required": null,
"subLabel": null,
"divider": null,
@ -223,7 +223,7 @@
"id": "rinseMode",
"name": "{i18n:scada.symbol.rinse-mode}",
"type": "switch",
"default": false,
"default": true,
"required": null,
"subLabel": null,
"divider": null,
@ -239,7 +239,7 @@
"id": "closedMode",
"name": "{i18n:scada.symbol.closed-mode}",
"type": "switch",
"default": false,
"default": true,
"required": null,
"subLabel": null,
"divider": null,

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

276
application/src/main/data/json/system/scada_symbols/waterstop.svg

@ -0,0 +1,276 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:tb="https://thingsboard.io/svg" width="200" height="200" fill="none" version="1.1" viewBox="0 0 200 200">
<tb:metadata xmlns=""><![CDATA[{
"title": "Water stop",
"description": "Remotely controlled water shutoff valve with configurable connectors and various states.",
"searchTags": [
"water stop"
],
"widgetSizeX": 1,
"widgetSizeY": 1,
"stateRenderFunction": "",
"tags": [
{
"tag": "clickArea",
"stateRenderFunction": null,
"actions": {
"click": {
"actionFunction": "var opened = ctx.values.opened;\nvar action = opened ? 'close' : 'open';\n\nctx.api.callAction(event, action, undefined, {\n next: () => {\n ctx.api.setValue('opened', !opened);\n }\n});"
}
}
},
{
"tag": "pipe-color",
"stateRenderFunction": "var color = ctx.properties.pipeColor;\nelement.attr({fill: color});",
"actions": null
},
{
"tag": "walve",
"stateRenderFunction": "var opened = ctx.values.opened;\nif (opened) {\n element.attr({fill: ctx.properties.openedColor});\n ctx.api.resetAnimation(element);\n} else {\n element.attr({fill: ctx.properties.closedColor});\n if (ctx.values.closeAnimation) {\n ctx.api.animate(element, 500).attr({opacity: 0.15}).loop(0, true);\n } else {\n ctx.api.resetAnimation(element);\n }\n}\n",
"actions": null
}
],
"behavior": [
{
"id": "opened",
"name": "{i18n:scada.symbol.opened}",
"hint": "{i18n:scada.symbol.opened-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.opened}",
"defaultGetValueSettings": {
"action": "GET_ATTRIBUTE",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"scope": "SHARED_SCOPE",
"key": "open"
},
"getTimeSeries": {
"key": "state"
},
"dataToValue": {
"type": "NONE",
"dataToValueFunction": "/* Should return boolean value */\nreturn data;",
"compareToValue": true
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
},
{
"id": "open",
"name": "{i18n:scada.symbol.open}",
"hint": "{i18n:scada.symbol.open-hint}",
"group": null,
"type": "action",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": {
"action": "SET_ATTRIBUTE",
"executeRpc": {
"method": "setState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"setAttribute": {
"scope": "SHARED_SCOPE",
"key": "open"
},
"putTimeSeries": {
"key": "state"
},
"valueToData": {
"type": "CONSTANT",
"constantValue": false,
"valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;"
}
},
"defaultWidgetActionSettings": null
},
{
"id": "close",
"name": "{i18n:scada.symbol.close}",
"hint": "{i18n:scada.symbol.close-hint}",
"group": null,
"type": "action",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": null,
"defaultGetValueSettings": null,
"defaultSetValueSettings": {
"action": "SET_ATTRIBUTE",
"executeRpc": {
"method": "setState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"setAttribute": {
"scope": "SHARED_SCOPE",
"key": "open"
},
"putTimeSeries": {
"key": "state"
},
"valueToData": {
"type": "CONSTANT",
"constantValue": true,
"valueToDataFunction": "/* Convert input boolean value to RPC parameters or attribute/time-series value */\nreturn value;"
}
},
"defaultWidgetActionSettings": null
},
{
"id": "closeAnimation",
"name": "{i18n:scada.symbol.close-state-animation}",
"hint": "{i18n:scada.symbol.close-state-animation-hint}",
"group": null,
"type": "value",
"valueType": "BOOLEAN",
"trueLabel": null,
"falseLabel": null,
"stateLabel": "{i18n:scada.symbol.animation}",
"defaultGetValueSettings": {
"action": "DO_NOTHING",
"defaultValue": false,
"executeRpc": {
"method": "getState",
"requestTimeout": 5000,
"requestPersistent": false,
"persistentPollingInterval": 1000
},
"getAttribute": {
"key": "state",
"scope": null
},
"getTimeSeries": {
"key": "state"
},
"dataToValue": {
"type": "NONE",
"compareToValue": true,
"dataToValueFunction": "/* Should return boolean value */\nreturn data;"
}
},
"defaultSetValueSettings": null,
"defaultWidgetActionSettings": null
}
],
"properties": [
{
"id": "openedColor",
"name": "{i18n:scada.symbol.opened-color}",
"type": "color",
"default": "#1C943E",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "closedColor",
"name": "{i18n:scada.symbol.closed-color}",
"type": "color",
"default": "#D12730",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
},
{
"id": "pipeColor",
"name": "{i18n:scada.symbol.pipe-color}",
"type": "color",
"default": "#FFFFFF",
"required": null,
"subLabel": null,
"divider": null,
"fieldSuffix": null,
"disableOnProperty": null,
"rowClass": "",
"fieldClass": "",
"min": null,
"max": null,
"step": null
}
]
}]]></tb:metadata><path d="M14 64H186V136H14V64Z" fill="#fff" tb:tag="pipe-color"/><path d="M14 64H186V136H14V64Z" fill="url(#paint0_linear_2089_217683)"/><path d="m15.5 65.5h169v69h-169v-69z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><rect x="187.5" y="51.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><rect x="1.5" y="51.5" width="11" height="97" rx="5.5" fill="#D9D9D9" stroke="#727171" stroke-width="3"/><path d="m70.5 46c0 1.933 1.567 3.5 3.5 3.5l52 1e-4c1.933 0 3.5-1.567 3.5-3.5v-37c0-4.6944-3.806-8.5-8.5-8.5h-42c-4.6944 0-8.5 3.8056-8.5 8.5v37z" fill="#1C943E" tb:tag="walve"/><path d="m70.5 46c0 1.933 1.567 3.5 3.5 3.5l52 1e-4c1.933 0 3.5-1.567 3.5-3.5v-37c0-4.6944-3.806-8.5-8.5-8.5h-42c-4.6944 0-8.5 3.8056-8.5 8.5v37z" fill="url(#paint1_linear_2089_217683)"/><path d="m70.5 46c0 1.933 1.567 3.5 3.5 3.5l52 1e-4c1.933 0 3.5-1.567 3.5-3.5v-37c0-4.6944-3.806-8.5-8.5-8.5h-42c-4.6944 0-8.5 3.8056-8.5 8.5v37z" stroke="#fff"/><path d="m110 136v-72h-21v72h21z" fill="#1C943E" tb:tag="walve"/><path d="m110 136v-72h-21v72h21z" fill="url(#paint2_linear_2089_217683)"/><path d="m110 136v-72h-21v72h21z" fill="url(#paint3_linear_2089_217683)"/><path d="m109.5 135.5v-71h-20v71h20z" stroke="#000" stroke-opacity=".12"/><path d="m126 64c2.209 0 4-1.7908 4-3.9999v-6c0-2.2092-1.791-4.0001-4-4.0001h-52c-2.2091 0-4 1.7909-4 4v6c0 2.2091 1.7909 4 4 4h52z" fill="#647484"/><path d="m126 64c2.209 0 4-1.7908 4-3.9999v-6c0-2.2092-1.791-4.0001-4-4.0001h-52c-2.2091 0-4 1.7909-4 4v6c0 2.2091 1.7909 4 4 4h52z" fill="url(#paint4_linear_2089_217683)"/><path d="m126 62.5c1.381 0 2.5-1.1193 2.5-2.5v-6c0-1.3807-1.119-2.5-2.5-2.5h-52c-1.3807 0-2.5 1.1193-2.5 2.5v6c0 1.3807 1.1193 2.5 2.5 2.5h52z" stroke="#000" stroke-opacity=".12" stroke-width="3"/><path d="m67.262 0s-67.262 0-67.262 33.5v164.18c0 1.3256 1.7909 2.3202 4 2.3202h192c2.209 0 4-0.9946 4-2.3202v-164.18c0-33.5-66.07-33.5-66.07-33.5h-33.93zm67.07 40.6c-1.2887 0-2.3332 0.6268-2.3332 1.4v150.2c0 0.7732 1.0447 1.4 2.3332 1.4h14.667c1.2887 0 2.3332-0.6268 2.3332-1.4v-150.2c0-0.7732-1.0447-1.4-2.3332-1.4z" fill="#000" fill-opacity="0" tb:tag="clickArea"/><defs>
<linearGradient id="paint0_linear_2089_217683" x1="58.72" x2="58.53" y1="64" y2="136" gradientUnits="userSpaceOnUse">
<stop stop-color="#727171" offset="0"/>
<stop stop-color="#727171" stop-opacity=".35" offset=".26388"/>
<stop stop-color="#727171" stop-opacity=".1" offset=".41759"/>
<stop stop-color="#fff" stop-opacity="0" offset=".49829"/>
<stop stop-color="#727171" stop-opacity=".1" offset=".58094"/>
<stop stop-color="#727171" stop-opacity=".35" offset=".71855"/>
<stop stop-color="#727171" offset="1"/>
</linearGradient>
<linearGradient id="paint1_linear_2089_217683" x1="134.3" x2="65.705" y1="8.0405" y2="9.0306" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".35" offset="0"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".14093"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".39937"/>
<stop stop-color="#fff" stop-opacity=".3" offset=".49829"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".59721"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".85946"/>
<stop stop-color="#020202" stop-opacity=".35" offset="1"/>
</linearGradient>
<linearGradient id="paint2_linear_2089_217683" x1="99.475" x2="100.06" y1="64" y2="134.92" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".35" offset="0"/>
<stop stop-color="#020202" stop-opacity=".15" offset=".15823"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".28193"/>
<stop stop-color="#fff" stop-opacity=".2" offset=".50291"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".72128"/>
<stop stop-color="#020202" stop-opacity=".15" offset=".8423"/>
<stop stop-color="#020202" stop-opacity=".35" offset=".99341"/>
</linearGradient>
<linearGradient id="paint3_linear_2089_217683" x1="110" x2="89" y1="100" y2="100" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".2" offset=".01"/>
<stop stop-color="#020202" stop-opacity=".12" offset=".05"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".15343"/>
<stop stop-color="#fff" stop-opacity=".2" offset=".5"/>
<stop stop-color="#fff" stop-opacity=".12" offset=".85"/>
<stop stop-opacity=".12" offset=".95"/>
<stop stop-opacity=".2" offset="1"/>
</linearGradient>
<linearGradient id="paint4_linear_2089_217683" x1="72.287" x2="127.11" y1="55.78" y2="57.142" gradientUnits="userSpaceOnUse">
<stop stop-color="#020202" stop-opacity=".2" offset="0"/>
<stop stop-color="#020202" stop-opacity=".18" offset=".090959"/>
<stop stop-color="#020202" stop-opacity=".1" offset=".1"/>
<stop stop-color="#020202" stop-opacity=".08" offset=".20513"/>
<stop stop-color="#020202" stop-opacity=".05" offset=".21555"/>
<stop stop-color="#020202" stop-opacity=".02" offset=".36962"/>
<stop stop-color="#fff" stop-opacity=".01" offset=".37768"/>
<stop stop-color="#fff" stop-opacity=".01" offset=".62413"/>
<stop stop-color="#020202" stop-opacity=".02" offset=".6313"/>
<stop stop-color="#020202" stop-opacity=".05" offset=".77601"/>
<stop stop-color="#020202" stop-opacity=".08" offset=".7898"/>
<stop stop-color="#020202" stop-opacity=".1" offset=".9"/>
<stop stop-color="#020202" stop-opacity=".18" offset=".90846"/>
<stop stop-color="#020202" stop-opacity=".2" offset="1"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

2
application/src/main/data/json/system/widget_bundles/scada_water_system_symbols.json

@ -36,6 +36,7 @@
"vertical_inline_flow_meter",
"left_analog_water_level_meter",
"right_analog_water_level_meter",
"leak_sensor",
"centrifugal_pump",
"small_right_motor_pump",
"small_left_motor_pump",
@ -52,6 +53,7 @@
"vertical_wheel_valve",
"horizontal_ball_valve",
"vertical_ball_valve",
"water_stop",
"vertical_tank",
"stand_vertical_tank",
"cylindrical_tank",

2
application/src/main/data/json/tenant/dashboards/gateways.json

@ -200,7 +200,7 @@
"useShowWidgetActionFunction": null,
"showWidgetActionFunction": "return true;",
"type": "customPretty",
"customHtml": "<mat-toolbar fxLayout=\"row\" color=\"primary\">\n <h2 translate>gateway.gateway-configuration</h2>\n <span fxFlex></span>\n <button mat-icon-button (click)=\"cancel()\" type=\"button\">\n <mat-icon class=\"material-icons\">close</mat-icon>\n </button>\n</mat-toolbar>\n<mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n</mat-progress-bar>\n<div style=\"height: 4px;\" *ngIf=\"!(isLoading$ | async)\"></div>\n<tb-gateway-configuration\n class=\"gateway-config\"\n mat-dialog-content\n [device]=\"device\"\n [dialogRef]=\"dialogRef\">\n</tb-gateway-configuration>",
"customHtml": "<mat-progress-bar color=\"warn\" mode=\"indeterminate\" *ngIf=\"isLoading$ | async\">\n</mat-progress-bar>\n<tb-gateway-configuration\n class=\"gateway-config\"\n mat-dialog-content\n [device]=\"device\"\n [dialogRef]=\"dialogRef\">\n</tb-gateway-configuration>",
"customCss": ".gateway-config {\n width: 800px !important;\n padding: 0 !important;\n min-height: 75vh;\n max-width: 100%;\n display: grid !important;\n}\n\n@media screen and (max-width: 599px) {\n .mat-mdc-dialog-content {\n max-height: calc(100% - 60px) !important;\n }\n}",
"customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\n\nopenAddEntityDialog();\n\nfunction openAddEntityDialog() {\n customDialog.customDialog(htmlTemplate, AddEntityDialogController).subscribe();\n}\n\nfunction AddEntityDialogController(instance) {\n let vm = instance;\n \n vm.device = additionalParams.entity.id;\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n}\n",
"customResources": [],

21
application/src/main/java/org/thingsboard/server/actors/app/AppActor.java

@ -37,12 +37,10 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.aware.TenantAwareMsg;
import org.thingsboard.server.common.msg.edge.EdgeSessionMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
@ -110,11 +108,6 @@ public class AppActor extends ContextAwareActor {
case REMOVE_RPC_TO_DEVICE_ACTOR_MSG:
onToDeviceActorMsg((TenantAwareMsg) msg, true);
break;
case EDGE_EVENT_UPDATE_TO_EDGE_SESSION_MSG:
case EDGE_SYNC_REQUEST_TO_EDGE_SESSION_MSG:
case EDGE_SYNC_RESPONSE_FROM_EDGE_SESSION_MSG:
onToEdgeSessionMsg((EdgeSessionMsg) msg);
break;
case SESSION_TIMEOUT_MSG:
ctx.broadcastToChildrenByType(msg, EntityType.TENANT);
break;
@ -206,20 +199,6 @@ public class AppActor extends ContextAwareActor {
() -> true));
}
private void onToEdgeSessionMsg(EdgeSessionMsg msg) {
TbActorRef target = null;
if (ModelConstants.SYSTEM_TENANT.equals(msg.getTenantId())) {
log.warn("Message has system tenant id: {}", msg);
} else {
target = getOrCreateTenantActor(msg.getTenantId()).orElse(null);
}
if (target != null) {
target.tellWithHighPriority(msg);
} else {
log.debug("[{}] Invalid edge session msg: {}", msg.getTenantId(), msg);
}
}
@Override
public ProcessFailureStrategy onProcessFailure(TbActorMsg msg, Throwable t) {
log.error("Failed to process msg: {}", msg, t);

7
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java

@ -56,6 +56,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.edge.EdgeHighPriorityMsg;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponseActorMsg;
@ -214,7 +215,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
log.debug("[{}][{}] device is related to edge: [{}]. Saving RPC request: [{}][{}] to edge queue", tenantId, deviceId, edgeId.getId(), rpcId, requestId);
try {
if (systemContext.getEdgeService().isEdgeActiveAsync(tenantId, edgeId, DefaultDeviceStateService.ACTIVITY_STATE).get()) {
saveRpcRequestToEdgeQueue(request, requestId).get();
saveRpcRequestToEdgeQueue(request, requestId);
} else {
log.error("[{}][{}][{}] Failed to save RPC request to edge queue {}. The Edge is currently offline or unreachable", tenantId, deviceId, edgeId.getId(), request);
}
@ -921,7 +922,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
systemContext.getTbCoreToTransportService().process(nodeId, msg);
}
private ListenableFuture<Void> saveRpcRequestToEdgeQueue(ToDeviceRpcRequest msg, Integer requestId) {
private void saveRpcRequestToEdgeQueue(ToDeviceRpcRequest msg, Integer requestId) {
ObjectNode body = JacksonUtil.newObjectNode();
body.put("requestId", requestId);
body.put("requestUUID", msg.getId().toString());
@ -935,7 +936,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
EdgeEvent edgeEvent = EdgeUtils.constructEdgeEvent(tenantId, edgeId, EdgeEventType.DEVICE, EdgeEventActionType.RPC_CALL, deviceId, body);
return systemContext.getEdgeEventService().saveAsync(edgeEvent);
systemContext.getClusterService().onEdgeHighPriorityMsg(new EdgeHighPriorityMsg(tenantId, edgeEvent));
}
void restoreSessions() {

25
application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java

@ -33,9 +33,7 @@ import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
@ -48,14 +46,12 @@ import org.thingsboard.server.common.msg.TbActorStopReason;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
import org.thingsboard.server.common.msg.edge.EdgeSessionMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.rule.engine.DeviceDeleteMsg;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper;
import java.util.HashSet;
@ -163,11 +159,6 @@ public class TenantActor extends RuleChainManagerActor {
case RULE_CHAIN_TO_RULE_CHAIN_MSG:
onRuleChainMsg((RuleChainAwareMsg) msg);
break;
case EDGE_EVENT_UPDATE_TO_EDGE_SESSION_MSG:
case EDGE_SYNC_REQUEST_TO_EDGE_SESSION_MSG:
case EDGE_SYNC_RESPONSE_FROM_EDGE_SESSION_MSG:
onToEdgeSessionMsg((EdgeSessionMsg) msg);
break;
default:
return false;
}
@ -270,18 +261,6 @@ public class TenantActor extends RuleChainManagerActor {
initRuleChains();
}
}
if (msg.getEntityId().getEntityType() == EntityType.EDGE && isCore) {
EdgeId edgeId = new EdgeId(msg.getEntityId().getId());
EdgeRpcService edgeRpcService = systemContext.getEdgeRpcService();
if (edgeRpcService != null) {
if (msg.getEvent() == ComponentLifecycleEvent.DELETED) {
edgeRpcService.deleteEdge(tenantId, edgeId);
} else if (msg.getEvent() == ComponentLifecycleEvent.UPDATED) {
Edge edge = systemContext.getEdgeService().findEdgeById(tenantId, edgeId);
edgeRpcService.updateEdge(tenantId, edge);
}
}
}
if (msg.getEntityId().getEntityType() == EntityType.DEVICE && ComponentLifecycleEvent.DELETED == msg.getEvent() && isMyPartition(msg.getEntityId())) {
DeviceId deviceId = (DeviceId) msg.getEntityId();
onToDeviceActorMsg(new DeviceDeleteMsg(tenantId, deviceId), true);
@ -311,10 +290,6 @@ public class TenantActor extends RuleChainManagerActor {
() -> true);
}
private void onToEdgeSessionMsg(EdgeSessionMsg msg) {
systemContext.getEdgeRpcService().onToEdgeSessionMsg(tenantId, msg);
}
private ApiUsageState getApiUsageState() {
if (apiUsageState == null) {
apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenantId));

78
application/src/main/java/org/thingsboard/server/config/RequestSizeFilter.java

@ -1,78 +0,0 @@
/**
* Copyright © 2016-2024 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.config;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.filter.OncePerRequestFilter;
import org.thingsboard.server.common.msg.tools.MaxPayloadSizeExceededException;
import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
import java.io.IOException;
import java.util.List;
@Slf4j
@Component
@RequiredArgsConstructor
public class RequestSizeFilter extends OncePerRequestFilter {
private final List<String> urls = List.of("/api/plugins/rpc/**", "/api/rpc/**");
private final AntPathMatcher pathMatcher = new AntPathMatcher();
private final ThingsboardErrorResponseHandler errorResponseHandler;
@Value("${transport.http.max_payload_size:65536}")
private int maxPayloadSize;
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
if (request.getContentLength() > maxPayloadSize) {
if (log.isDebugEnabled()) {
log.debug("Too large payload size. Url: {}, client ip: {}, content length: {}", request.getRequestURL(),
request.getRemoteAddr(), request.getContentLength());
}
errorResponseHandler.handle(new MaxPayloadSizeExceededException(), response);
return;
}
chain.doFilter(request, response);
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
for (String url : urls) {
if (pathMatcher.match(url, request.getRequestURI())) {
return false;
}
}
return true;
}
@Override
protected boolean shouldNotFilterAsyncDispatch() {
return false;
}
@Override
protected boolean shouldNotFilterErrorDispatch() {
return false;
}
}

14
application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java

@ -17,6 +17,7 @@ package org.thingsboard.server.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
@ -56,6 +57,7 @@ import org.thingsboard.server.service.security.auth.oauth2.HttpCookieOAuth2Autho
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationProvider;
import org.thingsboard.server.service.security.auth.rest.RestLoginProcessingFilter;
import org.thingsboard.server.service.security.auth.rest.RestPublicLoginProcessingFilter;
import org.thingsboard.server.transport.http.config.PayloadSizeFilter;
import java.util.ArrayList;
import java.util.Arrays;
@ -82,6 +84,9 @@ public class ThingsboardSecurityConfiguration {
public static final String MAIL_OAUTH2_PROCESSING_ENTRY_POINT = "/api/admin/mail/oauth2/code";
public static final String DEVICE_CONNECTIVITY_CERTIFICATE_DOWNLOAD_ENTRY_POINT = "/api/device-connectivity/mqtts/certificate/download";
@Value("${server.http.max_payload_size:/api/image*/**=52428800;/api/resource/**=52428800;/api/**=16777216}")
private String maxPayloadSizeConfig;
@Autowired
private ThingsboardErrorResponseHandler restAccessDeniedHandler;
@ -124,8 +129,10 @@ public class ThingsboardSecurityConfiguration {
@Autowired
private RateLimitProcessingFilter rateLimitProcessingFilter;
@Autowired
private RequestSizeFilter requestSizeFilter;
@Bean
protected PayloadSizeFilter payloadSizeFilter() {
return new PayloadSizeFilter(maxPayloadSizeConfig);
}
@Bean
protected FilterRegistrationBean<ShallowEtagHeaderFilter> buildEtagFilter() throws Exception {
@ -214,7 +221,6 @@ public class ThingsboardSecurityConfiguration {
.authorizeHttpRequests(config -> config
.requestMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points (webjars included)
.requestMatchers(
DEVICE_API_ENTRY_POINT, // Device HTTP Transport API
FORM_BASED_LOGIN_ENTRY_POINT, // Login end-point
PUBLIC_LOGIN_ENTRY_POINT, // Public login end-point
TOKEN_REFRESH_ENTRY_POINT, // Token refresh end-point
@ -228,7 +234,7 @@ public class ThingsboardSecurityConfiguration {
.addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(requestSizeFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(payloadSizeFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(rateLimitProcessingFilter, UsernamePasswordAuthenticationFilter.class);
if (oauth2Configuration != null) {
http.oauth2Login(login -> login

3
application/src/main/java/org/thingsboard/server/controller/EdgeController.java

@ -478,8 +478,7 @@ public class EdgeController extends BaseController {
edgeId = checkNotNull(edgeId);
SecurityUser user = getCurrentUser();
TenantId tenantId = user.getTenantId();
ToEdgeSyncRequest request = new ToEdgeSyncRequest(UUID.randomUUID(), tenantId, edgeId);
edgeRpcServiceOpt.get().processSyncRequest(request, fromEdgeSyncResponse -> reply(response, fromEdgeSyncResponse));
edgeRpcServiceOpt.get().processSyncRequest(tenantId, edgeId, fromEdgeSyncResponse -> reply(response, fromEdgeSyncResponse));
} else {
throw new ThingsboardException("Edges support disabled", ThingsboardErrorCode.GENERAL);
}

2
application/src/main/java/org/thingsboard/server/controller/OAuth2Controller.java

@ -75,7 +75,7 @@ public class OAuth2Controller extends BaseController {
@ApiOperation(value = "Get OAuth2 clients (getOAuth2Clients)", notes = "Get the list of OAuth2 clients " +
"to log in with, available for such domain scheme (HTTP or HTTPS) (if x-forwarded-proto request header is present - " +
"the scheme is known from it) and domain name and port (port may be known from x-forwarded-port header)")
@PostMapping(value = "/noauth/oauth2/client")
@PostMapping(value = "/noauth/oauth2Clients")
public List<OAuth2ClientLoginInfo> getOAuth2Clients(HttpServletRequest request,
@Parameter(description = "Mobile application package name, to find OAuth2 clients " +
"where there is configured mobile application with such package name")

222
application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java

@ -1,222 +0,0 @@
/**
* Copyright © 2016-2024 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;
import com.fasterxml.jackson.databind.node.ObjectNode;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.processor.alarm.AlarmEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.asset.AssetEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.asset.profile.AssetProfileEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.customer.CustomerEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.dashboard.DashboardEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.device.DeviceEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.device.profile.DeviceProfileEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.edge.EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.entityview.EntityViewEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.notification.NotificationEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.oauth2.OAuth2EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.ota.OtaPackageEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.queue.QueueEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.relation.RelationEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.resource.ResourceEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.rule.RuleChainEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.tenant.TenantEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.tenant.TenantProfileEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.user.UserEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.widget.WidgetBundleEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.widget.WidgetTypeEdgeProcessor;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
@Service
@TbCoreComponent
@Slf4j
public class DefaultEdgeNotificationService implements EdgeNotificationService {
public static final String EDGE_IS_ROOT_BODY_KEY = "isRoot";
@Autowired
private EdgeService edgeService;
@Autowired
private EdgeProcessor edgeProcessor;
@Autowired
private AssetEdgeProcessor assetProcessor;
@Autowired
private AssetProfileEdgeProcessor assetProfileEdgeProcessor;
@Autowired
private DeviceEdgeProcessor deviceProcessor;
@Autowired
private DeviceProfileEdgeProcessor deviceProfileEdgeProcessor;
@Autowired
private EntityViewEdgeProcessor entityViewProcessor;
@Autowired
private DashboardEdgeProcessor dashboardProcessor;
@Autowired
private RuleChainEdgeProcessor ruleChainProcessor;
@Autowired
private UserEdgeProcessor userProcessor;
@Autowired
private CustomerEdgeProcessor customerProcessor;
@Autowired
private OtaPackageEdgeProcessor otaPackageProcessor;
@Autowired
private WidgetBundleEdgeProcessor widgetBundleProcessor;
@Autowired
private WidgetTypeEdgeProcessor widgetTypeProcessor;
@Autowired
private QueueEdgeProcessor queueProcessor;
@Autowired
private TenantEdgeProcessor tenantEdgeProcessor;
@Autowired
private TenantProfileEdgeProcessor tenantProfileEdgeProcessor;
@Autowired
private AlarmEdgeProcessor alarmProcessor;
@Autowired
private RelationEdgeProcessor relationProcessor;
@Autowired
private ResourceEdgeProcessor resourceEdgeProcessor;
@Autowired
private NotificationEdgeProcessor notificationEdgeProcessor;
@Autowired
private OAuth2EdgeProcessor oAuth2EdgeProcessor;
@Autowired
protected ApplicationEventPublisher eventPublisher;
@Value("${actors.system.edge_dispatcher_pool_size:4}")
private int edgeDispatcherSize;
private ExecutorService executor;
@PostConstruct
public void initExecutor() {
executor = ThingsBoardExecutors.newWorkStealingPool(edgeDispatcherSize, "edge-notifications");
}
@PreDestroy
public void shutdownExecutor() {
if (executor != null) {
executor.shutdownNow();
}
}
@Override
public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) {
edge.setRootRuleChainId(ruleChainId);
Edge savedEdge = edgeService.saveEdge(edge);
ObjectNode isRootBody = JacksonUtil.newObjectNode();
isRootBody.put(EDGE_IS_ROOT_BODY_KEY, Boolean.TRUE);
eventPublisher.publishEvent(ActionEntityEvent.builder().tenantId(tenantId).edgeId(edge.getId()).entityId(ruleChainId)
.body(JacksonUtil.toString(isRootBody)).actionType(ActionType.UPDATED).build());
return savedEdge;
}
@Override
public void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) {
TenantId tenantId = TenantId.fromUUID(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB()));
log.debug("[{}] Pushing notification to edge {}", tenantId, edgeNotificationMsg);
final long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(60);
try {
executor.submit(() -> {
try {
if (deadline < System.nanoTime()) {
log.warn("[{}] Skipping notification message because deadline reached {}", tenantId, edgeNotificationMsg);
return;
}
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
switch (type) {
case EDGE -> edgeProcessor.processEdgeNotification(tenantId, edgeNotificationMsg);
case ASSET -> assetProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
case ASSET_PROFILE -> assetProfileEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
case DEVICE -> deviceProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
case DEVICE_PROFILE -> deviceProfileEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
case ENTITY_VIEW -> entityViewProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
case DASHBOARD -> dashboardProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
case RULE_CHAIN -> ruleChainProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
case USER -> userProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
case CUSTOMER -> customerProcessor.processCustomerNotification(tenantId, edgeNotificationMsg);
case OTA_PACKAGE -> otaPackageProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
case WIDGETS_BUNDLE -> widgetBundleProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
case WIDGET_TYPE -> widgetTypeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
case QUEUE -> queueProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
case ALARM -> alarmProcessor.processAlarmNotification(tenantId, edgeNotificationMsg);
case ALARM_COMMENT -> alarmProcessor.processAlarmCommentNotification(tenantId, edgeNotificationMsg);
case RELATION -> relationProcessor.processRelationNotification(tenantId, edgeNotificationMsg);
case TENANT -> tenantEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
case TENANT_PROFILE -> tenantProfileEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
case NOTIFICATION_RULE, NOTIFICATION_TARGET, NOTIFICATION_TEMPLATE ->
notificationEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
case TB_RESOURCE -> resourceEdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
case DOMAIN, OAUTH2_CLIENT -> oAuth2EdgeProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
default -> log.warn("[{}] Edge event type [{}] is not designed to be pushed to edge", tenantId, type);
}
} catch (Exception e) {
callBackFailure(tenantId, edgeNotificationMsg, callback, e);
}
});
callback.onSuccess();
} catch (Exception e) {
callBackFailure(tenantId, edgeNotificationMsg, callback, e);
}
}
private void callBackFailure(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback, Throwable throwable) {
log.error("[{}] Can't push to edge updates, edgeNotificationMsg [{}]", tenantId, edgeNotificationMsg, throwable);
callback.onFailure(throwable);
}
}

14
application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java

@ -48,6 +48,7 @@ import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.EdgeEventStorageSettings;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.edge.rpc.constructor.edge.EdgeMsgConstructor;
import org.thingsboard.server.service.edge.rpc.processor.alarm.AlarmEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.alarm.AlarmEdgeProcessorFactory;
@ -97,6 +98,9 @@ public class EdgeContextComponent {
@Autowired
private EdgeService edgeService;
@Autowired(required = false)
private EdgeRpcService edgeRpcService;
@Autowired
private EdgeEventService edgeEventService;
@ -224,19 +228,19 @@ public class EdgeContextComponent {
private AdminSettingsEdgeProcessor adminSettingsProcessor;
@Autowired
private OtaPackageEdgeProcessor otaPackageEdgeProcessor;
private OtaPackageEdgeProcessor otaPackageProcessor;
@Autowired
private QueueEdgeProcessor queueEdgeProcessor;
private QueueEdgeProcessor queueProcessor;
@Autowired
private TenantEdgeProcessor tenantEdgeProcessor;
private TenantEdgeProcessor tenantProcessor;
@Autowired
private TenantProfileEdgeProcessor tenantProfileEdgeProcessor;
private TenantProfileEdgeProcessor tenantProfileProcessor;
@Autowired
private ResourceEdgeProcessor resourceEdgeProcessor;
private ResourceEdgeProcessor resourceProcessor;
@Autowired
private NotificationEdgeProcessor notificationEdgeProcessor;

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

@ -130,8 +130,7 @@ public class EdgeEventSourcingListener {
@TransactionalEventListener(fallbackExecution = true)
public void handleEvent(ActionEntityEvent<?> event) {
if (EntityType.DEVICE.equals(event.getEntityId().getEntityType())
&& ActionType.ASSIGNED_TO_TENANT.equals(event.getActionType())) {
if (EntityType.DEVICE.equals(event.getEntityId().getEntityType()) && ActionType.ASSIGNED_TO_TENANT.equals(event.getActionType())) {
return;
}
try {

1
application/src/main/java/org/thingsboard/server/service/edge/instructions/DefaultEdgeInstallInstructionsService.java

@ -94,4 +94,5 @@ public class DefaultEdgeInstallInstructionsService extends BaseEdgeInstallUpgrad
protected String getBaseDirName() {
return INSTALL_DIR;
}
}

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

@ -74,7 +74,8 @@ public class DefaultEdgeUpgradeInstructionsService extends BaseEdgeInstallUpgrad
Optional<AttributeKvEntry> attributeKvEntryOpt = attributesService.find(tenantId, edgeId, AttributeScope.SERVER_SCOPE, DataConstants.EDGE_VERSION_ATTR_KEY).get();
if (attributeKvEntryOpt.isPresent()) {
String edgeVersionFormatted = convertEdgeVersionToDocsFormat(attributeKvEntryOpt.get().getValueAsString());
return isVersionGreaterOrEqualsThan(edgeVersionFormatted, "3.6.0") && !isVersionGreaterOrEqualsThan(edgeVersionFormatted, appVersion);
String appVersionFormatted = appVersion.replace("-SNAPSHOT", "");
return isVersionGreaterOrEqualsThan(edgeVersionFormatted, "3.6.0") && !isVersionGreaterOrEqualsThan(edgeVersionFormatted, appVersionFormatted);
}
return false;
}

1
application/src/main/java/org/thingsboard/server/service/edge/instructions/EdgeUpgradeInstructionsService.java

@ -31,4 +31,5 @@ public interface EdgeUpgradeInstructionsService {
void setAppVersion(String version);
boolean isUpgradeAvailable(TenantId tenantId, EdgeId edgeId) throws Exception;
}

157
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java

@ -28,14 +28,17 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.ResourceUtils;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
@ -47,12 +50,14 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.edge.EdgeEventUpdateMsg;
import org.thingsboard.server.common.msg.edge.EdgeHighPriorityMsg;
import org.thingsboard.server.common.msg.edge.EdgeSessionMsg;
import org.thingsboard.server.common.msg.edge.FromEdgeSyncResponse;
import org.thingsboard.server.common.msg.edge.ToEdgeSyncRequest;
import org.thingsboard.server.gen.edge.v1.EdgeRpcServiceGrpc;
import org.thingsboard.server.gen.edge.v1.RequestMsg;
import org.thingsboard.server.gen.edge.v1.ResponseMsg;
import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeContextComponent;
import org.thingsboard.server.service.state.DefaultDeviceStateService;
@ -84,7 +89,6 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
private final ConcurrentMap<EdgeId, Lock> sessionNewEventsLocks = new ConcurrentHashMap<>();
private final Map<EdgeId, Boolean> sessionNewEvents = new HashMap<>();
private final ConcurrentMap<EdgeId, ScheduledFuture<?>> sessionEdgeEventChecks = new ConcurrentHashMap<>();
private final ConcurrentMap<UUID, Consumer<FromEdgeSyncResponse>> localSyncEdgeRequests = new ConcurrentHashMap<>();
@Value("${edges.rpc.port}")
@ -111,7 +115,11 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
@Value("${edges.send_scheduler_pool_size}")
private int sendSchedulerPoolSize;
@Value("${edges.max_high_priority_queue_size_per_session:10000}")
private int maxHighPriorityQueueSizePerSession;
@Autowired
@Lazy
private EdgeContextComponent ctx;
@Autowired
@ -120,6 +128,12 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
@Autowired
private TbClusterService clusterService;
@Autowired
private TbServiceInfoProvider serviceInfoProvider;
@Autowired
private TbTransactionalCache<EdgeId, String> edgeIdServiceIdCache;
private Server server;
private ScheduledExecutorService edgeEventProcessingExecutorService;
@ -188,79 +202,100 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
@Override
public StreamObserver<RequestMsg> handleMsgs(StreamObserver<ResponseMsg> outputStream) {
return new EdgeGrpcSession(ctx, outputStream, this::onEdgeConnect, this::onEdgeDisconnect, sendDownlinkExecutorService, this.maxInboundMessageSize).getInputStream();
return new EdgeGrpcSession(ctx,
outputStream,
this::onEdgeConnect,
this::onEdgeDisconnect,
sendDownlinkExecutorService,
this.maxInboundMessageSize,
this.maxHighPriorityQueueSizePerSession).getInputStream();
}
@Override
public void onToEdgeSessionMsg(TenantId tenantId, EdgeSessionMsg msg) {
executorService.execute(() -> {
switch (msg.getMsgType()) {
case EDGE_EVENT_UPDATE_TO_EDGE_SESSION_MSG:
EdgeEventUpdateMsg edgeEventUpdateMsg = (EdgeEventUpdateMsg) msg;
log.trace("[{}] onToEdgeSessionMsg [{}]", tenantId, msg);
onEdgeEvent(tenantId, edgeEventUpdateMsg.getEdgeId());
break;
case EDGE_SYNC_REQUEST_TO_EDGE_SESSION_MSG:
ToEdgeSyncRequest toEdgeSyncRequest = (ToEdgeSyncRequest) msg;
log.trace("[{}] toEdgeSyncRequest [{}]", tenantId, msg);
startSyncProcess(tenantId, toEdgeSyncRequest.getEdgeId(), toEdgeSyncRequest.getId());
break;
case EDGE_SYNC_RESPONSE_FROM_EDGE_SESSION_MSG:
FromEdgeSyncResponse fromEdgeSyncResponse = (FromEdgeSyncResponse) msg;
log.trace("[{}] fromEdgeSyncResponse [{}]", tenantId, msg);
processSyncResponse(fromEdgeSyncResponse);
break;
switch (msg.getMsgType()) {
case EDGE_HIGH_PRIORITY_TO_EDGE_SESSION_MSG -> {
EdgeHighPriorityMsg edgeHighPriorityMsg = (EdgeHighPriorityMsg) msg;
log.trace("[{}] edgeEventMsg [{}]", tenantId, msg);
onEdgeHighPriorityEvent(edgeHighPriorityMsg);
}
});
case EDGE_EVENT_UPDATE_TO_EDGE_SESSION_MSG -> {
EdgeEventUpdateMsg edgeEventUpdateMsg = (EdgeEventUpdateMsg) msg;
log.trace("[{}] onToEdgeEventUpdateMsg [{}]", tenantId, msg);
onEdgeEventUpdate(tenantId, edgeEventUpdateMsg.getEdgeId());
}
case EDGE_SYNC_REQUEST_TO_EDGE_SESSION_MSG -> {
ToEdgeSyncRequest toEdgeSyncRequest = (ToEdgeSyncRequest) msg;
log.trace("[{}] toEdgeSyncRequest [{}]", tenantId, msg);
startSyncProcess(tenantId, toEdgeSyncRequest.getEdgeId(), toEdgeSyncRequest.getId(), toEdgeSyncRequest.getServiceId());
}
case EDGE_SYNC_RESPONSE_FROM_EDGE_SESSION_MSG -> {
FromEdgeSyncResponse fromEdgeSyncResponse = (FromEdgeSyncResponse) msg;
log.trace("[{}] fromEdgeSyncResponse [{}]", tenantId, msg);
processSyncResponse(fromEdgeSyncResponse);
}
}
}
@Override
public void updateEdge(TenantId tenantId, Edge edge) {
executorService.execute(() -> {
EdgeGrpcSession session = sessions.get(edge.getId());
if (session != null && session.isConnected()) {
log.debug("[{}] Updating configuration for edge [{}] [{}]", tenantId, edge.getName(), edge.getId());
session.onConfigurationUpdate(edge);
} else {
log.debug("[{}] Session doesn't exist for edge [{}] [{}]", tenantId, edge.getName(), edge.getId());
}
});
EdgeGrpcSession session = sessions.get(edge.getId());
if (session != null && session.isConnected()) {
log.debug("[{}] Updating configuration for edge [{}] [{}]", tenantId, edge.getName(), edge.getId());
session.onConfigurationUpdate(edge);
} else {
log.debug("[{}] Session doesn't exist for edge [{}] [{}]", tenantId, edge.getName(), edge.getId());
}
}
@Override
public void deleteEdge(TenantId tenantId, EdgeId edgeId) {
executorService.execute(() -> {
EdgeGrpcSession session = sessions.get(edgeId);
if (session != null && session.isConnected()) {
log.info("[{}] Closing and removing session for edge [{}]", tenantId, edgeId);
session.close();
sessions.remove(edgeId);
final Lock newEventLock = sessionNewEventsLocks.computeIfAbsent(edgeId, id -> new ReentrantLock());
newEventLock.lock();
try {
sessionNewEvents.remove(edgeId);
} finally {
newEventLock.unlock();
}
cancelScheduleEdgeEventsCheck(edgeId);
}
});
}
private void onEdgeEvent(TenantId tenantId, EdgeId edgeId) {
EdgeGrpcSession session = sessions.get(edgeId);
if (session != null && session.isConnected()) {
log.trace("[{}] onEdgeEvent [{}]", tenantId, edgeId.getId());
log.info("[{}] Closing and removing session for edge [{}]", tenantId, edgeId);
session.close();
sessions.remove(edgeId);
final Lock newEventLock = sessionNewEventsLocks.computeIfAbsent(edgeId, id -> new ReentrantLock());
newEventLock.lock();
try {
if (Boolean.FALSE.equals(sessionNewEvents.get(edgeId))) {
log.trace("[{}] set session new events flag to true [{}]", tenantId, edgeId.getId());
sessionNewEvents.put(edgeId, true);
}
sessionNewEvents.remove(edgeId);
} finally {
newEventLock.unlock();
}
cancelScheduleEdgeEventsCheck(edgeId);
}
}
private void onEdgeEventUpdate(TenantId tenantId, EdgeId edgeId) {
EdgeGrpcSession session = sessions.get(edgeId);
if (session != null && session.isConnected()) {
log.trace("[{}] onEdgeEventUpdate [{}]", tenantId, edgeId.getId());
updateSessionEventsFlag(tenantId, edgeId);
}
}
private void onEdgeHighPriorityEvent(EdgeHighPriorityMsg msg) {
TenantId tenantId = msg.getTenantId();
EdgeEvent edgeEvent = msg.getEdgeEvent();
EdgeId edgeId = edgeEvent.getEdgeId();
EdgeGrpcSession session = sessions.get(edgeId);
if (session != null && session.isConnected()) {
log.trace("[{}] onEdgeEvent [{}]", tenantId, edgeId);
session.addEventToHighPriorityQueue(edgeEvent);
updateSessionEventsFlag(tenantId, edgeId);
}
}
private void updateSessionEventsFlag(TenantId tenantId, EdgeId edgeId) {
final Lock newEventLock = sessionNewEventsLocks.computeIfAbsent(edgeId, id -> new ReentrantLock());
newEventLock.lock();
try {
if (Boolean.FALSE.equals(sessionNewEvents.get(edgeId))) {
log.trace("[{}] set session new events flag to true [{}]", tenantId, edgeId.getId());
sessionNewEvents.put(edgeId, true);
}
} finally {
newEventLock.unlock();
}
}
@ -279,37 +314,40 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
save(tenantId, edgeId, DefaultDeviceStateService.ACTIVITY_STATE, true);
long lastConnectTs = System.currentTimeMillis();
save(tenantId, edgeId, DefaultDeviceStateService.LAST_CONNECT_TIME, lastConnectTs);
edgeIdServiceIdCache.put(edgeId, serviceInfoProvider.getServiceId());
pushRuleEngineMessage(tenantId, edge, lastConnectTs, TbMsgType.CONNECT_EVENT);
cancelScheduleEdgeEventsCheck(edgeId);
scheduleEdgeEventsCheck(edgeGrpcSession);
}
private void startSyncProcess(TenantId tenantId, EdgeId edgeId, UUID requestId) {
private void startSyncProcess(TenantId tenantId, EdgeId edgeId, UUID requestId, String requestServiceId) {
EdgeGrpcSession session = sessions.get(edgeId);
if (session != null) {
if (!session.isSyncCompleted()) {
clusterService.pushEdgeSyncResponseToCore(new FromEdgeSyncResponse(requestId, tenantId, edgeId, false, "Sync process is active at the moment"));
clusterService.pushEdgeSyncResponseToCore(new FromEdgeSyncResponse(requestId, tenantId, edgeId, false, "Sync process is active at the moment"), requestServiceId);
} else {
boolean success = false;
if (session.isConnected()) {
session.startSyncProcess(true);
success = true;
}
clusterService.pushEdgeSyncResponseToCore(new FromEdgeSyncResponse(requestId, tenantId, edgeId, success, ""));
clusterService.pushEdgeSyncResponseToCore(new FromEdgeSyncResponse(requestId, tenantId, edgeId, success, ""), requestServiceId);
}
}
}
@Override
public void processSyncRequest(ToEdgeSyncRequest request, Consumer<FromEdgeSyncResponse> responseConsumer) {
public void processSyncRequest(TenantId tenantId, EdgeId edgeId, Consumer<FromEdgeSyncResponse> responseConsumer) {
ToEdgeSyncRequest request = new ToEdgeSyncRequest(UUID.randomUUID(), tenantId, edgeId, serviceInfoProvider.getServiceId());
UUID requestId = request.getId();
EdgeGrpcSession session = sessions.get(request.getEdgeId());
if (session != null && !session.isSyncCompleted()) {
responseConsumer.accept(new FromEdgeSyncResponse(requestId, request.getTenantId(), request.getEdgeId(), false, "Sync process is active at the moment"));
} else {
log.trace("[{}][{}] Processing sync edge request [{}]", request.getTenantId(), request.getId(), request.getEdgeId());
log.trace("[{}][{}] Processing sync edge request [{}], serviceId [{}]", request.getTenantId(), request.getId(), request.getEdgeId(), request.getServiceId());
localSyncEdgeRequests.put(requestId, responseConsumer);
clusterService.pushEdgeSyncRequestToCore(request);
clusterService.pushEdgeSyncRequestToEdge(request);
scheduleSyncRequestTimeout(request, requestId);
}
}
@ -415,6 +453,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i
} else {
log.debug("[{}] edge session [{}] is not available anymore, nothing to remove. most probably this session is already outdated!", edgeId, sessionId);
}
edgeIdServiceIdCache.evict(edgeId);
}
private void save(TenantId tenantId, EdgeId edgeId, String key, long value) {

62
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java

@ -42,6 +42,7 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.SortOrder;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.msg.edge.EdgeEventUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AlarmCommentUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg;
import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg;
@ -96,6 +97,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
@ -106,6 +108,7 @@ import java.util.function.BiConsumer;
public final class EdgeGrpcSession implements Closeable {
private static final ReentrantLock downlinkMsgLock = new ReentrantLock();
private static final ConcurrentLinkedQueue<EdgeEvent> highPriorityQueue = new ConcurrentLinkedQueue<>();
private static final int MAX_DOWNLINK_ATTEMPTS = 10; // max number of attemps to send downlink message if edge connected
@ -125,7 +128,7 @@ public final class EdgeGrpcSession implements Closeable {
private StreamObserver<RequestMsg> inputStream;
private StreamObserver<ResponseMsg> outputStream;
private boolean connected;
private boolean syncCompleted;
private volatile boolean syncCompleted;
private Long newStartTs;
private Long previousStartTs;
@ -137,11 +140,12 @@ public final class EdgeGrpcSession implements Closeable {
private int maxInboundMessageSize;
private int clientMaxInboundMessageSize;
private int maxHighPriorityQueueSizePerSession;
private ScheduledExecutorService sendDownlinkExecutorService;
EdgeGrpcSession(EdgeContextComponent ctx, StreamObserver<ResponseMsg> outputStream, BiConsumer<EdgeId, EdgeGrpcSession> sessionOpenListener,
BiConsumer<Edge, UUID> sessionCloseListener, ScheduledExecutorService sendDownlinkExecutorService, int maxInboundMessageSize) {
BiConsumer<Edge, UUID> sessionCloseListener, ScheduledExecutorService sendDownlinkExecutorService, int maxInboundMessageSize, int maxHighPriorityQueueSizePerSession) {
this.sessionId = UUID.randomUUID();
this.ctx = ctx;
this.outputStream = outputStream;
@ -149,6 +153,7 @@ public final class EdgeGrpcSession implements Closeable {
this.sessionCloseListener = sessionCloseListener;
this.sendDownlinkExecutorService = sendDownlinkExecutorService;
this.maxInboundMessageSize = maxInboundMessageSize;
this.maxHighPriorityQueueSizePerSession = maxHighPriorityQueueSizePerSession;
initInputStream();
}
@ -223,7 +228,7 @@ public final class EdgeGrpcSession implements Closeable {
}
public void startSyncProcess(boolean fullSync) {
log.trace("[{}][{}][{}] Staring edge sync process", this.tenantId, edge.getId(), this.sessionId);
log.info("[{}][{}][{}] Staring edge sync process", this.tenantId, edge.getId(), this.sessionId);
syncCompleted = false;
interruptGeneralProcessingOnSync();
doSync(new EdgeSyncCursor(ctx, edge, fullSync));
@ -255,18 +260,23 @@ public final class EdgeGrpcSession implements Closeable {
Futures.addCallback(sendDownlinkMsgsPack(Collections.singletonList(syncCompleteDownlinkMsg)), new FutureCallback<>() {
@Override
public void onSuccess(Boolean isInterrupted) {
syncCompleted = true;
ctx.getClusterService().onEdgeEventUpdate(edge.getTenantId(), edge.getId());
markSyncCompletedSendEdgeEventUpdate();
}
@Override
public void onFailure(Throwable t) {
log.error("[{}][{}] Exception during sending sync complete", tenantId, edge.getId(), t);
markSyncCompletedSendEdgeEventUpdate();
}
}, ctx.getGrpcCallbackExecutorService());
}
}
private void markSyncCompletedSendEdgeEventUpdate() {
syncCompleted = true;
ctx.getClusterService().onEdgeEventUpdate(new EdgeEventUpdateMsg(edge.getTenantId(), edge.getId()));
}
private void onUplinkMsg(UplinkMsg uplinkMsg) {
if (isRateLimitViolated(uplinkMsg)) {
return;
@ -429,6 +439,9 @@ public final class EdgeGrpcSession implements Closeable {
private void processEdgeEvents(EdgeEventFetcher fetcher, PageLink pageLink, SettableFuture<Pair<Long, Long>> result) {
try {
if (!highPriorityQueue.isEmpty()) {
processHighPriorityEvents();
}
PageData<EdgeEvent> pageData = fetcher.fetchEdgeEvents(edge.getTenantId(), edge, pageLink);
if (isConnected() && !pageData.getData().isEmpty()) {
log.trace("[{}][{}][{}] event(s) are going to be processed.", this.tenantId, this.sessionId, pageData.getData().size());
@ -472,6 +485,20 @@ public final class EdgeGrpcSession implements Closeable {
}
}
private void processHighPriorityEvents() {
try {
List<EdgeEvent> highPriorityEvents = new ArrayList<>();
EdgeEvent event;
while ((event = highPriorityQueue.poll()) != null) {
highPriorityEvents.add(event);
}
List<DownlinkMsg> downlinkMsgsPack = convertToDownlinkMsgsPack(highPriorityEvents);
sendDownlinkMsgsPack(downlinkMsgsPack).get();
} catch (Exception e) {
log.error("[{}] Failed to process high priority events", this.sessionId, e);
}
}
private ListenableFuture<Boolean> sendDownlinkMsgsPack(List<DownlinkMsg> downlinkMsgsPack) {
interruptPreviousSendDownlinkMsgsTask();
@ -556,7 +583,9 @@ public final class EdgeGrpcSession implements Closeable {
DownlinkMsg downlinkMsg = null;
try {
switch (edgeEvent.getAction()) {
case UPDATED, ADDED, DELETED, ASSIGNED_TO_EDGE, UNASSIGNED_FROM_EDGE, ALARM_ACK, ALARM_CLEAR, ALARM_DELETE, CREDENTIALS_UPDATED, RELATION_ADD_OR_UPDATE, RELATION_DELETED, RPC_CALL, ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER, ADDED_COMMENT, UPDATED_COMMENT, DELETED_COMMENT -> {
case UPDATED, ADDED, DELETED, ASSIGNED_TO_EDGE, UNASSIGNED_FROM_EDGE, ALARM_ACK, ALARM_CLEAR, ALARM_DELETE,
CREDENTIALS_UPDATED, RELATION_ADD_OR_UPDATE, RELATION_DELETED, RPC_CALL,
ASSIGNED_TO_CUSTOMER, UNASSIGNED_FROM_CUSTOMER, ADDED_COMMENT, UPDATED_COMMENT, DELETED_COMMENT -> {
downlinkMsg = convertEntityEventToDownlink(edgeEvent);
if (downlinkMsg != null && downlinkMsg.getWidgetTypeUpdateMsgCount() > 0) {
log.trace("[{}][{}] widgetTypeUpdateMsg message processed, downlinkMsgId = {}", this.tenantId, this.sessionId, downlinkMsg.getDownlinkMsgId());
@ -683,15 +712,15 @@ public final class EdgeGrpcSession implements Closeable {
case ADMIN_SETTINGS:
return ctx.getAdminSettingsProcessor().convertAdminSettingsEventToDownlink(edgeEvent, this.edgeVersion);
case OTA_PACKAGE:
return ctx.getOtaPackageEdgeProcessor().convertOtaPackageEventToDownlink(edgeEvent, this.edgeVersion);
return ctx.getOtaPackageProcessor().convertOtaPackageEventToDownlink(edgeEvent, this.edgeVersion);
case TB_RESOURCE:
return ctx.getResourceEdgeProcessor().convertResourceEventToDownlink(edgeEvent, this.edgeVersion);
return ctx.getResourceProcessor().convertResourceEventToDownlink(edgeEvent, this.edgeVersion);
case QUEUE:
return ctx.getQueueEdgeProcessor().convertQueueEventToDownlink(edgeEvent, this.edgeVersion);
return ctx.getQueueProcessor().convertQueueEventToDownlink(edgeEvent, this.edgeVersion);
case TENANT:
return ctx.getTenantEdgeProcessor().convertTenantEventToDownlink(edgeEvent, this.edgeVersion);
return ctx.getTenantProcessor().convertTenantEventToDownlink(edgeEvent, this.edgeVersion);
case TENANT_PROFILE:
return ctx.getTenantProfileEdgeProcessor().convertTenantProfileEventToDownlink(edgeEvent, this.edgeVersion);
return ctx.getTenantProfileProcessor().convertTenantProfileEventToDownlink(edgeEvent, this.edgeVersion);
case NOTIFICATION_RULE:
return ctx.getNotificationEdgeProcessor().convertNotificationRuleToDownlink(edgeEvent);
case NOTIFICATION_TARGET:
@ -914,4 +943,15 @@ public final class EdgeGrpcSession implements Closeable {
}
}
public void addEventToHighPriorityQueue(EdgeEvent edgeEvent) {
while (highPriorityQueue.size() > maxHighPriorityQueueSizePerSession) {
EdgeEvent oldestHighPriority = highPriorityQueue.poll();
if (oldestHighPriority != null) {
log.warn("[{}][{}][{}] High priority queue is full. Removing oldest high priority event from queue {}",
this.tenantId, edge.getId(), this.sessionId, oldestHighPriority);
}
}
highPriorityQueue.add(edgeEvent);
}
}

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

@ -32,5 +32,6 @@ public interface EdgeRpcService {
void deleteEdge(TenantId tenantId, EdgeId edgeId);
void processSyncRequest(ToEdgeSyncRequest request, Consumer<FromEdgeSyncResponse> responseConsumer);
void processSyncRequest(TenantId tenantId, EdgeId edgeId, Consumer<FromEdgeSyncResponse> responseConsumer);
}

1
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java

@ -30,4 +30,5 @@ public class EdgeSessionState {
private final Map<Integer, DownlinkMsg> pendingMsgsMap = Collections.synchronizedMap(new LinkedHashMap<>());
private SettableFuture<Boolean> sendDownlinkMsgsFuture;
private ScheduledFuture<?> scheduledSendDownlinkTask;
}

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/BasePageableEdgeEventFetcher.java

@ -35,7 +35,7 @@ public abstract class BasePageableEdgeEventFetcher<T> implements EdgeEventFetche
@Override
public PageData<EdgeEvent> fetchEdgeEvents(TenantId tenantId, Edge edge, PageLink pageLink) {
log.trace("[{}] start fetching edge events [{}]", tenantId, edge.getId());
log.trace("[{}][{}] start fetching edge events [{}], pageLink {}", getClass().getSimpleName(), tenantId, edge.getId(), pageLink);
PageData<T> entities = fetchEntities(tenantId, edge, pageLink);
List<EdgeEvent> result = new ArrayList<>();
if (!entities.getData().isEmpty()) {

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/RuleChainsEdgeEventFetcher.java

@ -30,7 +30,7 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.dao.rule.RuleChainService;
import static org.thingsboard.server.service.edge.DefaultEdgeNotificationService.EDGE_IS_ROOT_BODY_KEY;
import static org.thingsboard.server.dao.edge.EdgeServiceImpl.EDGE_IS_ROOT_BODY_KEY;
@Slf4j
@AllArgsConstructor

9
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java

@ -374,23 +374,20 @@ public abstract class BaseEdgeProcessor {
}, dbCallbackExecutorService);
}
private boolean doSaveIfEdgeIsOffline(EdgeEventType type,
EdgeEventActionType action) {
private boolean doSaveIfEdgeIsOffline(EdgeEventType type, EdgeEventActionType action) {
return switch (action) {
case TIMESERIES_UPDATED, ALARM_ACK, ALARM_CLEAR, ALARM_ASSIGNED, ALARM_UNASSIGNED, ADDED_COMMENT, UPDATED_COMMENT ->
true;
default -> switch (type) {
case ALARM, ALARM_COMMENT, RULE_CHAIN, RULE_CHAIN_METADATA, USER, CUSTOMER, TENANT, TENANT_PROFILE, WIDGETS_BUNDLE, WIDGET_TYPE,
ADMIN_SETTINGS, OTA_PACKAGE, QUEUE, RELATION, NOTIFICATION_TEMPLATE, NOTIFICATION_TARGET, NOTIFICATION_RULE ->
true;
ADMIN_SETTINGS, OTA_PACKAGE, QUEUE, RELATION, NOTIFICATION_TEMPLATE, NOTIFICATION_TARGET, NOTIFICATION_RULE -> true;
default -> false;
};
};
}
private ListenableFuture<Void> doSaveEdgeEvent(TenantId tenantId, EdgeId edgeId, EdgeEventType type, EdgeEventActionType action, EntityId entityId, JsonNode body) {
log.debug("Pushing event to edge queue. tenantId [{}], edgeId [{}], type[{}], " +
"action [{}], entityId [{}], body [{}]",
log.debug("Pushing event to edge queue. tenantId [{}], edgeId [{}], type[{}], action [{}], entityId [{}], body [{}]",
tenantId, edgeId, type, action, entityId, body);
EdgeEvent edgeEvent = EdgeUtils.constructEdgeEvent(tenantId, edgeId, type, action, entityId, body);

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/rule/RuleChainEdgeProcessor.java

@ -32,7 +32,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.rpc.constructor.rule.RuleChainMsgConstructor;
import org.thingsboard.server.service.edge.rpc.processor.BaseEdgeProcessor;
import static org.thingsboard.server.service.edge.DefaultEdgeNotificationService.EDGE_IS_ROOT_BODY_KEY;
import static org.thingsboard.server.dao.edge.EdgeServiceImpl.EDGE_IS_ROOT_BODY_KEY;
@Slf4j
@Component

6
application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java

@ -113,10 +113,8 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService {
if (ruleChainMetadataRequestMsg.getRuleChainIdMSB() == 0 || ruleChainMetadataRequestMsg.getRuleChainIdLSB() == 0) {
return Futures.immediateFuture(null);
}
RuleChainId ruleChainId =
new RuleChainId(new UUID(ruleChainMetadataRequestMsg.getRuleChainIdMSB(), ruleChainMetadataRequestMsg.getRuleChainIdLSB()));
return saveEdgeEvent(tenantId, edge.getId(),
EdgeEventType.RULE_CHAIN_METADATA, EdgeEventActionType.ADDED, ruleChainId, null);
RuleChainId ruleChainId = new RuleChainId(new UUID(ruleChainMetadataRequestMsg.getRuleChainIdMSB(), ruleChainMetadataRequestMsg.getRuleChainIdLSB()));
return saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN_METADATA, EdgeEventActionType.ADDED, ruleChainId, null);
}
@Override

18
application/src/main/java/org/thingsboard/server/service/entitiy/EntityStateSourcingListener.java

@ -35,7 +35,6 @@ import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
@ -48,6 +47,8 @@ import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.edge.EdgeEventUpdateMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.rule.engine.DeviceCredentialsUpdateNotificationMsg;
import org.thingsboard.server.dao.eventsourcing.ActionEntityEvent;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
@ -107,7 +108,7 @@ public class EntityStateSourcingListener {
onDeviceProfileUpdate(deviceProfile, event.getOldEntity(), isCreated);
}
case EDGE -> {
handleEdgeEvent(tenantId, entityId, event.getEntity(), lifecycleEvent);
onEdgeEvent(tenantId, entityId, event.getEntity(), lifecycleEvent);
}
case TB_RESOURCE -> {
TbResource tbResource = (TbResource) event.getEntity();
@ -221,10 +222,7 @@ public class EntityStateSourcingListener {
}
private DeviceProfile getOldDeviceProfile(Object oldEntity) {
if (oldEntity instanceof DeviceProfile) {
return (DeviceProfile) oldEntity;
}
return null;
return oldEntity instanceof DeviceProfile ? (DeviceProfile) oldEntity : null;
}
private void onDeviceProfileDelete(TenantId tenantId, EntityId entityId, DeviceProfile deviceProfile) {
@ -241,11 +239,11 @@ public class EntityStateSourcingListener {
tbClusterService.onDeviceUpdated(device, oldDevice);
}
private void handleEdgeEvent(TenantId tenantId, EntityId entityId, Object entity, ComponentLifecycleEvent lifecycleEvent) {
private void onEdgeEvent(TenantId tenantId, EntityId entityId, Object entity, ComponentLifecycleEvent lifecycleEvent) {
if (entity instanceof Edge) {
tbClusterService.broadcastEntityStateChangeEvent(tenantId, entityId, lifecycleEvent);
} else if (entity instanceof EdgeEvent) {
tbClusterService.onEdgeEventUpdate(tenantId, (EdgeId) entityId);
tbClusterService.onEdgeStateChangeEvent(new ComponentLifecycleMsg(tenantId, entityId, lifecycleEvent));
} else if (entity instanceof EdgeEvent edgeEvent) {
tbClusterService.onEdgeEventUpdate(new EdgeEventUpdateMsg(tenantId, edgeEvent.getEdgeId()));
}
}

7
application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java

@ -31,7 +31,6 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
@AllArgsConstructor
@ -40,7 +39,6 @@ import org.thingsboard.server.service.entitiy.AbstractTbEntityService;
@Slf4j
public class DefaultTbEdgeService extends AbstractTbEntityService implements TbEdgeService {
private final EdgeNotificationService edgeNotificationService;
private final RuleChainService ruleChainService;
@Override
@ -56,7 +54,7 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE
if (ActionType.ADDED.equals(actionType)) {
ruleChainService.assignRuleChainToEdge(tenantId, edgeTemplateRootRuleChain.getId(), edgeId);
edgeNotificationService.setEdgeRootRuleChain(tenantId, savedEdge, edgeTemplateRootRuleChain.getId());
savedEdge = edgeService.setEdgeRootRuleChain(tenantId, savedEdge, edgeTemplateRootRuleChain.getId());
edgeService.assignDefaultRuleChainsToEdge(tenantId, edgeId);
}
@ -143,7 +141,7 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE
TenantId tenantId = edge.getTenantId();
EdgeId edgeId = edge.getId();
try {
Edge updatedEdge = edgeNotificationService.setEdgeRootRuleChain(tenantId, edge, ruleChainId);
Edge updatedEdge = edgeService.setEdgeRootRuleChain(tenantId, edge, ruleChainId);
logEntityActionService.logEntityAction(tenantId, edgeId, edge, null, ActionType.UPDATED, user);
return updatedEdge;
} catch (Exception e) {
@ -152,4 +150,5 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE
throw e;
}
}
}

2
application/src/main/java/org/thingsboard/server/service/entitiy/edge/TbEdgeService.java

@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain;
public interface TbEdgeService {
Edge save(Edge edge, RuleChain edgeTemplateRootRuleChain, User user) throws Exception;
void delete(Edge edge, User user);
@ -36,4 +37,5 @@ public interface TbEdgeService {
Edge assignEdgeToPublicCustomer(TenantId tenantId, EdgeId edgeId, User user) throws ThingsboardException;
Edge setEdgeRootRuleChain(Edge edge, RuleChainId ruleChainId, User user) throws Exception;
}

4
application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java

@ -90,13 +90,13 @@ public abstract class AbstractPartitionBasedService<T extends EntityId> extends
@Override
protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
log.debug("onTbApplicationEvent, processing event: {}", partitionChangeEvent);
subscribeQueue.add(partitionChangeEvent.getPartitions());
subscribeQueue.add(partitionChangeEvent.getCorePartitions());
scheduledExecutor.submit(this::pollInitStateFromDB);
}
@Override
protected boolean filterTbApplicationEvent(PartitionChangeEvent event) {
return getServiceType().equals(event.getServiceType());
return event.getServiceType() == getServiceType();
}
protected void pollInitStateFromDB() {

123
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -23,6 +23,7 @@ import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.DataConstants;
@ -55,6 +56,7 @@ import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.msg.edge.EdgeEventUpdateMsg;
import org.thingsboard.server.common.msg.edge.EdgeHighPriorityMsg;
import org.thingsboard.server.common.msg.edge.FromEdgeSyncResponse;
import org.thingsboard.server.common.msg.edge.ToEdgeSyncRequest;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
@ -66,14 +68,23 @@ import org.thingsboard.server.common.msg.rule.engine.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.common.util.ProtoUtils;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ComponentLifecycleMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.EdgeNotificationMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.EntityDeleteMsg;
import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto;
import org.thingsboard.server.gen.transport.TransportProtos.QueueDeleteMsg;
import org.thingsboard.server.gen.transport.TransportProtos.QueueUpdateMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ResourceDeleteMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ResourceUpdateMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.MultipleTbQueueCallbackWrapper;
@ -112,6 +123,8 @@ public class DefaultTbClusterService implements TbClusterService {
private final AtomicInteger toRuleEngineMsgs = new AtomicInteger(0);
private final AtomicInteger toRuleEngineNfs = new AtomicInteger(0);
private final AtomicInteger toTransportNfs = new AtomicInteger(0);
private final AtomicInteger toEdgeMsgs = new AtomicInteger(0);
private final AtomicInteger toEdgeNfs = new AtomicInteger(0);
@Autowired
@Lazy
@ -133,6 +146,7 @@ public class DefaultTbClusterService implements TbClusterService {
private final TbAssetProfileCache assetProfileCache;
private final GatewayNotificationsService gatewayNotificationsService;
private final EdgeService edgeService;
private final TbTransactionalCache<EdgeId, String> edgeIdServiceIdCache;
@Override
public void pushMsgToCore(TenantId tenantId, EntityId entityId, ToCoreMsg msg, TbQueueCallback callback) {
@ -169,7 +183,7 @@ public class DefaultTbClusterService implements TbClusterService {
}
@Override
public void pushMsgToVersionControl(TenantId tenantId, TransportProtos.ToVersionControlServiceMsg msg, TbQueueCallback callback) {
public void pushMsgToVersionControl(TenantId tenantId, ToVersionControlServiceMsg msg, TbQueueCallback callback) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_VC_EXECUTOR, TenantId.SYS_TENANT_ID, tenantId);
log.trace("PUSHING msg: {} to:{}", msg, tpi);
producerProvider.getTbVersionControlMsgProducer().send(tpi, new TbProtoQueueMsg<>(tenantId.getId(), msg), callback);
@ -386,7 +400,7 @@ public class DefaultTbClusterService implements TbClusterService {
if (resource.getResourceType() == ResourceType.LWM2M_MODEL) {
TenantId tenantId = resource.getTenantId();
log.trace("[{}][{}][{}] Processing change resource", tenantId, resource.getResourceType(), resource.getResourceKey());
TransportProtos.ResourceUpdateMsg resourceUpdateMsg = TransportProtos.ResourceUpdateMsg.newBuilder()
ResourceUpdateMsg resourceUpdateMsg = ResourceUpdateMsg.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setResourceType(resource.getResourceType().name())
@ -401,7 +415,7 @@ public class DefaultTbClusterService implements TbClusterService {
public void onResourceDeleted(TbResourceInfo resource, TbQueueCallback callback) {
if (resource.getResourceType() == ResourceType.LWM2M_MODEL) {
log.trace("[{}][{}][{}] Processing delete resource", resource.getTenantId(), resource.getResourceType(), resource.getResourceKey());
TransportProtos.ResourceDeleteMsg resourceDeleteMsg = TransportProtos.ResourceDeleteMsg.newBuilder()
ResourceDeleteMsg resourceDeleteMsg = ResourceDeleteMsg.newBuilder()
.setTenantIdMSB(resource.getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(resource.getTenantId().getId().getLeastSignificantBits())
.setResourceType(resource.getResourceType().name())
@ -421,7 +435,7 @@ public class DefaultTbClusterService implements TbClusterService {
private void broadcastEntityDeleteToTransport(TenantId tenantId, EntityId entityId, String name, TbQueueCallback callback) {
log.trace("[{}][{}][{}] Processing [{}] delete event", tenantId, entityId.getEntityType(), entityId.getId(), name);
TransportProtos.EntityDeleteMsg entityDeleteMsg = TransportProtos.EntityDeleteMsg.newBuilder()
EntityDeleteMsg entityDeleteMsg = EntityDeleteMsg.newBuilder()
.setEntityType(entityId.getEntityType().name())
.setEntityIdMSB(entityId.getId().getMostSignificantBits())
.setEntityIdLSB(entityId.getId().getLeastSignificantBits())
@ -453,39 +467,76 @@ public class DefaultTbClusterService implements TbClusterService {
}
@Override
public void onEdgeEventUpdate(TenantId tenantId, EdgeId edgeId) {
log.trace("[{}] Processing edge {} event update ", tenantId, edgeId);
EdgeEventUpdateMsg msg = new EdgeEventUpdateMsg(tenantId, edgeId);
ToCoreNotificationMsg toCoreMsg = ToCoreNotificationMsg.newBuilder().setEdgeEventUpdate(toProto(msg)).build();
pushEdgeSyncMsgToCore(edgeId, toCoreMsg);
public void pushMsgToEdge(TenantId tenantId, EntityId entityId, ToEdgeMsg msg, TbQueueCallback callback) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, DataConstants.EDGE_QUEUE_NAME, tenantId, entityId);
TbQueueProducer<TbProtoQueueMsg<ToEdgeMsg>> toEdgeProducer = producerProvider.getTbEdgeMsgProducer();
toEdgeProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), callback);
toEdgeMsgs.incrementAndGet();
}
@Override
public void pushEdgeSyncRequestToCore(ToEdgeSyncRequest toEdgeSyncRequest) {
log.trace("[{}] Processing edge sync request {} ", toEdgeSyncRequest.getTenantId(), toEdgeSyncRequest);
ToCoreNotificationMsg toCoreMsg = ToCoreNotificationMsg.newBuilder().setToEdgeSyncRequest(toProto(toEdgeSyncRequest)).build();
pushEdgeSyncMsgToCore(toEdgeSyncRequest.getEdgeId(), toCoreMsg);
public void onEdgeHighPriorityMsg(EdgeHighPriorityMsg msg) {
log.trace("[{}] Processing edge event for edgeId: {}", msg.getTenantId(), msg.getEdgeEvent().getEdgeId());
ToEdgeNotificationMsg toEdgeNotificationMsg = ToEdgeNotificationMsg.newBuilder().setEdgeHighPriority(toProto(msg)).build();
processEdgeNotification(msg.getEdgeEvent().getEdgeId(), toEdgeNotificationMsg);
}
@Override
public void pushEdgeSyncResponseToCore(FromEdgeSyncResponse fromEdgeSyncResponse) {
log.trace("[{}] Processing edge sync response {}", fromEdgeSyncResponse.getTenantId(), fromEdgeSyncResponse);
ToCoreNotificationMsg toCoreMsg = ToCoreNotificationMsg.newBuilder().setFromEdgeSyncResponse(toProto(fromEdgeSyncResponse)).build();
pushEdgeSyncMsgToCore(fromEdgeSyncResponse.getEdgeId(), toCoreMsg);
public void onEdgeEventUpdate(EdgeEventUpdateMsg msg) {
log.trace("[{}] Processing edge event update for edgeId: {}", msg.getTenantId(), msg.getEdgeId());
ToEdgeNotificationMsg toEdgeNotificationMsg = ToEdgeNotificationMsg.newBuilder().setEdgeEventUpdate(toProto(msg)).build();
processEdgeNotification(msg.getEdgeId(), toEdgeNotificationMsg);
}
private void pushEdgeSyncMsgToCore(EdgeId edgeId, ToCoreNotificationMsg toCoreMsg) {
TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer();
Set<String> tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE);
for (String serviceId : tbCoreServices) {
TopicPartitionInfo tpi = topicService.getNotificationsTopic(ServiceType.TB_CORE, serviceId);
toCoreNfProducer.send(tpi, new TbProtoQueueMsg<>(edgeId.getId(), toCoreMsg), null);
toCoreNfs.incrementAndGet();
@Override
public void onEdgeStateChangeEvent(ComponentLifecycleMsg msg) {
log.trace("[{}] Processing {} state change event: {}", msg.getTenantId(), EntityType.EDGE, msg.getEvent());
ComponentLifecycleMsgProto componentLifecycleMsgProto = toProto(msg);
ToEdgeNotificationMsg toEdgeNotificationMsg = ToEdgeNotificationMsg.newBuilder().setComponentLifecycle(componentLifecycleMsgProto).build();
processEdgeNotification((EdgeId) msg.getEntityId(), toEdgeNotificationMsg);
}
@Override
public void pushEdgeSyncRequestToEdge(ToEdgeSyncRequest request) {
log.trace("[{}] Processing edge sync request for edgeId: {}", request.getTenantId(), request.getEdgeId());
ToEdgeNotificationMsg toEdgeNotificationMsg = ToEdgeNotificationMsg.newBuilder().setToEdgeSyncRequest(toProto(request)).build();
processEdgeNotification(request.getEdgeId(), toEdgeNotificationMsg);
}
@Override
public void pushEdgeSyncResponseToCore(FromEdgeSyncResponse response, String requestServiceId) {
log.trace("[{}] Processing edge sync response for edgeId: {}", response.getTenantId(), response.getEdgeId());
ToEdgeNotificationMsg toEdgeNotificationMsg = ToEdgeNotificationMsg.newBuilder().setFromEdgeSyncResponse(toProto(response)).build();
pushMsgToEdgeNotification(toEdgeNotificationMsg, requestServiceId);
}
private void processEdgeNotification(EdgeId edgeId, ToEdgeNotificationMsg toEdgeNotificationMsg) {
var serviceIdOpt = Optional.ofNullable(edgeIdServiceIdCache.get(edgeId));
serviceIdOpt.ifPresentOrElse(
serviceId -> pushMsgToEdgeNotification(toEdgeNotificationMsg, serviceId.get()),
() -> broadcastEdgeNotification(edgeId, toEdgeNotificationMsg)
);
}
private void pushMsgToEdgeNotification(ToEdgeNotificationMsg toEdgeNotificationMsg, String serviceId) {
TopicPartitionInfo tpi = topicService.getEdgeNotificationsTopic(serviceId);
TbQueueProducer<TbProtoQueueMsg<ToEdgeNotificationMsg>> toEdgeNotificationProducer = producerProvider.getTbEdgeNotificationsMsgProducer();
toEdgeNotificationProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), toEdgeNotificationMsg), null);
toEdgeNfs.incrementAndGet();
}
private void broadcastEdgeNotification(EdgeId edgeId, ToEdgeNotificationMsg toEdgeNotificationMsg) {
TbQueueProducer<TbProtoQueueMsg<ToEdgeNotificationMsg>> toEdgeNotificationProducer = producerProvider.getTbEdgeNotificationsMsgProducer();
Set<String> serviceIds = partitionService.getAllServiceIds(ServiceType.TB_CORE);
for (String serviceId : serviceIds) {
TopicPartitionInfo tpi = topicService.getEdgeNotificationsTopic(serviceId);
toEdgeNotificationProducer.send(tpi, new TbProtoQueueMsg<>(edgeId.getId(), toEdgeNotificationMsg), null);
toEdgeNfs.incrementAndGet();
}
}
private void broadcast(ComponentLifecycleMsg msg) {
TransportProtos.ComponentLifecycleMsgProto componentLifecycleMsgProto = toProto(msg);
ComponentLifecycleMsgProto componentLifecycleMsgProto = toProto(msg);
TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer();
Set<String> tbRuleEngineServices = partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE);
EntityType entityType = msg.getEntityId().getEntityType();
@ -497,7 +548,6 @@ public class DefaultTbClusterService implements TbClusterService {
|| entityType.equals(EntityType.API_USAGE_STATE)
|| (entityType.equals(EntityType.DEVICE) && msg.getEvent() == ComponentLifecycleEvent.UPDATED)
|| entityType.equals(EntityType.ENTITY_VIEW)
|| entityType.equals(EntityType.EDGE)
|| entityType.equals(EntityType.NOTIFICATION_RULE)) {
TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer();
Set<String> tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE);
@ -526,15 +576,17 @@ public class DefaultTbClusterService implements TbClusterService {
int toRuleEngineMsgsCnt = toRuleEngineMsgs.getAndSet(0);
int toRuleEngineNfsCnt = toRuleEngineNfs.getAndSet(0);
int toTransportNfsCnt = toTransportNfs.getAndSet(0);
if (toCoreMsgCnt > 0 || toCoreNfsCnt > 0 || toRuleEngineMsgsCnt > 0 || toRuleEngineNfsCnt > 0 || toTransportNfsCnt > 0) {
log.info("To TbCore: [{}] messages [{}] notifications; To TbRuleEngine: [{}] messages [{}] notifications; To Transport: [{}] notifications",
toCoreMsgCnt, toCoreNfsCnt, toRuleEngineMsgsCnt, toRuleEngineNfsCnt, toTransportNfsCnt);
int toEdgeMsgCnt = toEdgeMsgs.getAndSet(0);
int toEdgeNfsCnt = toEdgeNfs.getAndSet(0);
if (toCoreMsgCnt > 0 || toCoreNfsCnt > 0 || toRuleEngineMsgsCnt > 0 || toRuleEngineNfsCnt > 0 || toTransportNfsCnt > 0 || toEdgeMsgCnt > 0 || toEdgeNfsCnt > 0) {
log.info("To TbCore: [{}] messages [{}] notifications; To TbRuleEngine: [{}] messages [{}] notifications; To Transport: [{}] notifications;" +
"To Edge: [{}] messages [{}] notifications", toCoreMsgCnt, toCoreNfsCnt, toRuleEngineMsgsCnt, toRuleEngineNfsCnt, toTransportNfsCnt, toEdgeMsgCnt, toEdgeNfsCnt);
}
}
}
private void sendDeviceStateServiceEvent(TenantId tenantId, DeviceId deviceId, boolean added, boolean updated, boolean deleted) {
TransportProtos.DeviceStateServiceMsgProto.Builder builder = TransportProtos.DeviceStateServiceMsgProto.newBuilder();
DeviceStateServiceMsgProto.Builder builder = DeviceStateServiceMsgProto.newBuilder();
builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
builder.setDeviceIdMSB(deviceId.getId().getMostSignificantBits());
@ -542,8 +594,8 @@ public class DefaultTbClusterService implements TbClusterService {
builder.setAdded(added);
builder.setUpdated(updated);
builder.setDeleted(deleted);
TransportProtos.DeviceStateServiceMsgProto msg = builder.build();
pushMsgToCore(tenantId, deviceId, TransportProtos.ToCoreMsg.newBuilder().setDeviceStateServiceMsg(msg).build(), null);
DeviceStateServiceMsgProto msg = builder.build();
pushMsgToCore(tenantId, deviceId, ToCoreMsg.newBuilder().setDeviceStateServiceMsg(msg).build(), null);
}
@Override
@ -581,7 +633,7 @@ public class DefaultTbClusterService implements TbClusterService {
return;
}
}
TransportProtos.EdgeNotificationMsgProto.Builder builder = TransportProtos.EdgeNotificationMsgProto.newBuilder();
EdgeNotificationMsgProto.Builder builder = EdgeNotificationMsgProto.newBuilder();
builder.setTenantIdMSB(tenantId.getId().getMostSignificantBits());
builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits());
builder.setType(type.name());
@ -602,9 +654,9 @@ public class DefaultTbClusterService implements TbClusterService {
builder.setOriginatorEdgeIdMSB(originatorEdgeId.getId().getMostSignificantBits());
builder.setOriginatorEdgeIdLSB(originatorEdgeId.getId().getLeastSignificantBits());
}
TransportProtos.EdgeNotificationMsgProto msg = builder.build();
EdgeNotificationMsgProto msg = builder.build();
log.trace("[{}] sending notification to edge service {}", tenantId.getId(), msg);
pushMsgToCore(tenantId, entityId != null ? entityId : tenantId, TransportProtos.ToCoreMsg.newBuilder().setEdgeNotificationMsg(msg).build(), null);
pushMsgToEdge(tenantId, entityId != null ? entityId : tenantId, ToEdgeMsg.newBuilder().setEdgeNotificationMsg(msg).build(), null);
if (entityId != null && EntityType.DEVICE.equals(entityId.getEntityType())) {
pushDeviceUpdateMessage(tenantId, edgeId, entityId, action);
@ -690,4 +742,5 @@ public class DefaultTbClusterService implements TbClusterService {
toTransportNfs.incrementAndGet();
}
}
}

37
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -59,7 +59,6 @@ import org.thingsboard.server.dao.resource.ImageCacheKey;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.EdgeNotificationMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.ErrorEventProto;
import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto;
import org.thingsboard.server.gen.transport.TransportProtos.LifecycleEventProto;
@ -86,7 +85,6 @@ import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.notification.NotificationSchedulerService;
import org.thingsboard.server.service.ota.OtaPackageStateService;
import org.thingsboard.server.service.profile.TbAssetProfileCache;
@ -142,7 +140,6 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
private final TbLocalSubscriptionService localSubscriptionService;
private final SubscriptionManagerService subscriptionManagerService;
private final TbCoreDeviceRpcService tbCoreDeviceRpcService;
private final EdgeNotificationService edgeNotificationService;
private final OtaPackageStateService firmwareStateService;
private final GitVersionControlQueueService vcQueueService;
private final NotificationSchedulerService notificationSchedulerService;
@ -170,7 +167,6 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
TbApiUsageStateService statsService,
TbTenantProfileCache tenantProfileCache,
TbApiUsageStateService apiUsageStateService,
EdgeNotificationService edgeNotificationService,
OtaPackageStateService firmwareStateService,
GitVersionControlQueueService vcQueueService,
PartitionService partitionService,
@ -186,7 +182,6 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
this.localSubscriptionService = localSubscriptionService;
this.subscriptionManagerService = subscriptionManagerService;
this.tbCoreDeviceRpcService = tbCoreDeviceRpcService;
this.edgeNotificationService = edgeNotificationService;
this.stats = new TbCoreConsumerStats(statsFactory);
this.statsService = statsService;
this.firmwareStateService = firmwareStateService;
@ -248,16 +243,16 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
@Override
protected void onTbApplicationEvent(PartitionChangeEvent event) {
log.info("Subscribing to partitions: {}", event.getPartitions());
mainConsumer.update(event.getPartitions());
usageStatsConsumer.subscribe(event.getPartitions()
log.debug("Subscribing to partitions: {}", event.getCorePartitions());
mainConsumer.update(event.getCorePartitions());
usageStatsConsumer.subscribe(event.getCorePartitions()
.stream()
.map(tpi -> tpi.newByTopic(usageStatsConsumer.getConsumer().getTopic()))
.collect(Collectors.toSet()));
}
private void processMsgs(List<TbProtoQueueMsg<ToCoreMsg>> msgs, TbQueueConsumer<TbProtoQueueMsg<ToCoreMsg>> consumer, CoreQueueConfig config) throws Exception {
List<IdMsgPair<ToCoreMsg>> orderedMsgList = msgs.stream().map(msg -> new IdMsgPair<>(UUID.randomUUID(), msg)).collect(Collectors.toList());
List<IdMsgPair<ToCoreMsg>> orderedMsgList = msgs.stream().map(msg -> new IdMsgPair<>(UUID.randomUUID(), msg)).toList();
ConcurrentMap<UUID, TbProtoQueueMsg<ToCoreMsg>> pendingMap = orderedMsgList.stream().collect(
Collectors.toConcurrentMap(IdMsgPair::getUuid, IdMsgPair::getMsg));
CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
@ -282,9 +277,6 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
} else if (toCoreMsg.hasDeviceStateServiceMsg()) {
log.trace("[{}] Forwarding message to device state service {}", id, toCoreMsg.getDeviceStateServiceMsg());
forwardToStateService(toCoreMsg.getDeviceStateServiceMsg(), callback);
} else if (toCoreMsg.hasEdgeNotificationMsg()) {
log.trace("[{}] Forwarding message to edge service {}", id, toCoreMsg.getEdgeNotificationMsg());
forwardToEdgeNotificationService(toCoreMsg.getEdgeNotificationMsg(), callback);
} else if (toCoreMsg.hasDeviceConnectMsg()) {
log.trace("[{}] Forwarding message to device state service {}", id, toCoreMsg.getDeviceConnectMsg());
forwardToStateService(toCoreMsg.getDeviceConnectMsg(), callback);
@ -386,15 +378,6 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
} else if (toCoreNotification.hasComponentLifecycle()) {
handleComponentLifecycleMsg(id, ProtoUtils.fromProto(toCoreNotification.getComponentLifecycle()));
callback.onSuccess();
} else if (toCoreNotification.hasEdgeEventUpdate()) {
forwardToAppActor(id, ProtoUtils.fromProto(toCoreNotification.getEdgeEventUpdate()));
callback.onSuccess();
} else if (toCoreNotification.hasToEdgeSyncRequest()) {
forwardToAppActor(id, ProtoUtils.fromProto(toCoreNotification.getToEdgeSyncRequest()));
callback.onSuccess();
} else if (toCoreNotification.hasFromEdgeSyncResponse()) {
forwardToAppActor(id, ProtoUtils.fromProto(toCoreNotification.getFromEdgeSyncResponse()));
callback.onSuccess();
} else if (toCoreNotification.getQueueUpdateMsgsCount() > 0) {
partitionService.updateQueues(toCoreNotification.getQueueUpdateMsgsList());
callback.onSuccess();
@ -686,13 +669,6 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
}
}
private void forwardToEdgeNotificationService(EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) {
if (statsEnabled) {
stats.log(edgeNotificationMsg);
}
edgeNotificationService.pushNotificationToEdge(edgeNotificationMsg, callback);
}
private void forwardToDeviceActor(TransportToDeviceActorMsg toDeviceActorMsg, TbCallback callback) {
if (statsEnabled) {
stats.log(toDeviceActorMsg);
@ -700,11 +676,6 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
actorContext.tell(new TransportToDeviceActorMsgWrapper(toDeviceActorMsg, callback));
}
private void forwardToAppActor(UUID id, TbActorMsg actorMsg) {
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg);
actorContext.tell(actorMsg);
}
private void forwardToEventService(ErrorEventProto eventProto, TbCallback callback) {
Event event = ErrorEvent.builder()
.tenantId(toTenantId(eventProto.getTenantIdMSB(), eventProto.getTenantIdLSB()))

332
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbEdgeConsumerService.java

@ -0,0 +1,332 @@
/**
* Copyright © 2016-2024 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.queue;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.queue.QueueConfig;
import org.thingsboard.server.common.msg.edge.EdgeSessionMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.util.ProtoUtils;
import org.thingsboard.server.gen.transport.TransportProtos.EdgeNotificationMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.QueueKey;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.provider.TbCoreQueueFactory;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.edge.EdgeContextComponent;
import org.thingsboard.server.service.queue.consumer.MainQueueConsumerManager;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
import org.thingsboard.server.service.queue.processing.IdMsgPair;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
@Service
@TbCoreComponent
public class DefaultTbEdgeConsumerService extends AbstractConsumerService<ToEdgeNotificationMsg> implements TbEdgeConsumerService {
@Value("${queue.edge.pool-interval:25}")
private int pollInterval;
@Value("${queue.edge.pack-processing-timeout:10000}")
private int packProcessingTimeout;
@Value("${queue.edge.consumer-per-partition:false}")
private boolean consumerPerPartition;
@Value("${queue.edge.pack-processing-retries:3}")
private int packProcessingRetries;
@Value("${queue.edge.stats.enabled:false}")
private boolean statsEnabled;
private final TbCoreQueueFactory queueFactory;
private final EdgeContextComponent edgeCtx;
private final EdgeConsumerStats stats;
private MainQueueConsumerManager<TbProtoQueueMsg<ToEdgeMsg>, EdgeQueueConfig> mainConsumer;
public DefaultTbEdgeConsumerService(TbCoreQueueFactory tbCoreQueueFactory, ActorSystemContext actorContext,
StatsFactory statsFactory, EdgeContextComponent edgeCtx) {
super(actorContext, null, null, null, null, null,
null, null);
this.edgeCtx = edgeCtx;
this.stats = new EdgeConsumerStats(statsFactory);
this.queueFactory = tbCoreQueueFactory;
}
@PostConstruct
public void init() {
super.init("tb-edge");
this.mainConsumer = MainQueueConsumerManager.<TbProtoQueueMsg<ToEdgeMsg>, EdgeQueueConfig>builder()
.queueKey(new QueueKey(ServiceType.TB_CORE).withQueueName(DataConstants.EDGE_QUEUE_NAME))
.config(EdgeQueueConfig.of(consumerPerPartition, (int) pollInterval))
.msgPackProcessor(this::processMsgs)
.consumerCreator((config, partitionId) -> queueFactory.createEdgeMsgConsumer())
.consumerExecutor(consumersExecutor)
.scheduler(scheduler)
.taskExecutor(mgmtExecutor)
.build();
}
@PreDestroy
public void destroy() {
super.destroy();
}
@Override
protected void startConsumers() {
super.startConsumers();
}
@Override
protected void onTbApplicationEvent(PartitionChangeEvent event) {
var partitions = event.getEdgePartitions();
log.debug("Subscribing to partitions: {}", partitions);
mainConsumer.update(partitions);
}
private void processMsgs(List<TbProtoQueueMsg<ToEdgeMsg>> msgs, TbQueueConsumer<TbProtoQueueMsg<ToEdgeMsg>> consumer, EdgeQueueConfig edgeQueueConfig) throws InterruptedException {
List<IdMsgPair<ToEdgeMsg>> orderedMsgList = msgs.stream().map(msg -> new IdMsgPair<>(UUID.randomUUID(), msg)).toList();
ConcurrentMap<UUID, TbProtoQueueMsg<ToEdgeMsg>> pendingMap = orderedMsgList.stream().collect(
Collectors.toConcurrentMap(IdMsgPair::getUuid, IdMsgPair::getMsg));
CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
TbPackProcessingContext<TbProtoQueueMsg<ToEdgeMsg>> ctx = new TbPackProcessingContext<>(
processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>());
PendingMsgHolder pendingMsgHolder = new PendingMsgHolder();
Future<?> submitFuture = consumersExecutor.submit(() -> {
orderedMsgList.forEach((element) -> {
UUID id = element.getUuid();
TbProtoQueueMsg<ToEdgeMsg> msg = element.getMsg();
TbCallback callback = new TbPackCallback<>(id, ctx);
try {
ToEdgeMsg toEdgeMsg = msg.getValue();
pendingMsgHolder.setToEdgeMsg(toEdgeMsg);
if (toEdgeMsg.hasEdgeNotificationMsg()) {
pushNotificationToEdge(toEdgeMsg.getEdgeNotificationMsg(), 0, packProcessingRetries, callback);
}
if (statsEnabled) {
stats.log(toEdgeMsg);
}
} catch (Throwable e) {
log.warn("[{}] Failed to process message: {}", id, msg, e);
callback.onFailure(e);
}
});
});
if (!processingTimeoutLatch.await(packProcessingTimeout, TimeUnit.MILLISECONDS)) {
if (!submitFuture.isDone()) {
submitFuture.cancel(true);
ToEdgeMsg lastSubmitMsg = pendingMsgHolder.getToEdgeMsg();
log.info("Timeout to process message: {}", lastSubmitMsg);
}
ctx.getFailedMap().forEach((id, msg) -> log.warn("[{}] Failed to process message: {}", id, msg.getValue()));
}
consumer.commit();
}
private static class PendingMsgHolder {
@Getter
@Setter
private volatile ToEdgeMsg toEdgeMsg;
}
@Override
protected ServiceType getServiceType() {
return ServiceType.TB_CORE;
}
@Override
protected long getNotificationPollDuration() {
return pollInterval;
}
@Override
protected long getNotificationPackProcessingTimeout() {
return packProcessingTimeout;
}
@Override
protected int getMgmtThreadPoolSize() {
return Math.max(Runtime.getRuntime().availableProcessors(), 4);
}
@Override
protected TbQueueConsumer<TbProtoQueueMsg<ToEdgeNotificationMsg>> createNotificationsConsumer() {
return queueFactory.createToEdgeNotificationsMsgConsumer();
}
@Override
protected void handleNotification(UUID id, TbProtoQueueMsg<ToEdgeNotificationMsg> msg, TbCallback callback) {
ToEdgeNotificationMsg toEdgeNotificationMsg = msg.getValue();
try {
if (toEdgeNotificationMsg.hasEdgeHighPriority()) {
EdgeSessionMsg edgeSessionMsg = ProtoUtils.fromProto(toEdgeNotificationMsg.getEdgeHighPriority());
edgeCtx.getEdgeRpcService().onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg);
callback.onSuccess();
} else if (toEdgeNotificationMsg.hasEdgeEventUpdate()) {
EdgeSessionMsg edgeSessionMsg = ProtoUtils.fromProto(toEdgeNotificationMsg.getEdgeEventUpdate());
edgeCtx.getEdgeRpcService().onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg);
callback.onSuccess();
} else if (toEdgeNotificationMsg.hasToEdgeSyncRequest()) {
EdgeSessionMsg edgeSessionMsg = ProtoUtils.fromProto(toEdgeNotificationMsg.getToEdgeSyncRequest());
edgeCtx.getEdgeRpcService().onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg);
callback.onSuccess();
} else if (toEdgeNotificationMsg.hasFromEdgeSyncResponse()) {
EdgeSessionMsg edgeSessionMsg = ProtoUtils.fromProto(toEdgeNotificationMsg.getFromEdgeSyncResponse());
edgeCtx.getEdgeRpcService().onToEdgeSessionMsg(edgeSessionMsg.getTenantId(), edgeSessionMsg);
callback.onSuccess();
} else if (toEdgeNotificationMsg.hasComponentLifecycle()) {
ComponentLifecycleMsg componentLifecycle = ProtoUtils.fromProto(toEdgeNotificationMsg.getComponentLifecycle());
TenantId tenantId = componentLifecycle.getTenantId();
EdgeId edgeId = new EdgeId(componentLifecycle.getEntityId().getId());
if (ComponentLifecycleEvent.DELETED.equals(componentLifecycle.getEvent())) {
edgeCtx.getEdgeRpcService().deleteEdge(tenantId, edgeId);
} else if (ComponentLifecycleEvent.UPDATED.equals(componentLifecycle.getEvent())) {
Edge edge = edgeCtx.getEdgeService().findEdgeById(tenantId, edgeId);
edgeCtx.getEdgeRpcService().updateEdge(tenantId, edge);
}
callback.onSuccess();
}
} catch (Exception e) {
log.error("Error processing edge notification message", e);
callback.onFailure(e);
}
if (statsEnabled) {
stats.log(msg.getValue());
}
}
private void pushNotificationToEdge(EdgeNotificationMsgProto edgeNotificationMsg, int retryCount, int retryLimit, TbCallback callback) {
TenantId tenantId = TenantId.fromUUID(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB()));
log.debug("[{}] Pushing notification to edge {}", tenantId, edgeNotificationMsg);
try {
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
ListenableFuture<Void> future;
switch (type) {
case EDGE -> future = edgeCtx.getEdgeProcessor().processEdgeNotification(tenantId, edgeNotificationMsg);
case ASSET -> future = edgeCtx.getAssetProcessor().processEntityNotification(tenantId, edgeNotificationMsg);
case ASSET_PROFILE -> future = edgeCtx.getAssetProfileProcessor().processEntityNotification(tenantId, edgeNotificationMsg);
case DEVICE -> future = edgeCtx.getDeviceProcessor().processEntityNotification(tenantId, edgeNotificationMsg);
case DEVICE_PROFILE -> future = edgeCtx.getDeviceProfileProcessor().processEntityNotification(tenantId, edgeNotificationMsg);
case ENTITY_VIEW -> future = edgeCtx.getEntityViewProcessor().processEntityNotification(tenantId, edgeNotificationMsg);
case DASHBOARD -> future = edgeCtx.getDashboardProcessor().processEntityNotification(tenantId, edgeNotificationMsg);
case RULE_CHAIN -> future = edgeCtx.getRuleChainProcessor().processEntityNotification(tenantId, edgeNotificationMsg);
case USER -> future = edgeCtx.getUserProcessor().processEntityNotification(tenantId, edgeNotificationMsg);
case CUSTOMER -> future = edgeCtx.getCustomerProcessor().processCustomerNotification(tenantId, edgeNotificationMsg);
case OTA_PACKAGE -> future = edgeCtx.getOtaPackageProcessor().processEntityNotification(tenantId, edgeNotificationMsg);
case WIDGETS_BUNDLE -> future = edgeCtx.getWidgetBundleProcessor().processEntityNotification(tenantId, edgeNotificationMsg);
case WIDGET_TYPE -> future = edgeCtx.getWidgetTypeProcessor().processEntityNotification(tenantId, edgeNotificationMsg);
case QUEUE -> future = edgeCtx.getQueueProcessor().processEntityNotification(tenantId, edgeNotificationMsg);
case ALARM -> future = edgeCtx.getAlarmProcessor().processAlarmNotification(tenantId, edgeNotificationMsg);
case ALARM_COMMENT -> future = edgeCtx.getAlarmProcessor().processAlarmCommentNotification(tenantId, edgeNotificationMsg);
case RELATION -> future = edgeCtx.getRelationProcessor().processRelationNotification(tenantId, edgeNotificationMsg);
case TENANT -> future = edgeCtx.getTenantProcessor().processEntityNotification(tenantId, edgeNotificationMsg);
case TENANT_PROFILE -> future = edgeCtx.getTenantProfileProcessor().processEntityNotification(tenantId, edgeNotificationMsg);
case NOTIFICATION_RULE, NOTIFICATION_TARGET, NOTIFICATION_TEMPLATE ->
future = edgeCtx.getNotificationEdgeProcessor().processEntityNotification(tenantId, edgeNotificationMsg);
case TB_RESOURCE -> future = edgeCtx.getResourceProcessor().processEntityNotification(tenantId, edgeNotificationMsg);
case DOMAIN, OAUTH2_CLIENT -> future = edgeCtx.getOAuth2EdgeProcessor().processEntityNotification(tenantId, edgeNotificationMsg);
default -> {
future = Futures.immediateFuture(null);
log.warn("[{}] Edge event type [{}] is not designed to be pushed to edge", tenantId, type);
}
}
Futures.addCallback(future, new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void unused) {
callback.onSuccess();
}
@Override
public void onFailure(@NotNull Throwable throwable) {
if (retryCount < retryLimit) {
log.warn("[{}] Retry {} for message due to failure: {}", tenantId, retryCount + 1, throwable.getMessage());
pushNotificationToEdge(edgeNotificationMsg, retryCount + 1, retryLimit, callback);
} else {
callBackFailure(tenantId, edgeNotificationMsg, callback, throwable);
}
}
}, MoreExecutors.directExecutor());
} catch (Exception e) {
if (retryCount < retryLimit) {
log.warn("[{}] Retry {} for message due to exception: {}", tenantId, retryCount + 1, e.getMessage());
pushNotificationToEdge(edgeNotificationMsg, retryCount + 1, retryLimit, callback);
} else {
callBackFailure(tenantId, edgeNotificationMsg, callback, e);
}
}
}
private void callBackFailure(TenantId tenantId, EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback, Throwable throwable) {
log.error("[{}] Can't push to edge updates, edgeNotificationMsg [{}]", tenantId, edgeNotificationMsg, throwable);
callback.onFailure(throwable);
}
@Scheduled(fixedDelayString = "${queue.edge.stats.print-interval-ms}")
public void printStats() {
if (statsEnabled) {
stats.printStats();
stats.reset();
}
}
@Override
protected void stopConsumers() {
super.stopConsumers();
mainConsumer.stop();
mainConsumer.awaitStop();
}
@Data(staticConstructor = "of")
public static class EdgeQueueConfig implements QueueConfig {
private final boolean consumerPerPartition;
private final int pollInterval;
}
}

2
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java

@ -165,7 +165,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
}
@Override
protected void handleNotification(UUID id, TbProtoQueueMsg<ToRuleEngineNotificationMsg> msg, TbCallback callback) throws Exception {
protected void handleNotification(UUID id, TbProtoQueueMsg<ToRuleEngineNotificationMsg> msg, TbCallback callback) {
ToRuleEngineNotificationMsg nfMsg = msg.getValue();
if (nfMsg.hasComponentLifecycle()) {
handleComponentLifecycleMsg(id, ProtoUtils.fromProto(nfMsg.getComponentLifecycle()));

99
application/src/main/java/org/thingsboard/server/service/queue/EdgeConsumerStats.java

@ -0,0 +1,99 @@
/**
* Copyright © 2016-2024 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.queue;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.stats.StatsCounter;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsType;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeNotificationMsg;
import java.util.ArrayList;
import java.util.List;
@Slf4j
public class EdgeConsumerStats {
public static final String TOTAL_MSGS = "totalMsgs";
public static final String EDGE_NOTIFICATIONS = "edgeNfs";
public static final String TO_CORE_NF_EDGE_EVENT = "coreNfEdgeHPUpd";
public static final String TO_CORE_NF_EDGE_EVENT_UPDATE = "coreNfEdgeUpd";
public static final String TO_CORE_NF_EDGE_SYNC_REQUEST = "coreNfEdgeSyncReq";
public static final String TO_CORE_NF_EDGE_SYNC_RESPONSE = "coreNfEdgeSyncResp";
public static final String TO_CORE_NF_EDGE_COMPONENT_LIFECYCLE = "coreNfEdgeCompLfcl";
private final StatsCounter totalCounter;
private final StatsCounter edgeNotificationsCounter;
private final StatsCounter edgeHighPriorityCounter;
private final StatsCounter edgeEventUpdateCounter;
private final StatsCounter edgeSyncRequestCounter;
private final StatsCounter edgeSyncResponseCounter;
private final StatsCounter edgeComponentLifecycle;
private final List<StatsCounter> counters = new ArrayList<>(7);
public EdgeConsumerStats(StatsFactory statsFactory) {
String statsKey = StatsType.EDGE.getName();
this.totalCounter = register(statsFactory.createStatsCounter(statsKey, TOTAL_MSGS));
this.edgeNotificationsCounter = register(statsFactory.createStatsCounter(statsKey, EDGE_NOTIFICATIONS));
this.edgeHighPriorityCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NF_EDGE_EVENT));
this.edgeEventUpdateCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NF_EDGE_EVENT_UPDATE));
this.edgeSyncRequestCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NF_EDGE_SYNC_REQUEST));
this.edgeSyncResponseCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NF_EDGE_SYNC_RESPONSE));
this.edgeComponentLifecycle = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NF_EDGE_COMPONENT_LIFECYCLE));
}
private StatsCounter register(StatsCounter counter) {
counters.add(counter);
return counter;
}
public void log(ToEdgeNotificationMsg msg) {
totalCounter.increment();
if (msg.hasEdgeHighPriority()) {
edgeHighPriorityCounter.increment();
} else if (msg.hasEdgeEventUpdate()) {
edgeEventUpdateCounter.increment();
} else if (msg.hasToEdgeSyncRequest()) {
edgeSyncRequestCounter.increment();
} else if (msg.hasFromEdgeSyncResponse()) {
edgeSyncResponseCounter.increment();
} else if (msg.hasComponentLifecycle()) {
edgeComponentLifecycle.increment();
}
}
public void log(ToEdgeMsg msg) {
totalCounter.increment();
edgeNotificationsCounter.increment();
}
public void printStats() {
int total = totalCounter.get();
if (total > 0) {
StringBuilder stats = new StringBuilder();
counters.forEach(counter -> stats.append(counter.getName()).append(" = [").append(counter.get()).append("] "));
log.info("Edge Stats: {}", stats);
}
}
public void reset() {
counters.forEach(StatsCounter::clear);
}
}

31
application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerStats.java

@ -36,7 +36,6 @@ public class TbCoreConsumerStats {
public static final String DEVICE_CLAIMS = "claimDevice";
public static final String DEVICE_STATES = "deviceState";
public static final String SUBSCRIPTION_MSGS = "subMsgs";
public static final String EDGE_NOTIFICATIONS = "edgeNfs";
public static final String DEVICE_CONNECTS = "deviceConnect";
public static final String DEVICE_ACTIVITIES = "deviceActivity";
public static final String DEVICE_DISCONNECTS = "deviceDisconnect";
@ -45,9 +44,6 @@ public class TbCoreConsumerStats {
public static final String TO_CORE_NF_OTHER = "coreNfOther"; // normally, there is no messages when codebase is fine
public static final String TO_CORE_NF_COMPONENT_LIFECYCLE = "coreNfCompLfcl";
public static final String TO_CORE_NF_DEVICE_RPC_RESPONSE = "coreNfDevRpcRsp";
public static final String TO_CORE_NF_EDGE_EVENT_UPDATE = "coreNfEdgeUpd";
public static final String TO_CORE_NF_EDGE_SYNC_REQUEST = "coreNfEdgeSyncReq";
public static final String TO_CORE_NF_EDGE_SYNC_RESPONSE = "coreNfEdgeSyncResp";
public static final String TO_CORE_NF_NOTIFICATION_RULE_PROCESSOR = "coreNfNfRlProc";
public static final String TO_CORE_NF_QUEUE_UPDATE = "coreNfQueueUpd";
public static final String TO_CORE_NF_QUEUE_DELETE = "coreNfQueueDel";
@ -65,7 +61,6 @@ public class TbCoreConsumerStats {
private final StatsCounter claimDeviceCounter;
private final StatsCounter deviceStateCounter;
private final StatsCounter subscriptionMsgCounter;
private final StatsCounter edgeNotificationsCounter;
private final StatsCounter deviceConnectsCounter;
private final StatsCounter deviceActivitiesCounter;
private final StatsCounter deviceDisconnectsCounter;
@ -74,9 +69,6 @@ public class TbCoreConsumerStats {
private final StatsCounter toCoreNfOtherCounter;
private final StatsCounter toCoreNfComponentLifecycleCounter;
private final StatsCounter toCoreNfDeviceRpcResponseCounter;
private final StatsCounter toCoreNfEdgeEventUpdateCounter;
private final StatsCounter toCoreNfEdgeSyncRequestCounter;
private final StatsCounter toCoreNfEdgeSyncResponseCounter;
private final StatsCounter toCoreNfNotificationRuleProcessorCounter;
private final StatsCounter toCoreNfQueueUpdateCounter;
private final StatsCounter toCoreNfQueueDeleteCounter;
@ -84,7 +76,7 @@ public class TbCoreConsumerStats {
private final StatsCounter toCoreNfSubscriptionManagerCounter;
private final StatsCounter toCoreNfVersionControlResponseCounter;
private final List<StatsCounter> counters = new ArrayList<>(24);
private final List<StatsCounter> counters = new ArrayList<>(23);
public TbCoreConsumerStats(StatsFactory statsFactory) {
String statsKey = StatsType.CORE.getName();
@ -99,7 +91,6 @@ public class TbCoreConsumerStats {
this.claimDeviceCounter = register(statsFactory.createStatsCounter(statsKey, DEVICE_CLAIMS));
this.deviceStateCounter = register(statsFactory.createStatsCounter(statsKey, DEVICE_STATES));
this.subscriptionMsgCounter = register(statsFactory.createStatsCounter(statsKey, SUBSCRIPTION_MSGS));
this.edgeNotificationsCounter = register(statsFactory.createStatsCounter(statsKey, EDGE_NOTIFICATIONS));
this.deviceConnectsCounter = register(statsFactory.createStatsCounter(statsKey, DEVICE_CONNECTS));
this.deviceActivitiesCounter = register(statsFactory.createStatsCounter(statsKey, DEVICE_ACTIVITIES));
this.deviceDisconnectsCounter = register(statsFactory.createStatsCounter(statsKey, DEVICE_DISCONNECTS));
@ -109,9 +100,6 @@ public class TbCoreConsumerStats {
this.toCoreNfOtherCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NF_OTHER));
this.toCoreNfComponentLifecycleCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NF_COMPONENT_LIFECYCLE));
this.toCoreNfDeviceRpcResponseCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NF_DEVICE_RPC_RESPONSE));
this.toCoreNfEdgeEventUpdateCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NF_EDGE_EVENT_UPDATE));
this.toCoreNfEdgeSyncRequestCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NF_EDGE_SYNC_REQUEST));
this.toCoreNfEdgeSyncResponseCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NF_EDGE_SYNC_RESPONSE));
this.toCoreNfNotificationRuleProcessorCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NF_NOTIFICATION_RULE_PROCESSOR));
this.toCoreNfQueueUpdateCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NF_QUEUE_UPDATE));
this.toCoreNfQueueDeleteCounter = register(statsFactory.createStatsCounter(statsKey, TO_CORE_NF_QUEUE_DELETE));
@ -155,11 +143,6 @@ public class TbCoreConsumerStats {
deviceStateCounter.increment();
}
public void log(TransportProtos.EdgeNotificationMsgProto msg) {
totalCounter.increment();
edgeNotificationsCounter.increment();
}
public void log(TransportProtos.DeviceConnectProto msg) {
totalCounter.increment();
deviceConnectsCounter.increment();
@ -193,12 +176,6 @@ public class TbCoreConsumerStats {
toCoreNfDeviceRpcResponseCounter.increment();
} else if (msg.hasComponentLifecycle()) {
toCoreNfComponentLifecycleCounter.increment();
} else if (msg.hasEdgeEventUpdate()) {
toCoreNfEdgeEventUpdateCounter.increment();
} else if (msg.hasToEdgeSyncRequest()) {
toCoreNfEdgeSyncRequestCounter.increment();
} else if (msg.hasFromEdgeSyncResponse()) {
toCoreNfEdgeSyncResponseCounter.increment();
} else if (msg.getQueueUpdateMsgsCount() > 0) {
toCoreNfQueueUpdateCounter.increment();
} else if (msg.getQueueDeleteMsgsCount() > 0) {
@ -218,9 +195,8 @@ public class TbCoreConsumerStats {
int total = totalCounter.get();
if (total > 0) {
StringBuilder stats = new StringBuilder();
counters.forEach(counter -> {
stats.append(counter.getName()).append(" = [").append(counter.get()).append("] ");
});
counters.forEach(counter ->
stats.append(counter.getName()).append(" = [").append(counter.get()).append("] "));
log.info("Core Stats: {}", stats);
}
}
@ -228,4 +204,5 @@ public class TbCoreConsumerStats {
public void reset() {
counters.forEach(StatsCounter::clear);
}
}

19
application/src/main/java/org/thingsboard/server/service/queue/TbEdgeConsumerService.java

@ -0,0 +1,19 @@
/**
* Copyright © 2016-2024 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.queue;
public interface TbEdgeConsumerService {
}

3
application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java

@ -124,7 +124,7 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
protected abstract TbQueueConsumer<TbProtoQueueMsg<N>> createNotificationsConsumer();
protected void processNotifications(List<TbProtoQueueMsg<N>> msgs, TbQueueConsumer<TbProtoQueueMsg<N>> consumer) throws Exception {
List<IdMsgPair<N>> orderedMsgList = msgs.stream().map(msg -> new IdMsgPair<>(UUID.randomUUID(), msg)).collect(Collectors.toList());
List<IdMsgPair<N>> orderedMsgList = msgs.stream().map(msg -> new IdMsgPair<>(UUID.randomUUID(), msg)).toList();
ConcurrentMap<UUID, TbProtoQueueMsg<N>> pendingMap = orderedMsgList.stream().collect(
Collectors.toConcurrentMap(IdMsgPair::getUuid, IdMsgPair::getMsg));
CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
@ -211,4 +211,5 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
scheduler.shutdownNow();
}
}
}

63
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java

@ -157,7 +157,8 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
private final DbTypeInfoComponent dbTypeInfoComponent;
private final TbApiUsageReportClient apiUsageReportClient;
private final NotificationRuleProcessor notificationRuleProcessor;
@Autowired @Lazy
@Autowired
@Lazy
private TelemetrySubscriptionService tsSubService;
@Value("${state.defaultInactivityTimeoutInSec}")
@ -362,14 +363,16 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
if (proto.getAdded()) {
Futures.addCallback(fetchDeviceState(device), new FutureCallback<>() {
@Override
public void onSuccess(@Nullable DeviceStateData state) {
public void onSuccess(DeviceStateData state) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, device.getId());
if (addDeviceUsingState(tpi, state)) {
save(deviceId, ACTIVITY_STATE, false);
Set<DeviceId> deviceIds = partitionedEntities.get(tpi);
boolean isMyPartition = deviceIds != null;
if (isMyPartition) {
deviceIds.add(state.getDeviceId());
initializeActivityState(deviceId, state);
callback.onSuccess();
} else {
log.debug("[{}][{}] Device belongs to external partition. Probably rebalancing is in progress. Topic: {}"
, tenantId, deviceId, tpi.getFullTopicName());
log.debug("[{}][{}] Device belongs to external partition. Probably rebalancing is in progress. Topic: {}", tenantId, deviceId, tpi.getFullTopicName());
callback.onFailure(new RuntimeException("Device belongs to external partition " + tpi.getFullTopicName() + "!"));
}
}
@ -400,6 +403,21 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
}
}
private void onDeviceDeleted(TenantId tenantId, DeviceId deviceId) {
cleanupEntity(deviceId);
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId);
Set<DeviceId> deviceIdSet = partitionedEntities.get(tpi);
if (deviceIdSet != null) {
deviceIdSet.remove(deviceId);
}
}
private void initializeActivityState(DeviceId deviceId, DeviceStateData fetchedState) {
DeviceStateData cachedState = deviceStates.putIfAbsent(fetchedState.getDeviceId(), fetchedState);
boolean activityState = Objects.requireNonNullElse(cachedState, fetchedState).getState().isActive();
save(deviceId, ACTIVITY_STATE, activityState);
}
@Override
protected Map<TopicPartitionInfo, List<ListenableFuture<?>>> onAddedPartitions(Set<TopicPartitionInfo> addedPartitions) {
var result = new HashMap<TopicPartitionInfo, List<ListenableFuture<?>>>();
@ -436,10 +454,16 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
}
if (devicePackFutureHolder.future == null || !devicePackFutureHolder.future.isCancelled()) {
for (var state : states) {
if (!addDeviceUsingState(entry.getKey(), state)) {
return;
TopicPartitionInfo tpi = entry.getKey();
Set<DeviceId> deviceIds = partitionedEntities.get(tpi);
boolean isMyPartition = deviceIds != null;
if (isMyPartition) {
deviceIds.add(state.getDeviceId());
deviceStates.putIfAbsent(state.getDeviceId(), state);
checkAndUpdateState(state.getDeviceId(), state);
} else {
log.debug("[{}] Device belongs to external partition {}", state.getDeviceId(), tpi.getFullTopicName());
}
checkAndUpdateState(state.getDeviceId(), state);
}
log.info("[{}] Initialized {} out of {} device states", entry.getKey().getPartition().orElse(0), counter.addAndGet(states.size()), entry.getValue().size());
}
@ -475,18 +499,6 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
}
}
private boolean addDeviceUsingState(TopicPartitionInfo tpi, DeviceStateData state) {
Set<DeviceId> deviceIds = partitionedEntities.get(tpi);
if (deviceIds != null) {
deviceIds.add(state.getDeviceId());
deviceStates.putIfAbsent(state.getDeviceId(), state);
return true;
} else {
log.debug("[{}] Device belongs to external partition {}", state.getDeviceId(), tpi.getFullTopicName());
return false;
}
}
void checkStates() {
try {
final long ts = getCurrentTimeMillis();
@ -619,15 +631,6 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService<Dev
return cleanup;
}
private void onDeviceDeleted(TenantId tenantId, DeviceId deviceId) {
cleanupEntity(deviceId);
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId);
Set<DeviceId> deviceIdSet = partitionedEntities.get(tpi);
if (deviceIdSet != null) {
deviceIdSet.remove(deviceId);
}
}
@Override
protected void cleanupEntityOnPartitionRemoval(DeviceId deviceId) {
cleanupEntity(deviceId);

8
application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java

@ -117,7 +117,7 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
}
callback.onSuccess();
if (event.hasTsOrAttrSub()) {
sendSubEventCallback(serviceId, entityId, event.getSeqNumber());
sendSubEventCallback(tenantId, serviceId, entityId, event.getSeqNumber());
}
} else {
log.warn("[{}][{}][{}] Event belongs to external partition. Probably re-balancing is in progress. Topic: {}"
@ -141,12 +141,12 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene
}
}
private void sendSubEventCallback(String targetId, EntityId entityId, int seqNumber) {
private void sendSubEventCallback(TenantId tenantId, String targetId, EntityId entityId, int seqNumber) {
var update = getEntityUpdatesInfo(entityId);
if (serviceId.equals(targetId)) {
localSubscriptionService.onSubEventCallback(entityId, seqNumber, update, TbCallback.EMPTY);
localSubscriptionService.onSubEventCallback(tenantId, entityId, seqNumber, update, TbCallback.EMPTY);
} else {
sendCoreNotification(targetId, entityId, TbSubscriptionUtils.toProto(entityId.getId(), seqNumber, update));
sendCoreNotification(targetId, entityId, TbSubscriptionUtils.toProto(tenantId, entityId.getId(), seqNumber, update));
}
}

65
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java

@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.thingsboard.common.util.DeduplicationUtil;
import org.thingsboard.common.util.DonAsynchron;
import org.thingsboard.common.util.ThingsBoardExecutors;
@ -60,6 +61,7 @@ import org.thingsboard.server.service.ws.telemetry.sub.AlarmSubscriptionUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@ -121,12 +123,12 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
private String serviceId;
private ExecutorService subscriptionUpdateExecutor;
private final Lock subsLock = new ReentrantLock();
private final ConcurrentReferenceHashMap<TenantId, Lock> locks = new ConcurrentReferenceHashMap<>(16, ConcurrentReferenceHashMap.ReferenceType.SOFT);
@PostConstruct
public void initExecutor() {
subscriptionUpdateExecutor = ThingsBoardExecutors.newWorkStealingPool(20, getClass());
tsCallBackExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("ts-sub-callback"));
tsCallBackExecutor = Executors.newFixedThreadPool(8, ThingsBoardThreadFactory.forName("ts-sub-callback")); //since we are using locks by TenantId
serviceId = serviceInfoProvider.getServiceId();
staleSessionCleanupExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("stale-session-cleanup"));
staleSessionCleanupExecutor.scheduleWithFixedDelay(this::cleanupStaleSessions, 60, 60, TimeUnit.SECONDS);
@ -157,28 +159,29 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
* Even if we cache locally the list of active subscriptions by entity id, it is still time-consuming operation to get them from cache
* Since number of subscriptions is usually much less than number of devices that are pushing data.
*/
Set<UUID> staleSubs = new HashSet<>();
Map<TenantId, Set<UUID>> staleSubs = new HashMap<>();
subscriptionsByEntityId.forEach((id, sub) -> {
try {
pushSubEventToManagerService(sub.getTenantId(), sub.getEntityId(), sub.toEvent(ComponentLifecycleEvent.UPDATED));
} catch (TenantNotFoundException e) {
staleSubs.add(id);
staleSubs.computeIfAbsent(sub.getTenantId(), key -> new HashSet<>()).add(id);
log.warn("Cleaning up stale subscription {} for tenant {} due to TenantNotFoundException", id, sub.getTenantId());
} catch (Exception e) {
log.error("Failed to push subscription {} to manager service", sub, e);
}
});
if (!staleSubs.isEmpty()) {
staleSubs.forEach((tenantId, subs) -> {
var subsLock = getSubsLock(tenantId);
subsLock.lock();
try {
staleSubs.forEach(entityId -> {
subs.forEach(entityId -> {
subscriptionsByEntityId.remove(entityId);
entityUpdates.remove(entityId);
});
} finally {
subsLock.unlock();
}
}
});
}
}
@ -198,6 +201,10 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
});
}
Lock getSubsLock(TenantId tenantId) {
return locks.computeIfAbsent(tenantId, x -> new ReentrantLock());
}
@Override
public void addSubscription(TbSubscription<?> subscription, WebSocketSessionRef sessionRef) {
TenantId tenantId = subscription.getTenantId();
@ -213,6 +220,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
log.debug("[{}][{}] Register subscription: {}", tenantId, entityId, subscription);
SubscriptionModificationResult result;
final Lock subsLock = getSubsLock(tenantId);
subsLock.lock();
try {
Map<Integer, TbSubscription<?>> sessionSubscriptions = subscriptionsBySessionId.computeIfAbsent(subscription.getSessionId(), k -> new ConcurrentHashMap<>());
@ -228,19 +236,26 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
@Override
public void onSubEventCallback(TransportProtos.TbEntitySubEventCallbackProto subEventCallback, TbCallback callback) {
TenantId tenantId;
if (subEventCallback.getTenantIdMSB() == 0 && subEventCallback.getTenantIdLSB() == 0) {
tenantId = TenantId.SYS_TENANT_ID; //TODO: remove after release
} else {
tenantId = TenantId.fromUUID(new UUID(subEventCallback.getTenantIdMSB(), subEventCallback.getTenantIdLSB()));
}
UUID entityId = new UUID(subEventCallback.getEntityIdMSB(), subEventCallback.getEntityIdLSB());
onSubEventCallback(entityId, subEventCallback.getSeqNumber(), new TbEntityUpdatesInfo(subEventCallback.getAttributesUpdateTs(), subEventCallback.getTimeSeriesUpdateTs()), callback);
onSubEventCallback(tenantId, entityId, subEventCallback.getSeqNumber(), new TbEntityUpdatesInfo(subEventCallback.getAttributesUpdateTs(), subEventCallback.getTimeSeriesUpdateTs()), callback);
}
@Override
public void onSubEventCallback(EntityId entityId, int seqNumber, TbEntityUpdatesInfo entityUpdatesInfo, TbCallback callback) {
onSubEventCallback(entityId.getId(), seqNumber, entityUpdatesInfo, callback);
public void onSubEventCallback(TenantId tenantId, EntityId entityId, int seqNumber, TbEntityUpdatesInfo entityUpdatesInfo, TbCallback callback) {
onSubEventCallback(tenantId, entityId.getId(), seqNumber, entityUpdatesInfo, callback);
}
public void onSubEventCallback(UUID entityId, int seqNumber, TbEntityUpdatesInfo entityUpdatesInfo, TbCallback callback) {
log.debug("[{}][{}] Processing sub event callback: {}.", entityId, seqNumber, entityUpdatesInfo);
private void onSubEventCallback(TenantId tenantId, UUID entityId, int seqNumber, TbEntityUpdatesInfo entityUpdatesInfo, TbCallback callback) {
log.debug("[{}][{}][{}] Processing sub event callback: {}.", tenantId, entityId, seqNumber, entityUpdatesInfo);
entityUpdates.put(entityId, entityUpdatesInfo);
Set<TbSubscription<?>> pendingSubs = null;
Lock subsLock = getSubsLock(tenantId);
subsLock.lock();
try {
TbEntityLocalSubsInfo entitySubs = subscriptionsByEntityId.get(entityId);
@ -257,24 +272,26 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
}
@Override
public void cancelSubscription(String sessionId, int subscriptionId) {
log.debug("[{}][{}] Going to remove subscription.", sessionId, subscriptionId);
public void cancelSubscription(TenantId tenantId, String sessionId, int subscriptionId) {
log.debug("[{}][{}][{}] Going to remove subscription.", tenantId, sessionId, subscriptionId);
SubscriptionModificationResult result = null;
Lock subsLock = getSubsLock(tenantId);
subsLock.lock();
try {
Map<Integer, TbSubscription<?>> sessionSubscriptions = subscriptionsBySessionId.get(sessionId);
if (sessionSubscriptions != null) {
TbSubscription<?> subscription = sessionSubscriptions.remove(subscriptionId);
if (subscription != null) {
if (sessionSubscriptions.isEmpty()) {
subscriptionsBySessionId.remove(sessionId);
}
result = modifySubscription(subscription.getTenantId(), subscription.getEntityId(), subscription, false);
} else {
log.debug("[{}][{}] Subscription not found!", sessionId, subscriptionId);
log.debug("[{}][{}][{}] Subscription not found!", tenantId, sessionId, subscriptionId);
}
} else {
log.debug("[{}] No session subscriptions found!", sessionId);
log.debug("[{}][{}] No session subscriptions found!", tenantId, sessionId);
}
} finally {
subsLock.unlock();
@ -285,18 +302,19 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
}
@Override
public void cancelAllSessionSubscriptions(String sessionId) {
log.debug("[{}] Going to remove session subscriptions.", sessionId);
public void cancelAllSessionSubscriptions(TenantId tenantId, String sessionId) {
log.debug("[{}][{}] Going to remove session subscriptions.", tenantId, sessionId);
List<SubscriptionModificationResult> results = new ArrayList<>();
Lock subsLock = getSubsLock(tenantId);
subsLock.lock();
try {
Map<Integer, TbSubscription<?>> sessionSubscriptions = subscriptionsBySessionId.remove(sessionId);
if (sessionSubscriptions != null) {
for (TbSubscription<?> subscription : sessionSubscriptions.values()) {
results.add(modifySubscription(subscription.getTenantId(), subscription.getEntityId(), subscription, false));
results.add(modifySubscription(tenantId, subscription.getEntityId(), subscription, false));
}
} else {
log.debug("[{}] No session subscriptions found!", sessionId);
log.debug("[{}][{}] No session subscriptions found!", tenantId, sessionId);
}
} finally {
subsLock.unlock();
@ -604,7 +622,12 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
}
private void cleanupStaleSessions() {
subscriptionsBySessionId.keySet().forEach(webSocketService::cleanupIfStale);
subscriptionsBySessionId.forEach((sessionId, subscriptions) ->
subscriptions.values()
.stream()
.findAny()
.ifPresent(subscription -> webSocketService.cleanupIfStale(subscription.getTenantId(), sessionId))
);
}
private void handleRateLimitError(TbSubscription<?> subscription, WebSocketSessionRef sessionRef, String message) {

2
application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java

@ -113,7 +113,7 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
public void clearEntitySubscriptions() {
if (subToEntityIdMap != null) {
for (Integer subId : subToEntityIdMap.keySet()) {
localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId);
localSubscriptionService.cancelSubscription(getTenantId(), getSessionId(), subId);
}
subToEntityIdMap.clear();
}

2
application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractSubCtx.java

@ -303,7 +303,7 @@ public abstract class TbAbstractSubCtx<T extends EntityCountQuery> {
protected void clearDynamicValueSubscriptions() {
if (subToDynamicValueKeySet != null) {
for (Integer subId : subToDynamicValueKeySet) {
localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), subId);
localSubscriptionService.cancelSubscription(getTenantId(), sessionRef.getSessionId(), subId);
}
subToDynamicValueKeySet.clear();
}

2
application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java

@ -341,7 +341,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
long startTs = System.currentTimeMillis() - query.getPageLink().getTimeWindow();
newSubsList.forEach(entity -> createAlarmSubscriptionForEntity(query.getPageLink(), startTs, entity));
}
subIdsToCancel.forEach(subId -> localSubscriptionService.cancelSubscription(getSessionId(), subId));
subIdsToCancel.forEach(subId -> localSubscriptionService.cancelSubscription(getTenantId(), getSessionId(), subId));
subsToAdd.forEach(subscription -> localSubscriptionService.addSubscription(subscription, sessionRef));
}

2
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java

@ -225,7 +225,7 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
}
}
}
subIdsToCancel.forEach(subId -> localSubscriptionService.cancelSubscription(getSessionId(), subId));
subIdsToCancel.forEach(subId -> localSubscriptionService.cancelSubscription(getTenantId(), getSessionId(), subId));
subsToAdd.forEach(subscription -> localSubscriptionService.addSubscription(subscription, sessionRef));
sendWsMsg(new EntityDataUpdate(cmdId, data, null, maxEntitiesPerDataSubscription));
}

4
application/src/main/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfo.java

@ -27,8 +27,6 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Information about the local websocket subscriptions.
@ -42,8 +40,6 @@ public class TbEntityLocalSubsInfo {
@Getter
private final EntityId entityId;
@Getter
private final Lock lock = new ReentrantLock();
@Getter
private final Set<TbSubscription<?>> subs = ConcurrentHashMap.newKeySet();
private volatile TbSubscriptionsInfo state = new TbSubscriptionsInfo();

6
application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java

@ -34,11 +34,11 @@ public interface TbLocalSubscriptionService {
void onSubEventCallback(TransportProtos.TbEntitySubEventCallbackProto subEventCallback, TbCallback callback);
void onSubEventCallback(EntityId entityId, int seqNumber, TbEntityUpdatesInfo entityUpdatesInfo, TbCallback empty);
void onSubEventCallback(TenantId tenantId, EntityId entityId, int seqNumber, TbEntityUpdatesInfo entityUpdatesInfo, TbCallback empty);
void cancelSubscription(String sessionId, int subscriptionId);
void cancelSubscription(TenantId tenantId, String sessionId, int subscriptionId);
void cancelAllSessionSubscriptions(String sessionId);
void cancelAllSessionSubscriptions(TenantId tenantId, String sessionId);
void onTimeSeriesUpdate(TransportProtos.TbSubUpdateProto tsUpdate, TbCallback callback);

4
application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java

@ -82,10 +82,12 @@ public class TbSubscriptionUtils {
return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder).build();
}
public static ToCoreNotificationMsg toProto(UUID id, int seqNumber, TbEntityUpdatesInfo update) {
public static ToCoreNotificationMsg toProto(TenantId tenantId, UUID id, int seqNumber, TbEntityUpdatesInfo update) {
TransportProtos.TbEntitySubEventCallbackProto.Builder updateProto = TransportProtos.TbEntitySubEventCallbackProto.newBuilder()
.setEntityIdMSB(id.getMostSignificantBits())
.setEntityIdLSB(id.getLeastSignificantBits())
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setSeqNumber(seqNumber)
.setAttributesUpdateTs(update.attributesUpdateTs)
.setTimeSeriesUpdateTs(update.timeSeriesUpdateTs);

2
application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java

@ -78,7 +78,7 @@ public abstract class AbstractSubscriptionService extends TbApplicationEventList
protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) {
currentPartitions.clear();
currentPartitions.addAll(partitionChangeEvent.getPartitions());
currentPartitions.addAll(partitionChangeEvent.getCorePartitions());
}
}

22
application/src/main/java/org/thingsboard/server/service/ws/DefaultWebSocketService.java

@ -193,17 +193,18 @@ public class DefaultWebSocketService implements WebSocketService {
@Override
public void handleSessionEvent(WebSocketSessionRef sessionRef, SessionEvent event) {
String sessionId = sessionRef.getSessionId();
TenantId tenantId = sessionRef.getSecurityCtx().getTenantId();
log.debug(PROCESSING_MSG, sessionId, event);
switch (event.getEventType()) {
case ESTABLISHED:
wsSessionsMap.put(sessionId, new WsSessionMetaData(sessionRef));
break;
case ERROR:
log.debug("[{}] Unknown websocket session error: ", sessionId,
log.debug("[{}][{}] Unknown websocket session error: ", tenantId, sessionId,
event.getError().orElse(new RuntimeException("No error specified")));
break;
case CLOSED:
cleanupSessionById(sessionId);
cleanupSessionById(tenantId, sessionId);
processSessionClose(sessionRef);
break;
}
@ -297,10 +298,10 @@ public class DefaultWebSocketService implements WebSocketService {
}
@Override
public void cleanupIfStale(String sessionId) {
public void cleanupIfStale(TenantId tenantId, String sessionId) {
if (!msgEndpoint.isOpen(sessionId)) {
log.info("[{}] Cleaning up stale session ", sessionId);
cleanupSessionById(sessionId);
cleanupSessionById(tenantId, sessionId);
}
}
@ -754,22 +755,23 @@ public class DefaultWebSocketService implements WebSocketService {
}
private void unsubscribe(WebSocketSessionRef sessionRef, SubscriptionCmd cmd, String sessionId) {
TenantId tenantId = sessionRef.getSecurityCtx().getTenantId();
if (cmd.getEntityId() == null || cmd.getEntityId().isEmpty()) {
log.warn("[{}][{}] Cleanup session due to empty entity id.", sessionId, cmd.getCmdId());
cleanupSessionById(sessionId);
log.warn("[{}][{}][{}] Cleanup session due to empty entity id.", tenantId, sessionId, cmd.getCmdId());
cleanupSessionById(tenantId, sessionId);
} else {
Integer subId = sessionCmdMap.getOrDefault(sessionId, Collections.emptyMap()).remove(cmd.getCmdId());
if (subId == null) {
log.trace("[{}][{}] Failed to lookup subscription id mapping", sessionId, cmd.getCmdId());
log.trace("[{}][{}][{}] Failed to lookup subscription id mapping", tenantId, sessionId, cmd.getCmdId());
subId = cmd.getCmdId();
}
oldSubService.cancelSubscription(sessionId, subId);
oldSubService.cancelSubscription(tenantId, sessionId, subId);
}
}
private void cleanupSessionById(String sessionId) {
private void cleanupSessionById(TenantId tenantId, String sessionId) {
wsSessionsMap.remove(sessionId);
oldSubService.cancelAllSessionSubscriptions(sessionId);
oldSubService.cancelAllSessionSubscriptions(tenantId, sessionId);
sessionCmdMap.remove(sessionId);
entityDataSubService.cancelAllSessionSubscriptions(sessionId);
}

3
application/src/main/java/org/thingsboard/server/service/ws/WebSocketService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.service.ws;
import org.springframework.web.socket.CloseStatus;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.service.subscription.SubscriptionErrorCode;
import org.thingsboard.server.service.ws.telemetry.cmd.v2.CmdUpdate;
import org.thingsboard.server.service.ws.telemetry.sub.TelemetrySubscriptionUpdate;
@ -37,6 +38,6 @@ public interface WebSocketService {
void close(String sessionId, CloseStatus status);
void cleanupIfStale(String sessionId);
void cleanupIfStale(TenantId tenantId, String sessionId);
}

5
application/src/main/java/org/thingsboard/server/service/ws/WebSocketSessionRef.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.ws;
import lombok.Builder;
import lombok.Data;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.net.InetSocketAddress;
@ -39,6 +40,10 @@ public class WebSocketSessionRef {
private final WebSocketSessionType sessionType;
private final AtomicInteger sessionSubIdSeq = new AtomicInteger();
public TenantId getTenantId() {
return securityCtx != null ? securityCtx.getTenantId() : TenantId.SYS_TENANT_ID;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;

2
application/src/main/java/org/thingsboard/server/service/ws/notification/DefaultNotificationCommandsHandler.java

@ -249,7 +249,7 @@ public class DefaultNotificationCommandsHandler implements NotificationCommandsH
@Override
public void handleUnsubCmd(WebSocketSessionRef sessionRef, UnsubscribeCmd cmd) {
localSubscriptionService.cancelSubscription(sessionRef.getSessionId(), cmd.getCmdId());
localSubscriptionService.cancelSubscription(sessionRef.getTenantId(), sessionRef.getSessionId(), cmd.getCmdId());
}
private void sendUpdate(String sessionId, CmdUpdate update) {

47
application/src/main/resources/thingsboard.yml

@ -50,6 +50,10 @@ server:
key_alias: "${SSL_KEY_ALIAS:tomcat}"
# Password used to access the key
key_password: "${SSL_KEY_PASSWORD:thingsboard}"
# HTTP settings
http:
# Semi-colon-separated list of urlPattern=maxPayloadSize pairs that define max http request size for specified url pattern. After first match all other will be skipped
max_payload_size: "${HTTP_MAX_PAYLOAD_SIZE_LIMIT_CONFIGURATION:/api/image*/**=52428800;/api/resource/**=52428800;/api/**=16777216}"
# HTTP/2 support (takes effect only if server SSL is enabled)
http2:
# Enable/disable HTTP/2 support
@ -572,6 +576,9 @@ cache:
edges:
timeToLiveInMinutes: "${CACHE_SPECS_EDGES_TTL:1440}" # Edges cache TTL
maxSize: "${CACHE_SPECS_EDGES_MAX_SIZE:10000}" # 0 means the cache is disabled
edgeSessions:
timeToLiveInMinutes: "${CACHE_SPECS_EDGE_SESSIONS_TTL:0}" # Edge Sessions cache TTL; no expiration time if set to '0'
maxSize: "${CACHE_SPECS_EDGE_SESSIONS_MAX_SIZE:10000}" # 0 means the cache is disabled
relatedEdges:
timeToLiveInMinutes: "${CACHE_SPECS_RELATED_EDGES_TTL:1440}" # Related Edges cache TTL
maxSize: "${CACHE_SPECS_RELATED_EDGES_MAX_SIZE:10000}" # 0 means the cache is disabled
@ -995,8 +1002,8 @@ transport:
request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}"
# HTTP maximum request processing timeout in milliseconds
max_request_timeout: "${HTTP_MAX_REQUEST_TIMEOUT:300000}"
# Maximum request size
max_payload_size: "${HTTP_MAX_PAYLOAD_SIZE:65536}" # max payload size in bytes
# Semi-colon-separated list of urlPattern=maxPayloadSize pairs that define max http request size for specified url pattern. After first match all other will be skipped
max_payload_size: "${HTTP_TRANSPORT_MAX_PAYLOAD_SIZE_LIMIT_CONFIGURATION:/api/v1/*/rpc/**=65536;/api/v1/**=52428800}"
# Local MQTT transport parameters
mqtt:
# Enable/disable mqtt transport protocol.
@ -1380,6 +1387,8 @@ edges:
no_read_records_sleep: "${EDGES_NO_READ_RECORDS_SLEEP:1000}"
# Number of milliseconds to wait before resending failed batch of edge events to edge
sleep_between_batches: "${EDGES_SLEEP_BETWEEN_BATCHES:60000}"
# Max number of high priority edge events per edge session. No persistence - stored in memory
max_high_priority_queue_size_per_session: "${EDGES_MAX_HIGH_PRIORITY_QUEUE_SIZE_PER_SESSION:10000}"
# Number of threads that are used to check DB for edge events
scheduler_pool_size: "${EDGES_SCHEDULER_POOL_SIZE:1}"
# Number of threads that are used to send downlink messages to edge over gRPC
@ -1503,9 +1512,9 @@ queue:
- key: max.poll.interval.ms
# Example of specific consumer properties value per topic for VC
value: "${TB_QUEUE_KAFKA_VC_MAX_POLL_INTERVAL_MS:600000}"
# tb_rule_engine.sq:
# - key: max.poll.records
# value: "${TB_QUEUE_KAFKA_SQ_MAX_POLL_RECORDS:1024}"
# tb_rule_engine.sq:
# - key: max.poll.records
# value: "${TB_QUEUE_KAFKA_SQ_MAX_POLL_RECORDS:1024}"
tb_housekeeper:
# Consumer properties for Housekeeper tasks topic
- key: max.poll.records
@ -1541,6 +1550,8 @@ queue:
housekeeper: "${TB_QUEUE_KAFKA_HOUSEKEEPER_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:52428800;retention.bytes:1048576000;partitions:10;min.insync.replicas:1}"
# Kafka properties for Housekeeper reprocessing topic; retention.ms is set to 90 days; partitions is set to 1 since only one reprocessing service is running at a time
housekeeper-reprocessing: "${TB_QUEUE_KAFKA_HOUSEKEEPER_REPROCESSING_TOPIC_PROPERTIES:retention.ms:7776000000;segment.bytes:52428800;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
# Kafka properties for Edge topic
edge: "${TB_QUEUE_KAFKA_EDGE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:52428800;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}"
consumer-stats:
# Prints lag between consumer group offset and last messages offset in Kafka topics
enabled: "${TB_QUEUE_KAFKA_CONSUMER_STATS_ENABLED:true}"
@ -1576,6 +1587,8 @@ queue:
ota-updates: "${TB_QUEUE_AWS_SQS_OTA_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
# VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds
version-control: "${TB_QUEUE_AWS_SQS_VC_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
# VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds
edge: "${TB_QUEUE_AWS_SQS_EDGE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}"
pubsub:
# Project ID from Google Cloud
project_id: "${TB_QUEUE_PUBSUB_PROJECT_ID:YOUR_PROJECT_ID}"
@ -1600,6 +1613,8 @@ queue:
js-executor: "${TB_QUEUE_PUBSUB_JE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
# Pub/Sub properties for Transport Api subscribers, messages which will commit after ackDeadlineInSec period can be consumed again
version-control: "${TB_QUEUE_PUBSUB_VC_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
# Pub/Sub properties for Edge subscribers, messages which will commit after ackDeadlineInSec period can be consumed again
edge: "${TB_QUEUE_PUBSUB_EDGE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}"
service_bus:
# Azure namespace
namespace_name: "${TB_QUEUE_SERVICE_BUS_NAMESPACE_NAME:YOUR_NAMESPACE_NAME}"
@ -1622,6 +1637,8 @@ queue:
js-executor: "${TB_QUEUE_SERVICE_BUS_JE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
# Azure Service Bus properties for Version Control queues
version-control: "${TB_QUEUE_SERVICE_BUS_VC_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
# Azure Service Bus properties for Edge queues
edge: "${TB_QUEUE_SERVICE_BUS_EDGE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}"
rabbitmq:
# By default empty
exchange_name: "${TB_QUEUE_RABBIT_MQ_EXCHANGE_NAME:}"
@ -1654,6 +1671,8 @@ queue:
js-executor: "${TB_QUEUE_RABBIT_MQ_JE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
# RabbitMQ properties for Version Control queues
version-control: "${TB_QUEUE_RABBIT_MQ_VC_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
# RabbitMQ properties for Edge queues
edge: "${TB_QUEUE_RABBIT_MQ_EDGE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}"
partitions:
hash_function_name: "${TB_QUEUE_PARTITIONS_HASH_FUNCTION_NAME:murmur3_128}" # murmur3_32, murmur3_128 or sha256
transport_api:
@ -1772,6 +1791,24 @@ queue:
notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}"
# Interval in milliseconds to poll messages
poll_interval: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}"
edge:
# Default topic name for Kafka, RabbitMQ, etc.
topic: "${TB_QUEUE_EDGE_TOPIC:tb_edge}"
# Amount of partitions used by Edge services
partitions: "${TB_QUEUE_EDGE_PARTITIONS:10}"
# Poll interval for topics related to Edge services
poll-interval: "${TB_QUEUE_EDGE_POLL_INTERVAL_MS:25}"
# Timeout for processing a message pack by Edge services
pack-processing-timeout: "${TB_QUEUE_EDGE_PACK_PROCESSING_TIMEOUT_MS:10000}"
# Retries for processing a failure message pack by Edge services
pack-processing-retries: "${TB_QUEUE_EDGE_MESSAGE_PROCESSING_RETRIES:3}"
# Enable/disable a separate consumer per partition for Edge queue
consumer-per-partition: "${TB_QUEUE_EDGE_CONSUMER_PER_PARTITION:false}"
stats:
# Enable/disable statistics for Edge services
enabled: "${TB_QUEUE_EDGE_STATS_ENABLED:true}"
# Statistics printing interval for Edge services
print-interval-ms: "${TB_QUEUE_EDGE_STATS_PRINT_INTERVAL_MS:60000}"
# Event configuration parameters
event:

6
application/src/test/java/org/thingsboard/server/cache/CaffeineCacheDefaultConfigurationTest.java

@ -46,7 +46,11 @@ public class CaffeineCacheDefaultConfigurationTest {
SoftAssertions softly = new SoftAssertions();
cacheSpecsMap.getSpecs().forEach((name, cacheSpecs) -> {
softly.assertThat(name).as("cache name").isNotEmpty();
softly.assertThat(cacheSpecs.getTimeToLiveInMinutes()).as("cache %s time to live", name).isGreaterThan(0);
if (name.equals("edgeSessions")) {
softly.assertThat(cacheSpecs.getTimeToLiveInMinutes()).as("cache %s time to live", name).isEqualTo(0);
} else {
softly.assertThat(cacheSpecs.getTimeToLiveInMinutes()).as("cache %s time to live", name).isGreaterThan(0);
}
softly.assertThat(cacheSpecs.getMaxSize()).as("cache %s max size", name).isGreaterThan(0);
});
softly.assertAll();

26
application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java

@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.model.ModelConstants;
@ -230,6 +231,19 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEdgeStateChangeEventManyTimeMsgToEdgeServiceNever(HasName entity, EntityId entityId, EntityId originatorId,
TenantId tenantId, CustomerId customerId, UserId userId, String userName,
ActionType actionType, int cntTimeBroadcast, Object... additionalInfo) {
int cntTime = 1;
testNotificationMsgToEdgeServiceNeverWithActionType(entityId, actionType);
testLogEntityAction(entity, originatorId, tenantId, customerId, userId, userName, actionType, cntTime, additionalInfo);
ArgumentMatcher<EntityId> matcherOriginatorId = argument -> argument.equals(originatorId);
testPushMsgToRuleEngineTime(matcherOriginatorId, tenantId, entity, cntTime);
testBroadcastEdgeStateChangeEventTime(tenantId, cntTimeBroadcast);
Mockito.reset(tbClusterService, auditLogService);
}
protected void testNotifyEntityBroadcastEntityStateChangeEventMany(HasName entity, HasName originator,
TenantId tenantId, CustomerId customerId,
UserId userId, String userName, ActionType actionType,
@ -351,11 +365,17 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
protected void testBroadcastEntityStateChangeEventTime(EntityId entityId, TenantId tenantId, int cntTime) {
ArgumentMatcher<TenantId> matcherTenantIdId = cntTime > 1 || tenantId == null ? argument -> argument.getClass().equals(TenantId.class) :
argument -> argument.equals(tenantId) ;
argument -> argument.equals(tenantId);
Mockito.verify(tbClusterService, times(cntTime)).broadcastEntityStateChangeEvent(Mockito.argThat(matcherTenantIdId),
Mockito.any(entityId.getClass()), Mockito.any(ComponentLifecycleEvent.class));
}
protected void testBroadcastEdgeStateChangeEventTime(TenantId tenantId, int cntTime) {
ArgumentMatcher<TenantId> matcherTenantIdId = cntTime > 1 || tenantId == null ? argument -> argument.getClass().equals(TenantId.class) :
argument -> argument.equals(tenantId);
Mockito.verify(tbClusterService, times(cntTime)).onEdgeStateChangeEvent(Mockito.any(ComponentLifecycleMsg.class));
}
private void testPushMsgToCoreTime(int cntTime) {
Mockito.verify(tbClusterService, times(cntTime)).pushMsgToCore(Mockito.any(ToDeviceActorNotificationMsg.class), Mockito.isNull());
}
@ -608,8 +628,8 @@ public abstract class AbstractNotifyEntityTest extends AbstractWebTest {
}
private String entityClassToEntityTypeName(HasName entity) {
String entityType = entityClassToString(entity);
return "SAVE_OTA_PACKAGE_INFO_REQUEST".equals(entityType) || "OTA_PACKAGE_INFO".equals(entityType)?
String entityType = entityClassToString(entity);
return "SAVE_OTA_PACKAGE_INFO_REQUEST".equals(entityType) || "OTA_PACKAGE_INFO".equals(entityType) ?
EntityType.OTA_PACKAGE.name().toUpperCase(Locale.ENGLISH) : entityType;
}

3
application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java

@ -1503,11 +1503,12 @@ public class DeviceControllerTest extends AbstractControllerTest {
Assert.assertTrue(deviceBulkImportResult.getErrorsList().isEmpty());
Device updatedDevice = doGet("/api/device/" + savedDevice.getId().getId(), Device.class);
savedDevice.setVersion(updatedDevice.getVersion());
Assert.assertEquals(savedDevice, updatedDevice);
DeviceCredentials updatedCredentials =
doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
savedCredentials.setVersion(updatedCredentials.getVersion());
Assert.assertEquals(savedCredentials, updatedCredentials);
}

57
application/src/test/java/org/thingsboard/server/controller/EdgeControllerTest.java

@ -40,8 +40,8 @@ import org.springframework.test.context.TestPropertySource;
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.AttributeScope;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EdgeUpgradeInfo;
@ -62,6 +62,7 @@ import org.thingsboard.server.common.data.id.TenantId;
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.page.TimePageLink;
import org.thingsboard.server.common.data.queue.Queue;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
@ -166,7 +167,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
Assert.assertEquals(NULL_UUID, savedEdge.getCustomerId().getId());
Assert.assertEquals(edge.getName(), savedEdge.getName());
testNotifyEntityBroadcastEntityStateChangeEventManyTimeMsgToEdgeServiceNever(savedEdge, savedEdge.getId(), savedEdge.getId(),
testNotifyEdgeStateChangeEventManyTimeMsgToEdgeServiceNever(savedEdge, savedEdge.getId(), savedEdge.getId(),
tenantId, tenantAdminUser.getCustomerId(), tenantAdminUser.getId(), tenantAdminUser.getEmail(),
ActionType.ADDED, 2);
@ -176,7 +177,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
Edge foundEdge = doGet("/api/edge/" + savedEdge.getId().getId().toString(), Edge.class);
Assert.assertEquals(foundEdge.getName(), savedEdge.getName());
testNotifyEntityBroadcastEntityStateChangeEventManyTimeMsgToEdgeServiceNever(foundEdge, foundEdge.getId(), foundEdge.getId(),
testNotifyEdgeStateChangeEventManyTimeMsgToEdgeServiceNever(foundEdge, foundEdge.getId(), foundEdge.getId(),
tenantId, tenantAdminUser.getCustomerId(), tenantAdminUser.getId(), tenantAdminUser.getEmail(),
ActionType.UPDATED, 1);
}
@ -876,7 +877,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
Asset asset = new Asset();
asset.setName("Test Sync Edge Asset 1");
asset.setType("test");
asset.setType("default");
Asset savedAsset = doPost("/api/asset", asset, Asset.class);
Device device = new Device();
@ -896,7 +897,7 @@ public class EdgeControllerTest extends AbstractControllerTest {
edgeImitator.ignoreType(OAuth2ClientUpdateMsg.class);
edgeImitator.ignoreType(OAuth2DomainUpdateMsg.class);
edgeImitator.expectMessageAmount(27);
edgeImitator.expectMessageAmount(26);
edgeImitator.connect();
waitForMessages(edgeImitator);
@ -905,16 +906,16 @@ public class EdgeControllerTest extends AbstractControllerTest {
Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popDeviceMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Test Sync Edge Device 1"));
Assert.assertTrue(popDeviceCredentialsMsg(edgeImitator.getDownlinkMsgs(), savedDevice.getId()));
Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "test"));
Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popAssetMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Test Sync Edge Asset 1"));
Assert.assertTrue(edgeImitator.getDownlinkMsgs().isEmpty());
printQueueMsgsIfNotEmpty(edgeImitator);
edgeImitator.expectMessageAmount(22);
doPost("/api/edge/sync/" + edge.getId());
edgeImitator.expectMessageAmount(21);
doPost("/api/edge/sync/" + edge.getId()).andExpect(status().isOk());
waitForMessages(edgeImitator);
verifyFetchersMsgs(edgeImitator, savedDevice);
Assert.assertTrue(edgeImitator.getDownlinkMsgs().isEmpty());
printQueueMsgsIfNotEmpty(edgeImitator);
edgeImitator.allowIgnoredTypes();
try {
@ -930,6 +931,15 @@ public class EdgeControllerTest extends AbstractControllerTest {
.andExpect(status().isOk());
}
private static void printQueueMsgsIfNotEmpty(EdgeImitator edgeImitator) {
if (!edgeImitator.getDownlinkMsgs().isEmpty()) {
for (AbstractMessage downlinkMsg : edgeImitator.getDownlinkMsgs()) {
log.warn("Unexpected message in the queue: {}", downlinkMsg);
}
}
Assert.assertTrue(edgeImitator.getDownlinkMsgs().isEmpty());
}
private RuleChainId getEdgeRootRuleChainId(EdgeImitator edgeImitator) {
try {
EdgeId edgeId = new EdgeId(new UUID(edgeImitator.getConfiguration().getEdgeIdMSB(), edgeImitator.getConfiguration().getEdgeIdLSB()));
@ -948,11 +958,18 @@ public class EdgeControllerTest extends AbstractControllerTest {
}
private void simulateEdgeActivation(Edge edge) throws Exception {
Awaitility.await()
.atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> {
List<RuleChain> ruleChains = getEdgeRuleChains(edge.getId());
return ruleChains.size() == 1 && ruleChains.get(0).getId().equals(edge.getRootRuleChainId());
});
ObjectNode attributes = JacksonUtil.newObjectNode();
attributes.put("active", true);
doPost("/api/plugins/telemetry/EDGE/" + edge.getId() + "/attributes/" + DataConstants.SERVER_SCOPE, attributes);
doPost("/api/plugins/telemetry/EDGE/" + edge.getId() + "/attributes/" + AttributeScope.SERVER_SCOPE, attributes);
Awaitility.await()
.atMost(30, TimeUnit.SECONDS)
.atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> {
List<Map<String, Object>> values = doGetAsyncTyped("/api/plugins/telemetry/EDGE/" + edge.getId() +
"/values/attributes/SERVER_SCOPE", new TypeReference<>() {});
@ -960,8 +977,9 @@ public class EdgeControllerTest extends AbstractControllerTest {
if (activeAttrOpt.isEmpty()) {
return false;
}
List<RuleChain> ruleChains = getEdgeRuleChains(edge.getId());
Map<String, Object> activeAttr = activeAttrOpt.get();
return "true".equals(activeAttr.get("value").toString());
return "true".equals(activeAttr.get("value").toString()) && ruleChains.size() == 1;
});
}
@ -988,13 +1006,12 @@ public class EdgeControllerTest extends AbstractControllerTest {
Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "test"));
Assert.assertTrue(popUserMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, TENANT_ADMIN_EMAIL, Authority.TENANT_ADMIN));
Assert.assertTrue(popCustomerMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Public"));
Assert.assertTrue(popDeviceProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popDeviceMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Test Sync Edge Device 1"));
Assert.assertTrue(popDeviceCredentialsMsg(edgeImitator.getDownlinkMsgs(), savedDevice.getId()));
Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "test"));
Assert.assertTrue(popAssetProfileMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "default"));
Assert.assertTrue(popAssetMsg(edgeImitator.getDownlinkMsgs(), UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, "Test Sync Edge Asset 1"));
Assert.assertTrue(popTenantMsg(edgeImitator.getDownlinkMsgs(), tenantId));
Assert.assertTrue(popTenantProfileMsg(edgeImitator.getDownlinkMsgs(), tenantProfileId));
@ -1223,6 +1240,12 @@ public class EdgeControllerTest extends AbstractControllerTest {
return doPost("/api/edge", edge, Edge.class);
}
private List<RuleChain> getEdgeRuleChains(EdgeId edgeId) throws Exception {
return doGetTypedWithTimePageLink("/api/edge/" + edgeId + "/ruleChains?",
new TypeReference<PageData<RuleChain>>() {
}, new TimePageLink(10)).getData();
}
@Test
public void testGetEdgeInstallInstructions() throws Exception {
Edge edge = constructEdge(tenantId, "Edge for Test Docker Install Instructions", "default", "7390c3a6-69b0-9910-d155-b90aca4b772e", "l7q4zsjplzwhk16geqxy");
@ -1271,6 +1294,8 @@ public class EdgeControllerTest extends AbstractControllerTest {
Assert.assertFalse(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId()));
edgeUpgradeInstructionsService.setAppVersion("3.6.1.5");
Assert.assertTrue(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId()));
edgeUpgradeInstructionsService.setAppVersion("3.6.1.6-SNAPSHOT");
Assert.assertTrue(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId()));
edgeUpgradeInstructionsService.setAppVersion("3.6.2");
Assert.assertTrue(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId()));
@ -1281,6 +1306,8 @@ public class EdgeControllerTest extends AbstractControllerTest {
Assert.assertFalse(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId()));
edgeUpgradeInstructionsService.setAppVersion("3.6.2");
Assert.assertTrue(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId()));
edgeUpgradeInstructionsService.setAppVersion("3.6.2-SNAPSHOT");
Assert.assertTrue(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId()));
edgeUpgradeInstructionsService.setAppVersion("3.6.2.6");
Assert.assertTrue(edgeUpgradeInstructionsService.isUpgradeAvailable(savedEdge.getTenantId(), savedEdge.getId()));
}

2
application/src/test/java/org/thingsboard/server/controller/EntityQueryControllerTest.java

@ -629,7 +629,7 @@ public class EntityQueryControllerTest extends AbstractControllerTest {
Awaitility.await()
.alias("data by query")
.atMost(30, TimeUnit.SECONDS)
.atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> {
var data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<PageData<EntityData>>() {});
var loadedEntities = new ArrayList<>(data.getData());

13
application/src/test/java/org/thingsboard/server/controller/ImageControllerTest.java

File diff suppressed because one or more lines are too long

2
application/src/test/java/org/thingsboard/server/controller/RpcControllerTest.java

@ -40,7 +40,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@DaoSqlTest
@TestPropertySource(properties = {
"transport.http.max_payload_size=10000"
"server.http.max_payload_size=/api/image*/**=10;/api/**=10000"
})
public class RpcControllerTest extends AbstractControllerTest {

43
application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java

@ -30,6 +30,8 @@ import org.springframework.test.context.ContextConfiguration;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.action.TbCreateAlarmNode;
import org.thingsboard.rule.engine.action.TbCreateAlarmNodeConfiguration;
import org.thingsboard.rule.engine.debug.TbMsgGeneratorNode;
import org.thingsboard.rule.engine.debug.TbMsgGeneratorNodeConfiguration;
import org.thingsboard.rule.engine.metadata.TbGetRelatedAttributeNode;
import org.thingsboard.rule.engine.metadata.TbGetRelatedDataNodeConfiguration;
import org.thingsboard.server.common.data.StringUtils;
@ -356,6 +358,47 @@ public class RuleChainControllerTest extends AbstractControllerTest {
assertThat(error).contains("alarmType is malformed");
}
@Test
public void testSaveRuleChainWithOutdatedVersion() throws Exception {
RuleChain ruleChain = createRuleChain("My rule chain");
RuleChainMetaData ruleChainMetaData = new RuleChainMetaData();
ruleChainMetaData.setRuleChainId(ruleChain.getId());
RuleNode ruleNode = new RuleNode();
ruleNode.setName("Test");
ruleNode.setType(TbMsgGeneratorNode.class.getName());
TbMsgGeneratorNodeConfiguration config = new TbMsgGeneratorNodeConfiguration();
ruleNode.setConfiguration(JacksonUtil.valueToTree(config));
List<RuleNode> ruleNodes = new ArrayList<>();
ruleNodes.add(ruleNode);
ruleChainMetaData.setFirstNodeIndex(0);
ruleChainMetaData.setNodes(ruleNodes);
ruleChainMetaData = doPost("/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class);
assertThat(ruleChainMetaData.getVersion()).isEqualTo(2);
ruleChain = doGet("/api/ruleChain/" + ruleChain.getId(), RuleChain.class);
assertThat(ruleChain.getVersion()).isEqualTo(2);
ruleChain.setName("Updated");
ruleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class);
assertThat(ruleChain.getVersion()).isEqualTo(3);
ruleChain.setVersion(1L);
doPost("/api/ruleChain", ruleChain)
.andExpect(status().isConflict());
ruleChainMetaData.setVersion(1L);
doPost("/api/ruleChain/metadata", ruleChainMetaData)
.andExpect(status().isConflict());
ruleChainMetaData.setVersion(3L);
ruleChainMetaData = doPost("/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class);
assertThat(ruleChainMetaData.getVersion()).isEqualTo(4);
ruleChain.setVersion(4L);
ruleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class);
assertThat(ruleChain.getVersion()).isEqualTo(5);
}
private RuleChain createRuleChain(String name) {
RuleChain ruleChain = new RuleChain();
ruleChain.setName(name);

14
application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java

@ -664,7 +664,7 @@ public class TenantControllerTest extends AbstractControllerTest {
savedDifferentTenant.setTenantProfileId(tenantProfile.getId());
savedDifferentTenant = saveTenant(savedDifferentTenant);
TenantId tenantId = differentTenantId;
await().atMost(30, TimeUnit.SECONDS)
await().atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, MAIN_QUEUE_NAME, tenantId, tenantId);
return !tpi.getTenantId().get().isSysTenantId();
@ -678,7 +678,7 @@ public class TenantControllerTest extends AbstractControllerTest {
tenantProfile.setIsolatedTbRuleEngine(false);
tenantProfile.getProfileData().setQueueConfiguration(Collections.emptyList());
tenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class);
await().atMost(30, TimeUnit.SECONDS)
await().atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> partitionService.resolve(ServiceType.TB_RULE_ENGINE, MAIN_QUEUE_NAME, tenantId, tenantId)
.getTenantId().get().isSysTenantId());
@ -690,7 +690,7 @@ public class TenantControllerTest extends AbstractControllerTest {
submittedMsgs.add(tbMsg.getId());
Thread.sleep(timeLeft / msgs);
}
await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
verify(queueAdmin, times(1)).deleteTopic(eq(isolatedTopic));
});
@ -719,13 +719,13 @@ public class TenantControllerTest extends AbstractControllerTest {
savedDifferentTenant.setTenantProfileId(tenantProfile.getId());
savedDifferentTenant = saveTenant(savedDifferentTenant);
TenantId tenantId = differentTenantId;
await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(partitionService.getMyPartitions(new QueueKey(ServiceType.TB_RULE_ENGINE, tenantId))).isNotNull();
});
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tenantId);
assertThat(tpi.getTenantId()).hasValue(tenantId);
TbMsg tbMsg = publishTbMsg(tenantId, tpi);
await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
verify(actorContext).tell(argThat(msg -> {
return msg instanceof QueueToRuleEngineMsg && ((QueueToRuleEngineMsg) msg).getMsg().getId().equals(tbMsg.getId());
}));
@ -733,7 +733,7 @@ public class TenantControllerTest extends AbstractControllerTest {
deleteDifferentTenant();
await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(partitionService.getMyPartitions(new QueueKey(ServiceType.TB_RULE_ENGINE, tenantId))).isNull();
assertThatThrownBy(() -> partitionService.resolve(ServiceType.TB_RULE_ENGINE, tenantId, tenantId))
.isInstanceOf(TenantNotFoundException.class);
@ -753,7 +753,7 @@ public class TenantControllerTest extends AbstractControllerTest {
}
private void verifyUsedQueueAndMessage(String queue, TenantId tenantId, EntityId entityId, String msgType, Runnable action, Consumer<TopicPartitionInfo> tpiAssert) {
await().atMost(30, TimeUnit.SECONDS)
await().atMost(TIMEOUT, TimeUnit.SECONDS)
.untilAsserted(() -> {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, queue, tenantId, entityId);
tpiAssert.accept(tpi);

28
application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java

@ -17,6 +17,7 @@ package org.thingsboard.server.edge;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.protobuf.AbstractMessage;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.MessageLite;
@ -27,7 +28,6 @@ import org.junit.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestPropertySource;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
@ -127,9 +127,6 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
@Autowired
protected EdgeEventService edgeEventService;
@Autowired
protected TbClusterService clusterService;
@Before
public void setupEdgeTest() throws Exception {
loginSysAdmin();
@ -207,7 +204,7 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
+ "/asset/" + savedAsset.getUuidId(), Asset.class);
// wait until assign device and asset events are fully processed by edge notification service
TimeUnit.MILLISECONDS.sleep(500);
TimeUnit.MILLISECONDS.sleep(1000);
}
protected void extendDeviceProfileData(DeviceProfile deviceProfile) {
@ -697,4 +694,25 @@ abstract public class AbstractEdgeTest extends AbstractControllerTest {
.andExpect(status().isOk());
}
protected ObjectNode createDefaultRpc() {
return createDefaultRpc(1);
}
protected ObjectNode createDefaultRpc(Integer value) {
ObjectNode rpc = JacksonUtil.newObjectNode();
rpc.put("method", "setGpio");
ObjectNode params = JacksonUtil.newObjectNode();
params.put("pin", 7);
params.put("value", value);
rpc.set("params", params);
rpc.put("persistent", true);
rpc.put("timeout", 5000);
return rpc;
}
}

67
application/src/test/java/org/thingsboard/server/edge/DeviceEdgeTest.java

@ -51,7 +51,6 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
@ -79,9 +78,13 @@ import java.util.Optional;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.gen.edge.v1.UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE;
@TestPropertySource(properties = {
"transport.mqtt.enabled=true"
@ -107,7 +110,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest {
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceUpdateMsg.getMsgType());
Assert.assertEquals(ENTITY_DELETED_RPC_MESSAGE, deviceUpdateMsg.getMsgType());
Assert.assertEquals(savedDevice.getUuidId().getMostSignificantBits(), deviceUpdateMsg.getIdMSB());
Assert.assertEquals(savedDevice.getUuidId().getLeastSignificantBits(), deviceUpdateMsg.getIdLSB());
@ -184,7 +187,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest {
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
deviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceUpdateMsg.getMsgType());
Assert.assertEquals(ENTITY_DELETED_RPC_MESSAGE, deviceUpdateMsg.getMsgType());
Assert.assertEquals(savedDevice.getUuidId().getMostSignificantBits(), deviceUpdateMsg.getIdMSB());
Assert.assertEquals(savedDevice.getUuidId().getLeastSignificantBits(), deviceUpdateMsg.getIdLSB());
@ -399,7 +402,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest {
Device savedDevice = saveDeviceOnCloudAndVerifyDeliveryToEdge();
UplinkMsg.Builder upLinkMsgBuilder = UplinkMsg.newBuilder();
DeviceUpdateMsg.Builder deviceDeleteMsgBuilder = DeviceUpdateMsg.newBuilder();
deviceDeleteMsgBuilder.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE);
deviceDeleteMsgBuilder.setMsgType(ENTITY_DELETED_RPC_MESSAGE);
deviceDeleteMsgBuilder.setIdMSB(savedDevice.getId().getId().getMostSignificantBits());
deviceDeleteMsgBuilder.setIdLSB(savedDevice.getId().getId().getLeastSignificantBits());
testAutoGeneratedCodeByProtobuf(deviceDeleteMsgBuilder);
@ -624,17 +627,10 @@ public class DeviceEdgeTest extends AbstractEdgeTest {
if (keyValueProto.getKey().equals(expectedKey)) {
Assert.assertEquals(expectedKey, keyValueProto.getKey());
switch (keyValueProto.getType()) {
case STRING_V:
Assert.assertEquals(expectedValue, keyValueProto.getStringV());
break;
case LONG_V:
Assert.assertEquals(Long.parseLong(expectedValue), keyValueProto.getLongV());
break;
case JSON_V:
Assert.assertEquals(JacksonUtil.toJsonNode(expectedValue), JacksonUtil.toJsonNode(keyValueProto.getJsonV()));
break;
default:
Assert.fail("Unexpected data type: " + keyValueProto.getType());
case STRING_V -> Assert.assertEquals(expectedValue, keyValueProto.getStringV());
case LONG_V -> Assert.assertEquals(Long.parseLong(expectedValue), keyValueProto.getLongV());
case JSON_V -> Assert.assertEquals(JacksonUtil.toJsonNode(expectedValue), JacksonUtil.toJsonNode(keyValueProto.getJsonV()));
default -> Assert.fail("Unexpected data type: " + keyValueProto.getType());
}
found = true;
}
@ -768,9 +764,6 @@ public class DeviceEdgeTest extends AbstractEdgeTest {
Assert.assertEquals(1, relatedEdgeIds.size());
Assert.assertEquals(tmpEdge.getId(), relatedEdgeIds.get(0));
// clean up stored edge events
edgeEventService.cleanupEvents(1);
// edge is disconnected: perform rpc call - no edge event saved
doPostAsync(
"/api/rpc/oneway/" + device.getId().getId().toString(),
@ -779,10 +772,7 @@ public class DeviceEdgeTest extends AbstractEdgeTest {
status().isOk());
Awaitility.await()
.atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> {
PageData<EdgeEvent> result = edgeEventService.findEdgeEvents(tenantId, tmpEdge.getId(), 0L, null, new TimePageLink(1));
return result.getTotalElements() == 0;
});
.untilAsserted(() -> verify(tbClusterService, never()).onEdgeHighPriorityMsg(any()));
// edge is connected: perform rpc call to verify edgeId in DeviceActorMessageProcessor updated properly
simulateEdgeActivation(tmpEdge);
@ -792,21 +782,9 @@ public class DeviceEdgeTest extends AbstractEdgeTest {
String.class,
status().isOk());
final AtomicReference<PageData<EdgeEvent>> resultRef = new AtomicReference<>();
Awaitility.await()
.atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> {
PageData<EdgeEvent> result = edgeEventService.findEdgeEvents(tenantId, tmpEdge.getId(), 0L, null, new TimePageLink(1));
resultRef.set(result);
return result != null && result.getData().size() == 1;
});
PageData<EdgeEvent> result = resultRef.get();
EdgeEvent edgeEvent = result.getData().get(0);
Assert.assertEquals(EdgeEventActionType.RPC_CALL, edgeEvent.getAction());
Assert.assertEquals(EdgeEventType.DEVICE, edgeEvent.getType());
Assert.assertEquals(tmpEdge.getId(), edgeEvent.getEdgeId());
Assert.assertEquals(device.getId().getId(), edgeEvent.getEntityId());
.untilAsserted(() -> verify(tbClusterService, times(1)).onEdgeHighPriorityMsg(any()));
// clean up tmp edge
doDelete("/api/edge/" + tmpEdge.getId().getId().toString()).andExpect(status().isOk());
@ -824,27 +802,12 @@ public class DeviceEdgeTest extends AbstractEdgeTest {
private DeviceCredentials buildDeviceCredentialsForUplinkMsg(DeviceId deviceId) {
DeviceCredentials deviceCredentials = new DeviceCredentials();
deviceCredentials.setDeviceId(deviceId);
deviceCredentials.setCredentialsId(String.valueOf(UUID.randomUUID()));
deviceCredentials.setCredentialsValue("NEW_TOKEN");
deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
return deviceCredentials;
}
private ObjectNode createDefaultRpc() {
ObjectNode rpc = JacksonUtil.newObjectNode();
rpc.put("method", "setGpio");
ObjectNode params = JacksonUtil.newObjectNode();
params.put("pin", 7);
params.put("value", 1);
rpc.set("params", params);
rpc.put("persistent", true);
rpc.put("timeout", 5000);
return rpc;
}
private void simulateEdgeActivation(Edge edge) throws Exception {
ObjectNode attributes = JacksonUtil.newObjectNode();
attributes.put("active", true);

33
application/src/test/java/org/thingsboard/server/edge/TelemetryEdgeTest.java

@ -33,33 +33,46 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg;
import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg;
import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg;
import org.thingsboard.server.gen.edge.v1.EntityDataProto;
import org.thingsboard.server.gen.edge.v1.RpcRequestMsg;
import org.thingsboard.server.gen.edge.v1.UplinkMsg;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest
public class TelemetryEdgeTest extends AbstractEdgeTest {
@Test
public void testTimeseriesWithFailures() throws Exception {
int numberOfTimeseriesToSend = 333;
int numberOfHighPriorityRpcRequests = 13;
int frequencyOfRpcRequestsPerTimeseries = 25;
Device device = findDeviceByName("Edge Device 1");
edgeImitator.setRandomFailuresOnTimeseriesDownlink(true);
// imitator will generate failure in 5% of cases
edgeImitator.setFailureProbability(5.0);
edgeImitator.expectMessageAmount(numberOfTimeseriesToSend);
edgeImitator.expectMessageAmount(numberOfTimeseriesToSend + numberOfHighPriorityRpcRequests);
for (int idx = 1; idx <= numberOfTimeseriesToSend; idx++) {
String timeseriesData = "{\"data\":{\"idx\":" + idx + "},\"ts\":" + System.currentTimeMillis() + "}";
JsonNode timeseriesEntityData = JacksonUtil.toJsonNode(timeseriesData);
EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED,
device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData);
edgeEventService.saveAsync(edgeEvent).get();
if (idx % frequencyOfRpcRequestsPerTimeseries == 0) {
doPostAsync(
"/api/rpc/oneway/" + device.getId().getId().toString(),
JacksonUtil.toString(createDefaultRpc(idx)),
String.class,
status().isOk());
}
}
Assert.assertTrue(edgeImitator.waitForMessages(120));
@ -71,6 +84,14 @@ public class TelemetryEdgeTest extends AbstractEdgeTest {
Assert.assertTrue(isIdxExistsInTheDownlinkList(idx, allTelemetryMsgs));
}
List<DeviceRpcCallMsg> allDeviceRpcCallMsgs = edgeImitator.findAllMessagesByType(DeviceRpcCallMsg.class);
Assert.assertEquals(numberOfHighPriorityRpcRequests, allDeviceRpcCallMsgs.size());
for (int idx = 1; idx <= numberOfHighPriorityRpcRequests; idx++) {
int pinValue = idx * frequencyOfRpcRequestsPerTimeseries;
Assert.assertTrue(isPinValueExistsInTheRpcDownlinkList(pinValue, allDeviceRpcCallMsgs));
}
edgeImitator.setRandomFailuresOnTimeseriesDownlink(false);
}
@ -175,6 +196,16 @@ public class TelemetryEdgeTest extends AbstractEdgeTest {
return false;
}
private boolean isPinValueExistsInTheRpcDownlinkList(int idx, List<DeviceRpcCallMsg> rpcDownlinkMsgs) {
for (DeviceRpcCallMsg proto : rpcDownlinkMsgs) {
RpcRequestMsg rpcRequestMsg = proto.getRequestMsg();
if ((JacksonUtil.toJsonNode(rpcRequestMsg.getParams())).get("value").intValue() == idx) {
return true;
}
}
return false;
}
@Test
public void testTimeseriesDeliveryFailuresForever_deliverOnlyDeviceUpdateMsgs() throws Exception {
int numberOfMsgsToSend = 100;

15
application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java

@ -17,7 +17,6 @@ package org.thingsboard.server.edge;
import com.google.protobuf.AbstractMessage;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@ -46,10 +45,9 @@ public class UserEdgeTest extends AbstractEdgeTest {
private BCryptPasswordEncoder passwordEncoder;
@Test
@Ignore("Ignored until fix")
public void testCreateUpdateDeleteTenantUser() throws Exception {
// create user
edgeImitator.expectMessageAmount(4);
edgeImitator.expectMessageAmount(6);
User newTenantAdmin = new User();
newTenantAdmin.setAuthority(Authority.TENANT_ADMIN);
newTenantAdmin.setTenantId(tenantId);
@ -57,7 +55,9 @@ public class UserEdgeTest extends AbstractEdgeTest {
newTenantAdmin.setFirstName("Boris");
newTenantAdmin.setLastName("Johnson");
User savedTenantAdmin = createUser(newTenantAdmin, "tenant");
Assert.assertTrue(edgeImitator.waitForMessages()); // wait 4 messages - x2 user update msg and x2 user credentials update msgs
Assert.assertTrue(edgeImitator.waitForMessages()); // wait 6 messages - x2 user update msg and x4 user credentials update msgs (create + authenticate user)
Assert.assertEquals(2, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size());
Assert.assertEquals(4, edgeImitator.findAllMessagesByType(UserCredentialsUpdateMsg.class).size());
Optional<UserUpdateMsg> userUpdateMsgOpt = edgeImitator.findMessageByType(UserUpdateMsg.class);
Assert.assertTrue(userUpdateMsgOpt.isPresent());
UserUpdateMsg userUpdateMsg = userUpdateMsgOpt.get();
@ -133,7 +133,7 @@ public class UserEdgeTest extends AbstractEdgeTest {
Assert.assertTrue(edgeImitator.waitForMessages());
// create user
edgeImitator.expectMessageAmount(4);
edgeImitator.expectMessageAmount(6);
User customerUser = new User();
customerUser.setAuthority(Authority.CUSTOMER_USER);
customerUser.setTenantId(tenantId);
@ -142,7 +142,9 @@ public class UserEdgeTest extends AbstractEdgeTest {
customerUser.setFirstName("John");
customerUser.setLastName("Edwards");
User savedCustomerUser = createUser(customerUser, "customer");
Assert.assertTrue(edgeImitator.waitForMessages()); // wait 4 messages - x2 user update msg and x2 user credentials update msgs
Assert.assertTrue(edgeImitator.waitForMessages()); // wait 6 messages - x2 user update msg and x4 user credentials update msgs (create + authenticate user)
Assert.assertEquals(2, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size());
Assert.assertEquals(4, edgeImitator.findAllMessagesByType(UserCredentialsUpdateMsg.class).size());
Optional<UserUpdateMsg> userUpdateMsgOpt = edgeImitator.findMessageByType(UserUpdateMsg.class);
Assert.assertTrue(userUpdateMsgOpt.isPresent());
UserUpdateMsg userUpdateMsg = userUpdateMsgOpt.get();
@ -253,4 +255,5 @@ public class UserEdgeTest extends AbstractEdgeTest {
testAutoGeneratedCodeByProtobuf(userCredentialsUpdateMsg);
}
}

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

@ -429,6 +429,8 @@ public class HashPartitionServiceTest {
ReflectionTestUtils.setField(partitionService, "vcTopic", "tb.vc");
ReflectionTestUtils.setField(partitionService, "vcPartitions", 10);
ReflectionTestUtils.setField(partitionService, "hashFunctionName", hashFunctionName);
ReflectionTestUtils.setField(partitionService, "edgeTopic", "tb.edge");
ReflectionTestUtils.setField(partitionService, "edgePartitions", 10);
partitionService.init();
partitionService.partitionsInit();
return partitionService;

4
application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java

@ -904,7 +904,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
String expectedSubject = "Comment on 'test' alarm";
String expectedBody = TENANT_ADMIN_EMAIL + " added comment: text";
ArgumentCaptor<Map<String, String>> msgCaptor = ArgumentCaptor.forClass(Map.class);
await().atMost(30, TimeUnit.SECONDS).untilAsserted(() -> {
await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> {
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
eq(TEST_MOBILE_TOKEN), eq(expectedSubject),
eq(expectedBody),
@ -959,7 +959,7 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
}
private NotificationRequestStats awaitNotificationRequest(NotificationRequestId requestId) {
return await().atMost(30, TimeUnit.SECONDS)
return await().atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> getStats(requestId), Objects::nonNull);
}

4
application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java

@ -780,7 +780,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
assertThat(getMyNotifications(false, 100)).size().isZero();
createDevice("Device 1", "default", "111");
await().atMost(30, TimeUnit.SECONDS)
await().atMost(TIMEOUT, TimeUnit.SECONDS)
.untilAsserted(() -> {
assertThat(getMyNotifications(false, 100)).size().isEqualTo(1);
});
@ -798,7 +798,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest {
notificationRulesCache.evict(tenantId);
createDevice("Device 3", "default", "333");
await().atMost(30, TimeUnit.SECONDS)
await().atMost(TIMEOUT, TimeUnit.SECONDS)
.untilAsserted(() -> {
assertThat(getMyNotifications(false, 100)).size().isEqualTo(2);
});

7
application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java

@ -25,6 +25,7 @@ import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cache.TbTransactionalCache;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
@ -34,6 +35,7 @@ import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.AssetProfileId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.QueueId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType;
@ -98,6 +100,8 @@ public class DefaultTbClusterServiceTest {
protected TbQueueProducerProvider producerProvider;
@MockBean
protected TbRuleEngineProducerService ruleEngineProducerService;
@MockBean
protected TbTransactionalCache<EdgeId, String> edgeCache;
@SpyBean
protected TopicService topicService;
@ -400,4 +404,5 @@ public class DefaultTbClusterServiceTest {
((DefaultTbClusterService) clusterService).getRuleEngineProfileForEntityOrElseNull(tenantId, assetId, tbMsg);
verify(assetProfileCache, times(1)).get(tenantId, assetProfileId);
}
}
}

4
application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java

@ -69,14 +69,14 @@ class TbelInvokeServiceTest extends AbstractControllerTest {
for (int i = 0; i < 100; i++) {
msg.put("temperature", i);
boolean expected = i > 20;
boolean result = Boolean.valueOf(invokeScript(scriptId, JacksonUtil.toString(msg)));
boolean result = Boolean.parseBoolean(invokeScript(scriptId, JacksonUtil.toString(msg)));
Assert.assertEquals(expected, result);
}
long startTs = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
msg.put("temperature", i);
boolean expected = i > 20;
boolean result = Boolean.valueOf(invokeScript(scriptId, JacksonUtil.toString(msg)));
boolean result = Boolean.parseBoolean(invokeScript(scriptId, JacksonUtil.toString(msg)));
Assert.assertEquals(expected, result);
}
long duration = System.currentTimeMillis() - startTs;

108
application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.state;
import com.google.common.util.concurrent.Futures;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@ -28,8 +29,10 @@ import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceIdInfo;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.msg.TbMsgType;
import org.thingsboard.server.common.data.notification.rule.trigger.DeviceActivityTrigger;
@ -41,11 +44,13 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.notification.NotificationRuleProcessor;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.sql.query.EntityQueryRepository;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.QueueKey;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
@ -66,6 +71,7 @@ import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
@ -1070,4 +1076,106 @@ public class DefaultDeviceStateServiceTest {
then(service).should().fetchDeviceStateDataUsingSeparateRequests(deviceId);
}
@Test
public void givenDeviceAdded_whenOnQueueMsg_thenShouldCacheAndSaveActivityToFalse() throws InterruptedException {
// GIVEN
final long defaultTimeout = 1000;
initStateService(defaultTimeout);
given(deviceService.findDeviceById(any(TenantId.class), any(DeviceId.class))).willReturn(new Device(deviceId));
given(attributesService.find(any(TenantId.class), any(EntityId.class), any(AttributeScope.class), anyList())).willReturn(Futures.immediateFuture(Collections.emptyList()));
TransportProtos.DeviceStateServiceMsgProto proto = TransportProtos.DeviceStateServiceMsgProto.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setDeviceIdMSB(deviceId.getId().getMostSignificantBits())
.setDeviceIdLSB(deviceId.getId().getLeastSignificantBits())
.setAdded(true)
.setUpdated(false)
.setDeleted(false)
.build();
// WHEN
service.onQueueMsg(proto, TbCallback.EMPTY);
// THEN
await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(false);
then(telemetrySubscriptionService).should().saveAttrAndNotify(eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(false), any());
});
}
@Test
public void givenDeviceActivityEventHappenedAfterAdded_whenOnDeviceActivity_thenShouldCacheAndSaveActivityToTrue() throws InterruptedException {
// GIVEN
final long defaultTimeout = 1000;
initStateService(defaultTimeout);
long currentTime = System.currentTimeMillis();
DeviceState deviceState = DeviceState.builder()
.active(false)
.inactivityTimeout(service.getDefaultInactivityTimeoutInSec())
.build();
DeviceStateData stateData = DeviceStateData.builder()
.tenantId(tenantId)
.deviceId(deviceId)
.deviceCreationTime(currentTime - 10000)
.state(deviceState)
.metaData(TbMsgMetaData.EMPTY)
.build();
service.deviceStates.put(deviceId, stateData);
// WHEN
service.onDeviceActivity(tenantId, deviceId, currentTime);
// THEN
await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(true);
then(telemetrySubscriptionService).should().saveAttrAndNotify(eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(LAST_ACTIVITY_TIME), eq(currentTime), any());
then(telemetrySubscriptionService).should().saveAttrAndNotify(eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(true), any());
});
}
@Test
public void givenDeviceActivityEventHappenedBeforeAdded_whenOnQueueMsg_thenShouldSaveActivityStateUsingValueFromCache() throws InterruptedException {
// GIVEN
final long defaultTimeout = 1000;
initStateService(defaultTimeout);
given(deviceService.findDeviceById(any(TenantId.class), any(DeviceId.class))).willReturn(new Device(deviceId));
given(attributesService.find(any(TenantId.class), any(EntityId.class), any(AttributeScope.class), anyList())).willReturn(Futures.immediateFuture(Collections.emptyList()));
long currentTime = System.currentTimeMillis();
DeviceState deviceState = DeviceState.builder()
.active(true)
.lastConnectTime(currentTime - 8000)
.lastActivityTime(currentTime - 4000)
.lastDisconnectTime(0)
.lastInactivityAlarmTime(0)
.inactivityTimeout(3000)
.build();
DeviceStateData stateData = DeviceStateData.builder()
.tenantId(tenantId)
.deviceId(deviceId)
.deviceCreationTime(currentTime - 10000)
.state(deviceState)
.build();
service.deviceStates.put(deviceId, stateData);
// WHEN
TransportProtos.DeviceStateServiceMsgProto proto = TransportProtos.DeviceStateServiceMsgProto.newBuilder()
.setTenantIdMSB(tenantId.getId().getMostSignificantBits())
.setTenantIdLSB(tenantId.getId().getLeastSignificantBits())
.setDeviceIdMSB(deviceId.getId().getMostSignificantBits())
.setDeviceIdLSB(deviceId.getId().getLeastSignificantBits())
.setAdded(true)
.setUpdated(false)
.setDeleted(false)
.build();
service.onQueueMsg(proto, TbCallback.EMPTY);
// THEN
await().atMost(1, TimeUnit.SECONDS).untilAsserted(() -> {
assertThat(service.deviceStates.get(deviceId).getState().isActive()).isEqualTo(true);
then(telemetrySubscriptionService).should().saveAttrAndNotify(eq(TenantId.SYS_TENANT_ID), eq(deviceId), eq(AttributeScope.SERVER_SCOPE), eq(ACTIVITY_STATE), eq(true), any());
});
}
}

2
application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java

@ -241,7 +241,7 @@ public class VersionControlTest extends AbstractControllerTest {
checkImportedEntity(tenantId1, device, tenantId1, importedDevice);
assertThat(importedDevice.getDeviceProfileId()).isEqualTo(device.getDeviceProfileId());
assertThat(findDeviceCredentials(device.getId())).isEqualTo(deviceCredentials);
assertThat(findDeviceCredentials(device.getId())).isEqualToIgnoringGivenFields(deviceCredentials, "version");
assertThat(importedDevice.getFirmwareId()).isEqualTo(firmware.getId());
assertThat(importedDevice.getSoftwareId()).isEqualTo(software.getId());
}

22
application/src/test/java/org/thingsboard/server/system/BaseHttpDeviceApiTest.java

@ -42,7 +42,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*/
@TestPropertySource(properties = {
"transport.http.enabled=true",
"transport.http.max_payload_size=10000"
"transport.http.max_payload_size=/api/v1/*/rpc/**=10000;/api/v1/**=20000"
})
public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest {
@ -80,13 +80,13 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest {
@Test
public void testReplyToCommandWithLargeResponse() throws Exception {
String errorResponse = doPost("/api/v1/" + deviceCredentials.getCredentialsId() + "/rpc/5",
JacksonUtil.toString(createRpcResponsePayload(10001)),
JacksonUtil.toString(createJsonPayloadOfSize(10001)),
String.class,
status().isPayloadTooLarge());
assertThat(errorResponse).contains("Payload size exceeds the limit");
doPost("/api/v1/" + deviceCredentials.getCredentialsId() + "/rpc/5",
JacksonUtil.toString(createRpcResponsePayload(10000)),
JacksonUtil.toString(createJsonPayloadOfSize(10000)),
String.class,
status().isOk());
}
@ -105,7 +105,21 @@ public abstract class BaseHttpDeviceApiTest extends AbstractControllerTest {
status().isOk());
}
private String createRpcResponsePayload(int size) {
@Test
public void testPostLargeAttribute() throws Exception {
String errorResponse = doPost("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes",
JacksonUtil.toString(createJsonPayloadOfSize(20001)),
String.class,
status().isPayloadTooLarge());
assertThat(errorResponse).contains("Payload size exceeds the limit");
doPost("/api/v1/" + deviceCredentials.getCredentialsId() + "/attributes",
JacksonUtil.toString(createJsonPayloadOfSize(20000)),
String.class,
status().isOk());
}
private String createJsonPayloadOfSize(int size) {
String value = "a".repeat(size - 19);
return "{\"result\":\"" + value + "\"}";
}

4
application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java

@ -95,7 +95,7 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest {
expectedStatuses = Arrays.asList(QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, UPDATING, UPDATED);
List<TsKvEntry> ts = await("await on timeseries")
.atMost(30, TimeUnit.SECONDS)
.atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" +
savedDevice.getId().getId() + "/values/timeseries?orderBy=ASC&keys=fw_state&startTs=0&endTs=" +
System.currentTimeMillis(), new TypeReference<>() {
@ -127,7 +127,7 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest {
QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED);
List<TsKvEntry> ts = await("await on timeseries")
.atMost(30, TimeUnit.SECONDS)
.atMost(TIMEOUT, TimeUnit.SECONDS)
.until(() -> getSwStateTelemetryFromAPI(device.getId().getId()), this::predicateForStatuses);
log.warn("Object9: Got the ts: {}", ts);
}

5
common/cache/src/main/java/org/thingsboard/server/cache/RedisTbTransactionalCache.java

@ -72,9 +72,10 @@ public abstract class RedisTbTransactionalCache<K extends Serializable, V extend
this.evictExpiration = Expiration.from(configuration.getEvictTtlInMs(), TimeUnit.MILLISECONDS);
this.cacheTtl = Optional.ofNullable(cacheSpecsMap)
.map(CacheSpecsMap::getSpecs)
.map(x -> x.get(cacheName))
.map(specs -> specs.get(cacheName))
.map(CacheSpecs::getTimeToLiveInMinutes)
.map(t -> Expiration.from(t, TimeUnit.MINUTES))
.filter(ttl -> !ttl.equals(0))
.map(ttl -> Expiration.from(ttl, TimeUnit.MINUTES))
.orElseGet(Expiration::persistent);
this.cacheEnabled = Optional.ofNullable(cacheSpecsMap)
.map(CacheSpecsMap::getSpecs)

4
common/cache/src/main/java/org/thingsboard/server/cache/TbCaffeineCacheConfiguration.java

@ -75,8 +75,10 @@ public class TbCaffeineCacheConfiguration {
= Caffeine.newBuilder()
.weigher(collectionSafeWeigher())
.maximumWeight(cacheSpec.getMaxSize())
.expireAfterWrite(cacheSpec.getTimeToLiveInMinutes(), TimeUnit.MINUTES)
.ticker(ticker());
if (!cacheSpec.getTimeToLiveInMinutes().equals(0)) {
caffeineBuilder.expireAfterWrite(cacheSpec.getTimeToLiveInMinutes(), TimeUnit.MINUTES);
}
return new CaffeineCache(name, caffeineBuilder.build());
}

10
common/cache/src/main/java/org/thingsboard/server/cache/edge/RelatedEdgesCacheValue.java

@ -15,7 +15,9 @@
*/
package org.thingsboard.server.cache.edge;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.page.PageData;
@ -23,15 +25,13 @@ import java.io.Serial;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class RelatedEdgesCacheValue implements Serializable {
@Serial
private static final long serialVersionUID = -2765080094748518572L;
private final PageData<EdgeId> pageData;
public RelatedEdgesCacheValue(PageData<EdgeId> pageData) {
this.pageData = pageData;
}
private PageData<EdgeId> pageData;
}

20
common/cluster-api/src/main/java/org/thingsboard/server/cluster/TbClusterService.java

@ -29,13 +29,17 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.msg.edge.EdgeEventUpdateMsg;
import org.thingsboard.server.common.msg.edge.EdgeHighPriorityMsg;
import org.thingsboard.server.common.msg.edge.FromEdgeSyncResponse;
import org.thingsboard.server.common.msg.edge.ToEdgeSyncRequest;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.RestApiCallResponseMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdgeMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToVersionControlServiceMsg;
@ -52,7 +56,7 @@ public interface TbClusterService extends TbQueueClusterService {
void pushMsgToCore(ToDeviceActorNotificationMsg msg, TbQueueCallback callback);
void broadcastToCore(TransportProtos.ToCoreNotificationMsg msg);
void broadcastToCore(ToCoreNotificationMsg msg);
void pushMsgToVersionControl(TenantId tenantId, ToVersionControlServiceMsg msg, TbQueueCallback callback);
@ -96,11 +100,17 @@ public interface TbClusterService extends TbQueueClusterService {
void onResourceDeleted(TbResourceInfo resource, TbQueueCallback callback);
void onEdgeEventUpdate(TenantId tenantId, EdgeId edgeId);
void onEdgeHighPriorityMsg(EdgeHighPriorityMsg msg);
void pushEdgeSyncRequestToCore(ToEdgeSyncRequest toEdgeSyncRequest);
void onEdgeEventUpdate(EdgeEventUpdateMsg msg);
void pushEdgeSyncResponseToCore(FromEdgeSyncResponse fromEdgeSyncResponse);
void onEdgeStateChangeEvent(ComponentLifecycleMsg msg);
void pushEdgeSyncRequestToEdge(ToEdgeSyncRequest request);
void pushEdgeSyncResponseToCore(FromEdgeSyncResponse response, String requestServiceId);
void pushMsgToEdge(TenantId tenantId, EntityId entityId, ToEdgeMsg msg, TbQueueCallback callback);
void sendNotificationMsgToEdge(TenantId tenantId, EdgeId edgeId, EntityId entityId, String body, EdgeEventType type, EdgeEventActionType action, EdgeId sourceEdgeId);

3
common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java

@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.edge.EdgeSearchQuery;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.TenantProfileId;
import org.thingsboard.server.common.data.page.PageData;
@ -97,6 +98,8 @@ public interface EdgeService extends EntityDaoService {
String findMissingToRelatedRuleChains(TenantId tenantId, EdgeId edgeId, String tbRuleChainInputNodeClassName);
Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws Exception;
ListenableFuture<Boolean> isEdgeActiveAsync(TenantId tenantId, EdgeId edgeId, String activityState);
}

1
common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java

@ -25,6 +25,7 @@ public class CacheConstants {
public static final String USER_CACHE = "users";
public static final String ENTITY_VIEW_CACHE = "entityViews";
public static final String EDGE_CACHE = "edges";
public static final String EDGE_SESSIONS_CACHE = "edgeSessions";
public static final String RELATED_EDGES_CACHE = "relatedEdges";
public static final String CLAIM_DEVICES_CACHE = "claimDevices";
public static final String SECURITY_SETTINGS_CACHE = "securitySettings";

2
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -140,4 +140,6 @@ public class DataConstants {
public static final String SQ_QUEUE_TOPIC = "tb_rule_engine.sq";
public static final String QUEUE_NAME = "queueName";
public static final String EDGE_QUEUE_NAME = "Edge";
}

4
common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java

@ -75,9 +75,9 @@ public class Edge extends BaseDataWithAdditionalInfo<EdgeId> implements HasLabel
this.tenantId = edge.getTenantId();
this.customerId = edge.getCustomerId();
this.rootRuleChainId = edge.getRootRuleChainId();
this.name = edge.getName();
this.type = edge.getType();
this.label = edge.getLabel();
this.name = edge.getName();
this.routingKey = edge.getRoutingKey();
this.secret = edge.getSecret();
this.version = edge.getVersion();
@ -87,9 +87,9 @@ public class Edge extends BaseDataWithAdditionalInfo<EdgeId> implements HasLabel
this.tenantId = edge.getTenantId();
this.customerId = edge.getCustomerId();
this.rootRuleChainId = edge.getRootRuleChainId();
this.name = edge.getName();
this.type = edge.getType();
this.label = edge.getLabel();
this.name = edge.getName();
this.routingKey = edge.getRoutingKey();
this.secret = edge.getSecret();
this.version = edge.getVersion();

6
common/data/src/main/java/org/thingsboard/server/common/data/exception/EntityVersionMismatchException.java

@ -15,10 +15,16 @@
*/
package org.thingsboard.server.common.data.exception;
import org.thingsboard.server.common.data.EntityType;
public class EntityVersionMismatchException extends RuntimeException {
public EntityVersionMismatchException(String message, Throwable cause) {
super(message, cause);
}
public EntityVersionMismatchException(EntityType entityType, Throwable cause) {
this((entityType != null ? entityType.getNormalName() : "Entity") + " was already changed by someone else", cause);
}
}

6
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java

@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.rule;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.thingsboard.server.common.data.HasVersion;
import org.thingsboard.server.common.data.id.RuleChainId;
import java.util.ArrayList;
@ -27,11 +28,14 @@ import java.util.List;
*/
@Schema
@Data
public class RuleChainMetaData {
public class RuleChainMetaData implements HasVersion {
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "JSON object with Rule Chain Id.", accessMode = Schema.AccessMode.READ_ONLY)
private RuleChainId ruleChainId;
@Schema(requiredMode = Schema.RequiredMode.NOT_REQUIRED, description = "Version of the Rule Chain")
private Long version;
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "Index of the first rule node in the 'nodes' list")
private Integer firstNodeIndex;

2
common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/RuleChainExportData.java

@ -29,7 +29,7 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData;
public class RuleChainExportData extends EntityExportData<RuleChain> {
@JsonProperty(index = 3)
@JsonIgnoreProperties("ruleChainId")
@JsonIgnoreProperties({"ruleChainId", "version"})
private RuleChainMetaData metaData;
}

5
common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java

@ -16,6 +16,7 @@
package org.thingsboard.server.common.msg;
import lombok.Getter;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
@ -36,7 +37,7 @@ public enum MsgType {
/**
* ADDED/UPDATED/DELETED events for main entities.
*
* See {@link org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg}
* See {@link ComponentLifecycleMsg}
*/
COMPONENT_LIFE_CYCLE_MSG,
@ -126,6 +127,7 @@ public enum MsgType {
* Message that is sent on Edge Event to Edge Session
*/
EDGE_EVENT_UPDATE_TO_EDGE_SESSION_MSG,
EDGE_HIGH_PRIORITY_TO_EDGE_SESSION_MSG,
/**
* Messages that are sent to and from edge session to start edge synchronization process
@ -143,4 +145,5 @@ public enum MsgType {
MsgType(boolean ignoreOnStart) {
this.ignoreOnStart = ignoreOnStart;
}
}

1
common/message/src/main/java/org/thingsboard/server/common/msg/edge/EdgeEventUpdateMsg.java

@ -32,4 +32,5 @@ public class EdgeEventUpdateMsg implements EdgeSessionMsg {
public MsgType getMsgType() {
return MsgType.EDGE_EVENT_UPDATE_TO_EDGE_SESSION_MSG;
}
}

21
application/src/main/java/org/thingsboard/server/service/edge/EdgeNotificationService.java → common/message/src/main/java/org/thingsboard/server/common/msg/edge/EdgeHighPriorityMsg.java

@ -13,17 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge;
package org.thingsboard.server.common.msg.edge;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.RuleChainId;
import lombok.Data;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.common.msg.MsgType;
public interface EdgeNotificationService {
@Data
public class EdgeHighPriorityMsg implements EdgeSessionMsg {
Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws Exception;
private final TenantId tenantId;
private final EdgeEvent edgeEvent;
@Override
public MsgType getMsgType() {
return MsgType.EDGE_HIGH_PRIORITY_TO_EDGE_SESSION_MSG;
}
void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback);
}

11
common/message/src/main/java/org/thingsboard/server/common/msg/edge/EdgeSessionMsg.java

@ -15,8 +15,13 @@
*/
package org.thingsboard.server.common.msg.edge;
import org.thingsboard.server.common.msg.aware.TenantAwareMsg;
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.MsgType;
public interface EdgeSessionMsg {
TenantId getTenantId();
MsgType getMsgType();
public interface EdgeSessionMsg extends TenantAwareMsg, ToAllNodesMsg {
}

1
common/message/src/main/java/org/thingsboard/server/common/msg/edge/FromEdgeSyncResponse.java

@ -37,4 +37,5 @@ public class FromEdgeSyncResponse implements EdgeSessionMsg {
public MsgType getMsgType() {
return MsgType.EDGE_SYNC_RESPONSE_FROM_EDGE_SESSION_MSG;
}
}

2
common/message/src/main/java/org/thingsboard/server/common/msg/edge/ToEdgeSyncRequest.java

@ -30,9 +30,11 @@ public class ToEdgeSyncRequest implements EdgeSessionMsg {
private final UUID id;
private final TenantId tenantId;
private final EdgeId edgeId;
private final String serviceId;
@Override
public MsgType getMsgType() {
return MsgType.EDGE_SYNC_REQUEST_TO_EDGE_SESSION_MSG;
}
}

10
common/message/src/main/java/org/thingsboard/server/common/msg/tools/MaxPayloadSizeExceededException.java

@ -15,10 +15,16 @@
*/
package org.thingsboard.server.common.msg.tools;
import lombok.Getter;
public class MaxPayloadSizeExceededException extends RuntimeException {
public MaxPayloadSizeExceededException() {
super("Payload size exceeds the limit");
@Getter
private final long limit;
public MaxPayloadSizeExceededException(long limit) {
super("Payload size exceeds the limit of " + limit + " bytes");
this.limit = limit;
}
}

59
common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java

@ -16,6 +16,7 @@
package org.thingsboard.server.common.util;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.JacksonUtil;
@ -26,6 +27,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.ResourceSubType;
import org.thingsboard.server.common.data.ResourceType;
@ -36,6 +38,8 @@ import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfigu
import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.data.PowerMode;
import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.ApiUsageStateId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
@ -68,6 +72,7 @@ import org.thingsboard.server.common.data.sync.vc.RepositoryAuthMethod;
import org.thingsboard.server.common.data.sync.vc.RepositorySettings;
import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.common.msg.edge.EdgeEventUpdateMsg;
import org.thingsboard.server.common.msg.edge.EdgeHighPriorityMsg;
import org.thingsboard.server.common.msg.edge.FromEdgeSyncResponse;
import org.thingsboard.server.common.msg.edge.ToEdgeSyncRequest;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
@ -140,6 +145,7 @@ public class ProtoUtils {
.setRequestIdLSB(request.getId().getLeastSignificantBits())
.setEdgeIdMSB(request.getEdgeId().getId().getMostSignificantBits())
.setEdgeIdLSB(request.getEdgeId().getId().getLeastSignificantBits())
.setServiceId(request.getServiceId())
.build();
}
@ -147,7 +153,8 @@ public class ProtoUtils {
return new ToEdgeSyncRequest(
new UUID(proto.getRequestIdMSB(), proto.getRequestIdLSB()),
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
new EdgeId(new UUID(proto.getEdgeIdMSB(), proto.getEdgeIdLSB()))
EdgeId.fromUUID(new UUID(proto.getEdgeIdMSB(), proto.getEdgeIdLSB())),
proto.getServiceId()
);
}
@ -168,12 +175,56 @@ public class ProtoUtils {
return new FromEdgeSyncResponse(
new UUID(proto.getResponseIdMSB(), proto.getResponseIdLSB()),
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
new EdgeId(new UUID(proto.getEdgeIdMSB(), proto.getEdgeIdLSB())),
EdgeId.fromUUID(new UUID(proto.getEdgeIdMSB(), proto.getEdgeIdLSB())),
proto.getSuccess(),
proto.getError()
);
}
public static TransportProtos.EdgeHighPriorityMsgProto toProto(EdgeHighPriorityMsg msg) {
TransportProtos.EdgeHighPriorityMsgProto.Builder builder = TransportProtos.EdgeHighPriorityMsgProto.newBuilder()
.setTenantIdMSB(msg.getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(msg.getTenantId().getId().getLeastSignificantBits())
.setType(msg.getEdgeEvent().getType().name())
.setAction(msg.getEdgeEvent().getAction().name());
if (msg.getEdgeEvent().getEntityId() != null) {
builder.setEntityIdMSB(msg.getEdgeEvent().getEntityId().getMostSignificantBits());
builder.setEntityIdLSB(msg.getEdgeEvent().getEntityId().getLeastSignificantBits());
}
if (msg.getEdgeEvent().getEdgeId() != null) {
builder.setEdgeIdMSB(msg.getEdgeEvent().getEdgeId().getId().getMostSignificantBits());
builder.setEdgeIdLSB(msg.getEdgeEvent().getEdgeId().getId().getLeastSignificantBits());
}
if (msg.getEdgeEvent().getBody() != null) {
builder.setBody(JacksonUtil.toString(msg.getEdgeEvent().getBody()));
}
return builder.build();
}
public static EdgeHighPriorityMsg fromProto(TransportProtos.EdgeHighPriorityMsgProto proto) {
EdgeEventType type = EdgeEventType.valueOf(proto.getType());
EdgeEventActionType actionType = EdgeEventActionType.valueOf(proto.getAction());
JsonNode body = proto.hasBody() ? JacksonUtil.toJsonNode(proto.getBody()) : null;
EdgeId edgeId = null;
if (proto.hasEdgeIdMSB() && proto.hasEdgeIdLSB()) {
edgeId = EdgeId.fromUUID(new UUID(proto.getEdgeIdMSB(), proto.getEdgeIdLSB()));
}
EntityId entityId = null;
if (proto.hasEntityIdMSB() && proto.hasEntityIdLSB()) {
entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, new UUID(proto.getEntityIdMSB(), proto.getEntityIdLSB()));
}
return new EdgeHighPriorityMsg(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
EdgeUtils.constructEdgeEvent(TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
edgeId, type, actionType, entityId, body)
);
}
public static TransportProtos.EdgeEventUpdateMsgProto toProto(EdgeEventUpdateMsg msg) {
return TransportProtos.EdgeEventUpdateMsgProto.newBuilder()
.setTenantIdMSB(msg.getTenantId().getId().getMostSignificantBits())
@ -186,7 +237,7 @@ public class ProtoUtils {
public static EdgeEventUpdateMsg fromProto(TransportProtos.EdgeEventUpdateMsgProto proto) {
return new EdgeEventUpdateMsg(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),
new EdgeId(new UUID(proto.getEdgeIdMSB(), proto.getEdgeIdLSB()))
EdgeId.fromUUID(new UUID(proto.getEdgeIdMSB(), proto.getEdgeIdLSB()))
);
}
@ -208,7 +259,7 @@ public class ProtoUtils {
private static DeviceEdgeUpdateMsg fromProto(TransportProtos.DeviceEdgeUpdateMsgProto proto) {
EdgeId edgeId = null;
if (proto.hasEdgeIdMSB() && proto.hasEdgeIdLSB()) {
edgeId = new EdgeId(new UUID(proto.getEdgeIdMSB(), proto.getEdgeIdLSB()));
edgeId = EdgeId.fromUUID(new UUID(proto.getEdgeIdMSB(), proto.getEdgeIdLSB()));
}
return new DeviceEdgeUpdateMsg(
TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB())),

45
common/proto/src/main/proto/queue.proto

@ -146,9 +146,9 @@ message KeyValueProto {
}
enum AttributeScopeProto {
CLIENT_SCOPE = 0;
SERVER_SCOPE = 1;
SHARED_SCOPE = 2;
CLIENT_SCOPE = 0;
SERVER_SCOPE = 1;
SHARED_SCOPE = 2;
}
message AttributeKey {
@ -421,7 +421,7 @@ message GatewayDisconnectDeviceMsg {
}
enum TransportApiRequestErrorCode {
UNKNOWN_TRANSPORT_API_ERROR = 0;
UNKNOWN_TRANSPORT_API_ERROR = 0;
ENTITY_LIMIT = 1;
}
@ -834,6 +834,8 @@ message TbEntitySubEventCallbackProto {
int32 seqNumber = 3;
int64 attributesUpdateTs = 4;
int64 timeSeriesUpdateTs = 5;
int64 tenantIdMSB = 6;
int64 tenantIdLSB = 7;
}
message TsValueProto {
@ -1140,6 +1142,18 @@ message EdgeNotificationMsgProto {
int64 originatorEdgeIdLSB = 14;
}
message EdgeHighPriorityMsgProto {
int64 tenantIdMSB = 1;
int64 tenantIdLSB = 2;
optional int64 edgeIdMSB = 3;
optional int64 edgeIdLSB = 4;
string type = 5;
string action = 6;
optional string body = 7;
optional int64 entityIdMSB = 8;
optional int64 entityIdLSB = 9;
}
message EdgeEventUpdateMsgProto {
int64 tenantIdMSB = 1;
int64 tenantIdLSB = 2;
@ -1154,6 +1168,7 @@ message ToEdgeSyncRequestMsgProto {
int64 requestIdLSB = 4;
int64 edgeIdMSB = 5;
int64 edgeIdLSB = 6;
string serviceId = 7;
}
message FromEdgeSyncResponseMsgProto {
@ -1475,7 +1490,7 @@ message ToCoreMsg {
DeviceStateServiceMsgProto deviceStateServiceMsg = 2;
SubscriptionMgrMsgProto toSubscriptionMgrMsg = 3;
bytes toDeviceActorNotificationMsg = 4 [deprecated = true];
EdgeNotificationMsgProto edgeNotificationMsg = 5;
EdgeNotificationMsgProto edgeNotificationMsg = 5 [deprecated = true];
DeviceActivityProto deviceActivityMsg = 6;
NotificationSchedulerServiceMsg notificationSchedulerServiceMsg = 7;
LifecycleEventProto lifecycleEventMsg = 8;
@ -1498,13 +1513,27 @@ message ToCoreNotificationMsg {
NotificationRuleProcessorMsg notificationRuleProcessorMsg = 7;
ComponentLifecycleMsgProto componentLifecycle = 8;
CoreStartupMsg coreStartupMsg = 9;
EdgeEventUpdateMsgProto edgeEventUpdate = 10;
ToEdgeSyncRequestMsgProto toEdgeSyncRequest = 11;
FromEdgeSyncResponseMsgProto fromEdgeSyncResponse = 12;
EdgeEventUpdateMsgProto edgeEventUpdate = 10 [deprecated = true];
ToEdgeSyncRequestMsgProto toEdgeSyncRequest = 11 [deprecated = true];
FromEdgeSyncResponseMsgProto fromEdgeSyncResponse = 12 [deprecated = true];
ResourceCacheInvalidateMsg resourceCacheInvalidateMsg = 13;
RestApiCallResponseMsgProto restApiCallResponseMsg = 50;
}
/* Messages to Edge queue that are handled by ThingsBoard Core Service */
message ToEdgeMsg {
EdgeNotificationMsgProto edgeNotificationMsg = 1;
}
message ToEdgeNotificationMsg {
EdgeHighPriorityMsgProto edgeHighPriority = 1;
EdgeEventUpdateMsgProto edgeEventUpdate = 2;
ToEdgeSyncRequestMsgProto toEdgeSyncRequest = 3;
FromEdgeSyncResponseMsgProto fromEdgeSyncResponse = 4;
ComponentLifecycleMsgProto componentLifecycle = 5;
}
/* Messages that are handled by ThingsBoard RuleEngine Service */
message ToRuleEngineMsg {
int64 tenantIdMSB = 1;

13
common/proto/src/test/java/org/thingsboard/server/common/util/ProtoUtilsTest.java

@ -25,6 +25,7 @@ import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.Tenant;
@ -34,6 +35,8 @@ import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConf
import org.thingsboard.server.common.data.device.data.DeviceConfiguration;
import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId;
@ -54,6 +57,7 @@ import org.thingsboard.server.common.data.sync.vc.RepositorySettings;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration;
import org.thingsboard.server.common.msg.edge.EdgeEventUpdateMsg;
import org.thingsboard.server.common.msg.edge.EdgeHighPriorityMsg;
import org.thingsboard.server.common.msg.edge.FromEdgeSyncResponse;
import org.thingsboard.server.common.msg.edge.ToEdgeSyncRequest;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
@ -110,6 +114,13 @@ class ProtoUtilsTest {
}
}
@Test
void protoEdgeHighPrioritySerialization() {
EdgeHighPriorityMsg msg = new EdgeHighPriorityMsg(tenantId, EdgeUtils.constructEdgeEvent(tenantId, edgeId,
EdgeEventType.DEVICE, EdgeEventActionType.ADDED, deviceId, JacksonUtil.newObjectNode()));
assertThat(ProtoUtils.fromProto(ProtoUtils.toProto(msg))).as("deserialized").isEqualTo(msg);
}
@Test
void protoEdgeEventUpdateSerialization() {
EdgeEventUpdateMsg msg = new EdgeEventUpdateMsg(tenantId, edgeId);
@ -118,7 +129,7 @@ class ProtoUtilsTest {
@Test
void protoToEdgeSyncRequestSerialization() {
ToEdgeSyncRequest msg = new ToEdgeSyncRequest(id, tenantId, edgeId);
ToEdgeSyncRequest msg = new ToEdgeSyncRequest(id, tenantId, edgeId, "serviceId");
assertThat(ProtoUtils.fromProto(ProtoUtils.toProto(msg))).as("deserialized").isEqualTo(msg);
}

7
common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusQueueConfigs.java

@ -27,6 +27,7 @@ import java.util.Map;
@Component
@ConditionalOnExpression("'${queue.type:null}'=='service-bus'")
public class TbServiceBusQueueConfigs {
@Value("${queue.service-bus.queue-properties.core:}")
private String coreProperties;
@Value("${queue.service-bus.queue-properties.rule-engine:}")
@ -39,6 +40,9 @@ public class TbServiceBusQueueConfigs {
private String jsExecutorProperties;
@Value("${queue.service-bus.queue-properties.version-control:}")
private String vcProperties;
@Value("${queue.service-bus.queue-properties.edge:}")
private String edgeProperties;
@Getter
private Map<String, String> coreConfigs;
@Getter
@ -51,6 +55,8 @@ public class TbServiceBusQueueConfigs {
private Map<String, String> jsExecutorConfigs;
@Getter
private Map<String, String> vcConfigs;
@Getter
private Map<String, String> edgeConfigs;
@PostConstruct
private void init() {
@ -60,6 +66,7 @@ public class TbServiceBusQueueConfigs {
notificationsConfigs = PropertyUtils.getProps(notificationsProperties);
jsExecutorConfigs = PropertyUtils.getProps(jsExecutorProperties);
vcConfigs = PropertyUtils.getProps(vcProperties);
edgeConfigs = PropertyUtils.getProps(edgeProperties);
}
}

2
common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java

@ -88,7 +88,7 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider {
assignedTenantProfiles = Collections.emptySet();
}
generateNewServiceInfoWithCurrentSystemInfo();
generateNewServiceInfoWithCurrentSystemInfo();
}
@AfterContextReady

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save