Browse Source

Merge remote-tracking branch 'ce/develop/3.4' into aggregationTsDao_fix

pull/5905/head
van_vanich 4 years ago
parent
commit
4e8bd88fb3
  1. 3
      .gitignore
  2. 20
      README.md
  3. 2
      application/pom.xml
  4. 3
      application/src/main/data/json/system/widget_bundles/cards.json
  5. 11
      application/src/main/data/json/system/widget_bundles/charts.json
  6. 24
      application/src/main/data/json/system/widget_bundles/control_widgets.json
  7. 2
      application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json
  8. 18
      application/src/main/data/json/system/widget_bundles/input_widgets.json
  9. 26
      application/src/main/data/json/system/widget_bundles/maps.json
  10. 50
      application/src/main/data/upgrade/3.3.3/schema_event_ttl_procedure.sql
  11. 29
      application/src/main/data/upgrade/3.3.3/schema_update.sql
  12. 184
      application/src/main/java/org/apache/kafka/common/network/NetworkReceive.java
  13. 14
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  14. 3
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  15. 2
      application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java
  16. 38
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  17. 4
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  18. 7
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  19. 72
      application/src/main/java/org/thingsboard/server/controller/EdgeController.java
  20. 4
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  21. 37
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
  22. 9
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketMsg.java
  23. 11
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketMsgType.java
  24. 24
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketPingMsg.java
  25. 20
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketTextMsg.java
  26. 5
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  27. 134
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
  28. 12
      application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
  29. 110
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeLicenseService.java
  30. 11
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
  31. 8
      application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java
  32. 6
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  33. 8
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
  34. 7
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  35. 86
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java
  36. 43
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityEdgeProcessor.java
  37. 2
      application/src/main/java/org/thingsboard/server/service/importing/BulkImportColumnType.java
  38. 22
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  39. 17
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java
  40. 148
      application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java
  41. 83
      application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java
  42. 16
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  43. 45
      application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java
  44. 210
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  45. 3
      application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java
  46. 13
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java
  47. 14
      application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java
  48. 2
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
  49. 80
      application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java
  50. 5
      application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java
  51. 32
      application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java
  52. 11
      application/src/main/resources/thingsboard.yml
  53. 6
      application/src/test/java/org/thingsboard/server/actors/stats/StatsActorTest.java
  54. 33
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  55. 61
      application/src/test/java/org/thingsboard/server/controller/AbstractWebsocketTest.java
  56. 230
      application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
  57. 2
      application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
  58. 80
      application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java
  59. 2
      application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
  60. 23
      application/src/test/java/org/thingsboard/server/controller/sql/AlarmControllerSqlTest.java
  61. 435
      application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java
  62. 4
      application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
  63. 62
      application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
  64. 23
      application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
  65. 86
      application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java
  66. 1
      application/src/test/java/org/thingsboard/server/service/ttl/EventsCleanUpServiceTest.java
  67. 2
      application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java
  68. 50
      application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesIntegrationTest.java
  69. 14
      application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesProtoIntegrationTest.java
  70. 5
      application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java
  71. 319
      application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
  72. 104
      application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java
  73. 8
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java
  74. 222
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java
  75. 26
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java
  76. 13
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java
  77. 6
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java
  78. 235
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/Lwm2mServer.java
  79. 32
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java
  80. 12
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java
  81. 162
      application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java
  82. 168
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java
  83. 6
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java
  84. 7
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java
  85. 78
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java
  86. 25
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java
  87. 324
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java
  88. 76
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java
  89. 91
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java
  90. 115
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java
  91. 112
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java
  92. 69
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java
  93. 8
      application/src/test/resources/application-test.properties
  94. 0
      application/src/test/resources/logback-test.xml
  95. 299
      application/src/test/resources/lwm2m/credentials/shell/lwM2M_cfssl_chain_trusts_and_clients_for_test.sh
  96. 65
      application/src/test/resources/lwm2m/credentials/shell/lwm2m_cfssl_chain_for_test_All.sh
  97. 298
      application/src/test/resources/lwm2m/credentials/shell/lwm2m_cfssl_chain_server_for_test.sh
  98. 2
      common/actor/pom.xml
  99. 2
      common/cache/pom.xml
  100. 2
      common/cluster-api/pom.xml

3
.gitignore

@ -30,9 +30,10 @@ pom.xml.versionsBackup
**/build
**/target
**/Californium.properties
**/Californium3.properties
**/.env
.instance_id
rebuild-docker.sh
*/.run/**
.run/**
.run
.run

20
README.md

@ -12,20 +12,20 @@ ThingsBoard documentation is hosted on [thingsboard.io](https://thingsboard.io/d
## IoT use cases
[**Smart metering**](https://thingsboard.io/smart-metering/)
[![Smart metering](https://user-images.githubusercontent.com/8308069/31455788-6888a948-aec1-11e7-9819-410e0ba785e0.gif "Smart metering")](https://thingsboard.io/smart-metering/)
[**IoT Rule Engine**](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/)
[![IoT Rule Engine](https://thingsboard.io/images/demo/send-email-rule-chain.gif "IoT Rule Engine")](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/)
[**Smart energy**](https://thingsboard.io/smart-energy/)
[![Smart energy](https://cloud.githubusercontent.com/assets/8308069/24495682/aebd45d0-153e-11e7-8de4-7360ed5b41ae.gif "Smart energy")](https://thingsboard.io/smart-energy/)
[![Smart energy](https://user-images.githubusercontent.com/8308069/152984256-eb48564a-645c-468d-912b-f554b63104a5.gif "Smart energy")](https://thingsboard.io/smart-energy/)
[**Fleet tracking**](https://thingsboard.io/fleet-tracking/)
[![Fleet tracking](https://user-images.githubusercontent.com/8308069/152984528-0054ed55-8b8b-4cda-ba45-02fe95a81222.gif "Fleet tracking")](https://thingsboard.io/fleet-tracking/)
[**Smart farming**](https://thingsboard.io/smart-farming/)
[![Smart farming](https://cloud.githubusercontent.com/assets/8308069/24496824/10dc1144-1542-11e7-8aa1-5d3a281d5a1a.gif "Smart farming")](https://thingsboard.io/smart-farming/)
[![Smart farming](https://user-images.githubusercontent.com/8308069/152984443-a98b7d3d-ff7a-4037-9011-e71e1e6f755f.gif "Smart farming")](https://thingsboard.io/smart-farming/)
[**Fleet tracking**](https://thingsboard.io/fleet-tracking/)
[![Fleet tracking](https://cloud.githubusercontent.com/assets/8308069/24497169/3a1a61e0-1543-11e7-8d55-3c8a13f35634.gif "Fleet tracking")](https://thingsboard.io/fleet-tracking/)
[**IoT Rule Engine**](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/)
[![IoT Rule Engine](https://thingsboard.io/images/demo/send-email-rule-chain.gif "IoT Rule Engine")](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/)
[**Smart metering**](https://thingsboard.io/smart-metering/)
[![Smart metering](https://user-images.githubusercontent.com/8308069/31455788-6888a948-aec1-11e7-9819-410e0ba785e0.gif "Smart metering")](https://thingsboard.io/smart-metering/)
## Getting Started

2
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.3-SNAPSHOT</version>
<version>3.4.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>application</artifactId>

3
application/src/main/data/json/system/widget_bundles/cards.json

@ -54,9 +54,10 @@
"resources": [],
"templateHtml": "<tb-timeseries-table-widget \n [ctx]=\"ctx\">\n</tb-timeseries-table-widget>",
"templateCss": "",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}",
"controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onLatestDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true,\n hasAdditionalLatestDataKeys: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true,\n hasShowCondition: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"enableSearch\": {\n \"title\": \"Enable search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"reserveSpaceForHiddenAction\": {\n \"title\": \"Hidden cell button actions display mode\",\n \"type\": \"string\",\n \"default\": \"true\"\n },\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"useEntityLabel\": {\n \"title\": \"Use entity label in tab name\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"disableStickyHeader\": {\n \"title\": \"Disable sticky header\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"enableSearch\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n {\n \"key\": \"reserveSpaceForHiddenAction\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"true\",\n \"label\": \"Show empty space instead of hidden cell button action\"\n },\n {\n \"value\": \"false\",\n \"label\": \"Don't reserve space for hidden action buttons\"\n }\n ]\n },\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"useEntityLabel\",\n \"defaultPageSize\",\n \"hideEmptyLines\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/row_style_fn\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}",
"dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_style_fn\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_content_fn\",\n \"condition\": \"model.useCellContentFunction === true\"\n }\n ]\n}",
"latestDataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"LatestDataKeySettings\",\n \"properties\": {\n \"show\": {\n \"title\": \"Show latest data column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"order\": {\n \"title\": \"Latest data column order\",\n \"type\": \"number\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"show\",\n {\n \"key\": \"order\",\n \"condition\": \"model.show === true\"\n },\n {\n \"key\": \"useCellStyleFunction\",\n \"condition\": \"model.show === true\"\n },\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_style_fn\",\n \"condition\": \"model.show === true && model.useCellStyleFunction === true\"\n },\n {\n \"key\": \"useCellContentFunction\",\n \"condition\": \"model.show === true\"\n },\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"helpId\": \"widget/lib/timeseries/cell_content_fn\",\n \"condition\": \"model.show === true && model.useCellContentFunction === true\"\n }\n ]\n}",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{},\"showTitleIcon\":false,\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\"}"
}
},

11
application/src/main/data/json/system/widget_bundles/charts.json

@ -146,7 +146,7 @@
"resources": [],
"templateHtml": "",
"templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
"controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.flot.latestDataUpdate();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true,\n hasAdditionalLatestDataKeys: true\n };\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.getLatestDataKeySettingsSchema = function() {\n return TbFlot.latestDatakeySettingsSchema();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"direction\":\"column\",\",position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}"
@ -164,10 +164,11 @@
"resources": [],
"templateHtml": "",
"templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
"controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.flot.latestDataUpdate();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\n}\n\nself.getLatestDataKeySettingsSchema = function() {\n return TbFlot.latestDatakeySettingsSchema();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasAdditionalLatestDataKeys: true\n };\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":false,\"tooltipIndividual\":false},\"title\":\"Timeseries - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}"
"latestDataKeySettingsSchema": "{}",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":false,\"tooltipIndividual\":false},\"title\":\"Timeseries Line Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}"
}
},
{
@ -182,10 +183,10 @@
"resources": [],
"templateHtml": "",
"templateCss": ".legend {\n font-size: 13px;\n line-height: 10px;\n}\n\n.legend table { \n border-spacing: 0px;\n border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n cursor: crosshair; \n}\n\n",
"controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'bar'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('bar');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(false, 'bar');\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n",
"controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'bar'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onLatestDataUpdated = function() {\n self.ctx.flot.latestDataUpdate();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n return TbFlot.settingsSchema('bar');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(false, 'bar');\n}\n\nself.getLatestDataKeySettingsSchema = function() {\n return TbFlot.latestDatakeySettingsSchema();\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n\nself.typeParameters = function() {\n return {\n hasAdditionalLatestDataKeys: true\n };\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":true,\"tooltipIndividual\":false,\"defaultBarWidth\":600},\"title\":\"Timeseries Bars - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{}}"
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":true,\"tooltipIndividual\":false,\"defaultBarWidth\":600},\"title\":\"Timeseries Bar Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{}}"
}
}
]

24
application/src/main/data/json/system/widget_bundles/control_widgets.json

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

18
application/src/main/data/json/system/widget_bundles/input_widgets.json

File diff suppressed because one or more lines are too long

26
application/src/main/data/json/system/widget_bundles/maps.json

File diff suppressed because one or more lines are too long

50
application/src/main/data/upgrade/3.3.3/schema_event_ttl_procedure.sql

@ -0,0 +1,50 @@
--
-- Copyright © 2016-2022 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.
--
DROP PROCEDURE IF EXISTS public.cleanup_events_by_ttl(bigint, bigint, bigint);
CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(
IN regular_events_start_ts bigint,
IN regular_events_end_ts bigint,
IN debug_events_start_ts bigint,
IN debug_events_end_ts bigint,
INOUT deleted bigint)
LANGUAGE plpgsql AS
$$
DECLARE
ttl_deleted_count bigint DEFAULT 0;
debug_ttl_deleted_count bigint DEFAULT 0;
BEGIN
IF regular_events_start_ts > 0 AND regular_events_end_ts > 0 THEN
EXECUTE format(
'WITH deleted AS (DELETE FROM event WHERE id in (SELECT id from event WHERE ts > %L::bigint AND ts < %L::bigint AND ' ||
'(event_type != %L::varchar AND event_type != %L::varchar)) RETURNING *) ' ||
'SELECT count(*) FROM deleted', regular_events_start_ts, regular_events_end_ts,
'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into ttl_deleted_count;
END IF;
IF debug_events_start_ts > 0 AND debug_events_end_ts > 0 THEN
EXECUTE format(
'WITH deleted AS (DELETE FROM event WHERE id in (SELECT id from event WHERE ts > %L::bigint AND ts < %L::bigint AND ' ||
'(event_type = %L::varchar OR event_type = %L::varchar)) RETURNING *) ' ||
'SELECT count(*) FROM deleted', debug_events_start_ts, debug_events_end_ts,
'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into debug_ttl_deleted_count;
END IF;
RAISE NOTICE 'Events removed by ttl: %', ttl_deleted_count;
RAISE NOTICE 'Debug Events removed by ttl: %', debug_ttl_deleted_count;
deleted := ttl_deleted_count + debug_ttl_deleted_count;
END
$$;

29
application/src/main/data/upgrade/3.3.3/schema_update.sql

@ -0,0 +1,29 @@
--
-- Copyright © 2016-2022 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.
--
DELETE from ota_package as op WHERE NOT EXISTS(SELECT * FROM device_profile dp where op.device_profile_id = dp.id);
DO
$$
BEGIN
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'fk_device_profile_ota_package') THEN
ALTER TABLE ota_package
ADD CONSTRAINT fk_device_profile_ota_package
FOREIGN KEY (device_profile_id) REFERENCES device_profile (id)
ON DELETE CASCADE;
END IF;
END;
$$;

184
application/src/main/java/org/apache/kafka/common/network/NetworkReceive.java

@ -0,0 +1,184 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/*
* Content of this file was modified to addresses the issue https://issues.apache.org/jira/browse/KAFKA-4090
*
*/
package org.apache.kafka.common.network;
import org.apache.kafka.common.memory.MemoryPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thingsboard.server.common.data.exception.ThingsboardKafkaClientError;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ScatteringByteChannel;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* A size delimited Receive that consists of a 4 byte network-ordered size N followed by N bytes of content
*/
public class NetworkReceive implements Receive {
public final static String UNKNOWN_SOURCE = "";
public final static int UNLIMITED = -1;
public final static int TB_MAX_REQUESTED_BUFFER_SIZE = 100 * 1024 * 1024;
public final static int TB_LOG_REQUESTED_BUFFER_SIZE = 10 * 1024 * 1024;
private static final Logger log = LoggerFactory.getLogger(NetworkReceive.class);
private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
private final String source;
private final ByteBuffer size;
private final int maxSize;
private final MemoryPool memoryPool;
private int requestedBufferSize = -1;
private ByteBuffer buffer;
public NetworkReceive(String source, ByteBuffer buffer) {
this.source = source;
this.buffer = buffer;
this.size = null;
this.maxSize = TB_MAX_REQUESTED_BUFFER_SIZE;
this.memoryPool = MemoryPool.NONE;
}
public NetworkReceive(String source) {
this.source = source;
this.size = ByteBuffer.allocate(4);
this.buffer = null;
this.maxSize = TB_MAX_REQUESTED_BUFFER_SIZE;
this.memoryPool = MemoryPool.NONE;
}
public NetworkReceive(int maxSize, String source) {
this.source = source;
this.size = ByteBuffer.allocate(4);
this.buffer = null;
this.maxSize = getMaxSize(maxSize);
this.memoryPool = MemoryPool.NONE;
}
public NetworkReceive(int maxSize, String source, MemoryPool memoryPool) {
this.source = source;
this.size = ByteBuffer.allocate(4);
this.buffer = null;
this.maxSize = getMaxSize(maxSize);
this.memoryPool = memoryPool;
}
public NetworkReceive() {
this(UNKNOWN_SOURCE);
}
@Override
public String source() {
return source;
}
@Override
public boolean complete() {
return !size.hasRemaining() && buffer != null && !buffer.hasRemaining();
}
public long readFrom(ScatteringByteChannel channel) throws IOException {
int read = 0;
if (size.hasRemaining()) {
int bytesRead = channel.read(size);
if (bytesRead < 0)
throw new EOFException();
read += bytesRead;
if (!size.hasRemaining()) {
size.rewind();
int receiveSize = size.getInt();
if (receiveSize < 0)
throw new InvalidReceiveException("Invalid receive (size = " + receiveSize + ")");
if (maxSize != UNLIMITED && receiveSize > maxSize) {
throw new ThingsboardKafkaClientError("Invalid receive (size = " + receiveSize + " larger than " + maxSize + ")");
}
requestedBufferSize = receiveSize; //may be 0 for some payloads (SASL)
if (receiveSize == 0) {
buffer = EMPTY_BUFFER;
}
}
}
if (buffer == null && requestedBufferSize != -1) { //we know the size we want but havent been able to allocate it yet
if (requestedBufferSize > TB_LOG_REQUESTED_BUFFER_SIZE) {
String stackTrace = Arrays.stream(Thread.currentThread().getStackTrace()).map(StackTraceElement::toString).collect(Collectors.joining("|"));
log.error("Allocating buffer of size {} for source {}", requestedBufferSize, source);
log.error("Stack Trace: {}", stackTrace);
}
buffer = memoryPool.tryAllocate(requestedBufferSize);
if (buffer == null)
log.trace("Broker low on memory - could not allocate buffer of size {} for source {}", requestedBufferSize, source);
}
if (buffer != null) {
int bytesRead = channel.read(buffer);
if (bytesRead < 0)
throw new EOFException();
read += bytesRead;
}
return read;
}
@Override
public boolean requiredMemoryAmountKnown() {
return requestedBufferSize != -1;
}
@Override
public boolean memoryAllocated() {
return buffer != null;
}
@Override
public void close() throws IOException {
if (buffer != null && buffer != EMPTY_BUFFER) {
memoryPool.release(buffer);
buffer = null;
}
}
public ByteBuffer payload() {
return this.buffer;
}
public int bytesRead() {
if (buffer == null)
return size.position();
return buffer.position() + size.position();
}
/**
* Returns the total size of the receive including payload and size buffer
* for use in metrics. This is consistent with {@link NetworkSend#size()}
*/
public int size() {
return payload().limit() + size.limit();
}
private int getMaxSize(int maxSize) {
return maxSize == UNLIMITED ? TB_MAX_REQUESTED_BUFFER_SIZE : Math.min(maxSize, TB_MAX_REQUESTED_BUFFER_SIZE);
}
}

14
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -467,7 +467,7 @@ public class ActorSystemContext {
}
private void persistEvent(Event event) {
eventService.save(event);
eventService.saveAsync(event);
}
private String toString(Throwable e) {
@ -552,10 +552,10 @@ public class ActorSystemContext {
}
event.setBody(node);
ListenableFuture<Event> future = eventService.saveAsync(event);
Futures.addCallback(future, new FutureCallback<Event>() {
ListenableFuture<Void> future = eventService.saveAsync(event);
Futures.addCallback(future, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Event event) {
public void onSuccess(@Nullable Void event) {
}
@ -605,10 +605,10 @@ public class ActorSystemContext {
}
event.setBody(node);
ListenableFuture<Event> future = eventService.saveAsync(event);
Futures.addCallback(future, new FutureCallback<Event>() {
ListenableFuture<Void> future = eventService.saveAsync(event);
Futures.addCallback(future, new FutureCallback<Void>() {
@Override
public void onSuccess(@Nullable Event event) {
public void onSuccess(@Nullable Void event) {
}

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

@ -452,6 +452,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
.setRequestId(requestId)
.setSharedStateMsg(true)
.addAllSharedAttributeList(toTsKvProtos(result))
.setIsMultipleAttributesRequest(request.getSharedAttributeNamesCount() > 1)
.build();
sendToTransport(responseMsg, sessionInfo);
}
@ -473,6 +474,8 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
.setRequestId(requestId)
.addAllClientAttributeList(toTsKvProtos(result.get(0)))
.addAllSharedAttributeList(toTsKvProtos(result.get(1)))
.setIsMultipleAttributesRequest(
request.getSharedAttributeNamesCount() + request.getClientAttributeNamesCount() > 1)
.build();
sendToTransport(responseMsg, sessionInfo);
}

2
application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java

@ -59,7 +59,7 @@ public class StatsActor extends ContextAwareActor {
event.setTenantId(msg.getTenantId());
event.setType(DataConstants.STATS);
event.setBody(toBodyJson(systemContext.getServiceInfoProvider().getServiceId(), msg.getMessagesProcessed(), msg.getErrorsOccurred()));
systemContext.getEventService().save(event);
systemContext.getEventService().saveAsync(event);
}
private JsonNode toBodyJson(String serviceId, long messagesProcessed, long errorsOccurred) {

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

@ -26,10 +26,7 @@ import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.actors.TbEntityActorId;
import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate;
import org.thingsboard.server.actors.device.DeviceActorCreator;
import org.thingsboard.server.actors.device.SessionTimeoutCheckMsg;
import org.thingsboard.server.actors.ruleChain.RuleChainInputMsg;
import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor;
import org.thingsboard.server.actors.ruleChain.RuleChainOutputMsg;
import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.ApiUsageState;
@ -65,7 +62,7 @@ import java.util.Optional;
@Slf4j
public class TenantActor extends RuleChainManagerActor {
private boolean isRuleEngineForCurrentTenant;
private boolean isRuleEngine;
private boolean isCore;
private ApiUsageState apiUsageState;
@ -78,39 +75,37 @@ public class TenantActor extends RuleChainManagerActor {
@Override
public void init(TbActorCtx ctx) throws TbActorException {
super.init(ctx);
log.info("[{}] Starting tenant actor.", tenantId);
log.debug("[{}] Starting tenant actor.", tenantId);
try {
Tenant tenant = systemContext.getTenantService().findTenantById(tenantId);
if (tenant == null) {
cantFindTenant = true;
log.info("[{}] Started tenant actor for missing tenant.", tenantId);
} else {
apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenant.getId()));
// This Service may be started for specific tenant only.
Optional<TenantId> isolatedTenantId = systemContext.getServiceInfoProvider().getIsolatedTenant();
TenantProfile tenantProfile = systemContext.getTenantProfileCache().get(tenant.getTenantProfileId());
isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE);
isRuleEngineForCurrentTenant = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE);
if (isRuleEngineForCurrentTenant) {
isRuleEngine = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE);
if (isRuleEngine) {
try {
if (isolatedTenantId.map(id -> id.equals(tenantId)).orElseGet(() -> !tenantProfile.isIsolatedTbRuleEngine())) {
if (apiUsageState.isReExecEnabled()) {
log.info("[{}] Going to init rule chains", tenantId);
if (getApiUsageState().isReExecEnabled()) {
log.debug("[{}] Going to init rule chains", tenantId);
initRuleChains();
} else {
log.info("[{}] Skip init of the rule chains due to API limits", tenantId);
}
} else {
isRuleEngineForCurrentTenant = false;
isRuleEngine = false;
}
} catch (Exception e) {
cantFindTenant = true;
}
}
log.info("[{}] Tenant actor started.", tenantId);
log.debug("[{}] Tenant actor started.", tenantId);
}
} catch (Exception e) {
log.warn("[{}] Unknown failure", tenantId, e);
@ -193,12 +188,12 @@ public class TenantActor extends RuleChainManagerActor {
}
private void onQueueToRuleEngineMsg(QueueToRuleEngineMsg msg) {
if (!isRuleEngineForCurrentTenant) {
if (!isRuleEngine) {
log.warn("RECEIVED INVALID MESSAGE: {}", msg);
return;
}
TbMsg tbMsg = msg.getMsg();
if (apiUsageState.isReExecEnabled()) {
if (getApiUsageState().isReExecEnabled()) {
if (tbMsg.getRuleChainId() == null) {
if (getRootChainActor() != null) {
getRootChainActor().tell(msg);
@ -222,7 +217,7 @@ public class TenantActor extends RuleChainManagerActor {
}
private void onRuleChainMsg(RuleChainAwareMsg msg) {
if (apiUsageState.isReExecEnabled()) {
if (getApiUsageState().isReExecEnabled()) {
getOrCreateActor(msg.getRuleChainId()).tell(msg);
}
}
@ -241,7 +236,7 @@ public class TenantActor extends RuleChainManagerActor {
private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
if (msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE)) {
ApiUsageState old = apiUsageState;
ApiUsageState old = getApiUsageState();
apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenantId));
if (old.isReExecEnabled() && !apiUsageState.isReExecEnabled()) {
log.info("[{}] Received API state update. Going to DISABLE Rule Engine execution.", tenantId);
@ -261,7 +256,7 @@ public class TenantActor extends RuleChainManagerActor {
edgeRpcService.updateEdge(tenantId, edge);
}
}
} else if (isRuleEngineForCurrentTenant) {
} else if (isRuleEngine) {
TbActorRef target = getEntityActorRef(msg.getEntityId());
if (target != null) {
if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) {
@ -289,6 +284,13 @@ public class TenantActor extends RuleChainManagerActor {
systemContext.getEdgeRpcService().onEdgeEvent(tenantId, msg.getEdgeId());
}
private ApiUsageState getApiUsageState() {
if (apiUsageState == null) {
apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenantId));
}
return apiUsageState;
}
public static class ActorCreator extends ContextBasedCreator {
private final TenantId tenantId;

4
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -123,7 +123,6 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.action.EntityActionService;
import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.edge.EdgeLicenseService;
import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.ota.OtaPackageStateService;
@ -269,9 +268,6 @@ public abstract class BaseController {
@Autowired(required = false)
protected EdgeRpcService edgeGrpcService;
@Autowired(required = false)
protected EdgeLicenseService edgeLicenseService;
@Autowired
protected EntityActionService entityActionService;

7
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -38,7 +38,6 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg;
import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg;
import org.thingsboard.server.common.data.ClaimRequest;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants;
@ -875,9 +874,6 @@ public class DeviceController extends BaseController {
Device savedDevice = checkNotNull(deviceService.assignDeviceToEdge(getCurrentUser().getTenantId(), deviceId, edgeId));
tbClusterService.pushMsgToCore(new DeviceEdgeUpdateMsg(savedDevice.getTenantId(),
savedDevice.getId(), edgeId), null);
logEntityAction(deviceId, savedDevice,
savedDevice.getCustomerId(),
ActionType.ASSIGNED_TO_EDGE, null, strDeviceId, strEdgeId, edge.getName());
@ -918,9 +914,6 @@ public class DeviceController extends BaseController {
Device savedDevice = checkNotNull(deviceService.unassignDeviceFromEdge(getCurrentUser().getTenantId(), deviceId, edgeId));
tbClusterService.pushMsgToCore(new DeviceEdgeUpdateMsg(savedDevice.getTenantId(),
savedDevice.getId(), null), null);
logEntityAction(deviceId, device,
device.getCustomerId(),
ActionType.UNASSIGNED_FROM_EDGE, null, strDeviceId, strEdgeId, edge.getName());

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

@ -15,7 +15,6 @@
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
@ -23,7 +22,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
@ -52,7 +50,6 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
@ -118,11 +115,7 @@ public class EdgeController extends BaseController {
checkParameter(EDGE_ID, strEdgeId);
try {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
Edge edge = checkEdgeId(edgeId, Operation.READ);
if (Authority.CUSTOMER_USER.equals(getCurrentUser().getAuthority())) {
cleanUpLicenseKey(edge);
}
return edge;
return checkEdgeId(edgeId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
@ -139,11 +132,7 @@ public class EdgeController extends BaseController {
checkParameter(EDGE_ID, strEdgeId);
try {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
EdgeInfo edgeInfo = checkEdgeInfoId(edgeId, Operation.READ);
if (Authority.CUSTOMER_USER.equals(getCurrentUser().getAuthority())) {
cleanUpLicenseKey(edgeInfo);
}
return edgeInfo;
return checkEdgeInfoId(edgeId, Operation.READ);
} catch (Exception e) {
throw handleException(e);
}
@ -180,7 +169,7 @@ public class EdgeController extends BaseController {
accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation,
edge.getId(), edge);
Edge savedEdge = checkNotNull(edgeService.saveEdge(edge, true));
Edge savedEdge = checkNotNull(edgeService.saveEdge(edge));
onEdgeCreatedOrUpdated(tenantId, savedEdge, edgeTemplateRootRuleChain, !created, getCurrentUser());
return savedEdge;
@ -525,11 +514,6 @@ public class EdgeController extends BaseController {
} else {
result = edgeService.findEdgesByTenantIdAndCustomerId(tenantId, customerId, pageLink);
}
if (Authority.CUSTOMER_USER.equals(user.getAuthority())) {
for (Edge edge : result.getData()) {
cleanUpLicenseKey(edge);
}
}
return checkNotNull(result);
} catch (Exception e) {
throw handleException(e);
@ -570,11 +554,6 @@ public class EdgeController extends BaseController {
} else {
result = edgeService.findEdgeInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink);
}
if (Authority.CUSTOMER_USER.equals(user.getAuthority())) {
for (Edge edge : result.getData()) {
cleanUpLicenseKey(edge);
}
}
return checkNotNull(result);
} catch (Exception e) {
throw handleException(e);
@ -606,11 +585,6 @@ public class EdgeController extends BaseController {
edgesFuture = edgeService.findEdgesByTenantIdCustomerIdAndIdsAsync(tenantId, customerId, edgeIds);
}
List<Edge> edges = edgesFuture.get();
if (Authority.CUSTOMER_USER.equals(user.getAuthority())) {
for (Edge edge : edges) {
cleanUpLicenseKey(edge);
}
}
return checkNotNull(edges);
} catch (Exception e) {
throw handleException(e);
@ -642,11 +616,6 @@ public class EdgeController extends BaseController {
return false;
}
}).collect(Collectors.toList());
if (Authority.CUSTOMER_USER.equals(user.getAuthority())) {
for (Edge edge : edges) {
cleanUpLicenseKey(edge);
}
}
return edges;
} catch (Exception e) {
throw handleException(e);
@ -732,39 +701,4 @@ public class EdgeController extends BaseController {
}
});
}
private void cleanUpLicenseKey(Edge edge) {
edge.setEdgeLicenseKey(null);
}
@ApiOperation(value = "Check edge license (checkInstance)",
notes = "Checks license request from edge service by forwarding request to license portal.",
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/license/checkInstance", method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<JsonNode> checkInstance(@RequestBody JsonNode request) throws ThingsboardException {
log.debug("Checking instance [{}]", request);
try {
return edgeLicenseService.checkInstance(request);
} catch (Exception e) {
log.error("Error occurred: [{}]", e.getMessage(), e);
throw new ThingsboardException(e, ThingsboardErrorCode.SUBSCRIPTION_VIOLATION);
}
}
@ApiOperation(value = "Activate edge instance (activateInstance)",
notes = "Activates edge license on license portal.",
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/license/activateInstance", params = {"licenseSecret", "releaseDate"}, method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<JsonNode> activateInstance(@RequestParam String licenseSecret,
@RequestParam String releaseDate) throws ThingsboardException {
log.debug("Activating instance [{}], [{}]", licenseSecret, releaseDate);
try {
return edgeLicenseService.activateInstance(licenseSecret, releaseDate);
} catch (Exception e) {
log.error("Error occurred: [{}]", e.getMessage(), e);
throw new ThingsboardException(e, ThingsboardErrorCode.SUBSCRIPTION_VIOLATION);
}
}
}

4
application/src/main/java/org/thingsboard/server/controller/RuleChainController.java

@ -683,7 +683,7 @@ public class RuleChainController extends BaseController {
checkParameter(RULE_CHAIN_ID, strRuleChainId);
try {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
Edge edge = checkEdgeId(edgeId, Operation.READ);
Edge edge = checkEdgeId(edgeId, Operation.WRITE);
RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
checkRuleChain(ruleChainId, Operation.READ);
@ -723,7 +723,7 @@ public class RuleChainController extends BaseController {
checkParameter(RULE_CHAIN_ID, strRuleChainId);
try {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
Edge edge = checkEdgeId(edgeId, Operation.READ);
Edge edge = checkEdgeId(edgeId, Operation.WRITE);
RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.READ);

37
application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java

@ -67,7 +67,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
private static final ConcurrentMap<String, SessionMetaData> internalSessionMap = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, String> externalSessionMap = new ConcurrentHashMap<>();
private static final ByteBuffer PING_MSG = ByteBuffer.wrap(new byte[]{});
@Autowired
private TelemetryWebSocketService webSocketService;
@ -216,7 +216,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
private final TelemetryWebSocketSessionRef sessionRef;
private volatile boolean isSending = false;
private final Queue<String> msgQueue;
private final Queue<TbWebSocketMsg<?>> msgQueue;
private volatile long lastActivityTime;
@ -237,7 +237,7 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
log.warn("[{}] Closing session due to ping timeout", session.getId());
closeSession(CloseStatus.SESSION_NOT_RELIABLE);
} else if (timeSinceLastActivity >= pingTimeout / NUMBER_OF_PING_ATTEMPTS) {
this.asyncRemote.sendPing(PING_MSG);
sendMsg(TbWebSocketPingMsg.INSTANCE);
}
} catch (Exception e) {
log.trace("[{}] Failed to send ping msg", session.getId(), e);
@ -258,6 +258,10 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
}
synchronized void sendMsg(String msg) {
sendMsg(new TbWebSocketTextMsg(msg));
}
synchronized void sendMsg(TbWebSocketMsg<?> msg) {
if (isSending) {
try {
msgQueue.add(msg);
@ -275,9 +279,16 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
}
}
private void sendMsgInternal(String msg) {
private void sendMsgInternal(TbWebSocketMsg<?> msg) {
try {
this.asyncRemote.sendText(msg, this);
if (TbWebSocketMsgType.TEXT.equals(msg.getType())) {
TbWebSocketTextMsg textMsg = (TbWebSocketTextMsg) msg;
this.asyncRemote.sendText(textMsg.getMsg(), this);
} else {
TbWebSocketPingMsg pingMsg = (TbWebSocketPingMsg) msg;
this.asyncRemote.sendPing(pingMsg.getMsg());
processNextMsg();
}
} catch (Exception e) {
log.trace("[{}] Failed to send msg", session.getId(), e);
closeSession(CloseStatus.SESSION_NOT_RELIABLE);
@ -290,12 +301,16 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
log.trace("[{}] Failed to send msg", session.getId(), result.getException());
closeSession(CloseStatus.SESSION_NOT_RELIABLE);
} else {
String msg = msgQueue.poll();
if (msg != null) {
sendMsgInternal(msg);
} else {
isSending = false;
}
processNextMsg();
}
}
private void processNextMsg() {
TbWebSocketMsg<?> msg = msgQueue.poll();
if (msg != null) {
sendMsgInternal(msg);
} else {
isSending = false;
}
}
}

9
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2MResource.java → application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketMsg.java

@ -13,9 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.client;
package org.thingsboard.server.controller.plugin;
import org.eclipse.leshan.core.node.LwM2mResource;
public interface TbWebSocketMsg<T> {
TbWebSocketMsgType getType();
T getMsg();
public interface TbLwM2MResource extends LwM2mResource {
}

11
application/src/main/java/org/thingsboard/server/service/edge/EdgeLicenseService.java → application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketMsgType.java

@ -13,14 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge;
package org.thingsboard.server.controller.plugin;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.http.ResponseEntity;
public enum TbWebSocketMsgType {
public interface EdgeLicenseService {
ResponseEntity<JsonNode> checkInstance(JsonNode request);
ResponseEntity<JsonNode> activateInstance(String licenseSecret, String releaseDate);
PING, TEXT
}

24
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2MSingleResource.java → application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketPingMsg.java

@ -13,18 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.client;
package org.thingsboard.server.controller.plugin;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mSingleResource;
import lombok.RequiredArgsConstructor;
import java.io.Serializable;
import java.nio.ByteBuffer;
public class TbLwM2MSingleResource extends LwM2mSingleResource implements TbLwM2MResource, Serializable {
@RequiredArgsConstructor
public class TbWebSocketPingMsg implements TbWebSocketMsg<ByteBuffer> {
private static final long serialVersionUID = -878078368245340809L;
public static TbWebSocketPingMsg INSTANCE = new TbWebSocketPingMsg();
public TbLwM2MSingleResource(int id, Object value, ResourceModel.Type type) {
super(id, value, type);
private static final ByteBuffer PING_MSG = ByteBuffer.wrap(new byte[]{});
@Override
public TbWebSocketMsgType getType() {
return TbWebSocketMsgType.PING;
}
@Override
public ByteBuffer getMsg() {
return PING_MSG;
}
}

20
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2MResourceInstance.java → application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketTextMsg.java

@ -13,18 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.server.client;
package org.thingsboard.server.controller.plugin;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mResourceInstance;
import lombok.RequiredArgsConstructor;
import java.io.Serializable;
@RequiredArgsConstructor
public class TbWebSocketTextMsg implements TbWebSocketMsg<String> {
public class TbLwM2MResourceInstance extends LwM2mResourceInstance implements Serializable {
private final String value;
private static final long serialVersionUID = -8322290426892538345L;
@Override
public TbWebSocketMsgType getType() {
return TbWebSocketMsgType.TEXT;
}
protected TbLwM2MResourceInstance(int id, Object value, ResourceModel.Type type) {
super(id, value, type);
@Override
public String getMsg() {
return value;
}
}

5
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -214,6 +214,11 @@ public class ThingsboardInstallService {
log.info("Upgrading ThingsBoard from version 3.3.2 to 3.3.3 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.3.2");
dataUpdateService.updateData("3.3.2");
case "3.3.3":
log.info("Upgrading ThingsBoard from version 3.3.3 to 3.3.4 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.3.3");
case "3.3.4":
log.info("Upgrading ThingsBoard from version 3.3.4 to 3.4.0 ...");
log.info("Updating system data...");
systemDataLoaderService.updateSystemWidgets();
break;

134
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java

@ -16,6 +16,8 @@
package org.thingsboard.server.service.apiusage;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -23,9 +25,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageState;
@ -49,8 +51,8 @@ import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfigurat
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
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.common.msg.tools.SchedulerUtils;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
@ -58,11 +60,9 @@ import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.scheduler.SchedulerComponent;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.partition.AbstractPartitionBasedService;
import org.thingsboard.server.service.telemetry.InternalTelemetryService;
import javax.annotation.PostConstruct;
@ -79,7 +79,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@ -87,7 +86,7 @@ import java.util.stream.Collectors;
@Slf4j
@Service
public class DefaultTbApiUsageStateService extends TbApplicationEventListener<PartitionChangeEvent> implements TbApiUsageStateService {
public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService<EntityId> implements TbApiUsageStateService {
public static final String HOURLY = "Hourly";
public static final FutureCallback<Integer> VOID_CALLBACK = new FutureCallback<Integer>() {
@ -102,23 +101,22 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
private final TbClusterService clusterService;
private final PartitionService partitionService;
private final TenantService tenantService;
private final CustomerService customerService;
private final TimeseriesService tsService;
private final ApiUsageStateService apiUsageStateService;
private final SchedulerComponent scheduler;
private final TbTenantProfileCache tenantProfileCache;
private final MailService mailService;
private final DbCallbackExecutorService dbExecutor;
@Lazy
@Autowired
private InternalTelemetryService tsWsService;
// Entities that should be processed on this server
private final Map<EntityId, BaseApiUsageState> myUsageStates = new ConcurrentHashMap<>();
final Map<EntityId, BaseApiUsageState> myUsageStates = new ConcurrentHashMap<>();
// Entities that should be processed on other servers
private final Map<EntityId, ApiUsageState> otherUsageStates = new ConcurrentHashMap<>();
final Map<EntityId, ApiUsageState> otherUsageStates = new ConcurrentHashMap<>();
private final Set<EntityId> deletedEntities = Collections.newSetFromMap(new ConcurrentHashMap<>());
final Set<EntityId> deletedEntities = Collections.newSetFromMap(new ConcurrentHashMap<>());
@Value("${usage.stats.report.enabled:true}")
private boolean enabled;
@ -133,33 +131,42 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
public DefaultTbApiUsageStateService(TbClusterService clusterService,
PartitionService partitionService,
TenantService tenantService,
CustomerService customerService,
TimeseriesService tsService,
ApiUsageStateService apiUsageStateService,
SchedulerComponent scheduler,
TbTenantProfileCache tenantProfileCache,
MailService mailService) {
MailService mailService,
DbCallbackExecutorService dbExecutor) {
this.clusterService = clusterService;
this.partitionService = partitionService;
this.tenantService = tenantService;
this.customerService = customerService;
this.tsService = tsService;
this.apiUsageStateService = apiUsageStateService;
this.scheduler = scheduler;
this.tenantProfileCache = tenantProfileCache;
this.mailService = mailService;
this.mailExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("api-usage-svc-mail"));
this.dbExecutor = dbExecutor;
}
@PostConstruct
public void init() {
super.init();
if (enabled) {
log.info("Starting api usage service.");
scheduler.scheduleAtFixedRate(this::checkStartOfNextCycle, nextCycleCheckInterval, nextCycleCheckInterval, TimeUnit.MILLISECONDS);
scheduledExecutor.scheduleAtFixedRate(this::checkStartOfNextCycle, nextCycleCheckInterval, nextCycleCheckInterval, TimeUnit.MILLISECONDS);
log.info("Started api usage service.");
}
}
@Override
protected String getServiceName() {
return "API Usage";
}
@Override
protected String getSchedulerExecutorName() {
return "api-usage-scheduled";
}
@Override
public void process(TbProtoQueueMsg<ToUsageStatsServiceMsg> msg, TbCallback callback) {
ToUsageStatsServiceMsg statsMsg = msg.getValue();
@ -216,19 +223,6 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
}
}
@Override
protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
if (partitionChangeEvent.getServiceType().equals(ServiceType.TB_CORE)) {
myUsageStates.entrySet().removeIf(entry -> {
return !partitionService.resolve(ServiceType.TB_CORE, entry.getValue().getTenantId(), entry.getKey()).isMyPartition();
});
otherUsageStates.entrySet().removeIf(entry -> {
return partitionService.resolve(ServiceType.TB_CORE, entry.getValue().getTenantId(), entry.getKey()).isMyPartition();
});
initStatesFromDataBase();
}
}
@Override
public ApiUsageState getApiUsageState(TenantId tenantId) {
TenantApiUsageState tenantState = (TenantApiUsageState) myUsageStates.get(tenantId);
@ -242,17 +236,12 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
if (partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) {
return getOrFetchState(tenantId, tenantId).getApiUsageState();
} else {
updateLock.lock();
try {
state = otherUsageStates.get(tenantId);
if (state == null) {
state = apiUsageStateService.findTenantApiUsageState(tenantId);
if (state != null) {
otherUsageStates.put(tenantId, state);
}
state = otherUsageStates.get(tenantId);
if (state == null) {
state = apiUsageStateService.findTenantApiUsageState(tenantId);
if (state != null) {
otherUsageStates.put(tenantId, state);
}
} finally {
updateLock.unlock();
}
return state;
}
@ -311,6 +300,18 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
oldProfileData.getConfiguration(), profile.getProfileData().getConfiguration());
}
private void addEntityState(TopicPartitionInfo tpi, BaseApiUsageState state) {
EntityId entityId = state.getEntityId();
Set<EntityId> entityIds = partitionedEntities.get(tpi);
if (entityIds != null) {
entityIds.add(entityId);
myUsageStates.put(entityId, state);
} else {
log.debug("[{}] belongs to external partition {}", entityId, tpi.getFullTopicName());
throw new RuntimeException(entityId.getEntityType() + " belongs to external partition " + tpi.getFullTopicName() + "!");
}
}
private void updateProfileThresholds(TenantId tenantId, ApiUsageStateId id,
TenantProfileConfiguration oldData, TenantProfileConfiguration newData) {
long ts = System.currentTimeMillis();
@ -339,6 +340,11 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
myUsageStates.remove(customerId);
}
@Override
protected void cleanupEntityOnPartitionRemoval(EntityId entityId) {
myUsageStates.remove(entityId);
}
private void persistAndNotify(BaseApiUsageState state, Map<ApiFeature, ApiUsageStateValue> result) {
log.info("[{}] Detected update of the API state for {}: {}", state.getEntityId(), state.getEntityType(), result);
apiUsageStateService.update(state.getApiUsageState());
@ -420,7 +426,7 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), counts, VOID_CALLBACK);
}
private BaseApiUsageState getOrFetchState(TenantId tenantId, EntityId entityId) {
BaseApiUsageState getOrFetchState(TenantId tenantId, EntityId entityId) {
if (entityId == null || entityId.isNullUid()) {
entityId = tenantId;
}
@ -473,7 +479,12 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
}
}
log.debug("[{}] Initialized state: {}", entityId, storedState);
myUsageStates.put(entityId, state);
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (tpi.isMyPartition()) {
addEntityState(tpi, state);
} else {
otherUsageStates.put(entityId, state.getApiUsageState());
}
saveNewCounts(state, newCounts);
} catch (InterruptedException | ExecutionException e) {
log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e);
@ -482,38 +493,44 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
return state;
}
private void initStatesFromDataBase() {
@Override
protected void onRepartitionEvent() {
otherUsageStates.entrySet().removeIf(entry ->
partitionService.resolve(ServiceType.TB_CORE, entry.getValue().getTenantId(), entry.getKey()).isMyPartition());
}
@Override
protected void onAddedPartitions(Set<TopicPartitionInfo> addedPartitions) {
try {
log.info("Initializing tenant states.");
updateLock.lock();
try {
ExecutorService tmpInitExecutor = ThingsBoardExecutors.newWorkStealingPool(20, "init-tenant-states-from-db");
try {
PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024);
List<Future<?>> futures = new ArrayList<>();
for (Tenant tenant : tenantIterator) {
if (!myUsageStates.containsKey(tenant.getId()) && partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), tenant.getId()).isMyPartition()) {
PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024);
List<ListenableFuture<?>> futures = new ArrayList<>();
for (Tenant tenant : tenantIterator) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), tenant.getId());
if (addedPartitions.contains(tpi)) {
if (!myUsageStates.containsKey(tenant.getId()) && tpi.isMyPartition()) {
log.debug("[{}] Initializing tenant state.", tenant.getId());
futures.add(tmpInitExecutor.submit(() -> {
futures.add(dbExecutor.submit(() -> {
try {
updateTenantState((TenantApiUsageState) getOrFetchState(tenant.getId(), tenant.getId()), tenantProfileCache.get(tenant.getTenantProfileId()));
log.debug("[{}] Initialized tenant state.", tenant.getId());
} catch (Exception e) {
log.warn("[{}] Failed to initialize tenant API state", tenant.getId(), e);
}
return null;
}));
}
} else {
log.debug("[{}][{}] Tenant doesn't belong to current partition. tpi [{}]", tenant.getName(), tenant.getId(), tpi);
}
for (Future<?> future : futures) {
future.get();
}
} finally {
tmpInitExecutor.shutdownNow();
}
Futures.whenAllComplete(futures);
} finally {
updateLock.unlock();
}
log.info("Initialized tenant states.");
log.info("Initialized {} tenant states.", myUsageStates.size());
} catch (Exception e) {
log.warn("Unknown failure", e);
}
@ -521,6 +538,7 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
@PreDestroy
private void destroy() {
super.stop();
if (mailExecutor != null) {
mailExecutor.shutdownNow();
}

12
application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java

@ -34,11 +34,13 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
import org.thingsboard.server.common.data.device.data.PowerMode;
import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration;
import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
@ -52,6 +54,7 @@ import org.thingsboard.server.service.importing.BulkImportColumnType;
import org.thingsboard.server.service.security.model.SecurityUser;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Map;
import java.util.Objects;
@ -225,11 +228,14 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
deviceProfile.setTransportType(DeviceTransportType.LWM2M);
deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED);
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration();
transportConfiguration.setBootstrap(Collections.emptyList());
transportConfiguration.setClientLwM2mSettings(new OtherConfiguration(1,1,1, PowerMode.DRX, null, null, null, null, null));
transportConfiguration.setObserveAttr(new TelemetryMappingConfiguration(Collections.emptyMap(), Collections.emptySet(), Collections.emptySet(), Collections.emptySet(), Collections.emptyMap()));
DeviceProfileData deviceProfileData = new DeviceProfileData();
DefaultDeviceProfileConfiguration configuration = new DefaultDeviceProfileConfiguration();
DeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration();
DisabledDeviceProfileProvisionConfiguration provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null);
deviceProfileData.setConfiguration(configuration);
deviceProfileData.setTransportConfiguration(transportConfiguration);
deviceProfileData.setProvisionConfiguration(provisionConfiguration);

110
application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeLicenseService.java

@ -1,110 +0,0 @@
/**
* Copyright © 2016-2022 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.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.thingsboard.server.queue.util.TbCoreComponent;
import javax.annotation.PostConstruct;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.HashMap;
import java.util.Map;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
@Service
@TbCoreComponent
@Slf4j
public class DefaultEdgeLicenseService implements EdgeLicenseService {
private RestTemplate restTemplate;
private static final String EDGE_LICENSE_SERVER_ENDPOINT = "https://license.thingsboard.io";
@Value("${edges.enabled:false}")
private boolean edgesEnabled;
@PostConstruct
public void init() {
if (edgesEnabled) {
initRestTemplate();
}
}
@Override
public ResponseEntity<JsonNode> checkInstance(JsonNode request) {
return this.restTemplate.postForEntity(EDGE_LICENSE_SERVER_ENDPOINT + "/api/license/checkInstance", request, JsonNode.class);
}
@Override
public ResponseEntity<JsonNode> activateInstance(String edgeLicenseSecret, String releaseDate) {
Map<String, String> params = new HashMap<>();
params.put("licenseSecret", edgeLicenseSecret);
params.put("releaseDate", releaseDate);
return this.restTemplate.postForEntity(EDGE_LICENSE_SERVER_ENDPOINT + "/api/license/activateInstance?licenseSecret={licenseSecret}&releaseDate={releaseDate}", null, JsonNode.class, params);
}
private void initRestTemplate() {
boolean jdkHttpClientEnabled = isNotEmpty(System.getProperty("tb.proxy.jdk")) && System.getProperty("tb.proxy.jdk").equalsIgnoreCase("true");
boolean systemProxyEnabled = isNotEmpty(System.getProperty("tb.proxy.system")) && System.getProperty("tb.proxy.system").equalsIgnoreCase("true");
boolean proxyEnabled = isNotEmpty(System.getProperty("tb.proxy.host")) && isNotEmpty(System.getProperty("tb.proxy.port"));
if (jdkHttpClientEnabled) {
log.warn("Going to use plain JDK Http Client!");
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
if (proxyEnabled) {
log.warn("Going to use Proxy Server: [{}:{}]", System.getProperty("tb.proxy.host"), System.getProperty("tb.proxy.port"));
factory.setProxy(new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(System.getProperty("tb.proxy.host"), Integer.parseInt(System.getProperty("tb.proxy.port")))));
}
this.restTemplate = new RestTemplate(new SimpleClientHttpRequestFactory());
} else {
CloseableHttpClient httpClient;
HttpComponentsClientHttpRequestFactory requestFactory;
if (systemProxyEnabled) {
log.warn("Going to use System Proxy Server!");
httpClient = HttpClients.createSystem();
requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
this.restTemplate = new RestTemplate(requestFactory);
} else if (proxyEnabled) {
log.warn("Going to use Proxy Server: [{}:{}]", System.getProperty("tb.proxy.host"), System.getProperty("tb.proxy.port"));
httpClient = HttpClients.custom().setSSLHostnameVerifier(new DefaultHostnameVerifier()).setProxy(new HttpHost(System.getProperty("tb.proxy.host"), Integer.parseInt(System.getProperty("tb.proxy.port")), "https")).build();
requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
this.restTemplate = new RestTemplate(requestFactory);
} else {
httpClient = HttpClients.custom().setSSLHostnameVerifier(new DefaultHostnameVerifier()).build();
requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
this.restTemplate = new RestTemplate(requestFactory);
}
}
}
}

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

@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -38,7 +39,6 @@ import org.thingsboard.server.service.edge.rpc.processor.CustomerEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.EntityEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor;
import org.thingsboard.server.cluster.TbClusterService;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@ -93,7 +93,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
@Override
public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws IOException {
edge.setRootRuleChainId(ruleChainId);
Edge savedEdge = edgeService.saveEdge(edge, true);
Edge savedEdge = edgeService.saveEdge(edge);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, EdgeEventActionType.UPDATED, ruleChainId, null);
return savedEdge;
}
@ -122,7 +122,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
@Override
public void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) {
log.trace("Pushing notification to edge {}", edgeNotificationMsg);
log.debug("Pushing notification to edge {}", edgeNotificationMsg);
try {
TenantId tenantId = TenantId.fromUUID(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB()));
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
@ -153,11 +153,12 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
relationProcessor.processRelationNotification(tenantId, edgeNotificationMsg);
break;
default:
log.debug("Edge event type [{}] is not designed to be pushed to edge", type);
log.warn("Edge event type [{}] is not designed to be pushed to edge", type);
}
} catch (Exception e) {
callback.onFailure(e);
log.error("Can't push to edge updates, edgeNotificationMsg [{}]", edgeNotificationMsg, e);
String errMsg = String.format("Can't push to edge updates, edgeNotificationMsg [%s]", edgeNotificationMsg);
log.error(errMsg, e);
} finally {
callback.onSuccess();
}

8
application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java

@ -55,12 +55,6 @@ public class EdgeBulkImportService extends AbstractBulkImportService<Edge> {
case DESCRIPTION:
additionalInfo.set("description", new TextNode(value));
break;
case EDGE_LICENSE_KEY:
entity.setEdgeLicenseKey(value);
break;
case CLOUD_ENDPOINT:
entity.setCloudEndpoint(value);
break;
case ROUTING_KEY:
entity.setRoutingKey(value);
break;
@ -74,7 +68,7 @@ public class EdgeBulkImportService extends AbstractBulkImportService<Edge> {
@Override
protected Edge saveEntity(Edge entity, Map<BulkImportColumnType, String> fields) {
return edgeService.saveEdge(entity, true);
return edgeService.saveEdge(entity);
}
@Override

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

@ -643,7 +643,9 @@ public final class EdgeGrpcSession implements Closeable {
}
}
} catch (Exception e) {
log.error("[{}] Can't process uplink msg [{}]", this.sessionId, uplinkMsg, e);
String errMsg = String.format("[%s] Can't process uplink msg [%s]", this.sessionId, uplinkMsg);
log.error(errMsg, e);
return Futures.immediateFailedFuture(e);
}
return Futures.allAsList(result);
}
@ -690,8 +692,6 @@ public final class EdgeGrpcSession implements Closeable {
.setType(edge.getType())
.setRoutingKey(edge.getRoutingKey())
.setSecret(edge.getSecret())
.setEdgeLicenseKey(edge.getEdgeLicenseKey())
.setCloudEndpoint(edge.getCloudEndpoint())
.setAdditionalInfo(JacksonUtil.toString(edge.getAdditionalInfo()))
.setCloudType("CE");
if (edge.getCustomerId() != null) {

8
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java

@ -41,17 +41,17 @@ public class EdgeSyncCursor {
int currentIdx = 0;
public EdgeSyncCursor(EdgeContextComponent ctx, Edge edge) {
fetchers.add(new SystemWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService()));
fetchers.add(new TenantWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService()));
fetchers.add(new DeviceProfilesEdgeEventFetcher(ctx.getDeviceProfileService()));
fetchers.add(new RuleChainsEdgeEventFetcher(ctx.getRuleChainService()));
fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService(), ctx.getFreemarkerConfig()));
fetchers.add(new DeviceProfilesEdgeEventFetcher(ctx.getDeviceProfileService()));
fetchers.add(new TenantAdminUsersEdgeEventFetcher(ctx.getUserService()));
if (edge.getCustomerId() != null && !EntityId.NULL_UUID.equals(edge.getCustomerId().getId())) {
fetchers.add(new CustomerEdgeEventFetcher());
fetchers.add(new CustomerUsersEdgeEventFetcher(ctx.getUserService(), edge.getCustomerId()));
}
fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService(), ctx.getFreemarkerConfig()));
fetchers.add(new AssetsEdgeEventFetcher(ctx.getAssetService()));
fetchers.add(new SystemWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService()));
fetchers.add(new TenantWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService()));
fetchers.add(new DashboardsEdgeEventFetcher(ctx.getDashboardService()));
}

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

@ -19,6 +19,8 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
@ -43,6 +45,7 @@ import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService;
@ -62,7 +65,6 @@ import org.thingsboard.server.service.edge.rpc.constructor.WidgetTypeMsgConstruc
import org.thingsboard.server.service.edge.rpc.constructor.WidgetsBundleMsgConstructor;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.state.DeviceStateService;
@Slf4j
@ -129,6 +131,9 @@ public abstract class BaseEdgeProcessor {
@Autowired
protected WidgetTypeService widgetTypeService;
@Autowired
protected DataValidator<Device> deviceValidator;
@Autowired
protected EntityDataMsgConstructor entityDataMsgConstructor;

86
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java

@ -26,7 +26,6 @@ import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
@ -45,11 +44,14 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
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.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg;
@ -59,7 +61,6 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg;
import java.util.UUID;
@ -79,38 +80,37 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
String deviceName = deviceUpdateMsg.getName();
Device device = deviceService.findDeviceByTenantIdAndName(tenantId, deviceName);
if (device != null) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> pageData;
do {
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, device.getId(), pageLink);
boolean update = false;
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
if (pageData.getData().contains(edge.getId())) {
update = true;
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
boolean deviceAlreadyExistsForThisEdge = isDeviceAlreadyExistsOnCloudForThisEdge(tenantId, edge, device);
if (deviceAlreadyExistsForThisEdge) {
log.info("[{}] Device with name '{}' already exists on the cloud, and related to this edge [{}]. " +
"deviceUpdateMsg [{}], Updating device", tenantId, deviceName, edge.getId(), deviceUpdateMsg);
updateDevice(tenantId, edge, deviceUpdateMsg);
} else {
log.info("[{}] Device with name '{}' already exists on the cloud, but not related to this edge [{}]. deviceUpdateMsg [{}]." +
"Creating a new device with random prefix and relate to this edge", tenantId, deviceName, edge.getId(), deviceUpdateMsg);
String newDeviceName = deviceUpdateMsg.getName() + "_" + RandomStringUtils.randomAlphabetic(15);
Device newDevice;
try {
newDevice = createDevice(tenantId, edge, deviceUpdateMsg, newDeviceName);
} catch (DataValidationException e) {
String errMsg = String.format("[%s] Device update msg can't be processed due to data validation [%s]", tenantId, deviceUpdateMsg);
log.error(errMsg, e);
return Futures.immediateFuture(null);
}
if (update) {
log.info("[{}] Device with name '{}' already exists on the cloud, and related to this edge [{}]. " +
"deviceUpdateMsg [{}], Updating device", tenantId, deviceName, edge.getId(), deviceUpdateMsg);
updateDevice(tenantId, edge, deviceUpdateMsg);
} else {
log.info("[{}] Device with name '{}' already exists on the cloud, but not related to this edge [{}]. deviceUpdateMsg [{}]." +
"Creating a new device with random prefix and relate to this edge", tenantId, deviceName, edge.getId(), deviceUpdateMsg);
String newDeviceName = deviceUpdateMsg.getName() + "_" + RandomStringUtils.randomAlphabetic(15);
Device newDevice = createDevice(tenantId, edge, deviceUpdateMsg, newDeviceName);
ObjectNode body = mapper.createObjectNode();
body.put("conflictName", deviceName);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, newDevice.getId(), body);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, newDevice.getId(), null);
}
} while (pageData != null && pageData.hasNext());
ObjectNode body = mapper.createObjectNode();
body.put("conflictName", deviceName);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, newDevice.getId(), body);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, newDevice.getId(), null);
}
} else {
log.info("[{}] Creating new device and replacing device entity on the edge [{}]", tenantId, deviceUpdateMsg);
device = createDevice(tenantId, edge, deviceUpdateMsg, deviceUpdateMsg.getName());
try {
device = createDevice(tenantId, edge, deviceUpdateMsg, deviceUpdateMsg.getName());
} catch (DataValidationException e) {
String errMsg = String.format("[%s] Device update msg can't be processed due to data validation [%s]", tenantId, deviceUpdateMsg);
log.error(errMsg, e);
return Futures.immediateFuture(null);
}
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, device.getId(), null);
}
break;
@ -131,6 +131,23 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
return Futures.immediateFuture(null);
}
private boolean isDeviceAlreadyExistsOnCloudForThisEdge(TenantId tenantId, Edge edge, Device device) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> pageData;
do {
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, device.getId(), pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
if (pageData.getData().contains(edge.getId())) {
return true;
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
return false;
}
public ListenableFuture<Void> processDeviceCredentialsFromEdge(TenantId tenantId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg) {
log.debug("Executing onDeviceCredentialsUpdate, deviceCredentialsUpdateMsg [{}]", deviceCredentialsUpdateMsg);
DeviceId deviceId = new DeviceId(new UUID(deviceCredentialsUpdateMsg.getDeviceIdMSB(), deviceCredentialsUpdateMsg.getDeviceIdLSB()));
@ -194,7 +211,6 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
if (device == null) {
device = new Device();
device.setTenantId(tenantId);
device.setId(deviceId);
device.setCreatedTime(Uuids.unixTimestamp(deviceId.getId()));
created = true;
}
@ -214,6 +230,12 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
deviceUpdateMsg.getDeviceProfileIdLSB()));
device.setDeviceProfileId(deviceProfileId);
}
if (created) {
deviceValidator.validate(device, Device::getTenantId);
device.setId(deviceId);
} else {
deviceValidator.validate(device, Device::getTenantId);
}
Device savedDevice = deviceService.saveDevice(device, false);
tbClusterService.onDeviceUpdated(savedDevice, created ? null : device, false);
if (created) {

43
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityEdgeProcessor.java

@ -94,27 +94,20 @@ public class EntityEdgeProcessor extends BaseEdgeProcessor {
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type,
new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> pageData;
EdgeId edgeId = null;
if (edgeNotificationMsg.getEdgeIdMSB() != 0 && edgeNotificationMsg.getEdgeIdLSB() != 0) {
edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));
}
switch (actionType) {
case ADDED: // used only for USER entity
case UPDATED:
case CREDENTIALS_UPDATED:
do {
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
for (EdgeId relatedEdgeId : pageData.getData()) {
saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null);
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
pushNotificationToAllRelatedEdges(tenantId, entityId, type, actionType);
break;
case ASSIGNED_TO_CUSTOMER:
case UNASSIGNED_FROM_CUSTOMER:
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> pageData;
do {
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
@ -147,7 +140,11 @@ public class EntityEdgeProcessor extends BaseEdgeProcessor {
} while (pageData != null && pageData.hasNext());
break;
case DELETED:
saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null);
if (edgeId != null) {
saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null);
} else {
pushNotificationToAllRelatedEdges(tenantId, entityId, type, actionType);
}
break;
case ASSIGNED_TO_EDGE:
case UNASSIGNED_FROM_EDGE:
@ -159,6 +156,22 @@ public class EntityEdgeProcessor extends BaseEdgeProcessor {
}
}
private void pushNotificationToAllRelatedEdges(TenantId tenantId, EntityId entityId, EdgeEventType type, EdgeEventActionType actionType) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> pageData;
do {
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
for (EdgeId relatedEdgeId : pageData.getData()) {
saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null);
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
}
private void updateDependentRuleChains(TenantId tenantId, RuleChainId processingRuleChainId, EdgeId edgeId) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<RuleChain> pageData;

2
application/src/main/java/org/thingsboard/server/service/importing/BulkImportColumnType.java

@ -45,8 +45,6 @@ public enum BulkImportColumnType {
LWM2M_SERVER_CLIENT_SECRET_KEY("clientSecretKey"),
IS_GATEWAY,
DESCRIPTION,
EDGE_LICENSE_KEY,
CLOUD_ENDPOINT,
ROUTING_KEY,
SECRET;

22
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -512,6 +512,28 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.error("Failed updating schema!!!", e);
}
break;
case "3.3.3":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
try {
conn.createStatement().execute("ALTER TABLE edge DROP COLUMN edge_license_key;"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
conn.createStatement().execute("ALTER TABLE edge DROP COLUMN cloud_endpoint;"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
} catch (Exception ignored) {
}
log.info("Updating TTL cleanup procedure for the event table...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.3", "schema_event_ttl_procedure.sql");
loadSql(schemaUpdateFile, conn);
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.3", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn);
log.info("Updating schema settings...");
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003004;");
log.info("Schema updated.");
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}

17
application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java

@ -17,13 +17,14 @@ package org.thingsboard.server.service.install.update;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.context.annotation.Profile;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Objects;
import java.util.Optional;
@RequiredArgsConstructor
@Service
@ -32,6 +33,8 @@ import java.util.Objects;
public class DefaultCacheCleanupService implements CacheCleanupService {
private final CacheManager cacheManager;
private final Optional<RedisTemplate<String, Object>> redisTemplate;
/**
* Cleanup caches that can not deserialize anymore due to schema upgrade or data update using sql scripts.
@ -59,9 +62,11 @@ public class DefaultCacheCleanupService implements CacheCleanupService {
break;
case "3.3.2":
log.info("Clear cache to upgrade from version 3.3.2 to 3.3.3 ...");
clearCacheByName("devices");
clearCacheByName("deviceProfiles");
clearCacheByName("tenantProfiles");
clearAll();
break;
case "3.3.3":
log.info("Clear cache to upgrade from version 3.3.3 to 3.3.4 ...");
clearAll();
break;
default:
//Do nothing, since cache cleanup is optional.
@ -78,4 +83,8 @@ public class DefaultCacheCleanupService implements CacheCleanupService {
cache.clear();
}
void clearAll() {
redisTemplate.ifPresent(rt -> rt.execute(connection ->
connection.execute("FLUSHALL"), false));
}
}

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

@ -0,0 +1,148 @@
/**
* Copyright © 2016-2022 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.partition;
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import java.util.HashSet;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
@Slf4j
public abstract class AbstractPartitionBasedService<T extends EntityId> extends TbApplicationEventListener<PartitionChangeEvent> {
protected final ConcurrentMap<TopicPartitionInfo, Set<T>> partitionedEntities = new ConcurrentHashMap<>();
final Queue<Set<TopicPartitionInfo>> subscribeQueue = new ConcurrentLinkedQueue<>();
protected ListeningScheduledExecutorService scheduledExecutor;
abstract protected String getServiceName();
abstract protected String getSchedulerExecutorName();
abstract protected void onAddedPartitions(Set<TopicPartitionInfo> addedPartitions);
abstract protected void cleanupEntityOnPartitionRemoval(T entityId);
public Set<T> getPartitionedEntities(TopicPartitionInfo tpi) {
return partitionedEntities.get(tpi);
}
protected void init() {
// Should be always single threaded due to absence of locks.
scheduledExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(getSchedulerExecutorName())));
}
protected ServiceType getServiceType() {
return ServiceType.TB_CORE;
}
protected void stop() {
if (scheduledExecutor != null) {
scheduledExecutor.shutdownNow();
}
}
/**
* DiscoveryService will call this event from the single thread (one-by-one).
* Events order is guaranteed by DiscoveryService.
* The only concurrency is expected from the [main] thread on Application started.
* Async implementation. Locks is not allowed by design.
* Any locks or delays in this module will affect DiscoveryService and entire system
*/
@Override
protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
if (getServiceType().equals(partitionChangeEvent.getServiceType())) {
log.debug("onTbApplicationEvent, processing event: {}", partitionChangeEvent);
subscribeQueue.add(partitionChangeEvent.getPartitions());
scheduledExecutor.submit(this::pollInitStateFromDB);
}
}
protected void pollInitStateFromDB() {
final Set<TopicPartitionInfo> partitions = getLatestPartitions();
if (partitions == null) {
log.debug("Nothing to do. Partitions are empty.");
return;
}
initStateFromDB(partitions);
}
private void initStateFromDB(Set<TopicPartitionInfo> partitions) {
try {
log.info("[{}] CURRENT PARTITIONS: {}", getServiceName(), partitionedEntities.keySet());
log.info("[{}] NEW PARTITIONS: {}", getServiceName(), partitions);
Set<TopicPartitionInfo> addedPartitions = new HashSet<>(partitions);
addedPartitions.removeAll(partitionedEntities.keySet());
log.info("[{}] ADDED PARTITIONS: {}", getServiceName(), addedPartitions);
Set<TopicPartitionInfo> removedPartitions = new HashSet<>(partitionedEntities.keySet());
removedPartitions.removeAll(partitions);
log.info("[{}] REMOVED PARTITIONS: {}", getServiceName(), removedPartitions);
// We no longer manage current partition of entities;
removedPartitions.forEach(partition -> {
Set<T> entities = partitionedEntities.remove(partition);
entities.forEach(this::cleanupEntityOnPartitionRemoval);
});
onRepartitionEvent();
addedPartitions.forEach(tpi -> partitionedEntities.computeIfAbsent(tpi, key -> ConcurrentHashMap.newKeySet()));
if (!addedPartitions.isEmpty()) {
onAddedPartitions(addedPartitions);
}
log.info("[{}] Managing following partitions:", getServiceName());
partitionedEntities.forEach((tpi, entities) -> {
log.info("[{}][{}]: {} entities", getServiceName(), tpi.getFullTopicName(), entities.size());
});
} catch (Throwable t) {
log.warn("[{}] Failed to init entities state from DB", getServiceName(), t);
}
}
protected void onRepartitionEvent() {
}
private Set<TopicPartitionInfo> getLatestPartitions() {
log.debug("getLatestPartitionsFromQueue, queue size {}", subscribeQueue.size());
Set<TopicPartitionInfo> partitions = null;
while (!subscribeQueue.isEmpty()) {
partitions = subscribeQueue.poll();
log.debug("polled from the queue partitions {}", partitions);
}
log.debug("getLatestPartitionsFromQueue, partitions {}", partitions);
return partitions;
}
}

83
application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java

@ -29,12 +29,16 @@ import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.context.request.async.DeferredResult;
import org.thingsboard.common.util.KvUtil;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.AlarmData;
import org.thingsboard.server.common.data.query.AlarmDataQuery;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
@ -42,6 +46,10 @@ import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityDataSortOrder;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.FilterPredicateType;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.entity.EntityService;
@ -52,6 +60,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.security.AccessValidator;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope;
import java.util.ArrayList;
import java.util.Collection;
@ -59,7 +68,9 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@ -93,9 +104,81 @@ public class DefaultEntityQueryService implements EntityQueryService {
@Override
public PageData<EntityData> findEntityDataByQuery(SecurityUser securityUser, EntityDataQuery query) {
if (query.getKeyFilters() != null) {
resolveDynamicValuesInPredicates(
query.getKeyFilters().stream()
.map(KeyFilter::getPredicate)
.collect(Collectors.toList()),
securityUser
);
}
return entityService.findEntityDataByQuery(securityUser.getTenantId(), securityUser.getCustomerId(), query);
}
private void resolveDynamicValuesInPredicates(List<KeyFilterPredicate> predicates, SecurityUser user) {
predicates.forEach(predicate -> {
if (predicate.getType() == FilterPredicateType.COMPLEX) {
resolveDynamicValuesInPredicates(
((ComplexFilterPredicate) predicate).getPredicates(),
user
);
} else {
setResolvedValue(user, (SimpleKeyFilterPredicate<?>) predicate);
}
});
}
private void setResolvedValue(SecurityUser user, SimpleKeyFilterPredicate<?> predicate) {
DynamicValue<?> dynamicValue = predicate.getValue().getDynamicValue();
if (dynamicValue != null && dynamicValue.getResolvedValue() == null) {
resolveDynamicValue(dynamicValue, user, predicate.getType());
}
}
private <T> void resolveDynamicValue(DynamicValue<T> dynamicValue, SecurityUser user, FilterPredicateType predicateType) {
EntityId entityId;
switch (dynamicValue.getSourceType()) {
case CURRENT_TENANT:
entityId = user.getTenantId();
break;
case CURRENT_CUSTOMER:
entityId = user.getCustomerId();
break;
case CURRENT_USER:
entityId = user.getId();
break;
default:
throw new RuntimeException("Not supported operation for source type: {" + dynamicValue.getSourceType() + "}");
}
try {
Optional<AttributeKvEntry> valueOpt = attributesService.find(user.getTenantId(), entityId,
TbAttributeSubscriptionScope.SERVER_SCOPE.name(), dynamicValue.getSourceAttribute()).get();
if (valueOpt.isPresent()) {
AttributeKvEntry entry = valueOpt.get();
Object resolved = null;
switch (predicateType) {
case STRING:
resolved = KvUtil.getStringValue(entry);
break;
case NUMERIC:
resolved = KvUtil.getDoubleValue(entry);
break;
case BOOLEAN:
resolved = KvUtil.getBoolValue(entry);
break;
case COMPLEX:
break;
}
dynamicValue.setResolvedValue((T) resolved);
}
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
@Override
public PageData<AlarmData> findAlarmDataByQuery(SecurityUser securityUser, AlarmDataQuery query) {
EntityDataQuery entityDataQuery = this.buildEntityDataQuery(query);

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

@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.msg.DeviceEdgeUpdateMsg;
import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EdgeUtils;
@ -455,6 +456,21 @@ public class DefaultTbClusterService implements TbClusterService {
TransportProtos.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);
if (entityId != null && EntityType.DEVICE.equals(entityId.getEntityType())) {
pushDeviceUpdateMessage(tenantId, edgeId, entityId, action);
}
}
private void pushDeviceUpdateMessage(TenantId tenantId, EdgeId edgeId, EntityId entityId, EdgeEventActionType action) {
log.trace("{} Going to send edge update notification for device actor, device id {}, edge id {}", tenantId, entityId, edgeId);
switch (action) {
case ASSIGNED_TO_EDGE:
pushMsgToCore(new DeviceEdgeUpdateMsg(tenantId, new DeviceId(entityId.getId()), edgeId), null);
break;
case UNASSIGNED_FROM_EDGE:
pushMsgToCore(new DeviceEdgeUpdateMsg(tenantId, new DeviceId(entityId.getId()), null), null);
break;
}
}
}

45
application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java

@ -31,7 +31,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
public CustomerUserPermissions() {
super();
put(Resource.ALARM, TenantAdminPermissions.tenantEntityPermissionChecker);
put(Resource.ALARM, customerAlarmPermissionChecker);
put(Resource.ASSET, customerEntityPermissionChecker);
put(Resource.DEVICE, customerEntityPermissionChecker);
put(Resource.CUSTOMER, customerPermissionChecker);
@ -44,6 +44,19 @@ public class CustomerUserPermissions extends AbstractPermissions {
put(Resource.RPC, rpcPermissionChecker);
}
private static final PermissionChecker customerAlarmPermissionChecker = new PermissionChecker() {
@Override
public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) {
if (!user.getTenantId().equals(entity.getTenantId())) {
return false;
}
if (!(entity instanceof HasCustomerId)) {
return false;
}
return user.getCustomerId().equals(((HasCustomerId) entity).getCustomerId());
}
};
private static final PermissionChecker customerEntityPermissionChecker =
new PermissionChecker.GenericPermissionChecker(Operation.READ, Operation.READ_CREDENTIALS,
Operation.READ_ATTRIBUTES, Operation.READ_TELEMETRY, Operation.RPC_CALL, Operation.CLAIM_DEVICES,
@ -62,10 +75,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
if (!(entity instanceof HasCustomerId)) {
return false;
}
if (!operation.equals(Operation.CLAIM_DEVICES) && !user.getCustomerId().equals(((HasCustomerId) entity).getCustomerId())) {
return false;
}
return true;
return operation.equals(Operation.CLAIM_DEVICES) || user.getCustomerId().equals(((HasCustomerId) entity).getCustomerId());
}
};
@ -78,10 +88,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
if (!super.hasPermission(user, operation, entityId, entity)) {
return false;
}
if (!user.getCustomerId().equals(entityId)) {
return false;
}
return true;
return user.getCustomerId().equals(entityId);
}
};
@ -98,10 +105,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
if (!user.getTenantId().equals(dashboard.getTenantId())) {
return false;
}
if (!dashboard.isAssignedToCustomer(user.getCustomerId())) {
return false;
}
return true;
return dashboard.isAssignedToCustomer(user.getCustomerId());
}
};
@ -113,10 +117,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
if (!Authority.CUSTOMER_USER.equals(userEntity.getAuthority())) {
return false;
}
if (!user.getId().equals(userId)) {
return false;
}
return true;
return user.getId().equals(userId);
}
};
@ -132,10 +133,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
if (entity.getTenantId() == null || entity.getTenantId().isNullUid()) {
return true;
}
if (!user.getTenantId().equals(entity.getTenantId())) {
return false;
}
return true;
return user.getTenantId().equals(entity.getTenantId());
}
};
@ -151,10 +149,7 @@ public class CustomerUserPermissions extends AbstractPermissions {
if (entity.getTenantId() == null || entity.getTenantId().isNullUid()) {
return true;
}
if (!user.getTenantId().equals(entity.getTenantId())) {
return false;
}
return true;
return user.getTenantId().equals(entity.getTenantId());
}
};
}

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

@ -20,15 +20,15 @@ import com.google.common.base.Function;
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.ListeningScheduledExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Tenant;
@ -41,6 +41,7 @@ import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
@ -52,13 +53,10 @@ import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.partition.AbstractPartitionBasedService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import javax.annotation.Nonnull;
@ -68,14 +66,11 @@ import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@ -94,7 +89,7 @@ import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
@Service
@TbCoreComponent
@Slf4j
public class DefaultDeviceStateService extends TbApplicationEventListener<PartitionChangeEvent> implements DeviceStateService {
public class DefaultDeviceStateService extends AbstractPartitionBasedService<DeviceId> implements DeviceStateService {
public static final String ACTIVITY_STATE = "active";
public static final String LAST_CONNECT_TIME = "lastConnectTime";
@ -131,12 +126,9 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
@Getter
private int initFetchPackSize;
private ListeningScheduledExecutorService scheduledExecutor;
private ExecutorService deviceStateExecutor;
private final ConcurrentMap<TopicPartitionInfo, Set<DeviceId>> partitionedDevices = new ConcurrentHashMap<>();
final ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>();
final Queue<Set<TopicPartitionInfo>> subscribeQueue = new ConcurrentLinkedQueue<>();
final ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>();
public DefaultDeviceStateService(TenantService tenantService, DeviceService deviceService,
AttributesService attributesService, TimeseriesService tsService,
@ -156,25 +148,36 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
@PostConstruct
public void init() {
super.init();
deviceStateExecutor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors(), ThingsBoardThreadFactory.forName("device-state"));
// Should be always single threaded due to absence of locks.
scheduledExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("device-state-scheduled")));
scheduledExecutor.scheduleAtFixedRate(this::updateInactivityStateIfExpired, new Random().nextInt(defaultStateCheckIntervalInSec), defaultStateCheckIntervalInSec, TimeUnit.SECONDS);
}
@PreDestroy
public void stop() {
super.stop();
if (deviceStateExecutor != null) {
deviceStateExecutor.shutdownNow();
}
if (scheduledExecutor != null) {
scheduledExecutor.shutdownNow();
}
}
@Override
protected String getServiceName() {
return "Device State";
}
@Override
protected String getSchedulerExecutorName() {
return "device-state-scheduled";
}
@Override
public void onDeviceConnect(TenantId tenantId, DeviceId deviceId) {
if (cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId)) {
return;
}
log.trace("on Device Connect [{}]", deviceId.getId());
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
long ts = System.currentTimeMillis();
@ -182,17 +185,19 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
save(deviceId, LAST_CONNECT_TIME, ts);
pushRuleEngineMessage(stateData, CONNECT_EVENT);
checkAndUpdateState(deviceId, stateData);
cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId);
}
@Override
public void onDeviceActivity(TenantId tenantId, DeviceId deviceId, long lastReportedActivity) {
if (cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId)) {
return;
}
log.trace("on Device Activity [{}], lastReportedActivity [{}]", deviceId.getId(), lastReportedActivity);
final DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
if (lastReportedActivity > 0 && lastReportedActivity > stateData.getState().getLastActivityTime()) {
updateActivityState(deviceId, stateData, lastReportedActivity);
}
cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId);
}
void updateActivityState(DeviceId deviceId, DeviceStateData stateData, long lastReportedActivity) {
@ -208,18 +213,20 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
}
} else {
log.debug("updateActivityState - fetched state IN NULL for device {}, lastReportedActivity {}", deviceId, lastReportedActivity);
cleanUpDeviceStateMap(deviceId);
cleanupEntity(deviceId);
}
}
@Override
public void onDeviceDisconnect(TenantId tenantId, DeviceId deviceId) {
if (cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId)) {
return;
}
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
long ts = System.currentTimeMillis();
stateData.getState().setLastDisconnectTime(ts);
save(deviceId, LAST_DISCONNECT_TIME, ts);
pushRuleEngineMessage(stateData, DISCONNECT_EVENT);
cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId);
}
@Override
@ -227,11 +234,14 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
if (inactivityTimeout <= 0L) {
return;
}
if (cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId)) {
return;
}
log.trace("on Device Activity Timeout Update device id {} inactivityTimeout {}", deviceId, inactivityTimeout);
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
stateData.getState().setInactivityTimeout(inactivityTimeout);
checkAndUpdateState(deviceId, stateData);
cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId);
}
@Override
@ -246,11 +256,11 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
Device device = deviceService.findDeviceById(TenantId.SYS_TENANT_ID, deviceId);
if (device != null) {
if (proto.getAdded()) {
Futures.addCallback(fetchDeviceState(device), new FutureCallback<DeviceStateData>() {
Futures.addCallback(fetchDeviceState(device), new FutureCallback<>() {
@Override
public void onSuccess(@Nullable DeviceStateData state) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, device.getId());
if (partitionedDevices.containsKey(tpi)) {
if (partitionedEntities.containsKey(tpi)) {
addDeviceUsingState(tpi, state);
save(deviceId, ACTIVITY_STATE, false);
callback.onSuccess();
@ -286,105 +296,25 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
}
}
/**
* DiscoveryService will call this event from the single thread (one-by-one).
* Events order is guaranteed by DiscoveryService.
* The only concurrency is expected from the [main] thread on Application started.
* Async implementation. Locks is not allowed by design.
* Any locks or delays in this module will affect DiscoveryService and entire system
*/
@Override
protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) {
log.debug("onTbApplicationEvent ServiceType is TB_CORE, processing queue {}", partitionChangeEvent);
subscribeQueue.add(partitionChangeEvent.getPartitions());
scheduledExecutor.submit(this::pollInitStateFromDB);
}
}
void pollInitStateFromDB() {
final Set<TopicPartitionInfo> partitions = getLatestPartitionsFromQueue();
if (partitions == null) {
log.info("Device state service. Nothing to do. partitions is null");
return;
}
initStateFromDB(partitions);
}
// TODO: move to utils
Set<TopicPartitionInfo> getLatestPartitionsFromQueue() {
log.debug("getLatestPartitionsFromQueue, queue size {}", subscribeQueue.size());
Set<TopicPartitionInfo> partitions = null;
while (!subscribeQueue.isEmpty()) {
partitions = subscribeQueue.poll();
log.debug("polled from the queue partitions {}", partitions);
}
log.debug("getLatestPartitionsFromQueue, partitions {}", partitions);
return partitions;
}
private void initStateFromDB(Set<TopicPartitionInfo> partitions) {
try {
log.info("CURRENT PARTITIONS: {}", partitionedDevices.keySet());
log.info("NEW PARTITIONS: {}", partitions);
Set<TopicPartitionInfo> addedPartitions = new HashSet<>(partitions);
addedPartitions.removeAll(partitionedDevices.keySet());
log.info("ADDED PARTITIONS: {}", addedPartitions);
Set<TopicPartitionInfo> removedPartitions = new HashSet<>(partitionedDevices.keySet());
removedPartitions.removeAll(partitions);
log.info("REMOVED PARTITIONS: {}", removedPartitions);
// We no longer manage current partition of devices;
removedPartitions.forEach(partition -> {
Set<DeviceId> devices = partitionedDevices.remove(partition);
devices.forEach(this::cleanUpDeviceStateMap);
});
addedPartitions.forEach(tpi -> partitionedDevices.computeIfAbsent(tpi, key -> ConcurrentHashMap.newKeySet()));
initPartitions(addedPartitions);
scheduledExecutor.submit(() -> {
log.info("Managing following partitions:");
partitionedDevices.forEach((tpi, devices) -> {
log.info("[{}]: {} devices", tpi.getFullTopicName(), devices.size());
});
});
} catch (Throwable t) {
log.warn("Failed to init device states from DB", t);
}
}
//TODO 3.0: replace this dummy search with new functionality to search by partitions using SQL capabilities.
//Adding only entities that are in new partitions
boolean initPartitions(Set<TopicPartitionInfo> addedPartitions) {
if (addedPartitions.isEmpty()) {
return false;
}
List<Tenant> tenants = tenantService.findTenants(new PageLink(Integer.MAX_VALUE)).getData();
for (Tenant tenant : tenants) {
protected void onAddedPartitions(Set<TopicPartitionInfo> addedPartitions) {
PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024);
for (Tenant tenant : tenantIterator) {
log.debug("Finding devices for tenant [{}]", tenant.getName());
final PageLink pageLink = new PageLink(initFetchPackSize);
scheduledExecutor.submit(() -> processPageAndSubmitNextPage(addedPartitions, tenant, pageLink, scheduledExecutor));
processPageAndSubmitNextPage(addedPartitions, tenant, pageLink);
}
return true;
}
private void processPageAndSubmitNextPage(final Set<TopicPartitionInfo> addedPartitions, final Tenant tenant, final PageLink pageLink, final ExecutorService executor) {
private void processPageAndSubmitNextPage(final Set<TopicPartitionInfo> addedPartitions, final Tenant tenant, final PageLink pageLink) {
log.trace("[{}] Process page {} from {}", tenant, pageLink.getPage(), pageLink.getPageSize());
List<ListenableFuture<Void>> fetchFutures = new ArrayList<>();
PageData<Device> page = deviceService.findDevicesByTenantId(tenant.getId(), pageLink);
for (Device device : page.getData()) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), device.getId());
if (addedPartitions.contains(tpi)) {
if (addedPartitions.contains(tpi) && !deviceStates.containsKey(device.getId())) {
log.debug("[{}][{}] Device belong to current partition. tpi [{}]. Fetching state from DB", device.getName(), device.getId(), tpi);
ListenableFuture<Void> future = Futures.transform(fetchDeviceState(device), new Function<DeviceStateData, Void>() {
ListenableFuture<Void> future = Futures.transform(fetchDeviceState(device), new Function<>() {
@Nullable
@Override
public Void apply(@Nullable DeviceStateData state) {
@ -403,7 +333,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
}
}
Futures.addCallback(Futures.successfulAsList(fetchFutures), new FutureCallback<List<Void>>() {
Futures.addCallback(Futures.successfulAsList(fetchFutures), new FutureCallback<>() {
@Override
public void onSuccess(List<Void> result) {
log.trace("[{}] Success init device state from DB for batch size {}", tenant.getId(), result.size());
@ -419,7 +349,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
final PageLink nextPageLink = page.hasNext() ? pageLink.nextPageLink() : null;
if (nextPageLink != null) {
log.trace("[{}] Submit next page {} from {}", tenant, nextPageLink.getPage(), nextPageLink.getPageSize());
executor.submit(() -> processPageAndSubmitNextPage(addedPartitions, tenant, nextPageLink, executor));
processPageAndSubmitNextPage(addedPartitions, tenant, nextPageLink);
}
}
@ -435,10 +365,10 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
}
private void addDeviceUsingState(TopicPartitionInfo tpi, DeviceStateData state) {
Set<DeviceId> deviceIds = partitionedDevices.get(tpi);
Set<DeviceId> deviceIds = partitionedEntities.get(tpi);
if (deviceIds != null) {
deviceIds.add(state.getDeviceId());
deviceStates.put(state.getDeviceId(), state);
deviceStates.putIfAbsent(state.getDeviceId(), state);
} else {
log.debug("[{}] Device belongs to external partition {}", state.getDeviceId(), tpi.getFullTopicName());
throw new RuntimeException("Device belongs to external partition " + tpi.getFullTopicName() + "!");
@ -447,7 +377,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
void updateInactivityStateIfExpired() {
final long ts = System.currentTimeMillis();
partitionedDevices.forEach((tpi, deviceIds) -> {
partitionedEntities.forEach((tpi, deviceIds) -> {
log.debug("Calculating state updates. tpi {} for {} devices", tpi.getFullTopicName(), deviceIds.size());
for (DeviceId deviceId : deviceIds) {
updateInactivityStateIfExpired(ts, deviceId);
@ -464,16 +394,22 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
log.trace("Processing state {} for device {}", stateData, deviceId);
if (stateData != null) {
DeviceState state = stateData.getState();
if (!isActive(ts, state) && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime()) && stateData.getDeviceCreationTime() + state.getInactivityTimeout() < ts) {
state.setActive(false);
state.setLastInactivityAlarmTime(ts);
save(deviceId, INACTIVITY_ALARM_TIME, ts);
save(deviceId, ACTIVITY_STATE, false);
pushRuleEngineMessage(stateData, INACTIVITY_EVENT);
if (!isActive(ts, state)
&& (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime())
&& stateData.getDeviceCreationTime() + state.getInactivityTimeout() < ts) {
if (partitionService.resolve(ServiceType.TB_CORE, stateData.getTenantId(), deviceId).isMyPartition()) {
state.setActive(false);
state.setLastInactivityAlarmTime(ts);
save(deviceId, ACTIVITY_STATE, false);
save(deviceId, INACTIVITY_ALARM_TIME, ts);
pushRuleEngineMessage(stateData, INACTIVITY_EVENT);
} else {
cleanupEntity(deviceId);
}
}
} else {
log.debug("[{}] Device that belongs to other server is detected and removed.", deviceId);
cleanUpDeviceStateMap(deviceId);
cleanupEntity(deviceId);
}
}
@ -506,26 +442,36 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
}
}
private void cleanDeviceStateIfBelongsExternalPartition(TenantId tenantId, final DeviceId deviceId) {
private boolean cleanDeviceStateIfBelongsExternalPartition(TenantId tenantId, final DeviceId deviceId) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId);
if (!partitionedDevices.containsKey(tpi)) {
cleanUpDeviceStateMap(deviceId);
boolean cleanup = !partitionedEntities.containsKey(tpi);
if (cleanup) {
cleanupEntity(deviceId);
log.debug("[{}][{}] device belongs to external partition. Probably rebalancing is in progress. Topic: {}"
, tenantId, deviceId, tpi.getFullTopicName());
}
return cleanup;
}
private void onDeviceDeleted(TenantId tenantId, DeviceId deviceId) {
cleanUpDeviceStateMap(deviceId);
cleanupEntity(deviceId);
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId);
Set<DeviceId> deviceIdSet = partitionedDevices.get(tpi);
deviceIdSet.remove(deviceId);
Set<DeviceId> deviceIdSet = partitionedEntities.get(tpi);
if (deviceIdSet != null) {
deviceIdSet.remove(deviceId);
}
}
private void cleanUpDeviceStateMap(DeviceId deviceId) {
@Override
protected void cleanupEntityOnPartitionRemoval(DeviceId deviceId) {
cleanupEntity(deviceId);
}
private void cleanupEntity(DeviceId deviceId) {
deviceStates.remove(deviceId);
}
private ListenableFuture<DeviceStateData> fetchDeviceState(Device device) {
ListenableFuture<DeviceStateData> future;
if (persistToTelemetry) {

3
application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java

@ -19,6 +19,7 @@ import com.google.common.util.concurrent.FutureCallback;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.TenantId;
@ -95,7 +96,7 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS
}
});
ruleEngineStats.getTenantExceptions().forEach((tenantId, e) -> {
TsKvEntry tsKv = new BasicTsKvEntry(ts, new JsonDataEntry("ruleEngineException", e.toJsonString()));
TsKvEntry tsKv = new BasicTsKvEntry(e.getTs(), new JsonDataEntry("ruleEngineException", e.toJsonString()));
try {
tsService.saveAndNotifyInternal(tenantId, getServiceAssetId(tenantId, queueName), Collections.singletonList(tsKv), CALLBACK);
} catch (DataValidationException e2) {

13
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java

@ -128,7 +128,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
private int maxEntitiesPerDataSubscription;
@Value("${server.ws.max_entities_per_alarm_subscription:1000}")
private int maxEntitiesPerAlarmSubscription;
@Value("${server.ws.max_alarm_queries_per_refresh_interval:3}")
@Value("${server.ws.dynamic_page_link.max_alarm_queries_per_refresh_interval:10}")
private int maxAlarmQueriesPerRefreshInterval;
private ExecutorService wsCallBackExecutor;
@ -219,10 +219,13 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
@Override
public void onSuccess(@Nullable TbEntityDataSubCtx theCtx) {
try {
if (cmd.getLatestCmd() != null) {
handleLatestCmd(theCtx, cmd.getLatestCmd());
} else if (cmd.getTsCmd() != null) {
handleTimeSeriesCmd(theCtx, cmd.getTsCmd());
if (cmd.getLatestCmd() != null || cmd.getTsCmd() != null) {
if (cmd.getLatestCmd() != null) {
handleLatestCmd(theCtx, cmd.getLatestCmd());
}
if (cmd.getTsCmd() != null) {
handleTimeSeriesCmd(theCtx, cmd.getTsCmd());
}
} else if (!theCtx.isInitialDataSent()) {
EntityDataUpdate update = new EntityDataUpdate(theCtx.getCmdId(), theCtx.getData(), null, theCtx.getMaxEntitiesPerDataSubscription());
wsService.sendWsMsg(theCtx.getSessionId(), update);

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

@ -167,7 +167,7 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
}
private TbSubscription createAttrSub(EntityData entityData, int subIdx, EntityKeyType keysType, TbAttributeSubscriptionScope scope, List<EntityKey> subKeys) {
Map<String, Long> keyStates = buildKeyStats(entityData, keysType, subKeys);
Map<String, Long> keyStates = buildKeyStats(entityData, keysType, subKeys, true);
log.trace("[{}][{}][{}] Creating attributes subscription for [{}] with keys: {}", serviceId, cmdId, subIdx, entityData.getEntityId(), keyStates);
return TbAttributeSubscription.builder()
.serviceId(serviceId)
@ -183,7 +183,7 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
}
private TbSubscription createTsSub(EntityData entityData, int subIdx, List<EntityKey> subKeys, boolean latestValues, long startTs, long endTs) {
Map<String, Long> keyStates = buildKeyStats(entityData, EntityKeyType.TIME_SERIES, subKeys);
Map<String, Long> keyStates = buildKeyStats(entityData, EntityKeyType.TIME_SERIES, subKeys, latestValues);
if (!latestValues && entityData.getTimeseries() != null) {
entityData.getTimeseries().forEach((k, v) -> {
long ts = Arrays.stream(v).map(TsValue::getTs).max(Long::compareTo).orElse(0L);
@ -211,15 +211,17 @@ public abstract class TbAbstractDataSubCtx<T extends AbstractDataQuery<? extends
sendWsMsg(sessionId, subscriptionUpdate, keyType, true);
}
private Map<String, Long> buildKeyStats(EntityData entityData, EntityKeyType keysType, List<EntityKey> subKeys) {
private Map<String, Long> buildKeyStats(EntityData entityData, EntityKeyType keysType, List<EntityKey> subKeys, boolean latestValues) {
Map<String, Long> keyStates = new HashMap<>();
subKeys.forEach(key -> keyStates.put(key.getKey(), 0L));
if (entityData.getLatest() != null) {
if (latestValues && entityData.getLatest() != null) {
Map<String, TsValue> currentValues = entityData.getLatest().get(keysType);
if (currentValues != null) {
currentValues.forEach((k, v) -> {
log.trace("[{}][{}] Updating key: {} with ts: {}", serviceId, cmdId, k, v.getTs());
keyStates.put(k, v.getTs());
if (subKeys.contains(new EntityKey(keysType, k))) {
log.trace("[{}][{}] Updating key: {} with ts: {}", serviceId, cmdId, k, v.getTs());
keyStates.put(k, v.getTs());
}
});
}
}

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

@ -99,6 +99,8 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
log.trace("[{}] Fetching alarms: {}", cmdId, alarmInvocationAttempts);
if (alarmInvocationAttempts <= maxAlarmQueriesPerRefreshInterval) {
doFetchAlarms();
} else {
log.trace("[{}] Ignore alarm fetch due to rate limit: [{}] of maximum [{}]", cmdId, alarmInvocationAttempts, maxAlarmQueriesPerRefreshInterval);
}
}

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

@ -19,6 +19,7 @@ import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityKey;
@ -55,6 +56,7 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
private LatestValueCmd latestValueCmd;
@Getter
private final int maxEntitiesPerDataSubscription;
private Map<EntityId, Map<String, TsValue>> latestTsEntityData;
public TbEntityDataSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService,
TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService,
@ -63,6 +65,12 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
this.maxEntitiesPerDataSubscription = maxEntitiesPerDataSubscription;
}
@Override
public void fetchData() {
super.fetchData();
this.updateLatestTsData(this.data);
}
@Override
protected void sendWsMsg(String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType, boolean resultToLatestValues) {
EntityId entityId = subToEntityIdMap.get(subscriptionUpdate.getSubscriptionId());
@ -123,40 +131,37 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
Object[] data = (Object[]) v.get(0);
tsUpdate.computeIfAbsent(k, key -> new ArrayList<>()).add(new TsValue((Long) data[0], (String) data[1]));
});
EntityData entityData = getDataForEntity(entityId);
if (entityData != null && entityData.getLatest() != null) {
Map<String, TsValue> latestCtxValues = entityData.getLatest().get(keyType);
log.trace("[{}][{}][{}] Going to compare update with {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), latestCtxValues);
if (latestCtxValues != null) {
latestCtxValues.forEach((k, v) -> {
List<TsValue> updateList = tsUpdate.get(k);
if (updateList != null) {
for (TsValue update : new ArrayList<>(updateList)) {
if (update.getTs() < v.getTs()) {
log.trace("[{}][{}][{}] Removed stale update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs());
// Looks like this is redundant feature and our UI is ready to merge the updates.
//updateList.remove(update);
} else if ((update.getTs() == v.getTs() && update.getValue().equals(v.getValue()))) {
log.trace("[{}][{}][{}] Removed duplicate update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs());
updateList.remove(update);
}
if (updateList.isEmpty()) {
tsUpdate.remove(k);
}
Map<String, TsValue> latestCtxValues = getLatestTsValuesForEntity(entityId);
log.trace("[{}][{}][{}] Going to compare update with {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), latestCtxValues);
if (latestCtxValues != null) {
latestCtxValues.forEach((k, v) -> {
List<TsValue> updateList = tsUpdate.get(k);
if (updateList != null) {
for (TsValue update : new ArrayList<>(updateList)) {
if (update.getTs() < v.getTs()) {
log.trace("[{}][{}][{}] Removed stale update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs());
// Looks like this is redundant feature and our UI is ready to merge the updates.
//updateList.remove(update);
} else if ((update.getTs() == v.getTs() && update.getValue().equals(v.getValue()))) {
log.trace("[{}][{}][{}] Removed duplicate update for key: {} and ts: {}", sessionId, cmdId, subscriptionUpdate.getSubscriptionId(), k, update.getTs());
updateList.remove(update);
}
if (updateList.isEmpty()) {
tsUpdate.remove(k);
}
}
});
//Setting new values
tsUpdate.forEach((k, v) -> {
Optional<TsValue> maxValue = v.stream().max(Comparator.comparingLong(TsValue::getTs));
maxValue.ifPresent(max -> latestCtxValues.put(k, max));
});
}
}
});
//Setting new values
tsUpdate.forEach((k, v) -> {
Optional<TsValue> maxValue = v.stream().max(Comparator.comparingLong(TsValue::getTs));
maxValue.ifPresent(max -> latestCtxValues.put(k, max));
});
}
if (!tsUpdate.isEmpty()) {
Map<String, TsValue[]> tsMap = new HashMap<>();
tsUpdate.forEach((key, tsValue) -> tsMap.put(key, tsValue.toArray(new TsValue[tsValue.size()])));
entityData = new EntityData(entityId, null, tsMap);
EntityData entityData = new EntityData(entityId, null, tsMap);
wsService.sendWsMsg(sessionId, new EntityDataUpdate(cmdId, null, Collections.singletonList(entityData), maxEntitiesPerDataSubscription));
}
}
@ -165,8 +170,27 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx<EntityDataQuery> {
return data.getData().stream().filter(item -> item.getEntityId().equals(entityId)).findFirst().orElse(null);
}
private Map<String, TsValue> getLatestTsValuesForEntity(EntityId entityId) {
return latestTsEntityData.get(entityId);
}
private void updateLatestTsData(PageData<EntityData> data) {
latestTsEntityData = new HashMap<>();
data.getData().stream().forEach(entityData -> {
Map<String, TsValue> latestTsMap = new HashMap<>();
latestTsEntityData.put(entityData.getEntityId(), latestTsMap);
if (entityData.getLatest() != null) {
Map<String, TsValue> latestTsValues = entityData.getLatest().get(EntityKeyType.TIME_SERIES);
if (latestTsValues != null) {
latestTsValues.forEach(latestTsMap::put);
}
}
});
}
@Override
public synchronized void doUpdate(Map<EntityId, EntityData> newDataMap) {
this.updateLatestTsData(this.data);
List<Integer> subIdsToCancel = new ArrayList<>();
List<TbSubscription> subsToAdd = new ArrayList<>();
Set<EntityId> currentSubs = new HashSet<>();

5
application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java

@ -28,6 +28,9 @@ import org.thingsboard.server.queue.util.TbCoreComponent;
@Service
public class EdgeEventsCleanUpService extends AbstractCleanUpService {
public static final String RANDOM_DELAY_INTERVAL_MS_EXPRESSION =
"#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.edge_events.execution_interval_ms})}";
@Value("${sql.ttl.edge_events.edge_events_ttl}")
private long ttl;
@ -41,7 +44,7 @@ public class EdgeEventsCleanUpService extends AbstractCleanUpService {
this.edgeEventService = edgeEventService;
}
@Scheduled(initialDelayString = "${sql.ttl.edge_events.execution_interval_ms}", fixedDelayString = "${sql.ttl.edge_events.execution_interval_ms}")
@Scheduled(initialDelayString = RANDOM_DELAY_INTERVAL_MS_EXPRESSION, fixedDelayString = "${sql.ttl.edge_events.execution_interval_ms}")
public void cleanUp() {
if (ttlTaskExecutionEnabled && isSystemTenantPartitionMine()) {
edgeEventService.cleanupEvents(ttl);

32
application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java

@ -22,7 +22,8 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.ttl.AbstractCleanUpService;
import java.util.concurrent.TimeUnit;
@TbCoreComponent
@Slf4j
@ -33,10 +34,13 @@ public class EventsCleanUpService extends AbstractCleanUpService {
"#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.events.execution_interval_ms})}";
@Value("${sql.ttl.events.events_ttl}")
private long ttl;
private long ttlInSec;
@Value("${sql.ttl.events.debug_events_ttl}")
private long debugTtl;
private long debugTtlInSec;
@Value("${sql.ttl.events.execution_interval_ms}")
private long executionIntervalInMs;
@Value("${sql.ttl.events.enabled}")
private boolean ttlTaskExecutionEnabled;
@ -51,7 +55,27 @@ public class EventsCleanUpService extends AbstractCleanUpService {
@Scheduled(initialDelayString = RANDOM_DELAY_INTERVAL_MS_EXPRESSION, fixedDelayString = "${sql.ttl.events.execution_interval_ms}")
public void cleanUp() {
if (ttlTaskExecutionEnabled && isSystemTenantPartitionMine()) {
eventService.cleanupEvents(ttl, debugTtl);
long ts = System.currentTimeMillis();
long regularEventStartTs;
long regularEventEndTs;
long debugEventStartTs;
long debugEventEndTs;
if (ttlInSec > 0) {
regularEventEndTs = ts - TimeUnit.SECONDS.toMillis(ttlInSec);
regularEventStartTs = regularEventEndTs - 2 * executionIntervalInMs;
} else {
regularEventStartTs = regularEventEndTs = 0;
}
if (debugTtlInSec > 0) {
debugEventEndTs = ts - TimeUnit.SECONDS.toMillis(debugTtlInSec);
debugEventStartTs = debugEventEndTs - 2 * executionIntervalInMs;
} else {
debugEventStartTs = debugEventEndTs = 0;
}
eventService.cleanupEvents(regularEventStartTs, regularEventEndTs, debugEventStartTs, debugEventEndTs);
}
}

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

@ -71,7 +71,7 @@ server:
dynamic_page_link:
refresh_interval: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_INTERVAL_SEC:60}"
refresh_pool_size: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_POOL_SIZE:1}"
max_alarm_queries_per_refresh_interval: "${TB_SERVER_WS_MAX_ALARM_QUERIES_PER_REFRESH_INTERVAL:3}"
max_alarm_queries_per_refresh_interval: "${TB_SERVER_WS_MAX_ALARM_QUERIES_PER_REFRESH_INTERVAL:10}"
max_per_user: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_MAX_PER_USER:10}"
max_entities_per_data_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_DATA_SUBSCRIPTION:10000}"
max_entities_per_alarm_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_ALARM_SUBSCRIPTION:10000}"
@ -165,7 +165,7 @@ ui:
# Help parameters
help:
# Base url for UI help assets
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.3.3}"
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.3.4}"
database:
ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records
@ -278,6 +278,11 @@ sql:
stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}"
batch_threads: "${SQL_TS_LATEST_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution
update_by_latest_ts: "${SQL_TS_UPDATE_BY_LATEST_TIMESTAMP:true}"
events:
batch_size: "${SQL_EVENTS_BATCH_SIZE:10000}"
batch_max_delay: "${SQL_EVENTS_BATCH_MAX_DELAY_MS:100}"
stats_print_interval_ms: "${SQL_EVENTS_BATCH_STATS_PRINT_MS:10000}"
batch_threads: "${SQL_EVENTS_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution
# Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks
batch_sort: "${SQL_BATCH_SORT:false}"
# Specify whether to remove null characters from strValue of attributes and timeseries before insert
@ -831,8 +836,6 @@ transport:
log_max_length: "${LWM2M_LOG_MAX_LENGTH:1024}"
psm_activity_timer: "${LWM2M_PSM_ACTIVITY_TIMER:10000}"
paging_transmission_window: "${LWM2M_PAGING_TRANSMISSION_WINDOW:10000}"
# Use redis for Security and Registration stores
redis.enabled: "${LWM2M_REDIS_ENABLED:false}"
network_config: # In this section you can specify custom parameters for LwM2M network configuration and expose the env variables to configure outside
# - key: "PROTOCOL_STAGE_THREAD_COUNT"
# value: "${LWM2M_PROTOCOL_STAGE_THREAD_COUNT:4}"

6
application/src/test/java/org/thingsboard/server/actors/stats/StatsActorTest.java

@ -59,11 +59,11 @@ class StatsActorTest {
@Test
void givenNonEmptyStatMessage_whenOnStatsPersistMsg_thenNoAction() {
statsActor.onStatsPersistMsg(new StatsPersistMsg(0, 1, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID));
verify(eventService, times(1)).save(any(Event.class));
verify(eventService, times(1)).saveAsync(any(Event.class));
statsActor.onStatsPersistMsg(new StatsPersistMsg(1, 0, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID));
verify(eventService, times(2)).save(any(Event.class));
verify(eventService, times(2)).saveAsync(any(Event.class));
statsActor.onStatsPersistMsg(new StatsPersistMsg(1, 1, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID));
verify(eventService, times(3)).save(any(Event.class));
verify(eventService, times(3)).saveAsync(any(Event.class));
}
}

33
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -64,6 +64,7 @@ import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
@ -124,6 +125,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected String username;
protected TenantId tenantId;
protected CustomerId customerId;
@SuppressWarnings("rawtypes")
private HttpMessageConverter mappingJackson2HttpMessageConverter;
@ -192,6 +194,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
customer.setTitle("Customer");
customer.setTenantId(tenantId);
Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
customerId = savedCustomer.getId();
User customerUser = new User();
customerUser.setAuthority(Authority.CUSTOMER_USER);
@ -254,9 +257,14 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
}
private Tenant savedDifferentTenant;
private Customer savedDifferentCustomer;
protected void loginDifferentTenant() throws Exception {
loginSysAdmin();
if (savedDifferentTenant != null) {
deleteDifferentTenant();
}
Tenant tenant = new Tenant();
tenant.setTitle("Different tenant");
savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class);
@ -269,10 +277,27 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
createUserAndLogin(differentTenantAdmin, "testPassword");
}
protected void loginDifferentCustomer() throws Exception {
loginTenantAdmin();
Customer customer = new Customer();
customer.setTitle("Different customer");
savedDifferentCustomer = doPost("/api/customer", customer, Customer.class);
Assert.assertNotNull(savedDifferentCustomer);
User differentCustomerUser = new User();
differentCustomerUser.setAuthority(Authority.CUSTOMER_USER);
differentCustomerUser.setTenantId(tenantId);
differentCustomerUser.setCustomerId(savedDifferentCustomer.getId());
differentCustomerUser.setEmail("different_customer@thingsboard.org");
createUserAndLogin(differentCustomerUser, "testPassword");
}
protected void deleteDifferentTenant() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString())
.andExpect(status().isOk());
if (savedDifferentTenant != null) {
loginSysAdmin();
doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString())
.andExpect(status().isOk());
}
}
protected User createUserAndLogin(User user, String password) throws Exception {
@ -606,8 +631,6 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
edge.setType(type);
edge.setSecret(RandomStringUtils.randomAlphanumeric(20));
edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20));
edge.setEdgeLicenseKey(RandomStringUtils.randomAlphanumeric(20));
edge.setCloudEndpoint("http://localhost:8080");
return edge;
}
}

61
application/src/test/java/org/thingsboard/server/controller/AbstractWebsocketTest.java

@ -15,81 +15,20 @@
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootContextLoader;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.http.MockHttpInputMessage;
import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.config.ThingsboardSecurityConfiguration;
import org.thingsboard.server.service.mail.TestMailService;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest;
import org.thingsboard.server.service.security.auth.rest.LoginRequest;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
@ActiveProfiles("test")
@RunWith(SpringRunner.class)

230
application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java

@ -0,0 +1,230 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
public static final String TEST_ALARM_TYPE = "Test";
protected Device customerDevice;
@Before
public void setup() throws Exception {
loginTenantAdmin();
Device device = new Device();
device.setTenantId(tenantId);
device.setName("Test device");
device.setLabel("Label");
device.setType("Type");
device.setCustomerId(customerId);
customerDevice = doPost("/api/device", device, Device.class);
logout();
}
@After
public void teardown() throws Exception {
loginSysAdmin();
deleteDifferentTenant();
}
@Test
public void testCreateAlarmViaCustomer() throws Exception {
loginCustomerUser();
createAlarm(TEST_ALARM_TYPE);
logout();
}
@Test
public void testCreateAlarmViaTenant() throws Exception {
loginTenantAdmin();
createAlarm(TEST_ALARM_TYPE);
logout();
}
@Test
public void testUpdateAlarmViaCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
alarm.setSeverity(AlarmSeverity.MAJOR);
Alarm updatedAlarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(updatedAlarm);
Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity());
logout();
}
@Test
public void testUpdateAlarmViaTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
alarm.setSeverity(AlarmSeverity.MAJOR);
Alarm updatedAlarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(updatedAlarm);
Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity());
logout();
}
@Test
public void testUpdateAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
alarm.setSeverity(AlarmSeverity.MAJOR);
loginDifferentTenant();
doPost("/api/alarm", alarm).andExpect(status().isForbidden());
logout();
}
@Test
public void testUpdateAlarmViaDifferentCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentCustomer();
alarm.setSeverity(AlarmSeverity.MAJOR);
doPost("/api/alarm", alarm).andExpect(status().isForbidden());
logout();
}
@Test
public void testDeleteAlarmViaCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk());
logout();
}
@Test
public void testDeleteAlarmViaTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk());
logout();
}
@Test
public void testDeleteAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentTenant();
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isForbidden());
logout();
}
@Test
public void testDeleteAlarmViaAnotherCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentCustomer();
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isForbidden());
logout();
}
@Test
public void testClearAlarmViaCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isOk());
Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(AlarmStatus.CLEARED_UNACK, foundAlarm.getStatus());
logout();
}
@Test
public void testClearAlarmViaTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isOk());
Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(AlarmStatus.CLEARED_UNACK, foundAlarm.getStatus());
logout();
}
@Test
public void testAcknowledgeAlarmViaCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isOk());
Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(AlarmStatus.ACTIVE_ACK, foundAlarm.getStatus());
logout();
}
@Test
public void testClearAlarmViaDifferentCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentCustomer();
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isForbidden());
logout();
}
@Test
public void testClearAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentTenant();
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isForbidden());
logout();
}
@Test
public void testAcknowledgeAlarmViaDifferentCustomer() throws Exception {
loginCustomerUser();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentCustomer();
doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isForbidden());
logout();
}
@Test
public void testAcknowledgeAlarmViaDifferentTenant() throws Exception {
loginTenantAdmin();
Alarm alarm = createAlarm(TEST_ALARM_TYPE);
loginDifferentTenant();
doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isForbidden());
logout();
}
private Alarm createAlarm(String type) throws Exception {
Alarm alarm = Alarm.builder()
.tenantId(tenantId)
.customerId(customerId)
.originator(customerDevice.getId())
.status(AlarmStatus.ACTIVE_UNACK)
.severity(AlarmSeverity.CRITICAL)
.type(type)
.build();
alarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(alarm);
return alarm;
}
}

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

@ -105,8 +105,6 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
Assert.assertNotNull(savedEdge.getCustomerId());
Assert.assertEquals(NULL_UUID, savedEdge.getCustomerId().getId());
Assert.assertEquals(edge.getName(), savedEdge.getName());
Assert.assertTrue(StringUtils.isNoneBlank(savedEdge.getEdgeLicenseKey()));
Assert.assertTrue(StringUtils.isNoneBlank(savedEdge.getCloudEndpoint()));
savedEdge.setName("My new edge");
doPost("/api/edge", savedEdge, Edge.class);

80
application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java

@ -16,10 +16,13 @@
package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import org.awaitility.Awaitility;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
@ -29,6 +32,8 @@ import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.DeviceTypeFilter;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.DynamicValueSourceType;
import org.thingsboard.server.common.data.query.EntityCountQuery;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
@ -41,11 +46,13 @@ import org.thingsboard.server.common.data.query.EntityTypeFilter;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.common.data.security.Authority;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -299,6 +306,79 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe
List<String> deviceHighTemperatures = highTemperatures.stream().map(aLong -> Long.toString(aLong)).collect(Collectors.toList());
Assert.assertEquals(deviceHighTemperatures, loadedHighTemperatures);
}
@Test
public void testFindEntityDataByQueryWithDynamicValue() throws Exception {
int numOfDevices = 2;
for (int i = 0; i < numOfDevices; i++) {
Device device = new Device();
String name = "Device" + i;
device.setName(name);
device.setType("default");
device.setLabel("testLabel" + (int) (Math.random() * 1000));
Device savedDevice1 = doPost("/api/device?accessToken=" + name, device, Device.class);
JsonNode content = JacksonUtil.toJsonNode("{\"alarmActiveTime\": 1" + i + "}");
doPost("/api/plugins/telemetry/" + EntityType.DEVICE.name() + "/" + savedDevice1.getUuidId() + "/SERVER_SCOPE", content)
.andExpect(status().isOk());
}
JsonNode content = JacksonUtil.toJsonNode("{\"dynamicValue\": 0}");
doPost("/api/plugins/telemetry/" + EntityType.TENANT.name() + "/" + tenantId.getId() + "/SERVER_SCOPE", content)
.andExpect(status().isOk());
DeviceTypeFilter filter = new DeviceTypeFilter();
filter.setDeviceType("default");
filter.setDeviceNameFilter("");
KeyFilter highTemperatureFilter = new KeyFilter();
highTemperatureFilter.setKey(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, "alarmActiveTime"));
NumericFilterPredicate predicate = new NumericFilterPredicate();
DynamicValue<Double> dynamicValue =
new DynamicValue<>(DynamicValueSourceType.CURRENT_TENANT, "dynamicValue");
FilterPredicateValue<Double> predicateValue = new FilterPredicateValue<>(0.0, null, dynamicValue);
predicate.setValue(predicateValue);
predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperatureFilter.setPredicate(predicate);
List<KeyFilter> keyFilters = Collections.singletonList(highTemperatureFilter);
EntityDataSortOrder sortOrder = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
);
EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder);
List<EntityKey> entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name"));
List<EntityKey> latestValues = Collections.singletonList(new EntityKey(EntityKeyType.ATTRIBUTE, "alarmActiveTime"));
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
Awaitility.await()
.alias("data by query")
.atMost(30, TimeUnit.SECONDS)
.until(() -> {
var data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<PageData<EntityData>>() {});
var loadedEntities = new ArrayList<>(data.getData());
return loadedEntities.size() == numOfDevices;
});
var data = doPostWithTypedResponse("/api/entitiesQuery/find", query, new TypeReference<PageData<EntityData>>() {});
var loadedEntities = new ArrayList<>(data.getData());
Assert.assertEquals(numOfDevices, loadedEntities.size());
for (int i = 0; i < numOfDevices; i++) {
var entity = loadedEntities.get(i);
String name = entity.getLatest().get(EntityKeyType.ENTITY_FIELD).getOrDefault("name", new TsValue(0, "Invalid")).getValue();
String alarmActiveTime = entity.getLatest().get(EntityKeyType.ATTRIBUTE).getOrDefault("alarmActiveTime", new TsValue(0, "-1")).getValue();
Assert.assertEquals("Device" + i, name);
Assert.assertEquals("1" + i, alarmActiveTime);
}
}
}

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

@ -54,7 +54,7 @@ public class TbTestWebSocketClient extends WebSocketClient {
@Override
public void onClose(int i, String s, boolean b) {
log.error("CLOSED:");
log.info("CLOSED.");
}
@Override

23
application/src/test/java/org/thingsboard/server/controller/sql/AlarmControllerSqlTest.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.controller.sql;
import org.thingsboard.server.controller.BaseAlarmControllerTest;
import org.thingsboard.server.dao.service.DaoSqlTest;
@DaoSqlTest
public class AlarmControllerSqlTest extends BaseAlarmControllerTest {
}

435
application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java

@ -25,7 +25,6 @@ import com.google.gson.JsonObject;
import com.google.protobuf.AbstractMessage;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.MessageLite;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.awaitility.Awaitility;
import org.junit.After;
@ -34,6 +33,7 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DataConstants;
@ -42,6 +42,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
@ -80,6 +81,7 @@ import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.security.Authority;
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;
import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
@ -110,13 +112,13 @@ import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.UplinkMsg;
import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg;
import org.thingsboard.server.gen.edge.v1.UserCredentialsRequestMsg;
import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UserUpdateMsg;
import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg;
import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.cluster.TbClusterService;
import java.util.ArrayList;
import java.util.List;
@ -129,7 +131,6 @@ import java.util.concurrent.TimeUnit;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
abstract public class BaseEdgeTest extends AbstractControllerTest {
private static final String CUSTOM_DEVICE_PROFILE_NAME = "Thermostat";
@ -165,7 +166,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
// sleep 1 seconds to avoid CREDENTIALS updated message for the user
// sleep 0.5 second to avoid CREDENTIALS updated message for the user
// user credentials is going to be stored and updated event pushed to edge notification service
// while service will be processing this event edge could be already added and additional message will be pushed
Thread.sleep(500);
@ -176,7 +177,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectMessageAmount(13);
edgeImitator.connect();
testReceivedInitialData();
verifyEdgeConnectionAndInitialData();
}
@After
@ -187,41 +188,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
doDelete("/api/tenant/" + savedTenant.getUuidId())
.andExpect(status().isOk());
}
@Test
public void test() throws Exception {
testDevices();
testAssets();
testRuleChains();
testDashboards();
testRelations();
testAlarms();
testEntityView();
testCustomerAndNewUser();
testWidgetsBundleAndWidgetType();
testTimeseries();
testAttributes();
testRpcCall();
testTimeseriesWithFailures();
testSendMessagesToCloud();
}
private void installation() throws Exception {
edge = doPost("/api/edge", constructEdge("Test Edge", "test"), Edge.class);
@ -230,12 +200,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
Device savedDevice = saveDevice("Edge Device 1", CUSTOM_DEVICE_PROFILE_NAME);
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class);
doPost("/api/edge/" + edge.getUuidId()
+ "/device/" + savedDevice.getUuidId(), Device.class);
Asset savedAsset = saveAsset("Edge Asset 1");
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class);
doPost("/api/edge/" + edge.getUuidId()
+ "/asset/" + savedAsset.getUuidId(), Asset.class);
}
private void extendDeviceProfileData(DeviceProfile deviceProfile) {
@ -267,8 +237,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
profileData.setProvisionConfiguration(new AllowCreateNewDevicesDeviceProfileProvisionConfiguration("123"));
}
private void testReceivedInitialData() throws Exception {
log.info("Checking received data");
private void verifyEdgeConnectionAndInitialData() throws Exception {
Assert.assertTrue(edgeImitator.waitForMessages());
EdgeConfiguration configuration = edgeImitator.getConfiguration();
@ -283,7 +252,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
UUID deviceUUID = new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB());
Device device = doGet("/api/device/" + deviceUUID.toString(), Device.class);
Assert.assertNotNull(device);
List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/devices?",
List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?",
new TypeReference<PageData<Device>>() {}, new PageLink(100)).getData();
Assert.assertTrue(edgeDevices.contains(device));
@ -310,7 +279,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
UUID assetUUID = new UUID(assetUpdateMsg.getIdMSB(), assetUpdateMsg.getIdLSB());
Asset asset = doGet("/api/asset/" + assetUUID.toString(), Asset.class);
Assert.assertNotNull(asset);
List<Asset> edgeAssets = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/assets?",
List<Asset> edgeAssets = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/assets?",
new TypeReference<PageData<Asset>>() {}, new PageLink(100)).getData();
Assert.assertTrue(edgeAssets.contains(asset));
@ -323,15 +292,13 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
UUID ruleChainUUID = new UUID(ruleChainUpdateMsg.getIdMSB(), ruleChainUpdateMsg.getIdLSB());
RuleChain ruleChain = doGet("/api/ruleChain/" + ruleChainUUID.toString(), RuleChain.class);
Assert.assertNotNull(ruleChain);
List<RuleChain> edgeRuleChains = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/ruleChains?",
List<RuleChain> edgeRuleChains = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/ruleChains?",
new TypeReference<PageData<RuleChain>>() {}, new PageLink(100)).getData();
Assert.assertTrue(edgeRuleChains.contains(ruleChain));
testAutoGeneratedCodeByProtobuf(ruleChainUpdateMsg);
validateAdminSettings();
log.info("Received data checked");
}
private void validateAdminSettings() throws JsonProcessingException {
@ -367,39 +334,54 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertNotNull(jsonNode.get("test"));
}
private void testDevices() throws Exception {
log.info("Testing devices");
@Test
public void testDeviceProfiles() throws Exception {
// 1
DeviceProfile deviceProfile = this.createDeviceProfile("ONE_MORE_DEVICE_PROFILE", null);
extendDeviceProfileData(deviceProfile);
edgeImitator.expectMessageAmount(1);
Device savedDevice = saveDevice("Edge Device 2", "Default");
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class);
deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType());
Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getName(), savedDevice.getName());
Assert.assertEquals(deviceUpdateMsg.getType(), savedDevice.getType());
Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg);
DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType());
Assert.assertEquals(deviceProfileUpdateMsg.getIdMSB(), deviceProfile.getUuidId().getMostSignificantBits());
Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits());
// 2
edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class);
doDelete("/api/deviceProfile/" + deviceProfile.getUuidId())
.andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg);
deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType());
Assert.assertEquals(deviceProfileUpdateMsg.getIdMSB(), deviceProfile.getUuidId().getMostSignificantBits());
Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits());
}
@Test
public void testDevices() throws Exception {
// 1
Device savedDevice = saveDeviceOnCloudAndVerifyDeliveryToEdge();
// 2
edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getUuidId()
+ "/device/" + savedDevice.getUuidId(), Device.class);
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
deviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceUpdateMsg.getMsgType());
Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits());
// 3
edgeImitator.expectMessageAmount(1);
doDelete("/api/device/" + savedDevice.getId().getId().toString())
doDelete("/api/device/" + savedDevice.getUuidId())
.andExpect(status().isOk());
// we should not get any message because device is not assigned to edge any more
Assert.assertFalse(edgeImitator.waitForMessages(1));
@ -407,8 +389,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 4
edgeImitator.expectMessageAmount(1);
savedDevice = saveDevice("Edge Device 3", "Default");
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class);
doPost("/api/edge/" + edge.getUuidId()
+ "/device/" + savedDevice.getUuidId(), Device.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
@ -421,7 +403,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 5
edgeImitator.expectMessageAmount(1);
doDelete("/api/device/" + savedDevice.getId().getId().toString())
doDelete("/api/device/" + savedDevice.getUuidId())
.andExpect(status().isOk());
// in this case we should get messages because device was assigned to edge
Assert.assertTrue(edgeImitator.waitForMessages());
@ -432,17 +414,49 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits());
log.info("Devices tested successfully");
}
private void testAssets() throws Exception {
log.info("Testing assets");
@Test
public void testDeviceReachedMaximumAllowedOnCloud() throws Exception {
// update tenant profile configuration
loginSysAdmin();
TenantProfile tenantProfile = doGet("/api/tenantProfile/" + savedTenant.getTenantProfileId().getId(), TenantProfile.class);
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration();
profileConfiguration.setMaxDevices(1);
tenantProfile.getProfileData().setConfiguration(profileConfiguration);
doPost("/api/tenantProfile/", tenantProfile, TenantProfile.class);
loginTenantAdmin();
UUID uuid = Uuids.timeBased();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
DeviceUpdateMsg.Builder deviceUpdateMsgBuilder = DeviceUpdateMsg.newBuilder();
deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
deviceUpdateMsgBuilder.setName("Edge Device");
deviceUpdateMsgBuilder.setType("default");
deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build());
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
UplinkResponseMsg latestResponseMsg = edgeImitator.getLatestResponseMsg();
Assert.assertTrue(latestResponseMsg.getSuccess());
}
@Test
public void testAssets() throws Exception {
// 1
edgeImitator.expectMessageAmount(1);
Asset savedAsset = saveAsset("Edge Asset 2");
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class);
doPost("/api/edge/" + edge.getUuidId()
+ "/asset/" + savedAsset.getUuidId(), Asset.class);
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AssetUpdateMsg);
@ -455,8 +469,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 2
edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString()
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class);
doDelete("/api/edge/" + edge.getUuidId()
+ "/asset/" + savedAsset.getUuidId(), Asset.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AssetUpdateMsg);
@ -467,15 +481,15 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 3
edgeImitator.expectMessageAmount(1);
doDelete("/api/asset/" + savedAsset.getId().getId().toString())
doDelete("/api/asset/" + savedAsset.getUuidId())
.andExpect(status().isOk());
Assert.assertFalse(edgeImitator.waitForMessages(1));
// 4
edgeImitator.expectMessageAmount(1);
savedAsset = saveAsset("Edge Asset 3");
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class);
doPost("/api/edge/" + edge.getUuidId()
+ "/asset/" + savedAsset.getUuidId(), Asset.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AssetUpdateMsg);
@ -488,7 +502,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 5
edgeImitator.expectMessageAmount(1);
doDelete("/api/asset/" + savedAsset.getId().getId().toString())
doDelete("/api/asset/" + savedAsset.getUuidId())
.andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
@ -497,21 +511,18 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, assetUpdateMsg.getMsgType());
Assert.assertEquals(assetUpdateMsg.getIdMSB(), savedAsset.getUuidId().getMostSignificantBits());
Assert.assertEquals(assetUpdateMsg.getIdLSB(), savedAsset.getUuidId().getLeastSignificantBits());
log.info("Assets tested successfully");
}
private void testRuleChains() throws Exception {
log.info("Testing RuleChains");
@Test
public void testRuleChains() throws Exception {
// 1
edgeImitator.expectMessageAmount(2);
RuleChain ruleChain = new RuleChain();
ruleChain.setName("Edge Test Rule Chain");
ruleChain.setType(RuleChainType.EDGE);
RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class);
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/ruleChain/" + savedRuleChain.getId().getId().toString(), RuleChain.class);
doPost("/api/edge/" + edge.getUuidId()
+ "/ruleChain/" + savedRuleChain.getUuidId(), RuleChain.class);
createRuleChainMetadata(savedRuleChain);
Assert.assertTrue(edgeImitator.waitForMessages());
Optional<RuleChainUpdateMsg> ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class);
@ -528,8 +539,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 3
edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString()
+ "/ruleChain/" + savedRuleChain.getId().getId().toString(), RuleChain.class);
doDelete("/api/edge/" + edge.getUuidId()
+ "/ruleChain/" + savedRuleChain.getUuidId(), RuleChain.class);
Assert.assertTrue(edgeImitator.waitForMessages());
ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class);
Assert.assertTrue(ruleChainUpdateMsgOpt.isPresent());
@ -540,11 +551,9 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 4
edgeImitator.expectMessageAmount(1);
doDelete("/api/ruleChain/" + savedRuleChain.getId().getId().toString())
doDelete("/api/ruleChain/" + savedRuleChain.getUuidId())
.andExpect(status().isOk());
Assert.assertFalse(edgeImitator.waitForMessages(1));
log.info("RuleChains tested successfully");
}
private void testRuleChainMetadataRequestMsg(RuleChainId ruleChainId) throws Exception {
@ -603,21 +612,18 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
ruleChainMetaData.addConnectionInfo(0, 2, "fail");
ruleChainMetaData.addConnectionInfo(1, 2, "success");
ruleChainMetaData.addRuleChainConnectionInfo(2, edge.getRootRuleChainId(), "success", mapper.createObjectNode());
doPost("/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class);
}
private void testDashboards() throws Exception {
log.info("Testing Dashboards");
@Test
public void testDashboards() throws Exception {
// 1
edgeImitator.expectMessageAmount(1);
Dashboard dashboard = new Dashboard();
dashboard.setTitle("Edge Test Dashboard");
Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
doPost("/api/edge/" + edge.getUuidId()
+ "/dashboard/" + savedDashboard.getUuidId(), Dashboard.class);
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg);
@ -641,8 +647,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 3
edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString()
+ "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
doDelete("/api/edge/" + edge.getUuidId()
+ "/dashboard/" + savedDashboard.getUuidId(), Dashboard.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg);
@ -653,16 +659,13 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 4
edgeImitator.expectMessageAmount(1);
doDelete("/api/dashboard/" + savedDashboard.getId().getId().toString())
doDelete("/api/dashboard/" + savedDashboard.getUuidId())
.andExpect(status().isOk());
Assert.assertFalse(edgeImitator.waitForMessages(1));
log.info("Dashboards tested successfully");
}
private void testRelations() throws Exception {
log.info("Testing Relations");
@Test
public void testRelations() throws Exception {
// 1
edgeImitator.expectMessageAmount(1);
Device device = findDeviceByName("Edge Device 1");
@ -710,13 +713,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(relationUpdateMsg.getToIdLSB(), relation.getTo().getId().getLeastSignificantBits());
Assert.assertEquals(relationUpdateMsg.getToEntityType(), relation.getTo().getEntityType().name());
Assert.assertEquals(relationUpdateMsg.getTypeGroup(), relation.getTypeGroup().name());
log.info("Relations tested successfully");
}
private void testAlarms() throws Exception {
log.info("Testing Alarms");
@Test
public void testAlarms() throws Exception {
// 1
edgeImitator.expectMessageAmount(1);
Device device = findDeviceByName("Edge Device 1");
@ -739,7 +739,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 2
edgeImitator.expectMessageAmount(1);
doPost("/api/alarm/" + savedAlarm.getId().getId().toString() + "/ack");
doPost("/api/alarm/" + savedAlarm.getUuidId() + "/ack");
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg);
@ -752,7 +752,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 3
edgeImitator.expectMessageAmount(1);
doPost("/api/alarm/" + savedAlarm.getId().getId().toString() + "/clear");
doPost("/api/alarm/" + savedAlarm.getUuidId() + "/clear");
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg);
@ -765,7 +765,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 4
edgeImitator.expectMessageAmount(1);
doDelete("/api/alarm/" + savedAlarm.getId().getId().toString())
doDelete("/api/alarm/" + savedAlarm.getUuidId())
.andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
@ -776,13 +776,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(alarmUpdateMsg.getName(), savedAlarm.getName());
Assert.assertEquals(alarmUpdateMsg.getOriginatorName(), device.getName());
Assert.assertEquals(alarmUpdateMsg.getStatus(), AlarmStatus.CLEARED_ACK.name());
log.info("Alarms tested successfully");
}
private void testEntityView() throws Exception {
log.info("Testing EntityView");
@Test
public void testEntityView() throws Exception {
// 1
edgeImitator.expectMessageAmount(1);
Device device = findDeviceByName("Edge Device 1");
@ -791,8 +788,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
entityView.setType("test");
entityView.setEntityId(device.getId());
EntityView savedEntityView = doPost("/api/entityView", entityView, EntityView.class);
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/entityView/" + savedEntityView.getId().getId().toString(), EntityView.class);
doPost("/api/edge/" + edge.getUuidId()
+ "/entityView/" + savedEntityView.getUuidId(), EntityView.class);
Assert.assertTrue(edgeImitator.waitForMessages());
verifyEntityViewUpdateMsg(savedEntityView, device);
@ -818,8 +815,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 3
edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString()
+ "/entityView/" + savedEntityView.getId().getId().toString(), EntityView.class);
doDelete("/api/edge/" + edge.getUuidId()
+ "/entityView/" + savedEntityView.getUuidId(), EntityView.class);
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg);
@ -830,11 +827,9 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectMessageAmount(1);
doDelete("/api/entityView/" + savedEntityView.getId().getId().toString())
doDelete("/api/entityView/" + savedEntityView.getUuidId())
.andExpect(status().isOk());
Assert.assertFalse(edgeImitator.waitForMessages(1));
log.info("EntityView tested successfully");
}
private void verifyEntityViewUpdateMsg(EntityView entityView, Device device) {
@ -851,16 +846,15 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(entityViewUpdateMsg.getEntityType().name(), device.getId().getEntityType().name());
}
private void testCustomerAndNewUser() throws Exception {
log.info("Testing Customer");
@Test
public void testCustomerAndNewUser() throws Exception {
// 1
edgeImitator.expectMessageAmount(1);
Customer customer = new Customer();
customer.setTitle("Edge Customer 1");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
doPost("/api/customer/" + savedCustomer.getId().getId().toString()
+ "/edge/" + edge.getId().getId().toString(), Edge.class);
doPost("/api/customer/" + savedCustomer.getUuidId()
+ "/edge/" + edge.getUuidId(), Edge.class);
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg);
@ -893,7 +887,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 3
edgeImitator.expectMessageAmount(1);
doDelete("/api/customer/edge/" + edge.getId().getId().toString(), Edge.class);
doDelete("/api/customer/edge/" + edge.getUuidId(), Edge.class);
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg);
@ -903,16 +897,13 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(customerUpdateMsg.getIdLSB(), savedCustomer.getUuidId().getLeastSignificantBits());
edgeImitator.expectMessageAmount(1);
doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
doDelete("/api/customer/" + savedCustomer.getUuidId())
.andExpect(status().isOk());
Assert.assertFalse(edgeImitator.waitForMessages(1));
log.info("Customer tested successfully");
}
private void testWidgetsBundleAndWidgetType() throws Exception {
log.info("Testing WidgetsBundle and WidgetType");
@Test
public void testWidgetsBundleAndWidgetType() throws Exception {
// 1
edgeImitator.expectMessageAmount(1);
WidgetsBundle widgetsBundle = new WidgetsBundle();
@ -951,7 +942,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 3
edgeImitator.expectMessageAmount(1);
doDelete("/api/widgetType/" + savedWidgetType.getId().getId().toString())
doDelete("/api/widgetType/" + savedWidgetType.getUuidId())
.andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
@ -963,7 +954,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 4
edgeImitator.expectMessageAmount(1);
doDelete("/api/widgetsBundle/" + savedWidgetsBundle.getId().getId().toString())
doDelete("/api/widgetsBundle/" + savedWidgetsBundle.getUuidId())
.andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
@ -972,13 +963,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, widgetsBundleUpdateMsg.getMsgType());
Assert.assertEquals(widgetsBundleUpdateMsg.getIdMSB(), savedWidgetsBundle.getUuidId().getMostSignificantBits());
Assert.assertEquals(widgetsBundleUpdateMsg.getIdLSB(), savedWidgetsBundle.getUuidId().getLeastSignificantBits());
log.info("WidgetsBundle and WidgetType tested successfully");
}
private void testTimeseries() throws Exception {
log.info("Testing timeseries");
@Test
public void testTimeseries() throws Exception {
edgeImitator.expectMessageAmount(1);
Device device = findDeviceByName("Edge Device 1");
String timeseriesData = "{\"data\":{\"temperature\":25},\"ts\":" + System.currentTimeMillis() + "}";
@ -1004,18 +992,15 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
TransportProtos.KeyValueProto keyValueProto = tsKvListProto.getKv(0);
Assert.assertEquals("temperature", keyValueProto.getKey());
Assert.assertEquals(25, keyValueProto.getLongV());
log.info("Timeseries tested successfully");
}
private void testAttributes() throws Exception {
log.info("Testing attributes");
@Test
public void testAttributes() throws Exception {
Device device = findDeviceByName("Edge Device 1");
testAttributesUpdatedMsg(device);
testPostAttributesMsg(device);
testAttributesDeleteMsg(device);
log.info("Attributes tested successfully");
}
private void testAttributesUpdatedMsg(Device device) throws JsonProcessingException, InterruptedException {
@ -1094,7 +1079,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals("key2", attributeDeleteMsg.getAttributeNames(1));
}
private void testRpcCall() throws Exception {
@Test
public void testRpcCall() throws Exception {
Device device = findDeviceByName("Edge Device 1");
ObjectNode body = mapper.createObjectNode();
@ -1117,9 +1103,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals("test_method", latestDeviceRpcCallMsg.getRequestMsg().getMethod());
}
private void testTimeseriesWithFailures() throws Exception {
log.info("Testing timeseries with failures");
@Test
public void testTimeseriesWithFailures() throws Exception {
int numberOfTimeseriesToSend = 1000;
edgeImitator.setRandomFailuresOnTimeseriesDownlink(true);
@ -1147,7 +1132,6 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
}
edgeImitator.setRandomFailuresOnTimeseriesDownlink(false);
log.info("Timeseries with failures tested successfully");
}
private boolean isIdxExistsInTheDownlinkList(int idx, List<EntityDataProto> allTelemetryMsgs) {
@ -1165,25 +1149,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
return false;
}
private void testSendMessagesToCloud() throws Exception {
log.info("Sending messages to cloud");
sendDevice();
sendDeviceWithNameThatAlreadyExistsOnCloud();
sendRelationRequest();
sendAlarm();
sendTelemetry();
sendRelation();
sendDeleteDeviceOnEdge();
sendRuleChainMetadataRequest();
sendUserCredentialsRequest();
sendDeviceCredentialsRequest();
sendDeviceRpcResponse();
sendDeviceCredentialsUpdate();
sendAttributesRequest();
log.info("Messages were sent successfully");
}
private void sendDevice() throws Exception {
@Test
public void testSendDeviceToCloud() throws Exception {
UUID uuid = Uuids.timeBased();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
@ -1218,7 +1185,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals("Edge Device 2", device.getName());
}
private void sendDeviceWithNameThatAlreadyExistsOnCloud() throws Exception {
@Test
public void testSendDeviceToCloudWithNameThatAlreadyExistsOnCloud() throws Exception {
String deviceOnCloudName = RandomStringUtils.randomAlphanumeric(15);
Device deviceOnCloud = saveDevice(deviceOnCloudName, "Default");
@ -1270,7 +1238,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertNotEquals(deviceOnCloudName, device.getName());
}
private void sendRelationRequest() throws Exception {
@Test
public void testSendRelationRequestToCloud() throws Exception {
Device device = findDeviceByName("Edge Device 1");
Asset asset = findAssetByName("Edge Asset 1");
@ -1317,8 +1286,9 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(relation.getTypeGroup().name(), relationUpdateMsg.getTypeGroup());
}
private void sendAlarm() throws Exception {
Device device = findDeviceByName("Edge Device 2");
@Test
public void testSendAlarmToCloud() throws Exception {
Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
AlarmUpdateMsg.Builder alarmUpdateMgBuilder = AlarmUpdateMsg.newBuilder();
@ -1339,7 +1309,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
List<AlarmInfo> alarms = doGetTypedWithPageLink("/api/alarm/{entityType}/{entityId}?",
new TypeReference<PageData<AlarmInfo>>() {},
new PageLink(100), device.getId().getEntityType().name(), device.getId().getId().toString())
new PageLink(100), device.getId().getEntityType().name(), device.getUuidId())
.getData();
Optional<AlarmInfo> foundAlarm = alarms.stream().filter(alarm -> alarm.getType().equals("alarm from edge")).findAny();
Assert.assertTrue(foundAlarm.isPresent());
@ -1349,12 +1319,9 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(AlarmSeverity.CRITICAL, alarmInfo.getSeverity());
}
private void sendTelemetry() throws Exception {
List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/devices?",
new TypeReference<PageData<Device>>() {}, new PageLink(100)).getData();
Optional<Device> foundDevice = edgeDevices.stream().filter(device1 -> device1.getName().equals("Edge Device 2")).findAny();
Assert.assertTrue(foundDevice.isPresent());
Device device = foundDevice.get();
@Test
public void testSendTelemetryToCloud() throws Exception {
Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge();
edgeImitator.expectResponsesAmount(2);
@ -1402,7 +1369,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(1, timeseries.get(timeseriesKey).size());
Assert.assertEquals(timeseriesValue, timeseries.get(timeseriesKey).get(0).get("value"));
List<Map<String, String>> attributes = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/attributes/" + DataConstants.SERVER_SCOPE, new TypeReference<>() {});
String attributeValuesUrl = "/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/attributes/" + DataConstants.SERVER_SCOPE;
List<Map<String, String>> attributes = doGetAsyncTyped(attributeValuesUrl, new TypeReference<>() {});
Assert.assertEquals(2, attributes.size());
var result = attributes.stream().filter(kv -> kv.get("key").equals(attributesKey)).filter(kv -> kv.get("value").equals(attributesValue)).findFirst();
Assert.assertTrue(result.isPresent());
@ -1413,15 +1381,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
new TypeReference<>() {});
}
private void sendRelation() throws Exception {
List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/devices?",
new TypeReference<PageData<Device>>() {}, new PageLink(100)).getData();
Optional<Device> foundDevice1 = edgeDevices.stream().filter(device1 -> device1.getName().equals("Edge Device 1")).findAny();
Assert.assertTrue(foundDevice1.isPresent());
Device device1 = foundDevice1.get();
Optional<Device> foundDevice2 = edgeDevices.stream().filter(device2 -> device2.getName().equals("Edge Device 2")).findAny();
Assert.assertTrue(foundDevice2.isPresent());
Device device2 = foundDevice2.get();
@Test
public void testSendRelationToCloud() throws Exception {
Device device1 = saveDeviceOnCloudAndVerifyDeliveryToEdge();
Device device2 = saveDeviceOnCloudAndVerifyDeliveryToEdge();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
RelationUpdateMsg.Builder relationUpdateMsgBuilder = RelationUpdateMsg.newBuilder();
@ -1444,17 +1407,18 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertTrue(edgeImitator.waitForResponses());
EntityRelation relation = doGet("/api/relation?" +
"&fromId=" + device2.getId().getId().toString() +
"&fromId=" + device2.getUuidId() +
"&fromType=" + device2.getId().getEntityType().name() +
"&relationType=" + "test" +
"&relationTypeGroup=" + RelationTypeGroup.COMMON.name() +
"&toId=" + device1.getId().getId().toString() +
"&toId=" + device1.getUuidId() +
"&toType=" + device1.getId().getEntityType().name(), EntityRelation.class);
Assert.assertNotNull(relation);
}
private void sendDeleteDeviceOnEdge() throws Exception {
Device device = findDeviceByName("Edge Device 2");
@Test
public void testSendDeleteDeviceOnEdgeToCloud() throws Exception {
Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge();
UplinkMsg.Builder upLinkMsgBuilder = UplinkMsg.newBuilder();
DeviceUpdateMsg.Builder deviceDeleteMsgBuilder = DeviceUpdateMsg.newBuilder();
deviceDeleteMsgBuilder.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE);
@ -1468,15 +1432,16 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(upLinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
device = doGet("/api/device/" + device.getId().getId().toString(), Device.class);
device = doGet("/api/device/" + device.getUuidId(), Device.class);
Assert.assertNotNull(device);
List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/devices?",
List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?",
new TypeReference<PageData<Device>>() {
}, new PageLink(100)).getData();
Assert.assertFalse(edgeDevices.contains(device));
}
private void sendRuleChainMetadataRequest() throws Exception {
@Test
public void testSendRuleChainMetadataRequestToCloud() throws Exception {
RuleChainId edgeRootRuleChainId = edge.getRootRuleChainId();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
@ -1503,7 +1468,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
testAutoGeneratedCodeByProtobuf(ruleChainMetadataUpdateMsg);
}
private void sendUserCredentialsRequest() throws Exception {
@Test
public void testSendUserCredentialsRequestToCloud() throws Exception {
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
UserCredentialsRequestMsg.Builder userCredentialsRequestMsgBuilder = UserCredentialsRequestMsg.newBuilder();
userCredentialsRequestMsgBuilder.setUserIdMSB(tenantAdmin.getId().getId().getMostSignificantBits());
@ -1528,10 +1494,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
testAutoGeneratedCodeByProtobuf(userCredentialsUpdateMsg);
}
private void sendDeviceCredentialsRequest() throws Exception {
@Test
public void testSendDeviceCredentialsRequestToCloud() throws Exception {
Device device = findDeviceByName("Edge Device 1");
DeviceCredentials deviceCredentials = doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class);
DeviceCredentials deviceCredentials = doGet("/api/device/" + device.getUuidId() + "/credentials", DeviceCredentials.class);
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
DeviceCredentialsRequestMsg.Builder deviceCredentialsRequestMsgBuilder = DeviceCredentialsRequestMsg.newBuilder();
@ -1557,7 +1524,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(deviceCredentialsUpdateMsg.getCredentialsId(), deviceCredentials.getCredentialsId());
}
private void sendDeviceRpcResponse() throws Exception {
@Test
public void testSendDeviceRpcResponseToCloud() throws Exception {
Device device = findDeviceByName("Edge Device 1");
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
@ -1582,7 +1550,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertTrue(edgeImitator.waitForResponses());
}
private void sendDeviceCredentialsUpdate() throws Exception {
@Test
public void testSendDeviceCredentialsUpdateToCloud() throws Exception {
Device device = findDeviceByName("Edge Device 1");
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
@ -1601,24 +1570,30 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertTrue(edgeImitator.waitForResponses());
}
private void sendAttributesRequest() throws Exception {
@Test
public void testSendAttributesRequestToCloud() throws Exception {
Device device = findDeviceByName("Edge Device 1");
sendAttributesRequest(device, DataConstants.SERVER_SCOPE, "{\"key1\":\"value1\"}",
"key1", "value1", 2);
sendAttributesRequest(device, DataConstants.SHARED_SCOPE, "{\"key2\":\"value2\"}",
"key2", "value2", 1);
sendAttributesRequestAndVerify(device, DataConstants.SERVER_SCOPE, "{\"key1\":\"value1\"}",
"key1", "value1");
sendAttributesRequestAndVerify(device, DataConstants.SHARED_SCOPE, "{\"key2\":\"value2\"}",
"key2", "value2");
}
private void sendAttributesRequest(Device device, String scope, String attributesDataStr, String expectedKey,
String expectedValue, int expectedSize) throws Exception {
private void sendAttributesRequestAndVerify(Device device, String scope, String attributesDataStr, String expectedKey,
String expectedValue) throws Exception {
JsonNode attributesData = mapper.readTree(attributesDataStr);
doPost("/api/plugins/telemetry/DEVICE/" + device.getId().getId().toString() + "/attributes/" + scope,
doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/attributes/" + scope,
attributesData);
// Wait before device attributes saved to database before requesting them from edge
// queue used to save attributes to database
Thread.sleep(500);
Awaitility.await()
.atMost(10, TimeUnit.SECONDS)
.until(() -> {
String urlTemplate = "/api/plugins/telemetry/DEVICE/" + device.getId() + "/keys/attributes/" + scope;
List<String> actualKeys = doGetAsyncTyped(urlTemplate, new TypeReference<>() {});
return actualKeys != null && !actualKeys.isEmpty() && actualKeys.contains(expectedKey);
});
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
AttributesRequestMsg.Builder attributesRequestMsgBuilder = AttributesRequestMsg.newBuilder();
@ -1646,19 +1621,39 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertTrue(latestEntityDataMsg.hasAttributesUpdatedMsg());
TransportProtos.PostAttributeMsg attributesUpdatedMsg = latestEntityDataMsg.getAttributesUpdatedMsg();
Assert.assertEquals(expectedSize, attributesUpdatedMsg.getKvList().size());
boolean found = false;
for (TransportProtos.KeyValueProto keyValueProto : attributesUpdatedMsg.getKvList()) {
if (keyValueProto.getKey().equals(expectedKey)) {
Assert.assertEquals(expectedKey, keyValueProto.getKey());
Assert.assertEquals(expectedValue, keyValueProto.getStringV());
found = true;
}
}
Assert.assertTrue("Expected key and value must be found", found);
}
// Utility methods
private Device saveDeviceOnCloudAndVerifyDeliveryToEdge() throws Exception {
edgeImitator.expectMessageAmount(1);
Device savedDevice = saveDevice(RandomStringUtils.randomAlphanumeric(15), "Default");
doPost("/api/edge/" + edge.getUuidId()
+ "/device/" + savedDevice.getUuidId(), Device.class);
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType());
Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getName(), savedDevice.getName());
Assert.assertEquals(deviceUpdateMsg.getType(), savedDevice.getType());
return savedDevice;
}
private Device findDeviceByName(String deviceName) throws Exception {
List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/devices?",
List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?",
new TypeReference<PageData<Device>>() {
}, new PageLink(100)).getData();
Optional<Device> foundDevice = edgeDevices.stream().filter(d -> d.getName().equals(deviceName)).findAny();
@ -1669,7 +1664,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
}
private Asset findAssetByName(String assetName) throws Exception {
List<Asset> edgeAssets = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/assets?",
List<Asset> edgeAssets = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/assets?",
new TypeReference<PageData<Asset>>() {
}, new PageLink(100)).getData();

4
application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java

@ -88,6 +88,9 @@ public class EdgeImitator {
@Getter
private List<AbstractMessage> downlinkMsgs;
@Getter
private UplinkResponseMsg latestResponseMsg;
private boolean connected = false;
public EdgeImitator(String host, int port, String routingKey, String routingSecret) throws NoSuchFieldException, IllegalAccessException {
@ -132,6 +135,7 @@ public class EdgeImitator {
private void onUplinkResponse(UplinkResponseMsg msg) {
log.info("onUplinkResponse: {}", msg);
latestResponseMsg = msg;
responsesLatch.countDown();
}

62
application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java

@ -15,19 +15,28 @@
*/
package org.thingsboard.server.rules.flow;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.rule.engine.flow.TbRuleChainInputNodeConfiguration;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode;
@ -38,12 +47,14 @@ import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
import org.thingsboard.server.common.msg.queue.TbMsgCallback;
import org.thingsboard.server.controller.AbstractRuleEngineControllerTest;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.event.EventService;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static org.mockito.Mockito.spy;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
@ -61,8 +72,26 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
@Autowired
protected AttributesService attributesService;
@Autowired
protected EventService eventService;
@Before
public void beforeTest() throws Exception {
EventService spyEventService = spy(eventService);
Mockito.doAnswer((Answer<ListenableFuture<Void>>) invocation -> {
Object[] args = invocation.getArguments();
Event event = (Event) args[0];
ListenableFuture<Void> future = eventService.saveAsync(event);
try {
future.get();
} catch (Exception e) {}
return future;
}).when(spyEventService).saveAsync(Mockito.any(Event.class));
ReflectionTestUtils.setField(actorSystem, "eventService", spyEventService);
loginSysAdmin();
Tenant tenant = new Tenant();
@ -136,12 +165,9 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
device = doPost("/api/device", device, Device.class);
attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE,
Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey1", "serverAttributeValue1"), System.currentTimeMillis())));
Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey1", "serverAttributeValue1"), System.currentTimeMillis()))).get();
attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE,
Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey2", "serverAttributeValue2"), System.currentTimeMillis())));
Thread.sleep(1000);
Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey2", "serverAttributeValue2"), System.currentTimeMillis()))).get();
TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class);
Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true);
@ -216,16 +242,27 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
ruleNode1.setConfiguration(mapper.valueToTree(configuration1));
rootMetaData.setNodes(Collections.singletonList(ruleNode1));
RuleNode ruleNode12 = new RuleNode();
ruleNode12.setName("Simple Rule Node 1");
ruleNode12.setType(org.thingsboard.rule.engine.flow.TbRuleChainInputNode.class.getName());
ruleNode12.setDebugMode(true);
TbRuleChainInputNodeConfiguration configuration12 = new TbRuleChainInputNodeConfiguration();
configuration12.setRuleChainId(secondaryRuleChain.getId().getId().toString());
ruleNode12.setConfiguration(mapper.valueToTree(configuration12));
rootMetaData.setNodes(Arrays.asList(ruleNode1, ruleNode12));
rootMetaData.setFirstNodeIndex(0);
rootMetaData.addRuleChainConnectionInfo(0, secondaryRuleChain.getId(), "Success", mapper.createObjectNode());
NodeConnectionInfo connection = new NodeConnectionInfo();
connection.setFromIndex(0);
connection.setToIndex(1);
connection.setType("Success");
rootMetaData.setConnections(Collections.singletonList(connection));
rootMetaData = saveRuleChainMetaData(rootMetaData);
Assert.assertNotNull(rootMetaData);
rootRuleChain = getRuleChain(rootRuleChain.getId());
Assert.assertNotNull(rootRuleChain.getFirstRuleNodeId());
RuleChainMetaData secondaryMetaData = new RuleChainMetaData();
secondaryMetaData.setRuleChainId(secondaryRuleChain.getId());
@ -249,12 +286,9 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
device = doPost("/api/device", device, Device.class);
attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE,
Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey1", "serverAttributeValue1"), System.currentTimeMillis())));
Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey1", "serverAttributeValue1"), System.currentTimeMillis()))).get();
attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE,
Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey2", "serverAttributeValue2"), System.currentTimeMillis())));
Thread.sleep(1000);
Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey2", "serverAttributeValue2"), System.currentTimeMillis()))).get();
TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class);
Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true);

23
application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java

@ -15,13 +15,16 @@
*/
package org.thingsboard.server.rules.lifecycle;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.DataConstants;
@ -42,6 +45,7 @@ import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
import org.thingsboard.server.common.msg.queue.TbMsgCallback;
import org.thingsboard.server.controller.AbstractRuleEngineControllerTest;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.queue.memory.InMemoryStorage;
import java.util.Collections;
@ -50,6 +54,7 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.awaitility.Awaitility.await;
import static org.mockito.Mockito.spy;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
@ -67,8 +72,26 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
@Autowired
protected AttributesService attributesService;
@Autowired
protected EventService eventService;
@Before
public void beforeTest() throws Exception {
EventService spyEventService = spy(eventService);
Mockito.doAnswer((Answer<ListenableFuture<Void>>) invocation -> {
Object[] args = invocation.getArguments();
Event event = (Event) args[0];
ListenableFuture<Void> future = eventService.saveAsync(event);
try {
future.get();
} catch (Exception e) {}
return future;
}).when(spyEventService).saveAsync(Mockito.any(Event.class));
ReflectionTestUtils.setField(actorSystem, "eventService", spyEventService);
loginSysAdmin();
Tenant tenant = new Tenant();

86
application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java

@ -0,0 +1,86 @@
/**
* Copyright © 2016-2022 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.apiusage;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.scheduler.SchedulerComponent;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import java.util.UUID;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@RunWith(MockitoJUnitRunner.class)
public class DefaultTbApiUsageStateServiceTest {
@Mock
TenantService tenantService;
@Mock
TimeseriesService tsService;
@Mock
TbClusterService clusterService;
@Mock
PartitionService partitionService;
@Mock
TenantApiUsageState tenantUsageStateMock;
@Mock
ApiUsageStateService apiUsageStateService;
@Mock
TbTenantProfileCache tenantProfileCache;
@Mock
MailService mailService;
@Mock
DbCallbackExecutorService dbExecutor;
TenantId tenantId = TenantId.fromUUID(UUID.fromString("00797a3b-7aeb-4b5b-b57a-c2a810d0f112"));
DefaultTbApiUsageStateService service;
@Before
public void setUp() {
service = spy(new DefaultTbApiUsageStateService(clusterService, partitionService, tenantService, tsService, apiUsageStateService, tenantProfileCache, mailService, dbExecutor));
}
@Test
public void givenTenantIdFromEntityStatesMap_whenGetApiUsageState() {
service.myUsageStates.put(tenantId, tenantUsageStateMock);
ApiUsageState tenantUsageState = service.getApiUsageState(tenantId);
assertThat(tenantUsageState, is(tenantUsageStateMock.getApiUsageState()));
Mockito.verify(service, never()).getOrFetchState(tenantId, tenantId);
}
}

1
application/src/test/java/org/thingsboard/server/service/ttl/EventsCleanUpServiceTest.java

@ -42,7 +42,6 @@ public class EventsCleanUpServiceTest {
public void givenInterval_whenRandomDelay_ThenDelayInInterval() {
log.info("randomDelay {}", randomDelayMs);
log.info("executionIntervalMs {}", executionIntervalMs);
assertThat(executionIntervalMs, is(2220000L));
assertThat(randomDelayMs, greaterThanOrEqualTo(0L));
assertThat(randomDelayMs, lessThanOrEqualTo(executionIntervalMs));
}

2
application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java

@ -52,6 +52,8 @@ import static org.junit.Assert.assertNotNull;
@Slf4j
public abstract class AbstractCoapIntegrationTest extends AbstractTransportIntegrationTest {
protected final byte[] EMPTY_PAYLOAD = new byte[0];
protected CoapClient client;
@Override

50
application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesIntegrationTest.java

@ -17,19 +17,23 @@ package org.thingsboard.server.transport.coap.attributes.updates;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.CoapClient;
import org.awaitility.Awaitility;
import org.eclipse.californium.core.CoapHandler;
import org.eclipse.californium.core.CoapObserveRelation;
import org.eclipse.californium.core.CoapResponse;
import org.eclipse.californium.core.coap.CoAP;
import org.eclipse.californium.core.coap.Request;
import org.eclipse.californium.core.server.resources.Resource;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.coapserver.DefaultCoapServerService;
import org.thingsboard.server.common.transport.service.DefaultTransportService;
import org.thingsboard.server.transport.coap.CoapTransportResource;
import org.thingsboard.server.transport.coap.attributes.AbstractCoapAttributesIntegrationTest;
import org.thingsboard.server.common.msg.session.FeatureType;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@ -37,6 +41,7 @@ import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.spy;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
@ -47,8 +52,20 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr
protected static final String POST_ATTRIBUTES_PAYLOAD_ON_CURRENT_STATE_NOTIFICATION = "{\"attribute1\":\"value\",\"attribute2\":false,\"attribute3\":41.0,\"attribute4\":72," +
"\"attribute5\":{\"someNumber\":41,\"someArray\":[],\"someNestedObject\":{\"key\":\"value\"}}}";
CoapTransportResource coapTransportResource;
@Autowired
DefaultCoapServerService defaultCoapServerService;
@Autowired
DefaultTransportService defaultTransportService;
@Before
public void beforeTest() throws Exception {
Resource api = defaultCoapServerService.getCoapServer().getRoot().getChild("api");
coapTransportResource = spy( (CoapTransportResource) api.getChild("v1") );
api.delete(api.getChild("v1") );
api.add(coapTransportResource);
processBeforeTest("Test Subscribe to attribute updates", null, null);
}
@ -89,21 +106,23 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr
}
latch = new CountDownLatch(1);
int expectedObserveCnt = callback.getObserve().intValue() + 1;
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", POST_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
latch.await(3, TimeUnit.SECONDS);
validateUpdateAttributesResponse(callback);
validateUpdateAttributesResponse(callback, expectedObserveCnt);
latch = new CountDownLatch(1);
latch = new CountDownLatch(1);
int expectedObserveBeforeDeleteCnt = callback.getObserve().intValue() + 1;
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=attribute5", String.class);
latch.await(3, TimeUnit.SECONDS);
validateDeleteAttributesResponse(callback);
validateDeleteAttributesResponse(callback, expectedObserveBeforeDeleteCnt);
observeRelation.proactiveCancel();
assertTrue(observeRelation.isCanceled());
awaitClientAfterCancelObserve();
}
protected void validateCurrentStateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
@ -124,20 +143,20 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr
assertEquals("{}", response);
}
protected void validateUpdateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
protected void validateUpdateAttributesResponse(TestCoapCallback callback, int expectedObserveCnt) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
assertNotNull(callback.getObserve());
assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode());
assertEquals(1, callback.getObserve().intValue());
assertEquals(expectedObserveCnt, callback.getObserve().intValue());
String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(JacksonUtil.toJsonNode(POST_ATTRIBUTES_PAYLOAD), JacksonUtil.toJsonNode(response));
}
protected void validateDeleteAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
protected void validateDeleteAttributesResponse(TestCoapCallback callback, int expectedObserveCnt) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
assertNotNull(callback.getObserve());
assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode());
assertEquals(2, callback.getObserve().intValue());
assertEquals(expectedObserveCnt, callback.getObserve().intValue());
String response = new String(callback.getPayloadBytes(), StandardCharsets.UTF_8);
assertEquals(JacksonUtil.toJsonNode(RESPONSE_ATTRIBUTES_PAYLOAD_DELETED), JacksonUtil.toJsonNode(response));
}
@ -180,4 +199,13 @@ public abstract class AbstractCoapAttributesUpdatesIntegrationTest extends Abstr
}
}
private void awaitClientAfterCancelObserve() {
Awaitility.await("awaitClientAfterCancelObserve")
.pollInterval(10, TimeUnit.MILLISECONDS)
.atMost(5, TimeUnit.SECONDS)
.until(()->{
log.trace("awaiting defaultTransportService.sessions is empty");
return defaultTransportService.sessions.isEmpty();});
}
}

14
application/src/test/java/org/thingsboard/server/transport/coap/attributes/updates/AbstractCoapAttributesUpdatesProtoIntegrationTest.java

@ -25,15 +25,13 @@ import org.thingsboard.server.common.data.CoapDeviceType;
import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
@Slf4j
@ -88,17 +86,17 @@ public abstract class AbstractCoapAttributesUpdatesProtoIntegrationTest extends
}
protected void validateEmptyCurrentStateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
assertNull(callback.getPayloadBytes());
assertArrayEquals(EMPTY_PAYLOAD, callback.getPayloadBytes());
assertNotNull(callback.getObserve());
assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode());
assertEquals(0, callback.getObserve().intValue());
}
protected void validateUpdateAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
protected void validateUpdateAttributesResponse(TestCoapCallback callback, int expectedObserveCnt) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
assertNotNull(callback.getObserve());
assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode());
assertEquals(1, callback.getObserve().intValue());
assertEquals(expectedObserveCnt, callback.getObserve().intValue());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
List<TransportProtos.TsKvProto> tsKvProtoList = getTsKvProtoList();
attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList);
@ -114,11 +112,11 @@ public abstract class AbstractCoapAttributesUpdatesProtoIntegrationTest extends
}
protected void validateDeleteAttributesResponse(TestCoapCallback callback) throws InvalidProtocolBufferException {
protected void validateDeleteAttributesResponse(TestCoapCallback callback, int expectedObserveCnt) throws InvalidProtocolBufferException {
assertNotNull(callback.getPayloadBytes());
assertNotNull(callback.getObserve());
assertEquals(CoAP.ResponseCode.CONTENT, callback.getResponseCode());
assertEquals(2, callback.getObserve().intValue());
assertEquals(expectedObserveCnt, callback.getObserve().intValue());
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
attributeUpdateNotificationMsgBuilder.addSharedDeleted("attribute5");

5
application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java

@ -31,13 +31,12 @@ import org.thingsboard.server.common.data.TransportPayloadType;
import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.transport.coap.AbstractCoapIntegrationTest;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -186,7 +185,7 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC
}
private void validateCurrentStateNotification(TestCoapCallback callback) {
assertNull(callback.getPayloadBytes());
assertArrayEquals(EMPTY_PAYLOAD, callback.getPayloadBytes());
assertNotNull(callback.getObserve());
assertEquals(callback.getResponseCode(), CoAP.ResponseCode.VALID);
assertEquals(0, callback.getObserve().intValue());

319
application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java

@ -16,12 +16,17 @@
package org.thingsboard.server.transport.lwm2m;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.californium.elements.config.Configuration;
import org.eclipse.leshan.client.californium.LeshanClient;
import org.eclipse.leshan.client.object.Security;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.util.SocketUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
@ -41,6 +46,11 @@ import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileCon
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration;
import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.NoSecLwM2MBootstrapServerCredential;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
@ -57,94 +67,135 @@ import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd;
import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static org.awaitility.Awaitility.await;
import static org.eclipse.californium.core.config.CoapConfig.COAP_PORT;
import static org.eclipse.californium.core.config.CoapConfig.COAP_SECURE_PORT;
import static org.eclipse.leshan.client.object.Security.noSec;
import static org.eclipse.leshan.client.object.Security.noSecBootstap;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_STARTED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_INIT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE;
@Slf4j
@DaoSqlTest
public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest {
protected final String TRANSPORT_CONFIGURATION = "{\n" +
" \"type\": \"LWM2M\",\n" +
" \"observeAttr\": {\n" +
" \"keyName\": {\n" +
" \"/3_1.0/0/9\": \"batteryLevel\"\n" +
" },\n" +
" \"observe\": [],\n" +
" \"attribute\": [\n" +
" ],\n" +
" \"telemetry\": [\n" +
" \"/3_1.0/0/9\"\n" +
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" },\n" +
" \"bootstrapServerUpdateEnable\": true,\n" +
" \"bootstrap\": [\n" +
@SpyBean
DefaultLwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest;
@Autowired
private LwM2mClientContext clientContextTest;
// Lwm2m Server
public static final int port = 5685;
public static final int securityPort = 5686;
public static final int portBs = 5687;
public static final int securityPortBs = 5688;
public static final int[] SERVERS_PORT_NUMBERS = {port, securityPort, portBs, securityPortBs};
public static final String host = "localhost";
public static final String hostBs = "localhost";
public static final int shortServerId = 123;
public static final int shortServerIdBs = 111;
public static final String COAP = "coap://";
public static final String COAPS = "coaps://";
public static final String URI = COAP + host + ":" + port;
public static final String SECURE_URI = COAPS + host + ":" + securityPort;
public static final String URI_BS = COAP + hostBs + ":" + portBs;
public static final String SECURE_URI_BS = COAPS + hostBs + ":" + securityPortBs;
public static final Configuration COAP_CONFIG = new Configuration().set(COAP_PORT, port).set(COAP_SECURE_PORT, securityPort);
public static Configuration COAP_CONFIG_BS = new Configuration().set(COAP_PORT, portBs).set(COAP_SECURE_PORT, securityPortBs);
public static final Security SECURITY_NO_SEC = noSec(URI, shortServerId);
public static final Security SECURITY_NO_SEC_BS = noSecBootstap(URI_BS);
protected final String OBSERVE_ATTRIBUTES_WITHOUT_PARAMS =
" {\n" +
" \"host\": \"0.0.0.0\",\n" +
" \"port\": 5687,\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"shortServerId\": 111,\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
" \"bootstrapServerIs\": true,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" },\n" +
" \"keyName\": {},\n" +
" \"observe\": [],\n" +
" \"attribute\": [],\n" +
" \"telemetry\": [],\n" +
" \"attributeLwm2m\": {}\n" +
" }";
protected final String OBSERVE_ATTRIBUTES_WITH_PARAMS =
" {\n" +
" \"host\": \"0.0.0.0\",\n" +
" \"port\": 5685,\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"shortServerId\": 123,\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
" \"bootstrapServerIs\": false,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" }\n" +
" ],\n" +
" \"clientLwM2mSettings\": {\n" +
" \"edrxCycle\": null,\n" +
" \"powerMode\": \"DRX\",\n" +
" \"fwUpdateResource\": null,\n" +
" \"fwUpdateStrategy\": 1,\n" +
" \"psmActivityTimer\": null,\n" +
" \"swUpdateResource\": null,\n" +
" \"swUpdateStrategy\": 1,\n" +
" \"pagingTransmissionWindow\": null,\n" +
" \"clientOnlyObserveAfterConnect\": 1\n" +
" }\n" +
"}";
" \"keyName\": {\n" +
" \"/3_1.0/0/9\": \"batteryLevel\"\n" +
" },\n" +
" \"observe\": [],\n" +
" \"attribute\": [\n" +
" ],\n" +
" \"telemetry\": [\n" +
" \"/3_1.0/0/9\"\n" +
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" }";
protected final String CLIENT_LWM2M_SETTINGS =
" {\n" +
" \"edrxCycle\": null,\n" +
" \"powerMode\": \"DRX\",\n" +
" \"fwUpdateResource\": null,\n" +
" \"fwUpdateStrategy\": 1,\n" +
" \"psmActivityTimer\": null,\n" +
" \"swUpdateResource\": null,\n" +
" \"swUpdateStrategy\": 1,\n" +
" \"pagingTransmissionWindow\": null,\n" +
" \"clientOnlyObserveAfterConnect\": 1\n" +
" }";
protected final Set<Lwm2mTestHelper.LwM2MClientState> expectedStatusesBsSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_BOOTSTRAP_STARTED, ON_BOOTSTRAP_SUCCESS));
protected final Set<Lwm2mTestHelper.LwM2MClientState> expectedStatusesRegistrationLwm2mSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS));
protected final Set<Lwm2mTestHelper.LwM2MClientState> expectedStatusesRegistrationBsSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_BOOTSTRAP_STARTED, ON_BOOTSTRAP_SUCCESS, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS));
protected DeviceProfile deviceProfile;
protected ScheduledExecutorService executor;
protected TbTestWebSocketClient wsClient;
protected LwM2MTestClient client;
private final LwM2MBootstrapClientCredentials defaultBootstrapCredentials;
protected LwM2MTestClient lwM2MTestClient;
private String[] resources;
public AbstractLwM2MIntegrationTest() {
this.defaultBootstrapCredentials = new LwM2MBootstrapClientCredentials();
NoSecBootstrapClientCredential serverCredentials = new NoSecBootstrapClientCredential();
this.defaultBootstrapCredentials.setBootstrapServer(serverCredentials);
this.defaultBootstrapCredentials.setLwm2mServer(serverCredentials);
@Before
public void startInit() throws Exception {
init();
}
public void init () throws Exception{
@After
public void after() {
wsClient.close();
clientDestroy();
executor.shutdownNow();
}
@AfterClass
public static void afterClass () {
awaitServersDestroy();
}
private void init () throws Exception {
executor = Executors.newScheduledThreadPool(10, ThingsBoardThreadFactory.forName("test-lwm2m-scheduled"));
loginTenantAdmin();
for (String resourceName : this.resources) {
TbResource lwModel = new TbResource();
lwModel.setResourceType(ResourceType.LWM2M_MODEL);
@ -160,24 +211,13 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
wsClient = buildAndConnectWebSocketClient();
}
@Before
public void beforeTest() throws Exception {
this.init();
}
@After
public void after() {
wsClient.close();
clientDestroy();
executor.shutdownNow();
}
public void basicTestConnectionObserveTelemetry(Security security,
LwM2MClientCredential credentials,
NetworkConfig coapConfig,
LwM2MDeviceCredentials deviceCredentials,
Configuration coapConfig,
String endpoint) throws Exception {
createDeviceProfile(TRANSPORT_CONFIGURATION);
Device device = createDevice(credentials);
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS, getBootstrapServerCredentialsNoSec(NONE));
createDeviceProfile(transportConfiguration);
Device device = createDevice(deviceCredentials, endpoint);
SingleEntityFilter sef = new SingleEntityFilter();
sef.setSingleEntity(device.getId());
@ -194,7 +234,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
wsClient.waitForReply();
wsClient.registerWaitForUpdate();
createNewClient(security, coapConfig, false, endpoint);
createNewClient(security, coapConfig, false, endpoint, false, null);
String msg = wsClient.waitForUpdate();
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
@ -205,10 +245,14 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES));
var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel");
Assert.assertEquals(42, Long.parseLong(tsValue.getValue()));
Assert.assertThat(Long.parseLong(tsValue.getValue()), instanceOf(Long.class));
int expectedMax = 50;
int expectedMin = 5;
Assert.assertTrue(expectedMax >= Long.parseLong(tsValue.getValue()));
Assert.assertTrue(expectedMin <= Long.parseLong(tsValue.getValue()));
}
protected void createDeviceProfile(String transportConfiguration) throws Exception {
protected void createDeviceProfile(Lwm2mDeviceProfileTransportConfiguration transportConfiguration) throws Exception {
deviceProfile = new DeviceProfile();
deviceProfile.setName("LwM2M");
deviceProfile.setType(DeviceProfileType.DEFAULT);
@ -220,16 +264,16 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
DeviceProfileData deviceProfileData = new DeviceProfileData();
deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration());
deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null));
deviceProfileData.setTransportConfiguration(JacksonUtil.fromString(transportConfiguration, Lwm2mDeviceProfileTransportConfiguration.class));
deviceProfileData.setTransportConfiguration(transportConfiguration);
deviceProfile.setProfileData(deviceProfileData);
deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
Assert.assertNotNull(deviceProfile);
}
protected Device createDevice(LwM2MClientCredential clientCredentials) throws Exception {
protected Device createDevice(LwM2MDeviceCredentials credentials, String endpoint) throws Exception {
Device device = new Device();
device.setName("Device A");
device.setName(endpoint);
device.setDeviceProfileId(deviceProfile.getId());
device.setTenantId(tenantId);
device = doPost("/api/device", device, Device.class);
@ -239,11 +283,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class);
Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId());
deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS);
LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials();
credentials.setClient(clientCredentials);
credentials.setBootstrap(defaultBootstrapCredentials);
deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials));
doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk());
return device;
@ -259,16 +298,98 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
this.resources = resources;
}
public void createNewClient(Security security, NetworkConfig coapConfig, boolean isRpc, String endpoint) throws Exception {
clientDestroy();
client = new LwM2MTestClient(this.executor, endpoint);
int clientPort = SocketUtils.findAvailableTcpPort();
client.init(security, coapConfig, clientPort, isRpc);
public void createNewClient(Security security, Configuration coapConfig, boolean isRpc, String endpoint, boolean isBootstrap, Security securityBs) throws Exception {
this.clientDestroy();
lwM2MTestClient = new LwM2MTestClient(this.executor, endpoint);
int clientPort = SocketUtils.findAvailableUdpPort();
lwM2MTestClient.init(security, coapConfig, clientPort, isRpc, isBootstrap, this.shortServerId, this.shortServerIdBs,
securityBs, this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest);
}
private void clientDestroy() {
if (client != null) {
client.destroy();
try {
if (lwM2MTestClient != null) {
lwM2MTestClient.destroy();
awaitClientDestroy(lwM2MTestClient.getLeshanClient());
}
} catch (Exception e) {
log.error("Failed client Destroy", e);
}
}
protected Lwm2mDeviceProfileTransportConfiguration getTransportConfiguration(String observeAttr, List<LwM2MBootstrapServerCredential> bootstrapServerCredentials) {
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration();
TelemetryMappingConfiguration observeAttrConfiguration = JacksonUtil.fromString(observeAttr, TelemetryMappingConfiguration.class);
OtherConfiguration clientLwM2mSettings = JacksonUtil.fromString(CLIENT_LWM2M_SETTINGS, OtherConfiguration.class);
transportConfiguration.setBootstrapServerUpdateEnable(true);
transportConfiguration.setObserveAttr(observeAttrConfiguration);
transportConfiguration.setClientLwM2mSettings(clientLwM2mSettings);
transportConfiguration.setBootstrap(bootstrapServerCredentials);
return transportConfiguration;
}
protected List<LwM2MBootstrapServerCredential> getBootstrapServerCredentialsNoSec(LwM2MProfileBootstrapConfigType bootstrapConfigType) {
List<LwM2MBootstrapServerCredential> bootstrap = new ArrayList<>();
switch (bootstrapConfigType) {
case BOTH:
bootstrap.add(getBootstrapServerCredentialNoSec(false));
bootstrap.add(getBootstrapServerCredentialNoSec(true));
break;
case BOOTSTRAP_ONLY:
bootstrap.add(getBootstrapServerCredentialNoSec(true));
break;
case LWM2M_ONLY:
bootstrap.add(getBootstrapServerCredentialNoSec(false));
break;
case NONE:
}
return bootstrap;
}
private AbstractLwM2MBootstrapServerCredential getBootstrapServerCredentialNoSec(boolean isBootstrap) {
AbstractLwM2MBootstrapServerCredential bootstrapServerCredential = new NoSecLwM2MBootstrapServerCredential();
bootstrapServerCredential.setServerPublicKey("");
bootstrapServerCredential.setShortServerId(isBootstrap ? shortServerIdBs : shortServerId);
bootstrapServerCredential.setBootstrapServerIs(isBootstrap);
bootstrapServerCredential.setHost(isBootstrap ? hostBs : host);
bootstrapServerCredential.setPort(isBootstrap ? portBs : port);
return bootstrapServerCredential;
}
protected LwM2MDeviceCredentials getDeviceCredentialsNoSec(LwM2MClientCredential clientCredentials) {
LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials();
credentials.setClient(clientCredentials);
LwM2MBootstrapClientCredentials bootstrapCredentials = new LwM2MBootstrapClientCredentials();
NoSecBootstrapClientCredential serverCredentials = new NoSecBootstrapClientCredential();
bootstrapCredentials.setBootstrapServer(serverCredentials);
bootstrapCredentials.setLwm2mServer(serverCredentials);
credentials.setBootstrap(bootstrapCredentials);
return credentials;
}
private static void awaitServersDestroy() {
await("One of servers ports number is not free")
.atMost(3000, TimeUnit.MILLISECONDS)
.until(() -> isServerPortsAvailable() == null);
}
private static String isServerPortsAvailable() {
for (int port : SERVERS_PORT_NUMBERS) {
try (ServerSocket serverSocket = new ServerSocket(port)) {
serverSocket.close();
Assert.assertEquals(true, serverSocket.isClosed());
} catch (IOException e) {
log.warn(String.format("Port %n still in use", port));
return (String.format("Port %n still in use", port));
}
}
return null;
}
private static void awaitClientDestroy(LeshanClient leshanClient) {
await("Destroy LeshanClient: delete All is registered Servers.")
.atMost(2000, TimeUnit.MILLISECONDS)
.until(() -> leshanClient.getRegisteredServers().size() == 0);
}
}

104
application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java

@ -15,28 +15,8 @@
*/
package org.thingsboard.server.transport.lwm2m;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.leshan.client.object.Security;
import static org.eclipse.leshan.client.object.Security.noSec;
public class Lwm2mTestHelper {
// Server
public static final int SECURE_PORT = 5686;
public static final int SECURE_PORT_BS = 5688;
public static final int PORT = 5685;
public static final int PORT_BS = 5687;
public static final String HOST = "localhost";
public static final String HOST_BS = "localhost";
public static final int SHORT_SERVER_ID = 123;
public static final int SHORT_SERVER_ID_BS = 111;
public static final NetworkConfig SECURE_COAP_CONFIG = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(SECURE_PORT));
public static final String SECURE_URI = "coaps://" + HOST + ":" + SECURE_PORT;
public static final Security SECURITY = noSec("coap://"+ HOST +":" + PORT, SHORT_SERVER_ID);
public static final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT));
// Models
public static final String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml", "5.xml", "6.xml", "9.xml", "19.xml", "3303.xml"};
public static final int BINARY_APP_DATA_CONTAINER = 19;
@ -44,6 +24,7 @@ public class Lwm2mTestHelper {
// Ids in Client
public static final int OBJECT_ID_0 = 0;
public static final int OBJECT_ID_1 = 1;
public static final int OBJECT_INSTANCE_ID_0 = 0;
public static final int OBJECT_INSTANCE_ID_1 = 1;
public static final int OBJECT_INSTANCE_ID_2 = 2;
@ -65,4 +46,87 @@ public class Lwm2mTestHelper {
public static final String RESOURCE_ID_NAME_3_14 = "UtfOffset";
public static final String RESOURCE_ID_NAME_19_0_0 = "dataRead";
public static final String RESOURCE_ID_NAME_19_1_0 = "dataWrite";
public static final String RESOURCE_ID_NAME_19_0_3 = "dataDescription";
public enum LwM2MClientState {
ON_INIT(1, "onInit"),
ON_BOOTSTRAP_STARTED(1, "onBootstrapStarted"),
ON_BOOTSTRAP_SUCCESS(2, "onBootstrapSuccess"),
ON_BOOTSTRAP_FAILURE(3, "onBootstrapFailure"),
ON_BOOTSTRAP_TIMEOUT(4, "onBootstrapTimeout"),
ON_REGISTRATION_STARTED(5, "onRegistrationStarted"),
ON_REGISTRATION_SUCCESS(6, "onRegistrationSuccess"),
ON_REGISTRATION_FAILURE(7, "onRegistrationFailure"),
ON_REGISTRATION_TIMEOUT(7, "onRegistrationTimeout"),
ON_UPDATE_STARTED(8, "onUpdateStarted"),
ON_UPDATE_SUCCESS(9, "onUpdateSuccess"),
ON_UPDATE_FAILURE(10, "onUpdateFailure"),
ON_UPDATE_TIMEOUT(11, "onUpdateTimeout"),
ON_DEREGISTRATION_STARTED(12, "onDeregistrationStarted"),
ON_DEREGISTRATION_SUCCESS(13, "onDeregistrationSuccess"),
ON_DEREGISTRATION_FAILURE(14, "onDeregistrationFailure"),
ON_DEREGISTRATION_TIMEOUT(15, "onDeregistrationTimeout"),
ON_EXPECTED_ERROR(16, "onUnexpectedError");
public int code;
public String type;
LwM2MClientState(int code, String type) {
this.code = code;
this.type = type;
}
public static LwM2MClientState fromLwM2MClientStateByType(String type) {
for (LwM2MClientState to : LwM2MClientState.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported Client State type : %s", type));
}
public static LwM2MClientState fromLwM2MClientStateByCode(int code) {
for (LwM2MClientState to : LwM2MClientState.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported Client State code : %s", code));
}
}
public enum LwM2MProfileBootstrapConfigType {
LWM2M_ONLY(1, "only Lwm2m Server"),
BOOTSTRAP_ONLY(2, "only Bootstrap Server"),
BOTH(3, "Lwm2m Server and Bootstrap Server"),
NONE(4, "Without Lwm2m Server and Bootstrap Server");
public int code;
public String type;
LwM2MProfileBootstrapConfigType(int code, String type) {
this.code = code;
this.type = type;
}
public static LwM2MProfileBootstrapConfigType fromLwM2MBootstrapConfigByType(String type) {
for (LwM2MProfileBootstrapConfigType to : LwM2MProfileBootstrapConfigType.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported Profile Bootstrap Config type : %s", type));
}
public static LwM2MProfileBootstrapConfigType fromLwM2MBootstrapConfigByCode(int code) {
for (LwM2MProfileBootstrapConfigType to : LwM2MProfileBootstrapConfigType.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported Profile Bootstrap Config code : %s", code));
}
}
}

8
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java

@ -131,10 +131,10 @@ public class FwLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
scheduler.schedule(() -> {
try {
state.set(1);
fireResourcesChange(3);
fireResourceChange(3);
Thread.sleep(100);
state.set(2);
fireResourcesChange(3);
fireResourceChange(3);
} catch (Exception e) {
}
}, 100, TimeUnit.MILLISECONDS);
@ -144,10 +144,10 @@ public class FwLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
scheduler.schedule(() -> {
try {
state.set(3);
fireResourcesChange(3);
fireResourceChange(3);
Thread.sleep(100);
updateResult.set(1);
fireResourcesChange(5);
fireResourceChange(5);
} catch (Exception e) {
}
}, 100, TimeUnit.MILLISECONDS);

222
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java

@ -17,10 +17,7 @@ package org.thingsboard.server.transport.lwm2m.client;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.core.network.CoapEndpoint;
import org.eclipse.californium.core.network.config.NetworkConfig;
import org.eclipse.californium.core.observe.ObservationStore;
import org.eclipse.californium.scandium.DTLSConnector;
import org.eclipse.californium.elements.config.Configuration;
import org.eclipse.californium.scandium.config.DtlsConnectorConfig;
import org.eclipse.leshan.client.californium.LeshanClient;
import org.eclipse.leshan.client.californium.LeshanClientBuilder;
@ -29,10 +26,10 @@ import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.client.object.Server;
import org.eclipse.leshan.client.observer.LwM2mClientObserver;
import org.eclipse.leshan.client.resource.DummyInstanceEnabler;
import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler;
import org.eclipse.leshan.client.resource.ObjectsInitializer;
import org.eclipse.leshan.client.servers.ServerIdentity;
import org.eclipse.leshan.core.ResponseCode;
import org.eclipse.leshan.core.californium.EndpointFactory;
import org.eclipse.leshan.core.model.InvalidDDFFileException;
import org.eclipse.leshan.core.model.LwM2mModel;
import org.eclipse.leshan.core.model.ObjectLoader;
@ -44,15 +41,23 @@ import org.eclipse.leshan.core.request.BootstrapRequest;
import org.eclipse.leshan.core.request.DeregisterRequest;
import org.eclipse.leshan.core.request.RegisterRequest;
import org.eclipse.leshan.core.request.UpdateRequest;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import org.junit.Assert;
import org.mockito.Mockito;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY;
import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL;
import static org.eclipse.leshan.core.LwM2mId.DEVICE;
import static org.eclipse.leshan.core.LwM2mId.FIRMWARE;
@ -61,10 +66,29 @@ import static org.eclipse.leshan.core.LwM2mId.SECURITY;
import static org.eclipse.leshan.core.LwM2mId.SERVER;
import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_FAILURE;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_STARTED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_TIMEOUT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_FAILURE;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_STARTED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_TIMEOUT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_EXPECTED_ERROR;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_INIT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_FAILURE;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_TIMEOUT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_FAILURE;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_STARTED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_TIMEOUT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources;
@ -74,28 +98,61 @@ public class LwM2MTestClient {
private final ScheduledExecutorService executor;
private final String endpoint;
private LeshanClient client;
private LeshanClient leshanClient;
private Server lwm2mServer;
private Security lwm2mSecurity;
private Security lwm2mSecurityBs;
private Lwm2mServer lwm2mServer;
private Lwm2mServer lwm2mServerBs;
private SimpleLwM2MDevice lwM2MDevice;
private FwLwM2MDevice fwLwM2MDevice;
private SwLwM2MDevice swLwM2MDevice;
private LwM2mBinaryAppDataContainer lwM2MBinaryAppDataContainer;
private LwM2MLocationParams locationParams;
private LwM2mTemperatureSensor lwM2MTemperatureSensor;
public void init(Security security, NetworkConfig coapConfig, int port, boolean isRpc) throws InvalidDDFFileException, IOException {
Assert.assertNull("client already initialized", client);
private LwM2MClientState clientState;
private Set<LwM2MClientState> clientStates;
private DefaultLwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest;
private LwM2mClientContext clientContext;
public void init(Security security, Configuration coapConfig, int port, boolean isRpc, boolean isBootstrap,
int shortServerId, int shortServerIdBs, Security securityBs,
DefaultLwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler,
LwM2mClientContext clientContext) throws InvalidDDFFileException, IOException {
Assert.assertNull("client already initialized", leshanClient);
this.defaultLwM2mUplinkMsgHandlerTest = defaultLwM2mUplinkMsgHandler;
this.clientContext = clientContext;
List<ObjectModel> models = new ArrayList<>();
for (String resourceName : resources) {
models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName));
}
LwM2mModel model = new StaticModel(models);
ObjectsInitializer initializer = new ObjectsInitializer(model);
initializer.setInstancesForObject(SECURITY, security);
initializer.setInstancesForObject(SERVER, lwm2mServer = new Server(123, 300));
initializer.setInstancesForObject(DEVICE, lwM2MDevice = new SimpleLwM2MDevice());
if (securityBs == null) {
initializer.setInstancesForObject(SECURITY, this.lwm2mSecurity = security);
} else {
securityBs.setId(0);
security.setId(1);
LwM2mInstanceEnabler[] instances = new LwM2mInstanceEnabler[]{this.lwm2mSecurityBs = securityBs, this.lwm2mSecurity = security};
initializer.setClassForObject(SECURITY, Security.class);
initializer.setInstancesForObject(SECURITY, instances);
}
if (isBootstrap) {
initializer.setInstancesForObject(SERVER, lwm2mServerBs = new Lwm2mServer(shortServerIdBs, 300));
} else {
if (securityBs == null) {
initializer.setInstancesForObject(SERVER, lwm2mServer = new Lwm2mServer(shortServerId, 300));
} else {
lwm2mServerBs = new Lwm2mServer(shortServerIdBs, 300);
lwm2mServerBs.setId(0);
lwm2mServer = new Lwm2mServer(shortServerId, 300);
lwm2mServer.setId(1);
LwM2mInstanceEnabler[] instances = new LwM2mInstanceEnabler[]{lwm2mServerBs, lwm2mServer};
initializer.setClassForObject(SERVER, Server.class);
initializer.setInstancesForObject(SERVER, instances);
}
}
initializer.setInstancesForObject(DEVICE, lwM2MDevice = new SimpleLwM2MDevice(executor));
initializer.setInstancesForObject(FIRMWARE, fwLwM2MDevice = new FwLwM2MDevice());
initializer.setInstancesForObject(SOFTWARE_MANAGEMENT, swLwM2MDevice = new SwLwM2MDevice());
initializer.setClassForObject(ACCESS_CONTROL, DummyInstanceEnabler.class);
@ -106,147 +163,150 @@ public class LwM2MTestClient {
initializer.setInstancesForObject(LOCATION, new LwM2mLocation(locationParams.getLatitude(), locationParams.getLongitude(), locationParams.getScaleFactor(), executor, OBJECT_INSTANCE_ID_0));
initializer.setInstancesForObject(TEMPERATURE_SENSOR, lwM2MTemperatureSensor = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_0), new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_12));
DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder();
dtlsConfig.setRecommendedCipherSuitesOnly(true);
dtlsConfig.setClientOnly();
DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(coapConfig);
dtlsConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, true);
DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory();
engineFactory.setReconnectOnUpdate(false);
engineFactory.setResumeOnConnect(true);
EndpointFactory endpointFactory = new EndpointFactory() {
@Override
public CoapEndpoint createUnsecuredEndpoint(InetSocketAddress address, NetworkConfig coapConfig,
ObservationStore store) {
CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
builder.setInetSocketAddress(address);
builder.setNetworkConfig(coapConfig);
return builder.build();
}
@Override
public CoapEndpoint createSecuredEndpoint(DtlsConnectorConfig dtlsConfig, NetworkConfig coapConfig,
ObservationStore store) {
CoapEndpoint.Builder builder = new CoapEndpoint.Builder();
DtlsConnectorConfig.Builder dtlsConfigBuilder = new DtlsConnectorConfig.Builder(dtlsConfig);
builder.setConnector(new DTLSConnector(dtlsConfigBuilder.build()));
builder.setNetworkConfig(coapConfig);
return builder.build();
}
};
LeshanClientBuilder builder = new LeshanClientBuilder(endpoint);
builder.setLocalAddress("0.0.0.0", port);
builder.setObjects(initializer.createAll());
builder.setCoapConfig(coapConfig);
builder.setDtlsConfig(dtlsConfig);
builder.setRegistrationEngineFactory(engineFactory);
builder.setEndpointFactory(endpointFactory);
builder.setSharedExecutor(executor);
builder.setDecoder(new DefaultLwM2mDecoder(false));
builder.setEncoder(new DefaultLwM2mEncoder(new LwM2mValueConverterImpl(), false));
client = builder.build();
clientState = ON_INIT;
clientStates = new HashSet<>();
clientStates.add(clientState);
leshanClient = builder.build();
LwM2mClientObserver observer = new LwM2mClientObserver() {
@Override
public void onBootstrapStarted(ServerIdentity bsserver, BootstrapRequest request) {
log.info("ClientObserver -> onBootstrapStarted...");
clientState = ON_BOOTSTRAP_STARTED;
clientStates.add(clientState);
}
@Override
public void onBootstrapSuccess(ServerIdentity bsserver, BootstrapRequest request) {
log.info("ClientObserver -> onBootstrapSuccess...");
clientState = ON_BOOTSTRAP_SUCCESS;
clientStates.add(clientState);
}
@Override
public void onBootstrapFailure(ServerIdentity bsserver, BootstrapRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
log.info("ClientObserver -> onBootstrapFailure...");
clientState = ON_BOOTSTRAP_FAILURE;
clientStates.add(clientState);
}
@Override
public void onBootstrapTimeout(ServerIdentity bsserver, BootstrapRequest request) {
log.info("ClientObserver -> onBootstrapTimeout...");
clientState = ON_BOOTSTRAP_TIMEOUT;
clientStates.add(clientState);
}
@Override
public void onRegistrationStarted(ServerIdentity server, RegisterRequest request) {
// log.info("ClientObserver -> onRegistrationStarted... EndpointName [{}]", request.getEndpointName());
clientState = ON_REGISTRATION_STARTED;
clientStates.add(clientState);
}
@Override
public void onRegistrationSuccess(ServerIdentity server, RegisterRequest request, String registrationID) {
log.info("ClientObserver -> onRegistrationSuccess... EndpointName [{}] [{}]", request.getEndpointName(), registrationID);
clientState = ON_REGISTRATION_SUCCESS;
clientStates.add(clientState);
}
@Override
public void onRegistrationFailure(ServerIdentity server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
log.info("ClientObserver -> onRegistrationFailure... ServerIdentity [{}]", server);
clientState = ON_REGISTRATION_FAILURE;
clientStates.add(clientState);
}
@Override
public void onRegistrationTimeout(ServerIdentity server, RegisterRequest request) {
log.info("ClientObserver -> onRegistrationTimeout... RegisterRequest [{}]", request);
clientState = ON_REGISTRATION_TIMEOUT;
clientStates.add(clientState);
}
@Override
public void onUpdateStarted(ServerIdentity server, UpdateRequest request) {
// log.info("ClientObserver -> onUpdateStarted... UpdateRequest [{}]", request);
clientState = ON_UPDATE_STARTED;
clientStates.add(clientState);
}
@Override
public void onUpdateSuccess(ServerIdentity server, UpdateRequest request) {
// log.info("ClientObserver -> onUpdateSuccess... UpdateRequest [{}]", request);
clientState = ON_UPDATE_SUCCESS;
clientStates.add(clientState);
}
@Override
public void onUpdateFailure(ServerIdentity server, UpdateRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
clientState = ON_UPDATE_FAILURE;
clientStates.add(clientState);
}
@Override
public void onUpdateTimeout(ServerIdentity server, UpdateRequest request) {
clientState = ON_UPDATE_TIMEOUT;
clientStates.add(clientState);
}
@Override
public void onDeregistrationStarted(ServerIdentity server, DeregisterRequest request) {
log.info("ClientObserver ->onDeregistrationStarted... DeregisterRequest [{}]", request.getRegistrationId());
clientState = ON_DEREGISTRATION_STARTED;
clientStates.add(clientState);
}
@Override
public void onDeregistrationSuccess(ServerIdentity server, DeregisterRequest request) {
log.info("ClientObserver ->onDeregistrationSuccess... DeregisterRequest [{}]", request.getRegistrationId());
clientState = ON_DEREGISTRATION_SUCCESS;
clientStates.add(clientState);
}
@Override
public void onDeregistrationFailure(ServerIdentity server, DeregisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
log.info("ClientObserver ->onDeregistrationFailure... DeregisterRequest [{}] [{}]", request.getRegistrationId(), request.getRegistrationId());
clientState = ON_DEREGISTRATION_FAILURE;
clientStates.add(clientState);
}
@Override
public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) {
log.info("ClientObserver ->onDeregistrationTimeout... DeregisterRequest [{}] [{}]", request.getRegistrationId(), request.getRegistrationId());
clientState = ON_DEREGISTRATION_TIMEOUT;
clientStates.add(clientState);
}
@Override
public void onUnexpectedError(Throwable unexpectedError) {
clientState = ON_EXPECTED_ERROR;
clientStates.add(clientState);
}
};
this.client.addObserver(observer);
this.leshanClient.addObserver(observer);
if (!isRpc) {
client.start();
this.start(true);
}
}
public void destroy() {
if (client != null) {
client.destroy(true);
if (leshanClient != null) {
leshanClient.destroy(true);
}
if (lwm2mSecurityBs != null) {
lwm2mSecurityBs = null;
}
if (lwm2mSecurity != null) {
lwm2mSecurity = null;
}
if (lwm2mServerBs != null) {
lwm2mServerBs = null;
}
if (lwm2mServer != null) {
lwm2mServer = null;
@ -268,9 +328,29 @@ public class LwM2MTestClient {
}
}
public void start() {
if (client != null) {
client.start();
public void start(boolean isStartLw) {
if (leshanClient != null) {
leshanClient.start();
if (isStartLw) {
this.awaitClientAfterStartConnectLw();
}
}
}
private void awaitClientAfterStartConnectLw() {
LwM2mClient lwM2MClient = this.clientContext.getClientByEndpoint(endpoint);
CountDownLatch latch = new CountDownLatch(1);
Mockito.doAnswer(invocation -> {
latch.countDown();
return null;
}).when(defaultLwM2mUplinkMsgHandlerTest).initAttributes(lwM2MClient, true);
try {
if (!latch.await(1, TimeUnit.SECONDS)) {
throw new RuntimeException("Failed to await TimeOut lwm2m client initialization!");
}
} catch (InterruptedException e) {
throw new RuntimeException("Exception Failed to await lwm2m client initialization! ", e);
}
}
}

26
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java

@ -81,8 +81,11 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements
public LwM2mBinaryAppDataContainer(ScheduledExecutorService executorService, Integer id) {
try {
if (id != null) this.setId(id);
executorService.scheduleWithFixedDelay(() ->
fireResourcesChange(0, 2), 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN
executorService.scheduleWithFixedDelay(() -> {
fireResourceChange(0);
fireResourceChange(2);
}
, 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN
} catch (Throwable e) {
log.error("[{}]Throwable", e.toString());
e.printStackTrace();
@ -126,23 +129,23 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements
}
case 1:
setPriority((Integer) (value.getValue() instanceof Long ? ((Long) value.getValue()).intValue() : value.getValue()));
fireResourcesChange(resourceId);
fireResourceChange(resourceId);
return WriteResponse.success();
case 2:
setTimestamp(((Date) value.getValue()).getTime());
fireResourcesChange(resourceId);
fireResourceChange(resourceId);
return WriteResponse.success();
case 3:
setDescription((String) value.getValue());
fireResourcesChange(resourceId);
fireResourceChange(resourceId);
return WriteResponse.success();
case 4:
setDataFormat((String) value.getValue());
fireResourcesChange(resourceId);
fireResourceChange(resourceId);
return WriteResponse.success();
case 5:
setAppID((Integer) value.getValue());
fireResourcesChange(resourceId);
fireResourceChange(resourceId);
return WriteResponse.success();
default:
return super.write(identity, replace, resourceId, value);
@ -170,7 +173,8 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements
}
private String getDescription() {
return this.description == null ? "meter reading" : this.description;
// return this.description == null ? "meter reading" : this.description;
return this.description;
}
private void setTimestamp(long time) {
@ -185,7 +189,7 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements
try {
if (value instanceof LwM2mMultipleResource) {
if (replace || this.data == null) {
this.data = new HashMap<>();
this.data = new HashMap<>();
}
value.getInstances().values().forEach(v -> {
this.data.put(v.getId(), (byte[]) v.getValue());
@ -200,6 +204,10 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements
}
private Map<Integer, byte[]> getData() {
if (data == null) {
this.data = new HashMap<>();
this.data.put(0, new byte[]{(byte) 0xAC});
}
return data;
}

13
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mLocation.java

@ -74,8 +74,10 @@ public class LwM2mLocation extends BaseInstanceEnabler implements Destroyable {
}
this.scaleFactor = scaleFactor;
timestamp = new Date();
executorService.scheduleWithFixedDelay(() ->
fireResourcesChange(0, 1), 10000, 10000, TimeUnit.MILLISECONDS);
executorService.scheduleWithFixedDelay(() -> {
fireResourceChange(0);
fireResourceChange(1);
}, 10000, 10000, TimeUnit.MILLISECONDS);
} catch (Throwable e) {
log.error("[{}]Throwable", e.toString());
e.printStackTrace();
@ -117,13 +119,16 @@ public class LwM2mLocation extends BaseInstanceEnabler implements Destroyable {
private void moveLatitude(float delta) {
latitude = latitude + delta * scaleFactor;
timestamp = new Date();
fireResourcesChange(0, 5);
fireResourceChange(0);
fireResourceChange(5);
}
private void moveLongitude(float delta) {
longitude = longitude + delta * scaleFactor;
timestamp = new Date();
fireResourcesChange(1, 5);
fireResourceChange(1);
fireResourceChange(5);
}
public float getLatitude() {

6
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java

@ -93,10 +93,9 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr
float delta = (RANDOM.nextInt(20) - 10) / 10f;
currentTemp += delta;
Integer changedResource = adjustMinMaxMeasuredValue(currentTemp);
fireResourceChange(5700);
if (changedResource != null) {
fireResourcesChange(5700, changedResource);
} else {
fireResourcesChange(5700);
fireResourceChange(changedResource);
}
}
@ -127,5 +126,4 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr
}
}

235
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/Lwm2mServer.java

@ -0,0 +1,235 @@
/**
* Copyright © 2016-2022 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.transport.lwm2m.client;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler;
import org.eclipse.leshan.client.servers.ServerIdentity;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel.Type;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.request.BindingMode;
import org.eclipse.leshan.core.response.ExecuteResponse;
import org.eclipse.leshan.core.response.ReadResponse;
import org.eclipse.leshan.core.response.WriteResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
/**
* A simple {@link LwM2mInstanceEnabler} for the Server (1) object.
*/
@Slf4j
public class Lwm2mServer extends BaseInstanceEnabler {
private static final Logger LOG = LoggerFactory.getLogger(Lwm2mServer.class);
private final static List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3, 6, 7, 8, 22);
private int shortServerId;
private long lifetime;
private Long defaultMinPeriod;
private Long defaultMaxPeriod;
private EnumSet<BindingMode> binding;
private BindingMode preferredTransport;
private boolean notifyWhenDisable;
public Lwm2mServer() {
// should only be used at bootstrap time
}
public Lwm2mServer(int shortServerId, long lifetime, EnumSet<BindingMode> binding, boolean notifyWhenDisable,
BindingMode preferredTransport) {
this.shortServerId = shortServerId;
this.lifetime = lifetime;
this.binding = binding;
this.notifyWhenDisable = notifyWhenDisable;
this.preferredTransport = preferredTransport;
}
public Lwm2mServer(int shortServerId, long lifetime) {
this(shortServerId, lifetime, EnumSet.of(BindingMode.U), false, BindingMode.U);
}
@Override
public ReadResponse read(ServerIdentity identity, int resourceid) {
if (!identity.isSystem())
LOG.debug("Read on Server resource /{}/{}/{}", getModel().id, getId(), resourceid);
switch (resourceid) {
case 0: // short server ID
return ReadResponse.success(resourceid, shortServerId);
case 1: // lifetime
return ReadResponse.success(resourceid, lifetime);
case 2: // default min period
if (null == defaultMinPeriod)
return ReadResponse.notFound();
return ReadResponse.success(resourceid, defaultMinPeriod);
case 3: // default max period
if (null == defaultMaxPeriod)
return ReadResponse.notFound();
return ReadResponse.success(resourceid, defaultMaxPeriod);
case 6: // notification storing when disable or offline
return ReadResponse.success(resourceid, notifyWhenDisable);
case 7: // binding
return ReadResponse.success(resourceid, BindingMode.toString(binding));
case 22: // preferred transport
if (preferredTransport == null)
return ReadResponse.notFound();
return ReadResponse.success(resourceid, preferredTransport.toString());
default:
return super.read(identity, resourceid);
}
}
@Override
public WriteResponse write(ServerIdentity identity, boolean replace, int resourceid, LwM2mResource value) {
if (!identity.isSystem())
log.debug("Write on Server resource /{}/{}/{}", getModel().id, getId(), resourceid);
switch (resourceid) {
case 0:
if (value.getType() != Type.INTEGER) {
return WriteResponse.badRequest("invalid type");
}
int previousShortServerId = shortServerId;
shortServerId = ((Long) value.getValue()).intValue();
if (previousShortServerId != shortServerId)
fireResourceChange(resourceid);
return WriteResponse.success();
case 1:
if (value.getType() != Type.INTEGER) {
return WriteResponse.badRequest("invalid type");
}
long previousLifetime = lifetime;
lifetime = (Long) value.getValue();
if (previousLifetime != lifetime)
fireResourceChange(resourceid);
return WriteResponse.success();
case 2:
if (value.getType() != Type.INTEGER) {
return WriteResponse.badRequest("invalid type");
}
Long previousDefaultMinPeriod = defaultMinPeriod;
defaultMinPeriod = (Long) value.getValue();
if (!Objects.equals(previousDefaultMinPeriod, defaultMinPeriod))
fireResourceChange(resourceid);
return WriteResponse.success();
case 3:
if (value.getType() != Type.INTEGER) {
return WriteResponse.badRequest("invalid type");
}
Long previousDefaultMaxPeriod = defaultMaxPeriod;
defaultMaxPeriod = (Long) value.getValue();
if (!Objects.equals(previousDefaultMaxPeriod, defaultMaxPeriod))
fireResourceChange(resourceid);
return WriteResponse.success();
case 6: // notification storing when disable or offline
if (value.getType() != Type.BOOLEAN) {
return WriteResponse.badRequest("invalid type");
}
boolean previousNotifyWhenDisable = notifyWhenDisable;
notifyWhenDisable = (boolean) value.getValue();
if (previousNotifyWhenDisable != notifyWhenDisable)
fireResourceChange(resourceid);
return WriteResponse.success();
case 7: // binding
if (value.getType() != Type.STRING) {
return WriteResponse.badRequest("invalid type");
}
try {
EnumSet<BindingMode> previousBinding = binding;
binding = BindingMode.parse((String) value.getValue());
if (!Objects.equals(previousBinding, binding))
fireResourceChange(resourceid);
return WriteResponse.success();
} catch (IllegalArgumentException e) {
return WriteResponse.badRequest("invalid value");
}
case 22: // preferredTransport
if (value.getType() != Type.STRING) {
return WriteResponse.badRequest("invalid type");
}
try {
BindingMode previousPreferedTransport = preferredTransport;
preferredTransport = BindingMode.valueOf((String) value.getValue());
if (!Objects.equals(previousPreferedTransport, preferredTransport))
fireResourceChange(resourceid);
return WriteResponse.success();
} catch (IllegalArgumentException e) {
return WriteResponse.badRequest("invalid value");
}
default:
return super.write(identity, replace, resourceid, value);
}
}
@Override
public ExecuteResponse execute(ServerIdentity identity, int resourceid, String params) {
log.info("Execute on Server resource /{}/{}/{}", getModel().id, getId(), resourceid);
if (resourceid == 8) {
getLwM2mClient().triggerRegistrationUpdate(identity);
return ExecuteResponse.success();
} else if (resourceid == 9) {
boolean success = getLwM2mClient().triggerClientInitiatedBootstrap(true);
if (success) {
return ExecuteResponse.success();
}
else {
return ExecuteResponse.badRequest("probably no bootstrap server configured");
}
} else {
return super.execute(identity, resourceid, params);
}
}
@Override
public void reset(int resourceid) {
switch (resourceid) {
case 2:
defaultMinPeriod = null;
break;
case 3:
defaultMaxPeriod = null;
break;
default:
super.reset(resourceid);
}
}
@Override
public List<Integer> getAvailableResourceIds(ObjectModel model) {
return supportedResources;
}
}

32
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java

@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.client;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
import org.eclipse.leshan.client.servers.ServerIdentity;
import org.eclipse.leshan.core.Destroyable;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mResource;
@ -25,23 +26,45 @@ import org.eclipse.leshan.core.response.ExecuteResponse;
import org.eclipse.leshan.core.response.ReadResponse;
import org.eclipse.leshan.core.response.WriteResponse;
import javax.security.auth.Destroyable;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.PrimitiveIterator;
import java.util.Random;
import java.util.TimeZone;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j
public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
private static final Random RANDOM = new Random();
private static final int min = 5;
private static final int max = 50;
private static final PrimitiveIterator.OfInt randomIterator = new Random().ints(min,max + 1).iterator();
private static final List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21);
public SimpleLwM2MDevice() {
}
public SimpleLwM2MDevice(ScheduledExecutorService executorService) {
try {
executorService.scheduleWithFixedDelay(() -> {
fireResourceChange(9);
}
, 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN
} catch (Throwable e) {
log.error("[{}]Throwable", e.toString());
e.printStackTrace();
}
}
@Override
public ReadResponse read(ServerIdentity identity, int resourceId) {
if (!identity.isSystem())
@ -103,11 +126,11 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
return WriteResponse.notFound();
case 14:
setUtcOffset((String) value.getValue());
fireResourcesChange(resourceId);
fireResourceChange(resourceId);
return WriteResponse.success();
case 15:
setTimezone((String) value.getValue());
fireResourcesChange(resourceId);
fireResourceChange(resourceId);
return WriteResponse.success();
default:
return super.write(identity, replace, resourceId, value);
@ -135,7 +158,8 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
}
private int getBatteryLevel() {
return 42;
return randomIterator.nextInt();
// return 42;
}
private long getMemoryFree() {

12
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java

@ -128,16 +128,17 @@ public class SwLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
try {
state.set(1);
updateResult.set(1);
fireResourcesChange(7, 9);
fireResourceChange(7);
fireResourceChange(9);
Thread.sleep(100);
state.set(2);
fireResourcesChange(7);
fireResourceChange(7);
Thread.sleep(100);
state.set(3);
fireResourcesChange(7);
fireResourceChange(7);
Thread.sleep(100);
updateResult.set(3);
fireResourcesChange(9);
fireResourceChange(9);
} catch (Exception e) {
}
@ -148,7 +149,8 @@ public class SwLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
scheduler.schedule(() -> {
state.set(4);
updateResult.set(2);
fireResourcesChange(7, 9);
fireResourceChange(7);
fireResourceChange(9);
}, 100, TimeUnit.MILLISECONDS);
}

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

@ -20,7 +20,8 @@ import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus;
@ -46,105 +47,59 @@ import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.QUEU
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.COAP_CONFIG;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE;
@Slf4j
public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest {
public static final int TIMEOUT = 30;
private final String OTA_TRANSPORT_CONFIGURATION = "{\n" +
" \"type\": \"LWM2M\",\n" +
" \"observeAttr\": {\n" +
" \"keyName\": {\n" +
" \"/5_1.0/0/3\": \"state\",\n" +
" \"/5_1.0/0/5\": \"updateResult\",\n" +
" \"/5_1.0/0/6\": \"pkgname\",\n" +
" \"/5_1.0/0/7\": \"pkgversion\",\n" +
" \"/5_1.0/0/9\": \"firmwareUpdateDeliveryMethod\",\n" +
" \"/9_1.0/0/0\": \"pkgname\",\n" +
" \"/9_1.0/0/1\": \"pkgversion\",\n" +
" \"/9_1.0/0/7\": \"updateState\",\n" +
" \"/9_1.0/0/9\": \"updateResult\"\n" +
" },\n" +
" \"observe\": [\n" +
" \"/5_1.0/0/3\",\n" +
" \"/5_1.0/0/5\",\n" +
" \"/5_1.0/0/6\",\n" +
" \"/5_1.0/0/7\",\n" +
" \"/5_1.0/0/9\",\n" +
" \"/9_1.0/0/0\",\n" +
" \"/9_1.0/0/1\",\n" +
" \"/9_1.0/0/7\",\n" +
" \"/9_1.0/0/9\"\n" +
" ],\n" +
" \"attribute\": [],\n" +
" \"telemetry\": [\n" +
" \"/5_1.0/0/3\",\n" +
" \"/5_1.0/0/5\",\n" +
" \"/5_1.0/0/6\",\n" +
" \"/5_1.0/0/7\",\n" +
" \"/5_1.0/0/9\",\n" +
" \"/9_1.0/0/0\",\n" +
" \"/9_1.0/0/1\",\n" +
" \"/9_1.0/0/7\",\n" +
" \"/9_1.0/0/9\"\n" +
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" },\n" +
" \"bootstrapServerUpdateEnable\": true,\n" +
" \"bootstrap\": [\n" +
" {\n" +
" \"host\": \"0.0.0.0\",\n" +
" \"port\": 5687,\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"shortServerId\": 111,\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
" \"bootstrapServerIs\": true,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" },\n" +
" {\n" +
" \"host\": \"0.0.0.0\",\n" +
" \"port\": 5685,\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"shortServerId\": 123,\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
" \"bootstrapServerIs\": false,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" }\n" +
" ],\n" +
" \"clientLwM2mSettings\": {\n" +
" \"edrxCycle\": null,\n" +
" \"powerMode\": \"DRX\",\n" +
" \"fwUpdateResource\": null,\n" +
" \"fwUpdateStrategy\": 1,\n" +
" \"psmActivityTimer\": null,\n" +
" \"swUpdateResource\": null,\n" +
" \"swUpdateStrategy\": 1,\n" +
" \"pagingTransmissionWindow\": null,\n" +
" \"clientOnlyObserveAfterConnect\": 1\n" +
" }\n" +
"}";
@Test
public void testFirmwareUpdateWithClientWithoutFirmwareOtaInfoFromProfile() throws Exception {
createDeviceProfile(TRANSPORT_CONFIGURATION);
NoSecClientCredential credentials = createNoSecClientCredentials(this.CLIENT_ENDPOINT_WITHOUT_FW_INFO);
final Device device = createDevice(credentials);
createNewClient(SECURITY, COAP_CONFIG, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO);
Thread.sleep(1000);
protected final String OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA =
" {\n" +
" \"keyName\": {\n" +
" \"/5_1.0/0/3\": \"state\",\n" +
" \"/5_1.0/0/5\": \"updateResult\",\n" +
" \"/5_1.0/0/6\": \"pkgname\",\n" +
" \"/5_1.0/0/7\": \"pkgversion\",\n" +
" \"/5_1.0/0/9\": \"firmwareUpdateDeliveryMethod\",\n" +
" \"/9_1.0/0/0\": \"pkgname\",\n" +
" \"/9_1.0/0/1\": \"pkgversion\",\n" +
" \"/9_1.0/0/7\": \"updateState\",\n" +
" \"/9_1.0/0/9\": \"updateResult\"\n" +
" },\n" +
" \"observe\": [\n" +
" \"/5_1.0/0/3\",\n" +
" \"/5_1.0/0/5\",\n" +
" \"/5_1.0/0/6\",\n" +
" \"/5_1.0/0/7\",\n" +
" \"/5_1.0/0/9\",\n" +
" \"/9_1.0/0/0\",\n" +
" \"/9_1.0/0/1\",\n" +
" \"/9_1.0/0/7\",\n" +
" \"/9_1.0/0/9\"\n" +
" ],\n" +
" \"attribute\": [],\n" +
" \"telemetry\": [\n" +
" \"/5_1.0/0/3\",\n" +
" \"/5_1.0/0/5\",\n" +
" \"/5_1.0/0/6\",\n" +
" \"/5_1.0/0/7\",\n" +
" \"/5_1.0/0/9\",\n" +
" \"/9_1.0/0/0\",\n" +
" \"/9_1.0/0/1\",\n" +
" \"/9_1.0/0/7\",\n" +
" \"/9_1.0/0/9\"\n" +
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" }";
@Test
public void testFirmwareUpdateWithClientWithoutFirmwareOtaInfoFromProfile() throws Exception {
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS, getBootstrapServerCredentialsNoSec(NONE));
createDeviceProfile(transportConfiguration);
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_WITHOUT_FW_INFO));
final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO);
createNewClient(SECURITY_NO_SEC, COAP_CONFIG, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO, false, null);
device.setFirmwareId(createFirmware().getId());
final Device savedDevice = doPost("/api/device", device, Device.class);
@ -163,13 +118,11 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest {
@Test
public void testFirmwareUpdateByObject5() throws Exception {
createDeviceProfile(OTA_TRANSPORT_CONFIGURATION);
NoSecClientCredential credentials = createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA5);
final Device device = createDevice(credentials);
createNewClient(SECURITY, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA5);
Thread.sleep(1000);
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA, getBootstrapServerCredentialsNoSec(NONE));
createDeviceProfile(transportConfiguration);
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA5));
final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA5);
createNewClient(SECURITY_NO_SEC, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA5, false, null);
device.setFirmwareId(createFirmware().getId());
final Device savedDevice = doPost("/api/device", device, Device.class);
@ -200,10 +153,11 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest {
* */
@Test
public void testSoftwareUpdateByObject9() throws Exception {
createDeviceProfile(OTA_TRANSPORT_CONFIGURATION);
NoSecClientCredential credentials = createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA9);
final Device device = createDevice(credentials);
createNewClient(SECURITY, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA9);
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA, getBootstrapServerCredentialsNoSec(NONE));
createDeviceProfile(transportConfiguration);
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA9));
final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA9);
createNewClient(SECURITY_NO_SEC, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA9, false, null);
Thread.sleep(1000);

168
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java

@ -17,9 +17,11 @@ package org.thingsboard.server.transport.lwm2m.rpc;
import org.junit.Before;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
@ -31,8 +33,6 @@ import static org.eclipse.leshan.core.LwM2mId.FIRMWARE;
import static org.eclipse.leshan.core.LwM2mId.SERVER;
import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.COAP_CONFIG;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0;
@ -45,12 +45,12 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE;
@DaoSqlTest
public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
protected String RPC_TRANSPORT_CONFIGURATION;
protected String OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC;
protected String deviceId;
public Set expectedObjects;
public Set expectedObjectIdVers;
@ -60,7 +60,7 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg
protected String objectInstanceIdVer_1;
protected String objectIdVer_0;
protected String objectIdVer_2;
private static final Predicate PREDICATE_3 = path -> (!((String) path).contains("/" + TEMPERATURE_SENSOR) && ((String) path).contains("/" + DEVICE));
private static final Predicate PREDICATE_3 = path -> (!((String) path).startsWith("/" + TEMPERATURE_SENSOR) && ((String) path).startsWith("/" + DEVICE));
protected String objectIdVer_3;
protected String objectInstanceIdVer_3;
protected String objectInstanceIdVer_5;
@ -70,27 +70,29 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg
protected String objectIdVer_3303;
protected static AtomicInteger endpointSequence = new AtomicInteger();
protected static String DEVICE_ENDPOINT_RPC_PREF = "deviceEndpointRpc";
protected String idVer_3_0_9;
protected String idVer_19_0_0;
public AbstractRpcLwM2MIntegrationTest(){
public AbstractRpcLwM2MIntegrationTest() {
setResources(resources);
}
@Before
public void beforeTest() throws Exception {
String endpoint = DEVICE_ENDPOINT_RPC_PREF + endpointSequence.incrementAndGet();
init();
createNewClient (SECURITY, COAP_CONFIG, true, endpoint);
public void startInitRPC() throws Exception {
initRpc();
}
private void initRpc () throws Exception {
String endpoint = DEVICE_ENDPOINT_RPC_PREF + endpointSequence.incrementAndGet();
createNewClient(SECURITY_NO_SEC, COAP_CONFIG, true, endpoint, false, null);
expectedObjects = ConcurrentHashMap.newKeySet();
expectedObjectIdVers = ConcurrentHashMap.newKeySet();
expectedInstances = ConcurrentHashMap.newKeySet();
expectedObjectIdVerInstances = ConcurrentHashMap.newKeySet();
client.getClient().getObjectTree().getObjectEnablers().forEach((key, val) -> {
lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().forEach((key, val) -> {
if (key > 0) {
String objectVerId = "/" + key;
if (!val.getObjectModel().version.equals("1.0")) {
objectVerId += ("_" + val.getObjectModel().version);
}
objectVerId += ("_" + val.getObjectModel().version);
expectedObjects.add("/" + key);
expectedObjectIdVers.add(objectVerId);
String finalObjectVerId = objectVerId;
@ -100,102 +102,58 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg
});
}
});
String ver_Id_0 = client.getClient().getObjectTree().getModel().getObjectModel(OBJECT_ID_0).version;
if ("1.0".equals(ver_Id_0)) {
objectIdVer_0 = "/" + OBJECT_ID_0;
}
else {
objectIdVer_0 = "/" + OBJECT_ID_0 + "_" + ver_Id_0;
}
objectIdVer_2 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + ACCESS_CONTROL)).findFirst().get();
objectIdVer_3 = (String) expectedObjects.stream().filter(PREDICATE_3).findFirst().get();
objectIdVer_19 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + BINARY_APP_DATA_CONTAINER)).findFirst().get();
objectIdVer_3303 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + TEMPERATURE_SENSOR)).findFirst().get();
objectInstanceIdVer_1 = (String) expectedObjectIdVerInstances.stream().filter(path -> (!((String) path).contains("/" + BINARY_APP_DATA_CONTAINER) && ((String) path).contains("/" + SERVER))).findFirst().get();
String ver_Id_0 = lwM2MTestClient.getLeshanClient().getObjectTree().getModel().getObjectModel(OBJECT_ID_0).version;
objectIdVer_0 = "/" + OBJECT_ID_0 + "_" + ver_Id_0;
objectIdVer_2 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).startsWith("/" + ACCESS_CONTROL)).findFirst().get();
objectIdVer_3 = (String) expectedObjectIdVers.stream().filter(PREDICATE_3).findFirst().get();
objectIdVer_19 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).startsWith("/" + BINARY_APP_DATA_CONTAINER)).findFirst().get();
objectIdVer_3303 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).startsWith("/" + TEMPERATURE_SENSOR)).findFirst().get();
objectInstanceIdVer_1 = (String) expectedObjectIdVerInstances.stream().filter(path -> (!((String) path).startsWith("/" + BINARY_APP_DATA_CONTAINER) && ((String) path).startsWith("/" + SERVER))).findFirst().get();
objectInstanceIdVer_3 = (String) expectedObjectIdVerInstances.stream().filter(PREDICATE_3).findFirst().get();
objectInstanceIdVer_5 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).contains("/" + FIRMWARE)).findFirst().get();
objectInstanceIdVer_9 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).contains("/" + SOFTWARE_MANAGEMENT)).findFirst().get();
RPC_TRANSPORT_CONFIGURATION = "{\n" +
" \"type\": \"LWM2M\",\n" +
" \"observeAttr\": {\n" +
" \"keyName\": {\n" +
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_9 + "\": \"" + RESOURCE_ID_NAME_3_9 + "\",\n" +
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\": \"" + RESOURCE_ID_NAME_3_14 + "\",\n" +
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0 + "\": \"" + RESOURCE_ID_NAME_19_0_0 + "\",\n" +
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\": \"" + RESOURCE_ID_NAME_19_1_0 + "\"\n" +
" },\n" +
" \"observe\": [\n" +
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_9 + "\",\n" +
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0 + "\"\n" +
" ],\n" +
" \"attribute\": [\n" +
" ],\n" +
" \"telemetry\": [\n" +
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_9 + "\",\n" +
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\",\n" +
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0 + "\",\n" +
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\"\n" +
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" },\n" +
" \"bootstrapServerUpdateEnable\": true,\n" +
" \"bootstrap\": [\n" +
" {\n" +
" \"host\": \"0.0.0.0\",\n" +
" \"port\": 5687,\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"shortServerId\": 111,\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
" \"bootstrapServerIs\": true,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" },\n" +
objectInstanceIdVer_5 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).startsWith("/" + FIRMWARE)).findFirst().get();
objectInstanceIdVer_9 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).startsWith("/" + SOFTWARE_MANAGEMENT)).findFirst().get();
idVer_3_0_9 = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_9;
idVer_19_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0;
OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC =
" {\n" +
" \"host\": \"0.0.0.0\",\n" +
" \"port\": 5685,\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"shortServerId\": 123,\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
" \"bootstrapServerIs\": false,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" }\n" +
" ],\n" +
" \"clientLwM2mSettings\": {\n" +
" \"edrxCycle\": null,\n" +
" \"powerMode\": \"DRX\",\n" +
" \"fwUpdateResource\": null,\n" +
" \"fwUpdateStrategy\": 1,\n" +
" \"psmActivityTimer\": null,\n" +
" \"swUpdateResource\": null,\n" +
" \"swUpdateStrategy\": 1,\n" +
" \"pagingTransmissionWindow\": null,\n" +
" \"clientOnlyObserveAfterConnect\": 1\n" +
" }\n" +
"}";
createDeviceProfile(RPC_TRANSPORT_CONFIGURATION);
NoSecClientCredential credentials = createNoSecClientCredentials(endpoint);
final Device device = createDevice(credentials);
" \"keyName\": {\n" +
" \"" + idVer_3_0_9 + "\": \"" + RESOURCE_ID_NAME_3_9 + "\",\n" +
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\": \"" + RESOURCE_ID_NAME_3_14 + "\",\n" +
" \"" + idVer_19_0_0 + "\": \"" + RESOURCE_ID_NAME_19_0_0 + "\",\n" +
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\": \"" + RESOURCE_ID_NAME_19_1_0 + "\"\n" +
" },\n" +
" \"observe\": [\n" +
" \"" + idVer_3_0_9 + "\",\n" +
" \"" + idVer_19_0_0 + "\"\n" +
" ],\n" +
" \"attribute\": [\n" +
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\"\n" +
" ],\n" +
" \"telemetry\": [\n" +
" \"" + idVer_3_0_9 + "\",\n" +
" \"" + idVer_19_0_0 + "\",\n" +
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\"\n" +
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" }";
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC, getBootstrapServerCredentialsNoSec(NONE));
createDeviceProfile(transportConfiguration);
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(endpoint));
final Device device = createDevice(deviceCredentials, endpoint);
deviceId = device.getId().getId().toString();
client.start();
}
lwM2MTestClient.start(true);
}
protected String pathIdVerToObjectId(String pathIdVer) {
if (pathIdVer.contains("_")){
String [] objVer = pathIdVer.split("/");
objVer[1] = objVer[1].split("_")[0];
return String.join("/", objVer);
if (pathIdVer.contains("_")) {
String[] objVer = pathIdVer.split("/");
objVer[1] = objVer[1].split("_")[0];
return String.join("/", objVer);
}
return pathIdVer;
}

6
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java

@ -52,8 +52,8 @@ public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegration
Set actualObjects = ConcurrentHashMap.newKeySet();
Set actualInstances = ConcurrentHashMap.newKeySet();
rpcActualValue.forEach(node -> {
if (!node.get("url").asText().equals("/")) {
LwM2mPath path = new LwM2mPath(node.get("url").asText());
if (!node.get("uriReference").asText().equals("/")) {
LwM2mPath path = new LwM2mPath(node.get("uriReference").asText());
actualObjects.add("/" + path.getObjectId());
if (path.isObjectInstance()) {
actualInstances.add("/" + path.getObjectId() + "/" + path.getObjectInstanceId());
@ -124,7 +124,7 @@ public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegration
String expectedInstance = (String) expectedInstances.stream().findFirst().get();
String expectedObjectInstanceId = pathIdVerToObjectId(expectedInstance);
LwM2mPath expectedPath = new LwM2mPath(expectedObjectInstanceId);
int expectedResource = client.getClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey();
int expectedResource = lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey();
String expected = expectedInstance + "/" + expectedResource;
String actualResult = sendDiscover(expected);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);

7
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java

@ -121,8 +121,8 @@ public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationT
/**
* bad: resource operation not "E"
* Execute {"id":"5/0/3"}
* {"result":"BAD_REQUEST","error":"Resource with /5/0/3 is not executable."}
* Execute {"id":"5_1.0/0/3"}
* {"result":"BAD_REQUEST","error":"Resource with /5_1.0/0/3 is not executable."}
*/
@Test
public void testExecuteResourceWithOperationNotExecuteById_Result_METHOD_NOT_ALLOWED() throws Exception {
@ -130,8 +130,7 @@ public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationT
String actualResult = sendRPCExecuteById(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expectedObjectId = pathIdVerToObjectId((String) expectedPath);
String expected = "Resource with " + expectedObjectId + " is not executable.";
String expected = "Resource with " + expectedPath + " is not executable.";
String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected));
}

78
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java

@ -26,11 +26,11 @@ import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9;
import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId;
public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationTest {
@ -40,23 +40,42 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
*/
@Test
public void testObserveReadAllNothingObservation_Result_CONTENT_Value_Count_0() throws Exception {
String actualResultBefore = sendObserve("ObserveReadAll", null);
ObjectNode rpcActualResultBefore = JacksonUtil.fromString(actualResultBefore, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultBefore.get("result").asText());
assertTrue(rpcActualResultBefore.get("value").asText().contains(fromVersionedIdToObjectId(idVer_3_0_9)));
assertTrue(rpcActualResultBefore.get("value").asText().contains(fromVersionedIdToObjectId(idVer_19_0_0)));
String actualResult = sendObserve("ObserveCancelAll", null);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
actualResult = sendObserve("ObserveReadAll", null);
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("[]", rpcActualResult.get("value").asText());
assertEquals("2", rpcActualResult.get("value").asText());
String actualResultAfter = sendObserve("ObserveReadAll", null);
ObjectNode rpcActualResultAfter = JacksonUtil.fromString(actualResultAfter, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultAfter.get("result").asText());
String expectResultAfter = "[]";
assertEquals( expectResultAfter, rpcActualResultAfter.get("value").asText());
}
/**
* Observe {"id":"/3/0/9"}
* Observe {"id":"/3/0/0"}
* @throws Exception
*/
@Test
public void testObserveSingleResource_Result_CONTENT_Value_SingleResource() throws Exception {
String expectedIdVer = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9;
String actualResult = sendObserve("Observe", expectedIdVer);
public void testObserveSingleResourceWithout_IdVer_1_0_Result_CONTENT_Value_SingleResource() throws Exception {
String expectedId = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0;
String actualResult = sendObserve("Observe", fromVersionedIdToObjectId(expectedId));
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource"));
}
/**
* Observe {"id":"/3_1.0/0/14"}
* @throws Exception
*/
@Test
public void testObserveSingleResourceWith_IdVer_1_0_Result_CONTENT_Value_SingleResource() throws Exception {
String expectedId = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14;
String actualResult = sendObserve("Observe", expectedId);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource"));
@ -70,7 +89,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
public void testObserveWithBadVersion_Result_BadRequest_ErrorMsg_BadVersionMustBe1_0() throws Exception {
String expectedInstance = (String) expectedInstances.stream().filter(path -> !((String)path).contains("_")).findFirst().get();
LwM2mPath expectedPath = new LwM2mPath(expectedInstance);
int expectedResource = client.getClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey();
int expectedResource = lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey();
String expectedId = "/" + expectedPath.getObjectId() + "_1.2" + "/" + expectedPath.getObjectInstanceId() + "/" + expectedResource;
String actualResult = sendObserve("Observe", expectedId);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
@ -100,11 +119,10 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
*/
@Test
public void testObserveNoImplementedResourceOnDeviceValueNull_Result_BadRequest() throws Exception {
String objectIdVer = (String) expectedObjectIdVers.stream().filter(path -> ((String)path).contains("/" + BINARY_APP_DATA_CONTAINER)).findFirst().get();
String expected = objectIdVer + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0;
String expected = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_3;
String actualResult = sendObserve("Observe", expected);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
String expectedValue = "values MUST NOT be null";
String expectedValue = "value MUST NOT be null";
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
assertEquals(expectedValue, rpcActualResult.get("error").asText());
}
@ -125,14 +143,12 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
/**
* Repeated request on Observe
* Observe {"id":"/3/0/0"}
* Observe {"id":"/3/0/9"}
* @throws Exception
*/
@Test
public void testObserveRepeatedRequestObserveOnDevice_Result_BAD_REQUEST_ErrorMsg_AlreadyRegistered() throws Exception {
String expectedId = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0;
sendObserve("Observe", expectedId);
String actualResult = sendObserve("Observe", expectedId);
String actualResult = sendObserve("Observe", idVer_3_0_9);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Observation is already registered!";
@ -144,18 +160,18 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
* @throws Exception
*/
@Test
public void testObserveReadAll_Result_CONTENT_Value_Contains_Paths_Count_ObserveAll() throws Exception {
sendObserve("ObserveCancelAll", null);
String expectedId_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0;
String expectedId_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9;
sendObserve("Observe", expectedId_0);
sendObserve("Observe", expectedId_9);
public void testObserveReadAll_Result_CONTENT_Value_Contains_Paths_Count_ObserveReadAll() throws Exception {
String actualResultCancel = sendObserve("ObserveCancelAll", null);
ObjectNode rpcActualResultCancel = JacksonUtil.fromString(actualResultCancel, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultCancel.get("result").asText());
sendObserve("Observe",idVer_19_0_0);
sendObserve("Observe", idVer_3_0_9);
String actualResult = sendObserve("ObserveReadAll", null);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValues = rpcActualResult.get("value").asText();
assertTrue(actualValues.contains(expectedId_0));
assertTrue(actualValues.contains(expectedId_9));
assertTrue(actualValues.contains(fromVersionedIdToObjectId(idVer_19_0_0)));
assertTrue(actualValues.contains(fromVersionedIdToObjectId(idVer_3_0_9)));
assertEquals(2, actualValues.split(",").length);
}
@ -167,11 +183,11 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
@Test
public void testObserveCancelOneResource_Result_CONTENT_Value_Count_1() throws Exception {
sendObserve("ObserveCancelAll", null);
String expectedId_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0;
String expectedId_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
sendObserve("Observe", expectedId_0);
sendObserve("Observe", expectedId_3);
String actualResult = sendObserve("ObserveCancel", expectedId_0);
String expectedId_3_0_3 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_3;
String expectedId_5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
sendObserve("Observe", expectedId_3_0_3);
sendObserve("Observe", expectedId_5_0_3);
String actualResult = sendObserve("ObserveCancel", expectedId_3_0_3);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("1", rpcActualResult.get("value").asText());

25
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java

@ -30,6 +30,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_3;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9;
@ -181,11 +182,12 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest
* ReadComposite {"keys":["batteryLevel", "UtfOffset", "dataRead", "dataWrite"]}
*/
@Test
public void testReadCompositeSingleResourceByKeys_Result_CONTENT_Value_3_0_IsLwM2mSingleResource_19_0_0_AND_19_0_1_Null() throws Exception {
public void testReadCompositeSingleResourceByKeys_Result_CONTENT_Value_3_0_IsLwM2mSingleResource_19_0_0_AND_19_0_1__IsLwM2mMultipleResource() throws Exception {
String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9;
String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14;
String expectedKey19_0_0 = RESOURCE_ID_NAME_19_0_0;
String expectedKey19_1_0 = RESOURCE_ID_NAME_19_1_0;
String expectedKey19_X_0 = "=LwM2mMultipleResource [id=0, values={0=LwM2mResourceInstance [id=0, value=1Bytes, type=OPAQUE]";
String expectedKeys = "[\"" + expectedKey3_0_9 + "\", \"" + expectedKey3_0_14 + "\", \"" + expectedKey19_0_0 + "\", \"" + expectedKey19_1_0 + "\"]";
String actualResult = sendCompositeRPCByKeys(expectedKeys);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
@ -194,8 +196,8 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest
String objectId_19 = pathIdVerToObjectId(objectIdVer_19);
String expected3_0_9 = objectInstanceId_3 + "/" + RESOURCE_ID_9 + "=LwM2mSingleResource [id=" + RESOURCE_ID_9 + ", value=";
String expected3_0_14 = objectInstanceId_3 + "/" + RESOURCE_ID_14 + "=LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value=";
String expected19_0_0 = objectId_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0 + "=null";
String expected19_1_0 = objectId_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "=null";
String expected19_0_0 = objectId_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0 + expectedKey19_X_0;
String expected19_1_0 = objectId_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + expectedKey19_X_0;
String actualValues = rpcActualResult.get("value").asText();
assertTrue(actualValues.contains(expected3_0_9));
assertTrue(actualValues.contains(expected3_0_14));
@ -203,6 +205,23 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest
assertTrue(actualValues.contains(expected19_1_0));
}
/**
* ReadComposite {"keys":["batteryLevel", "UtfOffset", "dataDescription"]}
*/
@Test
public void testReadCompositeSingleResourceByKeys_Result_CONTENT_Value_3_0_IsLwM2mSingleResource_19_0_3_IsNotConfiguredInTheDeviceProfile() throws Exception {
String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9;
String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14;
String expectedKey19_0_3 = RESOURCE_ID_NAME_19_0_3;
String expectedKeys = "[\"" + expectedKey3_0_9 + "\", \"" + expectedKey3_0_14 + "\", \"" + expectedKey19_0_3 + "\"]";
String actualResult = sendCompositeRPCByKeys(expectedKeys);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String actualValue = rpcActualResult.get("error").asText();
String expectedValue = expectedKey19_0_3 + " is not configured in the device profile!";
assertEquals(actualValue, expectedValue);
}
private String sendRPCById(String path) throws Exception {
String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}";

324
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java

@ -15,11 +15,38 @@
*/
package org.thingsboard.server.transport.lwm2m.security;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.eclipse.californium.elements.config.Configuration;
import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.core.ResponseCode;
import org.eclipse.leshan.core.util.Hex;
import org.junit.Assert;
import org.springframework.test.web.servlet.MvcResult;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKBootstrapClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKBootstrapClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.X509BootstrapClientCredential;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.PSKLwM2MBootstrapServerCredential;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.RPKLwM2MBootstrapServerCredential;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.X509LwM2MBootstrapServerCredential;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest;
import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient;
import org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState;
import org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType;
import java.io.IOException;
import java.io.InputStream;
@ -27,28 +54,50 @@ import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.awaitility.Awaitility.await;
import static org.eclipse.leshan.client.object.Security.noSecBootstap;
import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_STARTED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_ID_1;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9;
@DaoSqlTest
@Slf4j
public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
protected final String CREDENTIALS_PATH = "lwm2m/credentials/"; // client public key or id used for PSK
protected final String CREDENTIALS_PATH = "lwm2m/credentials/"; // client public key or id used for PSK
// Get keys PSK
protected final String CLIENT_PSK_IDENTITY = "SOME_PSK_ID"; // client public key or id used for PSK
protected final String CLIENT_PSK_IDENTITY = "SOME_PSK_ID"; // client public key or id used for PSK
protected final String CLIENT_PSK_IDENTITY_BS = "SOME_PSK_ID_BS"; // client public key or id used for PSK
protected final String CLIENT_PSK_KEY = "73656372657450534b73656372657450"; // client private/secret key used for PSK
// Server
protected static final String SERVER_JKS_FOR_TEST = "lwm2mserver";
protected static final String SERVER_STORE_PWD = "server_ks_password";
protected static final String SERVER_CERT_ALIAS = "server";
protected final X509Certificate serverX509Cert; // server certificate signed by rootCA
protected final PublicKey serverPublicKeyFromCert; // server public key used for RPK
protected static final String SERVER_CERT_ALIAS_BS = "bootstrap";
protected final X509Certificate serverX509Cert; // server certificate signed by rootCA
protected final X509Certificate serverX509CertBs; // serverBs certificate signed by rootCA
protected final PublicKey serverPublicKeyFromCert; // server public key used for RPK
protected final PublicKey serverPublicKeyFromCertBs; // serverBs public key used for RPK
// Client
protected LwM2MTestClient client;
protected static final String CLIENT_ENDPOINT_NO_SEC = "LwNoSec00000000";
protected static final String CLIENT_ENDPOINT_NO_SEC_BS = "LwNoSecBs00000000";
protected static final String CLIENT_ENDPOINT_PSK = "LwPsk00000000";
protected static final String CLIENT_ENDPOINT_PSK_BS = "LwPskBs00000000";
protected static final String CLIENT_ENDPOINT_RPK = "LwRpk00000000";
protected static final String CLIENT_ENDPOINT_RPK_BS = "LwRpkBs00000000";
protected static final String CLIENT_ENDPOINT_X509_TRUST = "LwX50900000000";
protected static final String CLIENT_ENDPOINT_X509_TRUST_NO = "LwX509TrustNo";
protected static final String CLIENT_JKS_FOR_TEST = "lwm2mclient";
@ -56,19 +105,16 @@ protected final X509Certificate serverX509Cert;
protected static final String CLIENT_ALIAS_CERT_TRUST = "client_alias_00000000";
protected static final String CLIENT_ALIAS_CERT_TRUST_NO = "client_alias_trust_no";
protected final X509Certificate clientX509CertTrust; // client certificate signed by intermediate, rootCA with a good CN ("host name")
protected final PrivateKey clientPrivateKeyFromCertTrust; // client private key used for X509 and RPK
protected final PublicKey clientPublicKeyFromCertTrust; // client public key used for RPK
protected final X509Certificate clientX509CertTrustNo; // client certificate signed by intermediate, rootCA with a good CN ("host name")
protected final PrivateKey clientPrivateKeyFromCertTrustNo; // client private key used for X509 and RPK
protected final PublicKey clientPublicKeyFromCertTrustNo; // client public key used for RPK
private final String[] RESOURCES_SECURITY = new String[]{"1.xml", "2.xml", "3.xml", "5.xml", "9.xml"};
protected final X509Certificate clientX509CertTrust; // client certificate signed by intermediate, rootCA with a good CN ("host name")
protected final PrivateKey clientPrivateKeyFromCertTrust; // client private key used for X509 and RPK
protected final X509Certificate clientX509CertTrustNo; // client certificate signed by intermediate, rootCA with a good CN ("host name")
protected final PrivateKey clientPrivateKeyFromCertTrustNo; // client private key used for X509 and RPK
private final String[] RESOURCES_SECURITY = new String[]{"1.xml", "2.xml", "3.xml", "5.xml", "9.xml"};
private final LwM2MBootstrapClientCredentials defaultBootstrapCredentials;
public AbstractSecurityLwM2MIntegrationTest() {
// create client credentials
setResources(this.RESOURCES_SECURITY);
@ -82,12 +128,9 @@ protected final X509Certificate serverX509Cert;
// Trust
clientPrivateKeyFromCertTrust = (PrivateKey) clientKeyStore.getKey(CLIENT_ALIAS_CERT_TRUST, clientKeyStorePwd);
clientX509CertTrust = (X509Certificate) clientKeyStore.getCertificate(CLIENT_ALIAS_CERT_TRUST);
clientPublicKeyFromCertTrust = clientX509CertTrust != null ? clientX509CertTrust.getPublicKey() : null;
// No trust
clientPrivateKeyFromCertTrustNo = (PrivateKey) clientKeyStore.getKey(CLIENT_ALIAS_CERT_TRUST_NO, clientKeyStorePwd);
clientX509CertTrustNo = (X509Certificate) clientKeyStore.getCertificate(CLIENT_ALIAS_CERT_TRUST_NO);
clientPublicKeyFromCertTrustNo = clientX509CertTrustNo != null ? clientX509CertTrustNo.getPublicKey() : null;
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
@ -103,6 +146,9 @@ protected final X509Certificate serverX509Cert;
serverX509Cert = (X509Certificate) serverKeyStore.getCertificate(SERVER_CERT_ALIAS);
serverPublicKeyFromCert = serverX509Cert.getPublicKey();
serverX509CertBs = (X509Certificate) serverKeyStore.getCertificate(SERVER_CERT_ALIAS_BS);
serverPublicKeyFromCertBs = serverX509CertBs.getPublicKey();
} catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e);
}
@ -114,4 +160,248 @@ protected final X509Certificate serverX509Cert;
defaultBootstrapCredentials.setBootstrapServer(serverCredentials);
defaultBootstrapCredentials.setLwm2mServer(serverCredentials);
}
public void basicTestConnectionBefore(String clientEndpoint,
String awaitAlias,
LwM2MProfileBootstrapConfigType type,
Set<LwM2MClientState> expectedStatuses,
LwM2MClientState finishState) throws Exception {
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsNoSec(type));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint));
this.basicTestConnection(noSecBootstap(URI_BS),
deviceCredentials,
COAP_CONFIG_BS,
clientEndpoint,
transportConfiguration,
awaitAlias,
expectedStatuses,
true,
finishState,
false);
}
protected void basicTestConnection(Security security,
LwM2MDeviceCredentials deviceCredentials,
Configuration coapConfig,
String endpoint,
Lwm2mDeviceProfileTransportConfiguration transportConfiguration,
String awaitAlias,
Set<LwM2MClientState> expectedStatuses,
boolean isBootstrap,
LwM2MClientState finishState,
boolean isStartLw) throws Exception {
createNewClient(security, coapConfig, true, endpoint, isBootstrap, null);
createDeviceProfile(transportConfiguration);
final Device device = createDevice(deviceCredentials, endpoint);
device.getId().getId().toString();
lwM2MTestClient.start(isStartLw);
await(awaitAlias)
.atMost(1000, TimeUnit.MILLISECONDS)
.until(() -> finishState.equals(lwM2MTestClient.getClientState()));
Assert.assertEquals(expectedStatuses, lwM2MTestClient.getClientStates());
}
public void basicTestConnectionBootstrapRequestTriggerBefore(String clientEndpoint, String awaitAlias, LwM2MProfileBootstrapConfigType type) throws Exception {
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsNoSec(type));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint));
this.basicTestConnectionBootstrapRequestTrigger(
SECURITY_NO_SEC,
deviceCredentials,
COAP_CONFIG,
clientEndpoint,
transportConfiguration,
awaitAlias,
expectedStatusesRegistrationLwm2mSuccess,
expectedStatusesRegistrationBsSuccess,
false,
SECURITY_NO_SEC_BS);
}
private void basicTestConnectionBootstrapRequestTrigger(Security security,
LwM2MDeviceCredentials deviceCredentials,
Configuration coapConfig,
String endpoint,
Lwm2mDeviceProfileTransportConfiguration transportConfiguration,
String awaitAlias,
Set<LwM2MClientState> expectedStatusesLwm2m,
Set<LwM2MClientState> expectedStatusesBs,
boolean isBootstrap,
Security securityBs) throws Exception {
createNewClient(security, coapConfig, true, endpoint, isBootstrap, securityBs);
createDeviceProfile(transportConfiguration);
final Device device = createDevice(deviceCredentials, endpoint);
String deviceId = device.getId().getId().toString();
lwM2MTestClient.start(true);
await(awaitAlias)
.atMost(1000, TimeUnit.MILLISECONDS)
.until(() -> ON_REGISTRATION_SUCCESS.equals(lwM2MTestClient.getClientState()));
Assert.assertEquals(expectedStatusesLwm2m, lwM2MTestClient.getClientStates());
String executedPath = "/" + OBJECT_ID_1 + "_" + lwM2MTestClient.getLeshanClient().getObjectTree().getModel().getObjectModel(OBJECT_ID_1).version
+ "/0/" + RESOURCE_ID_9;
String actualResult = sendRPCSecurityExecuteById(executedPath, deviceId, endpoint);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
expectedStatusesBs.add(ON_DEREGISTRATION_STARTED);
expectedStatusesBs.add(ON_DEREGISTRATION_SUCCESS);
await(awaitAlias)
.atMost(1000, TimeUnit.MILLISECONDS)
.until(() -> ON_REGISTRATION_SUCCESS.equals(lwM2MTestClient.getClientState()));
Assert.assertEquals(expectedStatusesBs, lwM2MTestClient.getClientStates());
}
protected List<LwM2MBootstrapServerCredential> getBootstrapServerCredentialsSecure(LwM2MSecurityMode mode, LwM2MProfileBootstrapConfigType bootstrapConfigType) {
List<LwM2MBootstrapServerCredential> bootstrap = new ArrayList<>();
switch (bootstrapConfigType) {
case BOTH:
bootstrap.add(getBootstrapServerCredential(mode, false));
bootstrap.add(getBootstrapServerCredential(mode, true));
break;
case BOOTSTRAP_ONLY:
bootstrap.add(getBootstrapServerCredential(mode, true));
break;
case LWM2M_ONLY:
bootstrap.add(getBootstrapServerCredential(mode, false));
break;
case NONE:
}
return bootstrap;
}
private AbstractLwM2MBootstrapServerCredential getBootstrapServerCredential(LwM2MSecurityMode mode, boolean isBootstrap) {
AbstractLwM2MBootstrapServerCredential bootstrapServerCredential;
switch (mode) {
case PSK:
bootstrapServerCredential = new PSKLwM2MBootstrapServerCredential();
bootstrapServerCredential.setServerPublicKey("");
break;
case RPK:
bootstrapServerCredential = new RPKLwM2MBootstrapServerCredential();
if (isBootstrap) {
bootstrapServerCredential.setServerPublicKey(Base64.encodeBase64String(serverPublicKeyFromCertBs.getEncoded()));
} else {
bootstrapServerCredential.setServerPublicKey(Base64.encodeBase64String(serverPublicKeyFromCert.getEncoded()));
}
break;
case X509:
bootstrapServerCredential = new X509LwM2MBootstrapServerCredential();
try {
if (isBootstrap) {
bootstrapServerCredential.setServerPublicKey(Base64.encodeBase64String(serverX509CertBs.getEncoded()));
} else {
bootstrapServerCredential.setServerPublicKey(Base64.encodeBase64String(serverX509Cert.getEncoded()));
}
} catch (CertificateEncodingException e) {
e.printStackTrace();
}
break;
default:
throw new IllegalStateException("Unexpected value: " + mode);
}
bootstrapServerCredential.setShortServerId(isBootstrap ? shortServerIdBs : shortServerId);
bootstrapServerCredential.setBootstrapServerIs(isBootstrap);
bootstrapServerCredential.setHost(isBootstrap ? hostBs : host);
bootstrapServerCredential.setPort(isBootstrap ? securityPortBs : securityPort);
return bootstrapServerCredential;
}
protected LwM2MDeviceCredentials getDeviceCredentialsSecure(LwM2MClientCredential clientCredentials,
PrivateKey privateKey,
X509Certificate certificate,
LwM2MSecurityMode mode,
boolean privateKeyIsBad) {
LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials();
credentials.setClient(clientCredentials);
LwM2MBootstrapClientCredentials bootstrapCredentials;
switch (mode) {
case PSK:
bootstrapCredentials = getBootstrapClientCredentialsPsk(clientCredentials);
break;
case RPK:
bootstrapCredentials = getBootstrapClientCredentialsRpk(certificate, privateKey, privateKeyIsBad);
break;
case X509:
bootstrapCredentials = getBootstrapClientCredentialsX509(certificate, privateKey, privateKeyIsBad);
break;
default:
throw new IllegalStateException("Unexpected value: " + mode);
}
credentials.setBootstrap(bootstrapCredentials);
return credentials;
}
private LwM2MBootstrapClientCredentials getBootstrapClientCredentialsPsk(LwM2MClientCredential clientCredentials) {
LwM2MBootstrapClientCredentials bootstrapCredentials = new LwM2MBootstrapClientCredentials();
PSKBootstrapClientCredential serverCredentials = new PSKBootstrapClientCredential();
if (clientCredentials != null) {
serverCredentials.setClientSecretKey(((PSKClientCredential) clientCredentials).getKey());
serverCredentials.setClientPublicKeyOrId(((PSKClientCredential) clientCredentials).getIdentity());
}
bootstrapCredentials.setBootstrapServer(serverCredentials);
bootstrapCredentials.setLwm2mServer(serverCredentials);
return bootstrapCredentials;
}
private LwM2MBootstrapClientCredentials getBootstrapClientCredentialsRpk(X509Certificate certificate, PrivateKey privateKey, boolean privateKeyIsBad) {
LwM2MBootstrapClientCredentials bootstrapCredentials = new LwM2MBootstrapClientCredentials();
RPKBootstrapClientCredential serverCredentials = new RPKBootstrapClientCredential();
if (certificate != null && certificate.getPublicKey() != null && privateKey != null) {
serverCredentials.setClientPublicKeyOrId(Base64.encodeBase64String(certificate.getPublicKey().getEncoded()));
if (privateKeyIsBad) {
serverCredentials.setClientSecretKey(Hex.encodeHexString(privateKey.getEncoded()));
} else {
serverCredentials.setClientSecretKey(Base64.encodeBase64String(privateKey.getEncoded()));
}
}
bootstrapCredentials.setBootstrapServer(serverCredentials);
bootstrapCredentials.setLwm2mServer(serverCredentials);
return bootstrapCredentials;
}
private LwM2MBootstrapClientCredentials getBootstrapClientCredentialsX509(X509Certificate certificate, PrivateKey privateKey, boolean privateKeyIsBad) {
LwM2MBootstrapClientCredentials bootstrapCredentials = new LwM2MBootstrapClientCredentials();
X509BootstrapClientCredential serverCredentials = new X509BootstrapClientCredential();
if (certificate != null) {
try {
serverCredentials.setClientPublicKeyOrId(Base64.encodeBase64String(certificate.getEncoded()));
if (privateKeyIsBad) {
serverCredentials.setClientSecretKey(Hex.encodeHexString(privateKey.getEncoded()));
} else {
serverCredentials.setClientSecretKey(Base64.encodeBase64String(privateKey.getEncoded()));
}
} catch (CertificateEncodingException e) {
log.error("Client`s certificate [{}] is bad. [{}]", certificate, e.getMessage());
}
}
bootstrapCredentials.setBootstrapServer(serverCredentials);
bootstrapCredentials.setLwm2mServer(serverCredentials);
return bootstrapCredentials;
}
protected MvcResult createDeviceWithMvcResult(LwM2MDeviceCredentials credentials, String endpoint) throws Exception {
Device device = new Device();
device.setName(endpoint);
device.setDeviceProfileId(deviceProfile.getId());
device.setTenantId(tenantId);
device = doPost("/api/device", device, Device.class);
Assert.assertNotNull(device);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class);
Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId());
deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS);
deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials));
return doPost("/api/device/credentials", deviceCredentials).andReturn();
}
private String sendRPCSecurityExecuteById(String path, String deviceId, String endpoint) throws Exception {
log.info("endpoint1: [{}]", endpoint);
String setRpcRequest = "{\"method\": \"Execute\", \"params\": {\"id\": \"" + path + "\"}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
}

76
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java

@ -15,21 +15,81 @@
*/
package org.thingsboard.server.transport.lwm2m.security.sql;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.COAP_CONFIG;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOOTSTRAP_ONLY;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.LWM2M_ONLY;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE;
@Slf4j
public class NoSecLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest {
//Lwm2m only
@Test
public void testConnectAndObserveTelemetry() throws Exception {
NoSecClientCredential clientCredentials = createNoSecClientCredentials(CLIENT_ENDPOINT_NO_SEC);
super.basicTestConnectionObserveTelemetry(SECURITY, clientCredentials, COAP_CONFIG, CLIENT_ENDPOINT_NO_SEC);
public void testWithNoSecConnectLwm2mSuccessAndObserveTelemetry() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_NO_SEC;
LwM2MDeviceCredentials clientCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint));
super.basicTestConnectionObserveTelemetry(SECURITY_NO_SEC, clientCredentials, COAP_CONFIG, clientEndpoint);
}
// Bootstrap + Lwm2m
@Test
public void testWithNoSecConnectBsSuccess_UpdateTwoSectionsBootstrapAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS;
String awaitAlias = "await on client state (NoSecBS two section)";
basicTestConnectionBefore(clientEndpoint, awaitAlias, BOTH, expectedStatusesRegistrationBsSuccess, ON_REGISTRATION_SUCCESS);
}
@Test
public void testWithNoSecConnectBsSuccess_UpdateLwm2mSectionAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + LWM2M_ONLY.name();
String awaitAlias = "await on client state (NoSecBS Lwm2m section)";
basicTestConnectionBefore(clientEndpoint, awaitAlias, LWM2M_ONLY, expectedStatusesRegistrationBsSuccess, ON_REGISTRATION_SUCCESS);
}
@Test
public void testWithNoSecConnectBsSuccess_UpdateBootstrapSectionAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_NO_SEC + BOOTSTRAP_ONLY.name();
String awaitAlias = "await on client state (NoSecBS Bootstrap section)";
basicTestConnectionBefore(clientEndpoint, awaitAlias, BOOTSTRAP_ONLY, expectedStatusesBsSuccess, ON_BOOTSTRAP_SUCCESS);
}
@Test
public void testWithNoSecConnectBsSuccess_UpdateNoneSectionAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_NO_SEC + NONE.name();
String awaitAlias = "await on client state (NoSecBS None section)";
basicTestConnectionBefore(clientEndpoint, awaitAlias, NONE, expectedStatusesBsSuccess, ON_BOOTSTRAP_SUCCESS);
}
@Test
public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateTwoSectionAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + BOTH.name();
String awaitAlias = "await on client state (NoSecBS Trigger Two section)";
basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, BOTH);
}
@Test
public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateBootstrapSectionAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + BOOTSTRAP_ONLY.name();
String awaitAlias = "await on client state (NoSecBS Trigger Bootstrap section)";
basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, BOOTSTRAP_ONLY);
}
@Test
public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateLwm2mSectionAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + LWM2M_ONLY.name();
String awaitAlias = "await on client state (NoSecBS Trigger Lwm2m section)";
basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, LWM2M_ONLY);
}
@Test
public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateNoneSectionAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + NONE.name();
String awaitAlias = "await on client state (NoSecBS Trigger None section)";
basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, NONE);
}
}

91
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java

@ -18,28 +18,97 @@ package org.thingsboard.server.transport.lwm2m.security.sql;
import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.core.util.Hex;
import org.junit.Test;
import org.springframework.test.web.servlet.MvcResult;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets;
import static org.eclipse.leshan.client.object.Security.psk;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP_CONFIG;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_URI;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID;
import static org.eclipse.leshan.client.object.Security.pskBootstrap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.PSK;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE;
public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTest {
//Lwm2m only
@Test
public void testConnectWithPSKAndObserveTelemetry() throws Exception {
public void testWithPskConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_PSK;
String identity = CLIENT_PSK_IDENTITY;
String keyPsk = CLIENT_PSK_KEY;
PSKClientCredential clientCredentials = new PSKClientCredential();
clientCredentials.setEndpoint(CLIENT_ENDPOINT_PSK);
clientCredentials.setKey(CLIENT_PSK_KEY);
clientCredentials.setIdentity(CLIENT_PSK_IDENTITY);
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setIdentity(identity);
clientCredentials.setKey(keyPsk);
Security security = psk(SECURE_URI,
SHORT_SERVER_ID,
CLIENT_PSK_IDENTITY.getBytes(StandardCharsets.UTF_8),
Hex.decodeHex(CLIENT_PSK_KEY.toCharArray()));
super.basicTestConnectionObserveTelemetry(security, clientCredentials, SECURE_COAP_CONFIG, CLIENT_ENDPOINT_PSK);
shortServerId,
identity.getBytes(StandardCharsets.UTF_8),
Hex.decodeHex(keyPsk.toCharArray()));
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false);
this.basicTestConnection(security,
deviceCredentials,
COAP_CONFIG,
clientEndpoint,
transportConfiguration,
"await on client state (Psk_Lwm2m)",
expectedStatusesRegistrationLwm2mSuccess,
false,
ON_REGISTRATION_SUCCESS,
true);
}
@Test
public void testWithPskConnectLwm2mBadPskKeyByLength_BAD_REQUEST() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_PSK;
String identity = CLIENT_PSK_IDENTITY + "_BadLength";
String keyPsk = CLIENT_PSK_KEY + "05AC";
PSKClientCredential clientCredentials = new PSKClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setIdentity(identity);
clientCredentials.setKey(keyPsk);
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE));
createDeviceProfile(transportConfiguration);
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false);
MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint);
assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus());
String msgExpected = "Key must be HexDec format: 32, 64, 128 characters!";
assertTrue(result.getResponse().getContentAsString().contains(msgExpected));
}
// Bootstrap + Lwm2m
@Test
public void testWithPskConnectBsSuccess_UpdateTwoSectionsBootstrapAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_PSK_BS;
String identity = CLIENT_PSK_IDENTITY_BS;
String keyPsk = CLIENT_PSK_KEY;
PSKClientCredential clientCredentials = new PSKClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setIdentity(identity);
clientCredentials.setKey(keyPsk);
Security securityBs = pskBootstrap(SECURE_URI_BS,
identity.getBytes(StandardCharsets.UTF_8),
Hex.decodeHex(keyPsk.toCharArray()));
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, BOTH));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false);
this.basicTestConnection(securityBs,
deviceCredentials,
COAP_CONFIG_BS,
clientEndpoint,
transportConfiguration,
"await on client state (PskBS two section)",
expectedStatusesRegistrationBsSuccess,
true,
ON_REGISTRATION_SUCCESS,
true);
}
}

115
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java

@ -15,30 +15,117 @@
*/
package org.thingsboard.server.transport.lwm2m.security.sql;
import org.apache.commons.codec.binary.Base64;
import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.core.util.Hex;
import org.junit.Test;
import org.springframework.test.web.servlet.MvcResult;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredential;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest;
import org.apache.commons.codec.binary.Base64;;
import javax.servlet.http.HttpServletResponse;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import static org.eclipse.leshan.client.object.Security.rpk;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP_CONFIG;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_URI;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID;
import static org.eclipse.leshan.client.object.Security.rpkBootstrap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.RPK;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE;
public class RpkLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest {
//Lwm2m only
@Test
public void testConnectWithRPKAndObserveTelemetry() throws Exception {
RPKClientCredential rpkClientCredentials = new RPKClientCredential();
rpkClientCredentials.setEndpoint(CLIENT_ENDPOINT_RPK);
rpkClientCredentials.setKey(new String(Base64.encodeBase64(clientPublicKeyFromCertTrust.getEncoded())));
Security security = rpk(SECURE_URI,
SHORT_SERVER_ID,
clientPublicKeyFromCertTrust.getEncoded(),
clientPrivateKeyFromCertTrust.getEncoded(),
serverPublicKeyFromCert.getEncoded());
super.basicTestConnectionObserveTelemetry(security, rpkClientCredentials, SECURE_COAP_CONFIG, CLIENT_ENDPOINT_RPK);
public void testWithRpkConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_RPK;
X509Certificate certificate = clientX509CertTrust;
PrivateKey privateKey = clientPrivateKeyFromCertTrust;
RPKClientCredential clientCredentials = new RPKClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setKey(Base64.encodeBase64String(certificate.getPublicKey().getEncoded()));
Security securityBs = rpk(SECURE_URI,
shortServerId,
certificate.getPublicKey().getEncoded(),
privateKey.getEncoded(),
serverX509Cert.getPublicKey().getEncoded());
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(RPK, NONE));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, RPK, false);
this.basicTestConnection(securityBs,
deviceCredentials,
COAP_CONFIG,
clientEndpoint,
transportConfiguration,
"await on client state (Rpk_Lwm2m)",
expectedStatusesRegistrationLwm2mSuccess,
false,
ON_REGISTRATION_SUCCESS,
true);
}
@Test
public void testWithRpkValidationPublicKeyBase64format_BAD_REQUEST() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_RPK + "BadPublicKey";
X509Certificate certificate = clientX509CertTrust;
PrivateKey privateKey = clientPrivateKeyFromCertTrust;
RPKClientCredential clientCredentials = new RPKClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setKey(Hex.encodeHexString(certificate.getPublicKey().getEncoded()));
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(RPK, NONE));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, RPK, false);
createDeviceProfile(transportConfiguration);
MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint);
assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus());
String msgExpected = "LwM2M client RPK key must be in standard [RFC7250] and support only EC algorithm and then encoded to Base64 format!";
assertTrue(result.getResponse().getContentAsString().contains(msgExpected));
}
@Test
public void testWithRpkValidationPrivateKeyBase64format_BAD_REQUEST() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_RPK + "BadPrivateKey";
X509Certificate certificate = clientX509CertTrust;
PrivateKey privateKey = clientPrivateKeyFromCertTrust;
RPKClientCredential clientCredentials = new RPKClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setKey(Base64.encodeBase64String(certificate.getPublicKey().getEncoded()));
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(RPK, NONE));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, RPK, true);
createDeviceProfile(transportConfiguration);
MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint);
assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus());
String msgExpected = "Bootstrap server client RPK secret key must be in PKCS#8 format (DER encoding, standard [RFC5958]) and then encoded to Base64 format!";
assertTrue(result.getResponse().getContentAsString().contains(msgExpected));
}
// Bootstrap + Lwm2m
@Test
public void testWithRpkConnectBsSuccess_UpdateTwoSectionsBootstrapAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_RPK_BS;
X509Certificate certificate = clientX509CertTrust;
PrivateKey privateKey = clientPrivateKeyFromCertTrust;
RPKClientCredential clientCredentials = new RPKClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setKey(Base64.encodeBase64String(certificate.getPublicKey().getEncoded()));
Security securityBs = rpkBootstrap(SECURE_URI_BS,
certificate.getPublicKey().getEncoded(),
privateKey.getEncoded(),
serverX509CertBs.getPublicKey().getEncoded());
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(RPK, BOTH));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, clientPrivateKeyFromCertTrust, certificate, RPK, false);
this.basicTestConnection(securityBs,
deviceCredentials,
COAP_CONFIG_BS,
clientEndpoint,
transportConfiguration,
"await on client state (RpkBS two section)",
expectedStatusesRegistrationBsSuccess,
true,
ON_REGISTRATION_SUCCESS,
true);
}
}

112
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java

@ -16,28 +16,116 @@
package org.thingsboard.server.transport.lwm2m.security.sql;
import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.core.util.Hex;
import org.junit.Test;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.util.Base64Utils;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential;
import org.thingsboard.server.common.transport.util.SslUtil;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest;
import javax.servlet.http.HttpServletResponse;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import static org.eclipse.leshan.client.object.Security.x509;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP_CONFIG;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_URI;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID;
import static org.eclipse.leshan.client.object.Security.x509Bootstrap;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.X509;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE;
public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest {
//Lwm2m only
@Test
public void testConnectWithCertAndObserveTelemetry() throws Exception {
X509ClientCredential credentials = new X509ClientCredential();
credentials.setEndpoint(CLIENT_ENDPOINT_X509_TRUST_NO);
credentials.setCert(SslUtil.getCertificateString(clientX509CertTrustNo));
public void testWithX509NoTrustConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_X509_TRUST_NO;
X509Certificate certificate = clientX509CertTrustNo;
PrivateKey privateKey = clientPrivateKeyFromCertTrustNo;
X509ClientCredential clientCredentials = new X509ClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setCert(Base64Utils.encodeToString(certificate.getEncoded()));
Security security = x509(SECURE_URI,
SHORT_SERVER_ID,
clientX509CertTrustNo.getEncoded(),
clientPrivateKeyFromCertTrustNo.getEncoded(),
shortServerId,
certificate.getEncoded(),
privateKey.getEncoded(),
serverX509Cert.getEncoded());
super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, CLIENT_ENDPOINT_X509_TRUST_NO);
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, NONE));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false);
this.basicTestConnection(security,
deviceCredentials,
COAP_CONFIG,
clientEndpoint,
transportConfiguration,
"await on client state (X509_Trust_Lwm2m)",
expectedStatusesRegistrationLwm2mSuccess,
false,
ON_REGISTRATION_SUCCESS,
true);
}
@Test
public void testWithX509NoTrustValidationPublicKeyBase64format_BAD_REQUEST() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_X509_TRUST_NO + "BadPublicKey";
X509Certificate certificate = clientX509CertTrustNo;
PrivateKey privateKey = clientPrivateKeyFromCertTrustNo;
X509ClientCredential clientCredentials = new X509ClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setCert(Hex.encodeHexString(certificate.getEncoded()));
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, NONE));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false);
createDeviceProfile(transportConfiguration);
MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint);
assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus());
String msgExpected = "LwM2M client X509 certificate must be in DER-encoded X509v3 format and support only EC algorithm and then encoded to Base64 format!";
assertTrue(result.getResponse().getContentAsString().contains(msgExpected));
}
@Test
public void testWithX509NoTrustValidationPrivateKeyBase64format_BAD_REQUEST() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_X509_TRUST_NO + "BadPrivateKey";
X509Certificate certificate = clientX509CertTrustNo;
PrivateKey privateKey = clientPrivateKeyFromCertTrustNo;
X509ClientCredential clientCredentials = new X509ClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setCert(Base64Utils.encodeToString(certificate.getEncoded()));
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, NONE));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, true);
createDeviceProfile(transportConfiguration);
MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint);
assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus());
String msgExpected = "Bootstrap server client X509 secret key must be in PKCS#8 format (DER encoding, standard [RFC5958]) and then encoded to Base64 format!";
assertTrue(result.getResponse().getContentAsString().contains(msgExpected));
}
// Bootstrap + Lwm2m
@Test
public void testWithX509NoTrustConnectBsSuccess_UpdateTwoSectionsBootstrapAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_X509_TRUST_NO;
X509Certificate certificate = clientX509CertTrustNo;
PrivateKey privateKey = clientPrivateKeyFromCertTrustNo;
X509ClientCredential clientCredentials = new X509ClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setCert(Base64Utils.encodeToString(certificate.getEncoded()));
Security security = x509Bootstrap(SECURE_URI_BS,
certificate.getEncoded(),
privateKey.getEncoded(),
serverX509CertBs.getEncoded());
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, BOTH));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false);
this.basicTestConnection(security,
deviceCredentials,
COAP_CONFIG_BS,
clientEndpoint,
transportConfiguration,
"await on client state (X509NoTrust two section)",
expectedStatusesRegistrationBsSuccess,
true,
ON_REGISTRATION_SUCCESS,
true);
}
}

69
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java

@ -17,26 +17,75 @@ package org.thingsboard.server.transport.lwm2m.security.sql;
import org.eclipse.leshan.client.object.Security;
import org.junit.Test;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import static org.eclipse.leshan.client.object.Security.x509;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP_CONFIG;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_URI;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID;
import static org.eclipse.leshan.client.object.Security.x509Bootstrap;
import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.X509;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE;
public class X509_TrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest {
//Lwm2m only
@Test
public void testConnectAndObserveTelemetry() throws Exception {
X509ClientCredential credentials = new X509ClientCredential();
credentials.setEndpoint(CLIENT_ENDPOINT_X509_TRUST);
public void testWithX509TrustConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_X509_TRUST;
X509Certificate certificate = clientX509CertTrust;
PrivateKey privateKey = clientPrivateKeyFromCertTrust;
X509ClientCredential clientCredentials = new X509ClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setCert("");
Security security = x509(SECURE_URI,
SHORT_SERVER_ID,
clientX509CertTrust.getEncoded(),
clientPrivateKeyFromCertTrust.getEncoded(),
shortServerId,
certificate.getEncoded(),
privateKey.getEncoded(),
serverX509Cert.getEncoded());
super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, CLIENT_ENDPOINT_X509_TRUST);
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, NONE));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false);
this.basicTestConnection(security,
deviceCredentials,
COAP_CONFIG,
clientEndpoint,
transportConfiguration,
"await on client state (X509_Trust_Lwm2m)",
expectedStatusesRegistrationLwm2mSuccess,
false,
ON_REGISTRATION_SUCCESS,
true);
}
// Bootstrap + Lwm2m
@Test
public void testWithX509TrustConnectBsSuccess_UpdateTwoSectionsBootstrapAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_X509_TRUST;
X509Certificate certificate = clientX509CertTrust;
PrivateKey privateKey = clientPrivateKeyFromCertTrust;
X509ClientCredential clientCredentials = new X509ClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setCert("");
Security security = x509Bootstrap(SECURE_URI_BS,
certificate.getEncoded(),
privateKey.getEncoded(),
serverX509CertBs.getEncoded());
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, BOTH));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false);
this.basicTestConnection(security,
deviceCredentials,
COAP_CONFIG_BS,
clientEndpoint,
transportConfiguration,
"await on client state (X509Trust two section)",
expectedStatusesRegistrationBsSuccess,
true,
ON_REGISTRATION_SUCCESS,
true);
}
}

8
application/src/test/resources/application-test.properties

@ -1,20 +1,12 @@
transport.lwm2m.server.security.credentials.enabled=true
transport.lwm2m.server.security.credentials.type=KEYSTORE
transport.lwm2m.server.security.credentials.keystore.store_file=lwm2m/credentials/lwm2mserver.jks
#transport.lwm2m.server.security.credentials.keystore.store_password=server
#transport.lwm2m.server.security.credentials.keystore.key_alias=server
#transport.lwm2m.server.security.credentials.keystore.key_password=server
transport.lwm2m.bootstrap.enabled=false
transport.lwm2m.bootstrap.security.credentials.enabled=true
transport.lwm2m.bootstrap.security.credentials.type=KEYSTORE
transport.lwm2m.bootstrap.security.credentials.keystore.store_file=lwm2m/credentials/lwm2mserver.jks
#transport.lwm2m.bootstrap.security.credentials.keystore.store_password=server
#transport.lwm2m.bootstrap.security.credentials.keystore.key_alias=server
#transport.lwm2m.bootstrap.security.credentials.keystore.key_password=server
transport.lwm2m.security.trust-credentials.enabled=true
transport.lwm2m.security.trust-credentials.type=KEYSTORE
transport.lwm2m.security.trust-credentials.keystore.store_file=lwm2m/credentials/lwm2mtruststorechain.jks
#transport.lwm2m.security.trust-credentials.keystore.store_password=server
edges.enabled=true
edges.storage.no_read_records_sleep=500

0
application/src/test/resources/logback.xml → application/src/test/resources/logback-test.xml

299
application/src/test/resources/lwm2m/credentials/shell/lwM2M_cfssl_chain_trusts_and_clients_for_test.sh

@ -1,299 +0,0 @@
#!/usr/bin/env bash
# Change working directory
cd -- "$(
dirname "${0}"
)" || exit 1
readonly TRUST_PATH="Trust"
readonly CA_ROOT_CERT_KEY="ca-root"
readonly CA_ROOT_ALIAS="root"
readonly CA_INTERMEDIATE_CERT_KEY_PREF="intermediate_ca"
CA_INTERMEDIATE_START=0
CA_INTERMEDIATE_FINISH=2
CA_INTERMEDIATE_NUMBER=${CA_INTERMEDIATE_START}
CA_INTERMEDIATE_CERT_SIGN=${CA_ROOT_CERT_KEY}
CA_LIST_CERT_FOR_CAT=""
readonly CA_TRUST_STORE_ALL_CHAIN="lwm2mtruststorechain"
readonly CA_TRUST_STORE_PWD="server_ks_password"
readonly CA_TRUST_CERT_ALIAS="root"
readonly CA_TRUST_CERT_CHAIN_JKS="lwm2mtruststorechain"
readonly CA_TRUST_STORE_CHAIN_ALIAS="trust_cert_chain_alias"
readonly CLIENT_PATH="Client"
readonly CLIENT_JKS_FOR_TEST="lwm2mclient"
readonly CLIENT_CERT_KEY_PREF="LwX509"
readonly CLIENT_CERT_ALIAS_PREF="client_alias_"
readonly CLIENT_STORE_PWD="client_ks_password"
readonly CLIENT_HOST_NAME="thingsboard_test.io"
CLIENT_START=0
CLIENT_FINISH=1
CLIENT_NUMBER=${CLIENT_START}
SERVER_HOST_NAME="localhost.localdomain"
SERVER_LOCAL_HOST_NAME="localhost"
SERVER_PUBLIC_HOST_NAMES="-"
readonly CF_COMMANDS="
cfssl
cfssljson
"
if [ ! -z "$1" ]; then
CA_INTERMEDIATE_START=$1
CA_INTERMEDIATE_NUMBER=${CA_INTERMEDIATE_START}
fi
if [ ! -z "$2" ]; then
CA_INTERMEDIATE_FINISH=$2
fi
if [ ! -z "$3" ]; then
CLIENT_START=$1
CLIENT_NUMBER=${CLIENT_START}
fi
if [ ! -z "$4" ]; then
CLIENT_FINISH=$4
fi
# Change working directory
rm -rf ${TRUST_PATH}
mkdir -p ${TRUST_PATH}
rm -rf ${CLIENT_PATH}
mkdir -p ${CLIENT_PATH}
cd -- "$(
dirname "${0}"
)" || exit 1
rm *.csr
rm *.p12
rm *.json
rm *.pem
rm *.jks
intermediate_common_name() {
echo "${CA_INTERMEDIATE_CERT_KEY_PREF}${CA_INTERMEDIATE_NUMBER}"
}
set_list_sert_for_cat() {
local first="$1"
echo "$first ${CA_LIST_CERT_FOR_CAT}"
}
client_common_name() {
echo "${CLIENT_CERT_KEY_PREF}$(printf "%08d" ${CLIENT_NUMBER})"
}
client_alias_name() {
echo "${CLIENT_CERT_ALIAS_PREF}$(printf "%08d" ${CLIENT_NUMBER})"
}
for COMMAND in ${CF_COMMANDS}; do
if ! command -v ${COMMAND} &> /dev/null; then
echo "ERROR: Missing command ${COMMAND}" >&2
echo "Install the package from: https://pkg.cfssl.org/" >&2
exit 1
fi
done
tee ./${TRUST_PATH}/ca-config.json 1> /dev/null <<-CONFIG
{
"signing": {
"default": {
"expiry": "8760h",
"names": [
{
"C": "UK",
"ST": "Kyiv city",
"L": "Kyiv",
"O": "Thingsboard",
"OU": "DEVELOPER_TEST"
}
]
},
"profiles": {
"server": {
"expiry": "43800h",
"key": {
"algo": "ecdsa",
"size": 256
},
"usages": [
"signing",
"key encipherment",
"server auth"
]
},
"client": {
"expiry": "43800h",
"key": {
"algo": "ecdsa",
"size": 256
},
"usages": [
"signing",
"key encipherment",
"client auth"
]
},
"client-server": {
"expiry": "43800h",
"key": {
"algo": "ecdsa",
"size": 256
},
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
]
}
}
}
}
CONFIG
tee ./${TRUST_PATH}/ca-root-to-intermediate-config.json 1> /dev/null <<-CONFIG
{
"signing": {
"default": {
"expiry": "43800h",
"ca_constraint": {
"is_ca": true,
"max_path_len": 0,
"max_path_len_zero": true
},
"key": {
"algo": "ecdsa",
"size": 256
},
"usages": [
"digital signature",
"cert sign",
"crl sign",
"signing"
]
}
}
}
CONFIG
echo "===================================================="
echo -e "Generate the root of certificates: \n-${CA_ROOT_KEY}-key.pem (certificate key)\n-${CA_ROOT_KEY}.pem (certificate)\n-${CA_ROOT_KEY}.csr (sign request)"
echo "===================================================="
cfssl genkey \
-initca \
- \
<<-CONFIG | cfssljson -bare ./${TRUST_PATH}/${CA_ROOT_CERT_KEY}
{
"CN": "ROOT CA",
"key": {
"algo": "ecdsa",
"size": 256
},
"names": [
{
"C": "UK",
"ST": "Kyiv city",
"L": "Kyiv",
"O": "Thingsboard",
"OU": "DEVELOPER_TEST"
}
],
"ca": {
"expiry": "131400h"
}
}
CONFIG
CA_LIST_CERT_FOR_CAT=$(set_list_sert_for_cat ./${TRUST_PATH}/${CA_ROOT_CERT_KEY}.pem)
echo "===================================================="
echo -e "Generate and Signed the intermediates of our certificates: \n-${CA_INTERMEDIATE_CERT_KEY_PREF}?-key.pem (certificate key)\n-${CA_INTERMEDIATE_CERT_KEY_PREF}?.pem (certificate)\n-${CA_INTERMEDIATE_CERT_KEY_PREF}?.csr (sign request)"
echo "===================================================="
while [[ ${CA_INTERMEDIATE_NUMBER} -lt ${CA_INTERMEDIATE_FINISH} ]];
do
CA_INTERMEDIATE_CERT_KEY=$(intermediate_common_name)
CA_INTERMEDIATE_NUMBER=$((${CA_INTERMEDIATE_NUMBER} + 1))
cfssl gencert \
-ca ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_SIGN}.pem \
-ca-key ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_SIGN}-key.pem \
-config ./${TRUST_PATH}/ca-root-to-intermediate-config.json \
-hostname "${SERVER_HOST_NAME},${SERVER_LOCAL_HOST_NAME}${SERVER_PUBLIC_HOST_NAMES:+, }${SERVER_PUBLIC_HOST_NAMES}" \
- \
<<-CONFIG | cfssljson -bare ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY}
{
"CN": "${CA_INTERMEDIATE_CERT_KEY}",
"names": [
{
"C": "UK",
"ST": "Kyiv city",
"L": "Kyiv",
"O": "Thingsboard",
"OU": "DEVELOPER_TEST"
}
]
}
CONFIG
#openssl x509 -in ${CA_INTERMEDIATE_CERT_KEY}.pem -text -noout
CA_LIST_CERT_FOR_CAT=$(set_list_sert_for_cat ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem)
CA_INTERMEDIATE_CERT_SIGN=${CA_INTERMEDIATE_CERT_KEY}
done
echo "===================================================="
echo -e "Add the CA_certificate to keystore: ${CA_TRUST_CERT_CHAIN_JKS}.jks"
echo "===================================================="
cat ${CA_LIST_CERT_FOR_CAT} > ./${TRUST_PATH}/${CA_TRUST_STORE_ALL_CHAIN}.pem
openssl pkcs12 -export -in ./${TRUST_PATH}/${CA_TRUST_STORE_ALL_CHAIN}.pem -inkey ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY}-key.pem -out ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY}.p12 -name ${CA_TRUST_STORE_CHAIN_ALIAS} -CAfile ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem -caname ${CA_ROOT_ALIAS} -passin pass:${CA_TRUST_STORE_PWD} -passout pass:${CA_TRUST_STORE_PWD}
keytool -importkeystore -deststorepass ${CA_TRUST_STORE_PWD} -destkeypass ${CA_TRUST_STORE_PWD} -destkeystore ./${TRUST_PATH}/${CA_TRUST_CERT_CHAIN_JKS}.jks -srckeystore ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY}.p12 -srcstoretype PKCS12 -srcstorepass ${CA_TRUST_STORE_PWD} -alias ${CA_TRUST_STORE_CHAIN_ALIAS}
keytool -list -v -keystore ./${TRUST_PATH}/lwm2mtruststorechain.jks -storepass server_ks_password -storetype PKCS12
echo "===================================================="
echo -e "Generate and Signed the clients of our certificates: \n-${CLIENT_CERT_KEY_PREF}?-key.pem (certificate key)\n-${CLIENT_CERT_KEY_PREF}?.pem (certificate)\n-${CCLIENT_CERT_KEY_PREF}?.csr (sign request)"
echo "===================================================="
while [[ ${CLIENT_NUMBER} -lt ${CLIENT_FINISH} ]];
do
CLIENT_CERT_KEY=$(client_common_name)
CLIENT_CERT_ALIAS=$(client_alias_name)
CLIENT_NUMBER=$((${CLIENT_NUMBER} + 1))
cfssl gencert \
-ca ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem \
-ca-key ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY}-key.pem \
-config ./${TRUST_PATH}/ca-config.json \
-profile client \
-hostname "${CLIENT_HOST_NAME}" \
- \
<<-CONFIG | cfssljson -bare ./${CLIENT_PATH}/${CLIENT_CERT_KEY}
{
"CN": "${CLIENT_CERT_KEY}"
}
CONFIG
echo "===================================================="
echo -e "Add the client certificate (${CLIENT_CERT_KEY}.pem) to keystore: ${CLIENT_JKS_FOR_TEST}.jks"
echo "===================================================="
cat ./${CLIENT_PATH}/${CLIENT_CERT_KEY}.pem ${CA_LIST_CERT_FOR_CAT} > ./${CLIENT_PATH}/${CLIENT_CERT_KEY}_chain.pem
openssl pkcs12 -export -in ./${CLIENT_PATH}/${CLIENT_CERT_KEY}_chain.pem -inkey ./${CLIENT_PATH}/${CLIENT_CERT_KEY}-key.pem -out ./${CLIENT_PATH}/${CLIENT_CERT_KEY}.p12 -name ${CLIENT_CERT_ALIAS} -CAfile ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem -caname ${CA_ROOT_ALIAS} -passin pass:${CLIENT_STORE_PWD} -passout pass:${CLIENT_STORE_PWD}
keytool -importkeystore -deststorepass ${CLIENT_STORE_PWD} -destkeypass ${CLIENT_STORE_PWD} -destkeystore ./${CLIENT_PATH}/${CLIENT_JKS_FOR_TEST}.jks -srckeystore ./${CLIENT_PATH}/${CLIENT_CERT_KEY}.p12 -srcstoretype PKCS12 -srcstorepass ${CLIENT_STORE_PWD} -alias ${CLIENT_CERT_ALIAS}
done
keytool -list -v -keystore ./${CLIENT_PATH}/lwm2mclient.jks -storepass client_ks_password -storetype PKCS12
rm ./${TRUST_PATH}/*.p12
rm ./${TRUST_PATH}/*.csr
rm ./${TRUST_PATH}/*.json
rm ./${TRUST_PATH}/${CA_ROOT_CERT_KEY}*
rm ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY_PREF}*
rm ./${CLIENT_PATH}/*.p12 2> /dev/null
rm ./${CLIENT_PATH}/*.csr 2> /dev/null

65
application/src/test/resources/lwm2m/credentials/shell/lwm2m_cfssl_chain_for_test_All.sh

@ -1,65 +0,0 @@
#!/usr/bin/env bash
readonly INTERMEDIATE_START=0
readonly INTERMEDIATE_FINISH=2
readonly CLIENT_START=0
readonly CLIENT_FINISH=5
IS_IHFO=false
IS_SERVER_CREATED_KEY=true
IS_TRUST_CLIENT_CREATED_KEY=true
cd -- "$(
dirname "${0}"
)" || exit 1
Help()
{
# Display Help
echo "Description of the script functions."
echo
echo "Syntax: scriptTemplate [-g|h|v|V]"
echo "options:"
echo "h Print this Help."
echo "v Verbose mode."
echo "V Print software version and exit."
echo
}
if [ "$1" == "-h" ] ; then
echo -e "Usage 2: ./`basename $0` \"Information is not displayed\" : \"Keys for the server are generated\" : \"Keys for the clients and trusts are generated\""
echo -e "Usage 1: ./`basename $0` true \"Information is displayed\" : \"Keys for the server are generated\" : \"Keys for the clients and trusts are generated\""
echo -e "Usage 3: ./`basename $0` true false \"Information is displayed\" : \"Keys for the server are not generated\" : \"Keys for the clients and trusts are generated\""
echo -e "Usage 4: ./`basename $0` true false false \"Information is displayed\" : \"Keys for the server are not generated\" : \"Keys for the clients and trusts are not generated\""
echo -e "Usage 4: ./`basename $0` true true false \"Information is displayed\" : \"Keys for the server are generated\" : \"Keys for the clients and trusts are not generated\""
echo "This Help File: ./`basename $0` -h"
exit 0
fi
if [ -n "$1" ]; then
IS_IHFO=$1
fi
if [ -n "$2" ]; then
IS_SERVER_CREATED_KEY=$2
fi
if [ -n "$3" ]; then
IS_TRUST_CLIENT_CREATED_KEY=$3
fi
if [ "$IS_IHFO" = false ] ; then
if [ "$IS_SERVER_CREATED_KEY" = true ] ; then
./lwm2m_cfssl_chain_server_for_test.sh > /dev/null 2>&1 &
fi
if [ "$IS_TRUST_CLIENT_CREATED_KEY" = true ] ; then
./lwM2M_cfssl_chain_trusts_and_clients_for_test.sh ${INTERMEDIATE_START} ${INTERMEDIATE_FINISH} ${CLIENT_START} ${CLIENT_FINISH} > /dev/null 2>&1 &
fi
else
if [ "$IS_SERVER_CREATED_KEY" = true ] ; then
./lwm2m_cfssl_chain_server_for_test.sh
fi
if [ "$IS_TRUST_CLIENT_CREATED_KEY" = true ] ; then
./lwM2M_cfssl_chain_trusts_and_clients_for_test.sh ${INTERMEDIATE_START} ${INTERMEDIATE_FINISH} ${CLIENT_START} ${CLIENT_FINISH}
fi
fi

298
application/src/test/resources/lwm2m/credentials/shell/lwm2m_cfssl_chain_server_for_test.sh

@ -1,298 +0,0 @@
#!/usr/bin/env bash
# REF: https://github.com/cloudflare/cfssl
# Change working directory
cd -- "$(
dirname "${0}"
)" || exit 1
readonly CA_ROOT_CERT_KEY="ca-root"
readonly CA_ROOT_ALIAS="root"
readonly CA_INTERMEDIATE_CERT_KEY_PREF="intermediate_ca"
CA_INTERMEDIATE_NUMBER=0
CA_LIST_CERT_FOR_CAT=""
readonly CF_COMMANDS="
cfssl
cfssljson
"
readonly SERVER_JKS_FOR_TEST="lwm2mserver"
readonly STORE_PASS_PWD="server_ks_password"
readonly SERVER_PATH="Server"
readonly SERVER_CERT_KEY="lwm2mserver"
readonly SERVER_CERT_CHAIN="lwm2mserver_chain"
readonly SERVER_CERT_ALIAS="server"
readonly BS_SERVER_CERT_KEY="lwm2mserverbs"
readonly BS_SERVER_CERT_CHAIN="lwm2mserverbs_chain"
readonly BS_SERVER_CERT_ALIAS="bootstrap"
SERVER_HOST_NAME="localhost.localdomain"
SERVER_LOCAL_HOST_NAME="localhost"
SERVER_PUBLIC_HOST_NAMES="-"
intermediate_common_name() {
echo "${CA_INTERMEDIATE_CERT_KEY_PREF}${CA_INTERMEDIATE_NUMBER}"
}
set_list_sert_for_cat() {
local first="$1"
echo "$first ${CA_LIST_CERT_FOR_CAT}"
}
# Change working directory
rm -rf ${SERVER_PATH}
mkdir -p ${SERVER_PATH}
cd -- "$(
dirname ./${SERVER_PATH}
)" || exit 1
rm *.csr
rm *.p12
rm *.json
rm *.pem
rm *.jks
CA_INTERMEDIATE_CERT_SIGN=${CA_ROOT_CERT_KEY}
CA_INTERMEDIATE_CERT_KEY=$(intermediate_common_name)
CA_INTERMEDIATE_NUMBER=$((${CA_INTERMEDIATE_NUMBER} + 1))
CA_LIST_CERT_FOR_CAT=""
for COMMAND in ${CF_COMMANDS}; do
if ! command -v ${COMMAND} &> /dev/null; then
echo "ERROR: Missing command ${COMMAND}" >&2
echo "Install the package from: https://pkg.cfssl.org/" >&2
exit 1
fi
done
tee ./${SERVER_PATH}/ca-config.json 1> /dev/null <<-CONFIG
{
"signing": {
"default": {
"expiry": "8760h",
"names": [
{
"C": "UK",
"ST": "Kyiv city",
"L": "Kyiv",
"O": "Thingsboard",
"OU": "DEVELOPER_TEST"
}
]
},
"profiles": {
"server": {
"expiry": "43800h",
"key": {
"algo": "ecdsa",
"size": 256
},
"usages": [
"signing",
"key encipherment",
"server auth"
]
},
"client": {
"expiry": "43800h",
"key": {
"algo": "ecdsa",
"size": 256
},
"usages": [
"signing",
"key encipherment",
"client auth"
]
},
"client-server": {
"expiry": "43800h",
"key": {
"algo": "ecdsa",
"size": 256
},
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
]
}
}
}
}
CONFIG
tee ./${SERVER_PATH}/ca-root-to-intermediate-config.json 1> /dev/null <<-CONFIG
{
"signing": {
"default": {
"expiry": "43800h",
"ca_constraint": {
"is_ca": true,
"max_path_len": 0,
"max_path_len_zero": true
},
"key": {
"algo": "ecdsa",
"size": 256
},
"usages": [
"digital signature",
"cert sign",
"crl sign",
"signing"
]
}
}
}
CONFIG
echo "===================================================="
echo -e "Generate the root of certificates: \n-${CA_ROOT_KEY}-key.pem (certificate key)\n-${CA_ROOT_KEY}.pem (certificate)\n-${CA_ROOT_KEY}.csr (sign request)"
echo "===================================================="
cfssl genkey \
-initca \
- \
<<-CONFIG | cfssljson -bare ./${SERVER_PATH}/${CA_ROOT_CERT_KEY}
{
"CN": "ROOT CA for servers",
"key": {
"algo": "ecdsa",
"size": 256
},
"names": [
{
"C": "UK",
"ST": "Kyiv city",
"L": "Kyiv",
"O": "Thingsboard",
"OU": "DEVELOPER_TEST"
}
],
"ca": {
"expiry": "131400h"
}
}
CONFIG
CA_LIST_CERT_FOR_CAT=$(set_list_sert_for_cat ./${SERVER_PATH}/${CA_ROOT_CERT_KEY}.pem)
echo "===================================================="
echo -e "Generate and Signed the first intermediates of our certificates: \n-${CA_INTERMEDIATE_CERT_KEY}-key.pem (certificate key)\n-${CA_INTERMEDIATE_CERT_KEY}.pem (certificate)\n-${CA_INTERMEDIATE_CERT_KEY}.csr (sign request)"
echo "===================================================="
cfssl gencert \
-ca ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_SIGN}.pem \
-ca-key ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_SIGN}-key.pem \
-config ./${SERVER_PATH}/ca-root-to-intermediate-config.json \
-hostname "${SERVER_HOST_NAME},${SERVER_LOCAL_HOST_NAME}${SERVER_PUBLIC_HOST_NAMES:+, }${SERVER_PUBLIC_HOST_NAMES}" \
- \
<<-CONFIG | cfssljson -bare ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}
{
"CN": "${CA_INTERMEDIATE_CERT_KEY}",
"names": [
{
"C": "UK",
"ST": "Kyiv city",
"L": "Kyiv",
"O": "Thingsboard",
"OU": "DEVELOPER_TEST"
}
]
}
CONFIG
CA_LIST_CERT_FOR_CAT=$(set_list_sert_for_cat ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem)
## Lwm2m Server certificate
echo "===================================================="
echo -e "Generate and Signed the server certificate: \n-${SERVER_CERT_KEY}-key.pem (certificate key)\n-${SERVER_CERT_KEY}.pem (certificate)\n-${SERVER_CERT_KEY}.csr (sign request)"
echo "===================================================="
cfssl gencert \
-ca ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem \
-ca-key ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}-key.pem \
-config ./${SERVER_PATH}/ca-config.json \
-profile server \
-hostname "${SERVER_HOST_NAME},${SERVER_LOCAL_HOST_NAME}${SERVER_PUBLIC_HOST_NAMES:+, }${SERVER_PUBLIC_HOST_NAMES}" \
- \
<<-CONFIG | cfssljson -bare ./${SERVER_PATH}/${SERVER_CERT_KEY}
{
"CN": "${SERVER_LOCAL_HOST_NAME}"
}
CONFIG
echo "===================================================="
echo -e "Add the server certificate (${SERVER_CERT_KEY}.pem) to keystore: ${SERVER_JKS_FOR_TEST}.jks"
echo "===================================================="
cat ./${SERVER_PATH}/${SERVER_CERT_KEY}.pem ${CA_LIST_CERT_FOR_CAT} > ./${SERVER_PATH}/${SERVER_CERT_CHAIN}.pem
openssl pkcs12 -export -in ./${SERVER_PATH}/${SERVER_CERT_CHAIN}.pem -inkey ./${SERVER_PATH}/${SERVER_CERT_KEY}-key.pem -out ./${SERVER_PATH}/${SERVER_CERT_KEY}.p12 -name ${SERVER_CERT_ALIAS} -CAfile ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem -caname ${CA_ROOT_ALIAS} -passin pass:${STORE_PASS_PWD} -passout pass:${STORE_PASS_PWD}
keytool -importkeystore -deststorepass ${STORE_PASS_PWD} -destkeypass ${STORE_PASS_PWD} -destkeystore ./${SERVER_PATH}/${SERVER_JKS_FOR_TEST}.jks -srckeystore ./${SERVER_PATH}/${SERVER_CERT_KEY}.p12 -srcstoretype PKCS12 -srcstorepass ${STORE_PASS_PWD} -alias ${SERVER_CERT_ALIAS}
CA_INTERMEDIATE_CERT_SIGN=${CA_INTERMEDIATE_CERT_KEY}
CA_INTERMEDIATE_CERT_KEY=$(intermediate_common_name)
CA_INTERMEDIATE_NUMBER=$((${CA_INTERMEDIATE_NUMBER} + 1))
echo "===================================================="
echo -e "Generate and Signed the second intermediates of our certificates: \n-${CA_INTERMEDIATE_CERT_KEY}-key.pem (certificate key)\n-${CA_INTERMEDIATE_CERT_KEY}.pem (certificate)\n-${CA_INTERMEDIATE_CERT_KEY}.csr (sign request)"
echo "===================================================="
cfssl gencert \
-ca ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_SIGN}.pem \
-ca-key ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_SIGN}-key.pem \
-config ./${SERVER_PATH}/ca-root-to-intermediate-config.json \
-hostname "${SERVER_HOST_NAME},${SERVER_LOCAL_HOST_NAME}${SERVER_PUBLIC_HOST_NAMES:+, }${SERVER_PUBLIC_HOST_NAMES}" \
- \
<<-CONFIG | cfssljson -bare ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}
{
"CN": "${CA_INTERMEDIATE_CERT_KEY}",
"names": [
{
"C": "UK",
"ST": "Kyiv city",
"L": "Kyiv",
"O": "Thingsboard",
"OU": "DEVELOPER_TEST"
}
]
}
CONFIG
CA_LIST_CERT_FOR_CAT=$(set_list_sert_for_cat ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem)
## Bootstrap server certificate
echo "===================================================="
echo -e "Generate and Signed the server certificate: \n-${BS_SERVER_CERT_KEY}-key.pem (certificate key)\n-${BS_SERVER_CERT_KEY}.pem (certificate)\n-${BS_SERVER_CERT_KEY}.csr (sign request)"
echo "===================================================="
cfssl gencert \
-ca ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem \
-ca-key ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}-key.pem \
-config ./${SERVER_PATH}/ca-config.json \
-profile server \
-hostname "${SERVER_HOST_NAME},${SERVER_LOCAL_HOST_NAME}${SERVER_PUBLIC_HOST_NAMES:+, }${SERVER_PUBLIC_HOST_NAMES}" \
- \
<<-CONFIG | cfssljson -bare ./${SERVER_PATH}/${BS_SERVER_CERT_KEY}
{
"CN": "${SERVER_LOCAL_HOST_NAME}"
}
CONFIG
echo "===================================================="
echo -e "Add the Bootstrap server certificate (${BS_SERVER_CERT_KEY}.pem) to keystore: ${SERVER_JKS_FOR_TEST}.jks"
echo "===================================================="
cat ./${SERVER_PATH}/${BS_SERVER_CERT_KEY}.pem ${CA_LIST_CERT_FOR_CAT} > ./${SERVER_PATH}/${BS_SERVER_CERT_CHAIN}.pem
openssl pkcs12 -export -in ./${SERVER_PATH}/${BS_SERVER_CERT_CHAIN}.pem -inkey ./${SERVER_PATH}/${BS_SERVER_CERT_KEY}-key.pem -out ./${SERVER_PATH}/${BS_SERVER_CERT_KEY}.p12 -name ${BS_SERVER_CERT_ALIAS} -CAfile ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem -caname ${CA_ROOT_ALIAS} -passin pass:${STORE_PASS_PWD} -passout pass:${STORE_PASS_PWD}
keytool -importkeystore -deststorepass ${STORE_PASS_PWD} -destkeypass ${STORE_PASS_PWD} -destkeystore ./${SERVER_PATH}/${SERVER_JKS_FOR_TEST}.jks -srckeystore ./${SERVER_PATH}/${BS_SERVER_CERT_KEY}.p12 -srcstoretype PKCS12 -srcstorepass ${STORE_PASS_PWD} -alias ${BS_SERVER_CERT_ALIAS}
keytool -list -v -keystore ./${SERVER_PATH}/lwm2mserver.jks -storepass server_ks_password -storetype PKCS12
rm ./${SERVER_PATH}/*.p12 2> /dev/null
rm ./${SERVER_PATH}/*.csr 2> /dev/null
rm ./${SERVER_PATH}/*.json 2> /dev/null
rm ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY_PREF}* 2> /dev/null
rm ./${SERVER_PATH}/${CA_ROOT_CERT_KEY}* 2> /dev/null
mv ./${SERVER_PATH}/${SERVER_CERT_KEY}-key.pem ./${SERVER_PATH}/${SERVER_CERT_KEY}_key.pem
mv ./${SERVER_PATH}/${BS_SERVER_CERT_KEY}-key.pem ./${SERVER_PATH}/${BS_SERVER_CERT_KEY}_key.pem

2
common/actor/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.3-SNAPSHOT</version>
<version>3.4.0-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/cache/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.3-SNAPSHOT</version>
<version>3.4.0-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
common/cluster-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>3.3.3-SNAPSHOT</version>
<version>3.4.0-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

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

Loading…
Cancel
Save