thingsboard-aplcache
Changes
.gitignore 1(+1 -0)
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java 168(+127 -41)
application/src/main/java/org/thingsboard/server/actors/device/SessionTimeoutCheckMsg.java 39(+39 -0)
application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java 24(+6 -18)
application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java 39(+27 -12)
application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java 3(+2 -1)
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java 9(+8 -1)
application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java 68(+35 -33)
application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java 50(+50 -0)
application/src/main/java/org/thingsboard/server/service/session/DeviceSessionCacheService.java 30(+30 -0)
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java 3(+2 -1)
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java 6(+4 -2)
application/src/main/java/org/thingsboard/server/service/transport/LocalTransportService.java 53(+21 -32)
application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java 28(+28 -0)
application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java 5(+3 -2)
application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java 3(+2 -1)
common/message/src/main/java/org/thingsboard/server/common/msg/system/ServiceToRuleEngineMsg.java 4(+3 -1)
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java 19(+8 -11)
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java 13(+9 -4)
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java 130(+128 -2)
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/RemoteTransportService.java 121(+57 -64)
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java 17(+16 -1)
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java 4(+4 -0)
dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java 22(+21 -1)
docker/.env 25(+15 -10)
docker/.gitignore 2(+2 -0)
docker/check-dirs.sh 2(+1 -1)
docker/compose-utils.sh 50(+50 -0)
docker/docker-compose.cassandra.yml 29(+20 -9)
docker/docker-compose.postgres.yml 33(+24 -9)
docker/docker-compose.yml 173(+148 -25)
docker/docker-install-tb.sh 14(+13 -1)
docker/docker-remove-services.sh 12(+6 -6)
docker/docker-start-services.sh 14(+6 -8)
docker/docker-stop-services.sh 12(+6 -6)
docker/docker-update-service.sh 15(+7 -8)
docker/docker-upgrade-tb.sh 16(+15 -1)
docker/haproxy/config/haproxy.cfg 33(+26 -7)
docker/kafka.env 12(+12 -0)
docker/README.md 95(+95 -0)
docker/tb-coap-transport.env 2(+1 -1)
docker/tb-http-transport.env 2(+1 -1)
docker/tb-js-executor.env 2(+1 -1)
docker/tb-mqtt-transport.env 2(+1 -1)
docker/tb-node.cassandra.env 5(+5 -0)
docker/tb-node.env 10(+10 -0)
docker/tb-node.postgres.env 9(+9 -0)
docker/tb-node/conf/logback.xml 51(+51 -0)
docker/tb-node/conf/thingsboard.conf 24(+24 -0)
docker/tb-web-ui.env 2(+1 -1)
msa/js-executor/pom.xml 67(+59 -8)
msa/pom.xml 4(+2 -2)
msa/tb/docker/install-tb.sh 56(+56 -0)
msa/tb/docker/logback.xml 0(+0 -0)
msa/tb/docker/start-tb.sh 39(+39 -0)
msa/tb/docker/thingsboard.conf 0(+0 -0)
msa/tb/docker/upgrade-tb.sh 37(+28 -9)
msa/tb/docker-cassandra/Dockerfile 59(+59 -0)
msa/tb/docker-cassandra/start-db.sh 25(+15 -10)
msa/tb/docker-cassandra/stop-db.sh 2(+1 -1)
msa/tb/docker-postgres/Dockerfile 62(+62 -0)
msa/tb/docker-postgres/start-db.sh 26(+14 -12)
msa/tb/docker-postgres/stop-db.sh 4(+1 -3)
msa/tb/docker-tb/Dockerfile 48(+48 -0)
msa/tb/docker-tb/start-db.sh 3(+1 -2)
msa/tb/docker-tb/stop-db.sh 9(+2 -7)
msa/tb/pom.xml 370(+370 -0)
msa/tb/README.md 83(+83 -0)
msa/tb-node/docker/Dockerfile 2(+1 -1)
msa/tb-node/pom.xml 69(+61 -8)
msa/transport/coap/docker/Dockerfile 2(+1 -1)
msa/transport/coap/pom.xml 69(+61 -8)
msa/transport/http/docker/Dockerfile 2(+1 -1)
msa/transport/http/pom.xml 69(+61 -8)
msa/transport/mqtt/docker/Dockerfile 2(+1 -1)
msa/transport/mqtt/pom.xml 69(+61 -8)
msa/web-ui/pom.xml 67(+59 -8)
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java 2(+2 -0)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java 2(+1 -1)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java 6(+6 -0)
ui/package.json 4(+2 -2)
ui/src/app/app.js 32(+18 -14)
ui/src/app/common/thirdparty-fix.js 459(+459 -0)
ui/src/app/layout/index.js 2(+0 -2)
ui/src/app/locale/locale.constant-tr_TR.json 128(+64 -64)
ui/src/app/user/user.controller.js 2(+1 -1)
Details
.gitignore 1(+1 -0)
diff --git a/.gitignore b/.gitignore
index 9039e8e..da8569b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,3 +32,4 @@ pom.xml.versionsBackup
**/Californium.properties
**/.env
.instance_id
+rebuild-docker.sh
diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json
index 1ccfdc9..6550f58 100644
--- a/application/src/main/data/json/system/widget_bundles/cards.json
+++ b/application/src/main/data/json/system/widget_bundles/cards.json
@@ -15,7 +15,7 @@
"resources": [],
"templateHtml": "",
"templateCss": "#container {\n overflow: auto;\n}\n\n.tbDatasource-container {\n margin: 5px;\n padding: 8px;\n}\n\n.tbDatasource-title {\n font-size: 1.200rem;\n font-weight: 500;\n padding-bottom: 10px;\n}\n\n.tbDatasource-table {\n width: 100%;\n box-shadow: 0 0 10px #ccc;\n border-collapse: collapse;\n white-space: nowrap;\n font-size: 1.000rem;\n color: #757575;\n}\n\n.tbDatasource-table td {\n position: relative;\n border-top: 1px solid rgba(0, 0, 0, 0.12);\n border-bottom: 1px solid rgba(0, 0, 0, 0.12);\n padding: 0px 18px;\n box-sizing: border-box;\n}",
- "controllerScript": "self.onInit = function() {\n \n self.ctx.datasourceTitleCells = [];\n self.ctx.valueCells = [];\n self.ctx.labelCells = [];\n \n for (var i=0; i < self.ctx.datasources.length; i++) {\n var tbDatasource = self.ctx.datasources[i];\n\n var datasourceId = 'tbDatasource' + i;\n self.ctx.$container.append(\n \"<div id='\" + datasourceId +\n \"' class='tbDatasource-container'></div>\"\n );\n\n var datasourceContainer = $('#' + datasourceId,\n self.ctx.$container);\n\n datasourceContainer.append(\n \"<div class='tbDatasource-title'>\" +\n tbDatasource.name + \"</div>\"\n );\n \n var datasourceTitleCell = $('.tbDatasource-title', datasourceContainer);\n self.ctx.datasourceTitleCells.push(datasourceTitleCell);\n \n var tableId = 'table' + i;\n datasourceContainer.append(\n \"<table id='\" + tableId +\n \"' class='tbDatasource-table'><col width='30%'><col width='70%'></table>\"\n );\n var table = $('#' + tableId, self.ctx.$container);\n\n for (var a = 0; a < tbDatasource.dataKeys.length; a++) {\n var dataKey = tbDatasource.dataKeys[a];\n var labelCellId = 'labelCell' + a;\n var cellId = 'cell' + a;\n table.append(\"<tr><td id='\" + labelCellId + \"'>\" + dataKey.label +\n \"</td><td id='\" + cellId +\n \"'></td></tr>\");\n var labelCell = $('#' + labelCellId, table);\n self.ctx.labelCells.push(labelCell);\n var valueCell = $('#' + cellId, table);\n self.ctx.valueCells.push(valueCell);\n }\n } \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.valueCells.length; i++) {\n var cellData = self.ctx.data[i];\n console.log(self.ctx); //del\n if (cellData && cellData.data && cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length -\n 1];\n var value = tvPair[1];\n var textValue;\n //toDo -> + IsNumber\n \n if (isNumber(value)) {\n var decimals = self.ctx.decimals;\n var units = self.ctx.units;\n if (cellData.dataKey.decimals || cellData.dataKey.decimals === 0) {\n decimals = cellData.dataKey.decimals;\n }\n if (cellData.dataKey.units) {\n units = cellData.dataKey.units;\n }\n txtValue = self.ctx.utils.formatValue(value, decimals, units, true);\n } else {\n txtValue = value;\n }\n self.ctx.valueCells[i].html(txtValue);\n }\n }\n \n function isNumber(n) {\n return !isNaN(parseFloat(n)) && isFinite(n);\n }\n}\n\nself.onResize = function() {\n var datasoirceTitleFontSize = self.ctx.height/8;\n if (self.ctx.width/self.ctx.height <= 1.5) {\n datasoirceTitleFontSize = self.ctx.width/12;\n }\n datasoirceTitleFontSize = Math.min(datasoirceTitleFontSize, 20);\n for (var i = 0; i < self.ctx.datasourceTitleCells.length; i++) {\n self.ctx.datasourceTitleCells[i].css('font-size', datasoirceTitleFontSize+'px');\n }\n var valueFontSize = self.ctx.height/9;\n var labelFontSize = self.ctx.height/9;\n if (self.ctx.width/self.ctx.height <= 1.5) {\n valueFontSize = self.ctx.width/15;\n labelFontSize = self.ctx.width/15;\n }\n valueFontSize = Math.min(valueFontSize, 18);\n labelFontSize = Math.min(labelFontSize, 18);\n\n for (i = 0; i < self.ctx.valueCells; i++) {\n self.ctx.valueCells[i].css('font-size', valueFontSize+'px');\n self.ctx.valueCells[i].css('height', valueFontSize*2.5+'px');\n self.ctx.valueCells[i].css('padding', '0px ' + valueFontSize + 'px');\n self.ctx.labelCells[i].css('font-size', labelFontSize+'px');\n self.ctx.labelCells[i].css('height', labelFontSize*2.5+'px');\n self.ctx.labelCells[i].css('padding', '0px ' + labelFontSize + 'px');\n } \n}\n\nself.onDestroy = function() {\n}\n",
+ "controllerScript": "self.onInit = function() {\n \n self.ctx.datasourceTitleCells = [];\n self.ctx.valueCells = [];\n self.ctx.labelCells = [];\n \n for (var i=0; i < self.ctx.datasources.length; i++) {\n var tbDatasource = self.ctx.datasources[i];\n\n var datasourceId = 'tbDatasource' + i;\n self.ctx.$container.append(\n \"<div id='\" + datasourceId +\n \"' class='tbDatasource-container'></div>\"\n );\n\n var datasourceContainer = $('#' + datasourceId,\n self.ctx.$container);\n\n datasourceContainer.append(\n \"<div class='tbDatasource-title'>\" +\n tbDatasource.name + \"</div>\"\n );\n \n var datasourceTitleCell = $('.tbDatasource-title', datasourceContainer);\n self.ctx.datasourceTitleCells.push(datasourceTitleCell);\n \n var tableId = 'table' + i;\n datasourceContainer.append(\n \"<table id='\" + tableId +\n \"' class='tbDatasource-table'><col width='30%'><col width='70%'></table>\"\n );\n var table = $('#' + tableId, self.ctx.$container);\n\n for (var a = 0; a < tbDatasource.dataKeys.length; a++) {\n var dataKey = tbDatasource.dataKeys[a];\n var labelCellId = 'labelCell' + a;\n var cellId = 'cell' + a;\n table.append(\"<tr><td id='\" + labelCellId + \"'>\" + dataKey.label +\n \"</td><td id='\" + cellId +\n \"'></td></tr>\");\n var labelCell = $('#' + labelCellId, table);\n self.ctx.labelCells.push(labelCell);\n var valueCell = $('#' + cellId, table);\n self.ctx.valueCells.push(valueCell);\n }\n } \n \n self.onResize();\n}\n\nself.onDataUpdated = function() {\n for (var i = 0; i < self.ctx.valueCells.length; i++) {\n var cellData = self.ctx.data[i];\n if (cellData && cellData.data && cellData.data.length > 0) {\n var tvPair = cellData.data[cellData.data.length -\n 1];\n var value = tvPair[1];\n var textValue;\n //toDo -> + IsNumber\n \n if (isNumber(value)) {\n var decimals = self.ctx.decimals;\n var units = self.ctx.units;\n if (cellData.dataKey.decimals || cellData.dataKey.decimals === 0) {\n decimals = cellData.dataKey.decimals;\n }\n if (cellData.dataKey.units) {\n units = cellData.dataKey.units;\n }\n txtValue = self.ctx.utils.formatValue(value, decimals, units, true);\n } else {\n txtValue = value;\n }\n self.ctx.valueCells[i].html(txtValue);\n }\n }\n \n function isNumber(n) {\n return !isNaN(parseFloat(n)) && isFinite(n);\n }\n}\n\nself.onResize = function() {\n var datasoirceTitleFontSize = self.ctx.height/8;\n if (self.ctx.width/self.ctx.height <= 1.5) {\n datasoirceTitleFontSize = self.ctx.width/12;\n }\n datasoirceTitleFontSize = Math.min(datasoirceTitleFontSize, 20);\n for (var i = 0; i < self.ctx.datasourceTitleCells.length; i++) {\n self.ctx.datasourceTitleCells[i].css('font-size', datasoirceTitleFontSize+'px');\n }\n var valueFontSize = self.ctx.height/9;\n var labelFontSize = self.ctx.height/9;\n if (self.ctx.width/self.ctx.height <= 1.5) {\n valueFontSize = self.ctx.width/15;\n labelFontSize = self.ctx.width/15;\n }\n valueFontSize = Math.min(valueFontSize, 18);\n labelFontSize = Math.min(labelFontSize, 18);\n\n for (i = 0; i < self.ctx.valueCells; i++) {\n self.ctx.valueCells[i].css('font-size', valueFontSize+'px');\n self.ctx.valueCells[i].css('height', valueFontSize*2.5+'px');\n self.ctx.valueCells[i].css('padding', '0px ' + valueFontSize + 'px');\n self.ctx.labelCells[i].css('font-size', labelFontSize+'px');\n self.ctx.labelCells[i].css('height', labelFontSize*2.5+'px');\n self.ctx.labelCells[i].css('padding', '0px ' + labelFontSize + 'px');\n } \n}\n\nself.onDestroy = function() {\n}\n",
"settingsSchema": "{}",
"dataKeySettingsSchema": "{}\n",
"defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"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\":{},\"title\":\"Attributes card\"}"
@@ -27,7 +27,7 @@
"descriptor": {
"type": "latest",
"sizeX": 7.5,
- "sizeY": 4.5,
+ "sizeY": 6.5,
"resources": [],
"templateHtml": "<tb-entities-table-widget \n table-id=\"tableId\"\n ctx=\"ctx\">\n</tb-entities-table-widget>",
"templateCss": "",
diff --git a/application/src/main/data/upgrade/2.2.0/schema_update.sql b/application/src/main/data/upgrade/2.2.0/schema_update.sql
new file mode 100644
index 0000000..1832b79
--- /dev/null
+++ b/application/src/main/data/upgrade/2.2.0/schema_update.sql
@@ -0,0 +1,17 @@
+--
+-- Copyright © 2016-2018 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.
+--
+
+ALTER TABLE component_descriptor ADD UNIQUE (clazz);
diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
index 5e19d69..091b504 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
@@ -67,6 +67,7 @@ import org.thingsboard.server.service.mail.MailExecutorService;
import org.thingsboard.server.service.rpc.DeviceRpcService;
import org.thingsboard.server.service.script.JsExecutorService;
import org.thingsboard.server.service.script.JsInvokeService;
+import org.thingsboard.server.service.session.DeviceSessionCacheService;
import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.service.transport.RuleEngineTransportService;
@@ -201,6 +202,10 @@ public class ActorSystemContext {
@Getter
private DeviceStateService deviceStateService;
+ @Autowired
+ @Getter
+ private DeviceSessionCacheService deviceSessionCacheService;
+
@Lazy
@Autowired
@Getter
@@ -254,6 +259,14 @@ public class ActorSystemContext {
@Getter
private boolean allowSystemMailService;
+ @Value("${transport.sessions.inactivity_timeout}")
+ @Getter
+ private long sessionInactivityTimeout;
+
+ @Value("${transport.sessions.report_timeout}")
+ @Getter
+ private long sessionReportTimeout;
+
@Getter
@Setter
private ActorSystem actorSystem;
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
index bd2a0f4..7b412b1 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java
@@ -44,11 +44,19 @@ public class DeviceActor extends ContextAwareActor {
}
@Override
+ public void preStart() {
+ logger.debug("[{}][{}] Starting device actor.", processor.tenantId, processor.deviceId);
+ try {
+ processor.initSessionTimeout(context());
+ logger.debug("[{}][{}] Device actor started.", processor.tenantId, processor.deviceId);
+ } catch (Exception e) {
+ logger.error(e, "[{}][{}] Unknown failure", processor.tenantId, processor.deviceId);
+ }
+ }
+
+ @Override
protected boolean process(TbActorMsg msg) {
switch (msg.getMsgType()) {
- case CLUSTER_EVENT_MSG:
- processor.processClusterEventMsg((ClusterEventMsg) msg);
- break;
case TRANSPORT_TO_DEVICE_ACTOR_MSG:
processor.process(context(), (TransportToDeviceActorMsgWrapper) msg);
break;
@@ -73,6 +81,9 @@ public class DeviceActor extends ContextAwareActor {
case DEVICE_ACTOR_CLIENT_SIDE_RPC_TIMEOUT_MSG:
processor.processClientSideRpcTimeout(context(), (DeviceActorClientSideRpcTimeoutMsg) msg);
break;
+ case SESSION_TIMEOUT_MSG:
+ processor.checkSessionsTimeout();
+ break;
default:
return false;
}
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
index 05902a6..a16bd78 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
@@ -41,7 +41,6 @@ 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.cluster.ClusterEventMsg;
-import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.common.msg.timeout.DeviceActorClientSideRpcTimeoutMsg;
@@ -89,11 +88,11 @@ import java.util.stream.Collectors;
/**
* @author Andrew Shvayka
*/
-public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
+class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
- private final TenantId tenantId;
- private final DeviceId deviceId;
- private final Map<UUID, SessionInfo> sessions;
+ final TenantId tenantId;
+ final DeviceId deviceId;
+ private final Map<UUID, SessionInfoMetaData> sessions;
private final Map<UUID, SessionInfo> attributeSubscriptions;
private final Map<UUID, SessionInfo> rpcSubscriptions;
private final Map<Integer, ToDeviceRpcRequestMetadata> toDeviceRpcPendingMap;
@@ -117,6 +116,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
this.toDeviceRpcPendingMap = new HashMap<>();
this.toServerRpcPendingMap = new HashMap<>();
initAttributes();
+ restoreSessions();
}
private void initAttributes() {
@@ -152,7 +152,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
if (request.isOneway() && sent) {
logger.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId());
- systemContext.getDeviceRpcService().processRpcResponseFromDevice(new FromDeviceRpcResponse(msg.getMsg().getId(), msg.getServerAddress(), null, null));
+ systemContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromDeviceActor(new FromDeviceRpcResponse(msg.getMsg().getId(), null, null));
} else {
registerPendingRpcRequest(context, msg, sent, rpcRequest, timeout);
}
@@ -161,7 +161,6 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
} else {
logger.debug("[{}] RPC request {} is NOT sent!", deviceId, request.getId());
}
-
}
private void registerPendingRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg, boolean sent, ToDeviceRpcRequestMsg rpcRequest, long timeout) {
@@ -174,8 +173,8 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(msg.getId());
if (requestMd != null) {
logger.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId());
- systemContext.getDeviceRpcService().processRpcResponseFromDevice(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
- requestMd.getMsg().getServerAddress(), null, requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION));
+ systemContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
+ null, requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION));
}
}
@@ -202,12 +201,11 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
private Consumer<Map.Entry<Integer, ToDeviceRpcRequestMetadata>> processPendingRpc(ActorContext context, UUID sessionId, String nodeId, Set<Integer> sentOneWayIds) {
return entry -> {
- ToDeviceRpcRequestActorMsg requestActorMsg = entry.getValue().getMsg();
ToDeviceRpcRequest request = entry.getValue().getMsg().getMsg();
ToDeviceRpcRequestBody body = request.getBody();
if (request.isOneway()) {
sentOneWayIds.add(entry.getKey());
- systemContext.getDeviceRpcService().processRpcResponseFromDevice(new FromDeviceRpcResponse(request.getId(), requestActorMsg.getServerAddress(), null, null));
+ systemContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromDeviceActor(new FromDeviceRpcResponse(request.getId(), null, null));
}
ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder().setRequestId(
entry.getKey()).setMethodName(body.getMethod()).setParams(body.getParams()).build();
@@ -228,11 +226,11 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
}
if (msg.hasPostAttributes()) {
handlePostAttributesRequest(context, msg.getSessionInfo(), msg.getPostAttributes());
- reportActivity();
+ reportLogicalDeviceActivity();
}
if (msg.hasPostTelemetry()) {
handlePostTelemetryRequest(context, msg.getSessionInfo(), msg.getPostTelemetry());
- reportActivity();
+ reportLogicalDeviceActivity();
}
if (msg.hasGetAttributes()) {
handleGetAttributesRequest(context, msg.getSessionInfo(), msg.getGetAttributes());
@@ -242,11 +240,14 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
}
if (msg.hasToServerRPCCallRequest()) {
handleClientSideRPCRequest(context, msg.getSessionInfo(), msg.getToServerRPCCallRequest());
- reportActivity();
+ reportLogicalDeviceActivity();
+ }
+ if (msg.hasSubscriptionInfo()) {
+ handleSessionActivity(context, msg.getSessionInfo(), msg.getSubscriptionInfo());
}
}
- private void reportActivity() {
+ private void reportLogicalDeviceActivity() {
systemContext.getDeviceStateService().onDeviceActivity(deviceId);
}
@@ -400,35 +401,27 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId());
boolean success = requestMd != null;
if (success) {
- systemContext.getDeviceRpcService().processRpcResponseFromDevice(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
- requestMd.getMsg().getServerAddress(), responseMsg.getPayload(), null));
+ systemContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(),
+ responseMsg.getPayload(), null));
} else {
logger.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId());
}
}
- void processClusterEventMsg(ClusterEventMsg msg) {
-// if (!msg.isAdded()) {
-// logger.debug("[{}] Clearing attributes/rpc subscription for server [{}]", deviceId, msg.getServerAddress());
-// Predicate<Map.Entry<SessionId, SessionInfo>> filter = e -> e.getValue().getServer()
-// .map(serverAddress -> serverAddress.equals(msg.getServerAddress())).orElse(false);
-// attributeSubscriptions.entrySet().removeIf(filter);
-// rpcSubscriptions.entrySet().removeIf(filter);
-// }
- }
-
private void processSubscriptionCommands(ActorContext context, SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg subscribeCmd) {
UUID sessionId = getSessionId(sessionInfo);
if (subscribeCmd.getUnsubscribe()) {
logger.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId);
attributeSubscriptions.remove(sessionId);
} else {
- SessionInfo session = sessions.get(sessionId);
- if (session == null) {
- session = new SessionInfo(TransportProtos.SessionType.SYNC, sessionInfo.getNodeId());
+ SessionInfoMetaData sessionMD = sessions.get(sessionId);
+ if (sessionMD == null) {
+ sessionMD = new SessionInfoMetaData(new SessionInfo(TransportProtos.SessionType.SYNC, sessionInfo.getNodeId()));
}
+ sessionMD.setSubscribedToAttributes(true);
logger.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId);
- attributeSubscriptions.put(sessionId, session);
+ attributeSubscriptions.put(sessionId, sessionMD.getSessionInfo());
+ dumpSessions();
}
}
@@ -442,20 +435,22 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
logger.debug("[{}] Canceling rpc subscription for session [{}]", deviceId, sessionId);
rpcSubscriptions.remove(sessionId);
} else {
- SessionInfo session = sessions.get(sessionId);
- if (session == null) {
- session = new SessionInfo(TransportProtos.SessionType.SYNC, sessionInfo.getNodeId());
+ SessionInfoMetaData sessionMD = sessions.get(sessionId);
+ if (sessionMD == null) {
+ sessionMD = new SessionInfoMetaData(new SessionInfo(TransportProtos.SessionType.SYNC, sessionInfo.getNodeId()));
}
+ sessionMD.setSubscribedToRPC(true);
logger.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId);
- rpcSubscriptions.put(sessionId, session);
+ rpcSubscriptions.put(sessionId, sessionMD.getSessionInfo());
sendPendingRequests(context, sessionId, sessionInfo);
+ dumpSessions();
}
}
private void processSessionStateMsgs(SessionInfoProto sessionInfo, SessionEventMsg msg) {
UUID sessionId = getSessionId(sessionInfo);
if (msg.getEvent() == SessionEvent.OPEN) {
- if(sessions.containsKey(sessionId)){
+ if (sessions.containsKey(sessionId)) {
logger.debug("[{}] Received duplicate session open event [{}]", deviceId, sessionId);
return;
}
@@ -463,13 +458,14 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
if (sessions.size() >= systemContext.getMaxConcurrentSessionsPerDevice()) {
UUID sessionIdToRemove = sessions.keySet().stream().findFirst().orElse(null);
if (sessionIdToRemove != null) {
- closeSession(sessionIdToRemove, sessions.remove(sessionIdToRemove));
+ notifyTransportAboutClosedSession(sessionIdToRemove, sessions.remove(sessionIdToRemove));
}
}
- sessions.put(sessionId, new SessionInfo(TransportProtos.SessionType.ASYNC, sessionInfo.getNodeId()));
+ sessions.put(sessionId, new SessionInfoMetaData(new SessionInfo(TransportProtos.SessionType.ASYNC, sessionInfo.getNodeId())));
if (sessions.size() == 1) {
reportSessionOpen();
}
+ dumpSessions();
} else if (msg.getEvent() == SessionEvent.CLOSED) {
logger.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId);
sessions.remove(sessionId);
@@ -478,21 +474,40 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
if (sessions.isEmpty()) {
reportSessionClose();
}
+ dumpSessions();
+ }
+ }
+
+ private void handleSessionActivity(ActorContext context, SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto subscriptionInfo) {
+ UUID sessionId = getSessionId(sessionInfo);
+ SessionInfoMetaData sessionMD = sessions.get(sessionId);
+ if (sessionMD != null) {
+ sessionMD.setLastActivityTime(subscriptionInfo.getLastActivityTime());
+ sessionMD.setSubscribedToAttributes(subscriptionInfo.getAttributeSubscription());
+ sessionMD.setSubscribedToRPC(subscriptionInfo.getRpcSubscription());
+ if (subscriptionInfo.getAttributeSubscription()) {
+ attributeSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo());
+ }
+ if (subscriptionInfo.getRpcSubscription()) {
+ rpcSubscriptions.putIfAbsent(sessionId, sessionMD.getSessionInfo());
+ }
}
+ dumpSessions();
}
void processCredentialsUpdate() {
- sessions.forEach(this::closeSession);
+ sessions.forEach(this::notifyTransportAboutClosedSession);
attributeSubscriptions.clear();
rpcSubscriptions.clear();
+ dumpSessions();
}
- private void closeSession(UUID sessionId, SessionInfo sessionInfo) {
+ private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd) {
DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder()
.setSessionIdMSB(sessionId.getMostSignificantBits())
.setSessionIdLSB(sessionId.getLeastSignificantBits())
.setSessionCloseNotification(SessionCloseNotificationProto.getDefaultInstance()).build();
- systemContext.getRuleEngineTransportService().process(sessionInfo.getNodeId(), msg);
+ systemContext.getRuleEngineTransportService().process(sessionMd.getSessionInfo().getNodeId(), msg);
}
void processNameOrTypeUpdate(DeviceNameOrTypeUpdateMsg msg) {
@@ -606,4 +621,75 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
}
return builder.build();
}
+
+ private void restoreSessions() {
+ logger.debug("[{}] Restoring sessions from cache", deviceId);
+ TransportProtos.DeviceSessionsCacheEntry sessionsDump = systemContext.getDeviceSessionCacheService().get(deviceId);
+ if (sessionsDump.getSerializedSize() == 0) {
+ logger.debug("[{}] No session information found", deviceId);
+ return;
+ }
+ for (TransportProtos.SessionSubscriptionInfoProto sessionSubscriptionInfoProto : sessionsDump.getSessionsList()) {
+ SessionInfoProto sessionInfoProto = sessionSubscriptionInfoProto.getSessionInfo();
+ UUID sessionId = getSessionId(sessionInfoProto);
+ SessionInfo sessionInfo = new SessionInfo(TransportProtos.SessionType.ASYNC, sessionInfoProto.getNodeId());
+ TransportProtos.SubscriptionInfoProto subInfo = sessionSubscriptionInfoProto.getSubscriptionInfo();
+ SessionInfoMetaData sessionMD = new SessionInfoMetaData(sessionInfo, subInfo.getLastActivityTime());
+ sessions.put(sessionId, sessionMD);
+ if (subInfo.getAttributeSubscription()) {
+ attributeSubscriptions.put(sessionId, sessionInfo);
+ sessionMD.setSubscribedToAttributes(true);
+ }
+ if (subInfo.getRpcSubscription()) {
+ rpcSubscriptions.put(sessionId, sessionInfo);
+ sessionMD.setSubscribedToRPC(true);
+ }
+ logger.debug("[{}] Restored session: {}", deviceId, sessionMD);
+ }
+ logger.debug("[{}] Restored sessions: {}, rpc subscriptions: {}, attribute subscriptions: {}", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size());
+ }
+
+ private void dumpSessions() {
+ logger.debug("[{}] Dumping sessions: {}, rpc subscriptions: {}, attribute subscriptions: {} to cache", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size());
+ List<TransportProtos.SessionSubscriptionInfoProto> sessionsList = new ArrayList<>(sessions.size());
+ sessions.forEach((uuid, sessionMD) -> {
+ if (sessionMD.getSessionInfo().getType() == TransportProtos.SessionType.SYNC) {
+ return;
+ }
+ SessionInfo sessionInfo = sessionMD.getSessionInfo();
+ TransportProtos.SubscriptionInfoProto subscriptionInfoProto = TransportProtos.SubscriptionInfoProto.newBuilder()
+ .setLastActivityTime(sessionMD.getLastActivityTime())
+ .setAttributeSubscription(sessionMD.isSubscribedToAttributes())
+ .setRpcSubscription(sessionMD.isSubscribedToRPC()).build();
+ TransportProtos.SessionInfoProto sessionInfoProto = TransportProtos.SessionInfoProto.newBuilder()
+ .setSessionIdMSB(uuid.getMostSignificantBits())
+ .setSessionIdLSB(uuid.getLeastSignificantBits())
+ .setNodeId(sessionInfo.getNodeId()).build();
+ sessionsList.add(TransportProtos.SessionSubscriptionInfoProto.newBuilder()
+ .setSessionInfo(sessionInfoProto)
+ .setSubscriptionInfo(subscriptionInfoProto).build());
+ logger.debug("[{}] Dumping session: {}", deviceId, sessionMD);
+ });
+ systemContext.getDeviceSessionCacheService()
+ .put(deviceId, TransportProtos.DeviceSessionsCacheEntry.newBuilder()
+ .addAllSessions(sessionsList).build());
+ }
+
+ void initSessionTimeout(ActorContext context) {
+ schedulePeriodicMsgWithDelay(context, SessionTimeoutCheckMsg.instance(), systemContext.getSessionInactivityTimeout(), systemContext.getSessionInactivityTimeout());
+ }
+
+ void checkSessionsTimeout() {
+ long expTime = System.currentTimeMillis() - systemContext.getSessionInactivityTimeout();
+ Map<UUID, SessionInfoMetaData> sessionsToRemove = sessions.entrySet().stream().filter(kv -> kv.getValue().getLastActivityTime() < expTime).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ sessionsToRemove.forEach((sessionId, sessionMD) -> {
+ sessions.remove(sessionId);
+ rpcSubscriptions.remove(sessionId);
+ attributeSubscriptions.remove(sessionId);
+ notifyTransportAboutClosedSession(sessionId, sessionMD);
+ });
+ if (!sessionsToRemove.isEmpty()) {
+ dumpSessions();
+ }
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java
index 43ae592..fe09077 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java
@@ -25,4 +25,5 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionType;
public class SessionInfo {
private final SessionType type;
private final String nodeId;
+ private long lastActivityTime;
}
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/SessionInfoMetaData.java b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfoMetaData.java
new file mode 100644
index 0000000..dd08394
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfoMetaData.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright © 2016-2018 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.actors.device;
+
+import lombok.Data;
+import org.thingsboard.server.gen.transport.TransportProtos.SessionType;
+
+/**
+ * @author Andrew Shvayka
+ */
+@Data
+class SessionInfoMetaData {
+ private final SessionInfo sessionInfo;
+ private long lastActivityTime;
+ private boolean subscribedToAttributes;
+ private boolean subscribedToRPC;
+
+ SessionInfoMetaData(SessionInfo sessionInfo) {
+ this(sessionInfo, System.currentTimeMillis());
+ }
+
+ SessionInfoMetaData(SessionInfo sessionInfo, long lastActivityTime) {
+ this.sessionInfo = sessionInfo;
+ this.lastActivityTime = lastActivityTime;
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/SessionTimeoutCheckMsg.java b/application/src/main/java/org/thingsboard/server/actors/device/SessionTimeoutCheckMsg.java
new file mode 100644
index 0000000..d9172ae
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/device/SessionTimeoutCheckMsg.java
@@ -0,0 +1,39 @@
+/**
+ * Copyright © 2016-2018 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.actors.device;
+
+import org.thingsboard.server.common.msg.MsgType;
+import org.thingsboard.server.common.msg.TbActorMsg;
+
+/**
+ * Created by ashvayka on 29.10.18.
+ */
+public class SessionTimeoutCheckMsg implements TbActorMsg {
+
+ private static final SessionTimeoutCheckMsg INSTANCE = new SessionTimeoutCheckMsg();
+
+ private SessionTimeoutCheckMsg() {
+ }
+
+ public static SessionTimeoutCheckMsg instance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public MsgType getMsgType() {
+ return MsgType.SESSION_TIMEOUT_MSG;
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java b/application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java
index 14bb636..1eba066 100644
--- a/application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java
+++ b/application/src/main/java/org/thingsboard/server/actors/rpc/BasicRpcSessionListener.java
@@ -32,7 +32,7 @@ public class BasicRpcSessionListener implements GrpcSessionListener {
private final ActorRef manager;
private final ActorRef self;
- public BasicRpcSessionListener(ActorService service, ActorRef manager, ActorRef self) {
+ BasicRpcSessionListener(ActorService service, ActorRef manager, ActorRef self) {
this.service = service;
this.manager = manager;
this.self = self;
diff --git a/application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java
index 608a77f..31320ce 100644
--- a/application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java
@@ -45,7 +45,7 @@ public class RpcManagerActor extends ContextAwareActor {
private final ServerAddress instance;
- public RpcManagerActor(ActorSystemContext systemContext) {
+ RpcManagerActor(ActorSystemContext systemContext) {
super(systemContext);
this.sessionActors = new HashMap<>();
this.pendingMsgs = new HashMap<>();
@@ -55,7 +55,6 @@ public class RpcManagerActor extends ContextAwareActor {
.filter(otherServer -> otherServer.getServerAddress().compareTo(instance) > 0)
.forEach(otherServer -> onCreateSessionRequest(
new RpcSessionCreateRequestMsg(UUID.randomUUID(), otherServer.getServerAddress(), null)));
-
}
@Override
@@ -104,10 +103,10 @@ public class RpcManagerActor extends ContextAwareActor {
ServerAddress address = new ServerAddress(msg.getServerAddress().getHost(), msg.getServerAddress().getPort(), ServerType.CORE);
SessionActorInfo session = sessionActors.get(address);
if (session != null) {
- log.debug("{} Forwarding msg to session actor", address);
+ log.debug("{} Forwarding msg to session actor: {}", address, msg);
session.getActor().tell(msg, ActorRef.noSender());
} else {
- log.debug("{} Storing msg to pending queue", address);
+ log.debug("{} Storing msg to pending queue: {}", address, msg);
Queue<ClusterAPIProtos.ClusterMessage> queue = pendingMsgs.get(address);
if (queue == null) {
queue = new LinkedList<>();
@@ -117,7 +116,7 @@ public class RpcManagerActor extends ContextAwareActor {
queue.add(msg);
}
} else {
- logger.warning("Cluster msg doesn't have set Server Address [{}]", msg);
+ logger.warning("Cluster msg doesn't have server address [{}]", msg);
}
}
@@ -162,9 +161,9 @@ public class RpcManagerActor extends ContextAwareActor {
}
private void onSessionClose(boolean reconnect, ServerAddress remoteAddress) {
- log.debug("[{}] session closed. Should reconnect: {}", remoteAddress, reconnect);
+ log.info("[{}] session closed. Should reconnect: {}", remoteAddress, reconnect);
SessionActorInfo sessionRef = sessionActors.get(remoteAddress);
- if (context().sender() != null && context().sender().equals(sessionRef.actor)) {
+ if (sessionRef != null && context().sender() != null && context().sender().equals(sessionRef.actor)) {
sessionActors.remove(remoteAddress);
pendingMsgs.remove(remoteAddress);
if (reconnect) {
@@ -182,18 +181,18 @@ public class RpcManagerActor extends ContextAwareActor {
private void register(ServerAddress remoteAddress, UUID uuid, ActorRef sender) {
sessionActors.put(remoteAddress, new SessionActorInfo(uuid, sender));
- log.debug("[{}][{}] Registering session actor.", remoteAddress, uuid);
+ log.info("[{}][{}] Registering session actor.", remoteAddress, uuid);
Queue<ClusterAPIProtos.ClusterMessage> data = pendingMsgs.remove(remoteAddress);
if (data != null) {
- log.debug("[{}][{}] Forwarding {} pending messages.", remoteAddress, uuid, data.size());
+ log.info("[{}][{}] Forwarding {} pending messages.", remoteAddress, uuid, data.size());
data.forEach(msg -> sender.tell(new RpcSessionTellMsg(msg), ActorRef.noSender()));
} else {
- log.debug("[{}][{}] No pending messages to forward.", remoteAddress, uuid);
+ log.info("[{}][{}] No pending messages to forward.", remoteAddress, uuid);
}
}
private ActorRef createSessionActor(RpcSessionCreateRequestMsg msg) {
- log.debug("[{}] Creating session actor.", msg.getMsgUid());
+ log.info("[{}] Creating session actor.", msg.getMsgUid());
ActorRef actor = context().actorOf(
Props.create(new RpcSessionActor.ActorCreator(systemContext, msg.getMsgUid())).withDispatcher(DefaultActorService.RPC_DISPATCHER_NAME));
actor.tell(msg, context().self());
diff --git a/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionActor.java b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionActor.java
index c9cf869..86509ca 100644
--- a/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcSessionActor.java
@@ -18,6 +18,7 @@ package org.thingsboard.server.actors.rpc;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import io.grpc.Channel;
+import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;
import org.thingsboard.server.actors.ActorSystemContext;
@@ -88,8 +89,8 @@ public class RpcSessionActor extends ContextAwareActor {
systemContext.getRpcService().onSessionCreated(msg.getMsgUid(), session.getInputStream());
} else {
// Client session
- Channel channel = ManagedChannelBuilder.forAddress(remoteServer.getHost(), remoteServer.getPort()).usePlaintext(true).build();
- session = new GrpcSession(remoteServer, listener);
+ ManagedChannel channel = ManagedChannelBuilder.forAddress(remoteServer.getHost(), remoteServer.getPort()).usePlaintext().build();
+ session = new GrpcSession(remoteServer, listener, channel);
session.initInputStream();
ClusterRpcServiceGrpc.ClusterRpcServiceStub stub = ClusterRpcServiceGrpc.newStub(channel);
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
index 4e779d4..0baecea 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.actors.ruleChain;
import akka.actor.ActorRef;
import com.datastax.driver.core.utils.UUIDs;
+import org.springframework.util.StringUtils;
import org.thingsboard.rule.engine.api.ListeningExecutor;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest;
@@ -35,6 +36,8 @@ import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
+import org.thingsboard.server.common.msg.cluster.ServerAddress;
+import org.thingsboard.server.common.msg.cluster.ServerType;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
@@ -232,16 +235,22 @@ class DefaultTbContext implements TbContext {
return new RuleEngineRpcService() {
@Override
public void sendRpcReply(DeviceId deviceId, int requestId, String body) {
- mainCtx.getDeviceRpcService().sendRpcReplyToDevice(nodeCtx.getTenantId(), deviceId, requestId, body);
+ mainCtx.getDeviceRpcService().sendReplyToRpcCallFromDevice(nodeCtx.getTenantId(), deviceId, requestId, body);
}
@Override
public void sendRpcRequest(RuleEngineDeviceRpcRequest src, Consumer<RuleEngineDeviceRpcResponse> consumer) {
ToDeviceRpcRequest request = new ToDeviceRpcRequest(src.getRequestUUID(), nodeCtx.getTenantId(), src.getDeviceId(),
src.isOneway(), src.getExpirationTime(), new ToDeviceRpcRequestBody(src.getMethod(), src.getBody()));
- mainCtx.getDeviceRpcService().processRpcRequestToDevice(request, response -> {
+ mainCtx.getDeviceRpcService().forwardServerSideRPCRequestToDeviceActor(request, response -> {
if (src.isRestApiCall()) {
- mainCtx.getDeviceRpcService().processRestAPIRpcResponseFromRuleEngine(response);
+ ServerAddress requestOriginAddress;
+ if (!StringUtils.isEmpty(src.getOriginHost())) {
+ requestOriginAddress = new ServerAddress(src.getOriginHost(), src.getOriginPort(), ServerType.CORE);
+ } else {
+ requestOriginAddress = mainCtx.getRoutingService().getCurrentServer();
+ }
+ mainCtx.getDeviceRpcService().processResponseToServerSideRPCRequestFromRuleEngine(requestOriginAddress, response);
}
consumer.accept(RuleEngineDeviceRpcResponse.builder()
.deviceId(src.getDeviceId())
diff --git a/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java b/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java
index f7e80e4..08d1dd8 100644
--- a/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java
+++ b/application/src/main/java/org/thingsboard/server/actors/service/ActorService.java
@@ -35,5 +35,4 @@ public interface ActorService extends SessionMsgProcessor, RpcMsgListener, Disco
void onDeviceNameOrTypeUpdate(TenantId tenantId, DeviceId deviceId, String deviceName, String deviceType);
- void onMsg(ServiceToRuleEngineMsg serviceToRuleEngineMsg);
}
diff --git a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
index c2e8fd0..85b8943 100644
--- a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
+++ b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java
@@ -42,7 +42,6 @@ import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.cluster.ToAllNodesMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
-import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.discovery.DiscoveryService;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
@@ -66,10 +65,7 @@ public class DefaultActorService implements ActorService {
public static final String APP_DISPATCHER_NAME = "app-dispatcher";
public static final String CORE_DISPATCHER_NAME = "core-dispatcher";
public static final String SYSTEM_RULE_DISPATCHER_NAME = "system-rule-dispatcher";
- public static final String SYSTEM_PLUGIN_DISPATCHER_NAME = "system-plugin-dispatcher";
public static final String TENANT_RULE_DISPATCHER_NAME = "rule-dispatcher";
- public static final String TENANT_PLUGIN_DISPATCHER_NAME = "plugin-dispatcher";
- public static final String SESSION_DISPATCHER_NAME = "session-dispatcher";
public static final String RPC_DISPATCHER_NAME = "rpc-dispatcher";
@Autowired
@@ -162,11 +158,6 @@ public class DefaultActorService implements ActorService {
appActor.tell(new SendToClusterMsg(deviceId, msg), ActorRef.noSender());
}
- @Override
- public void onMsg(ServiceToRuleEngineMsg msg) {
- appActor.tell(msg, ActorRef.noSender());
- }
-
public void broadcast(ToAllNodesMsg msg) {
actorContext.getEncodingService().encode(msg);
rpcService.broadcast(new RpcBroadcastMsg(ClusterAPIProtos.ClusterMessage
@@ -186,9 +177,9 @@ public class DefaultActorService implements ActorService {
@Override
public void onReceivedMsg(ServerAddress source, ClusterAPIProtos.ClusterMessage msg) {
ServerAddress serverAddress = new ServerAddress(source.getHost(), source.getPort(), source.getServerType());
- log.info("Received msg [{}] from [{}]", msg.getMessageType().name(), serverAddress);
if (log.isDebugEnabled()) {
- log.info("MSG: ", msg);
+ log.info("Received msg [{}] from [{}]", msg.getMessageType().name(), serverAddress);
+ log.info("MSG: {}", msg);
}
switch (msg.getMessageType()) {
case CLUSTER_ACTOR_MESSAGE:
@@ -222,7 +213,7 @@ public class DefaultActorService implements ActorService {
actorContext.getTsSubService().onRemoteTsUpdate(serverAddress, msg.getPayload().toByteArray());
break;
case CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE:
- actorContext.getDeviceRpcService().processRemoteResponseFromDevice(serverAddress, msg.getPayload().toByteArray());
+ actorContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromRemoteServer(serverAddress, msg.getPayload().toByteArray());
break;
case CLUSTER_DEVICE_STATE_SERVICE_MESSAGE:
actorContext.getDeviceStateService().onRemoteMsg(serverAddress, msg.getPayload().toByteArray());
diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java
index 8864486..c809782 100644
--- a/application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/shared/AbstractContextAwareMsgProcessor.java
@@ -40,43 +40,31 @@ public abstract class AbstractContextAwareMsgProcessor {
this.logger = logger;
}
- protected ActorRef getAppActor() {
- return systemContext.getAppActor();
- }
-
- protected Scheduler getScheduler() {
+ private Scheduler getScheduler() {
return systemContext.getScheduler();
}
- protected ExecutionContextExecutor getSystemDispatcher() {
+ private ExecutionContextExecutor getSystemDispatcher() {
return systemContext.getActorSystem().dispatcher();
}
protected void schedulePeriodicMsgWithDelay(ActorContext ctx, Object msg, long delayInMs, long periodInMs) {
- schedulePeriodicMsgWithDelay(ctx, msg, delayInMs, periodInMs, ctx.self());
+ schedulePeriodicMsgWithDelay(msg, delayInMs, periodInMs, ctx.self());
}
- protected void schedulePeriodicMsgWithDelay(ActorContext ctx, Object msg, long delayInMs, long periodInMs, ActorRef target) {
+ private void schedulePeriodicMsgWithDelay(Object msg, long delayInMs, long periodInMs, ActorRef target) {
logger.debug("Scheduling periodic msg {} every {} ms with delay {} ms", msg, periodInMs, delayInMs);
getScheduler().schedule(Duration.create(delayInMs, TimeUnit.MILLISECONDS), Duration.create(periodInMs, TimeUnit.MILLISECONDS), target, msg, getSystemDispatcher(), null);
}
-
protected void scheduleMsgWithDelay(ActorContext ctx, Object msg, long delayInMs) {
- scheduleMsgWithDelay(ctx, msg, delayInMs, ctx.self());
+ scheduleMsgWithDelay(msg, delayInMs, ctx.self());
}
- protected void scheduleMsgWithDelay(ActorContext ctx, Object msg, long delayInMs, ActorRef target) {
+ private void scheduleMsgWithDelay(Object msg, long delayInMs, ActorRef target) {
logger.debug("Scheduling msg {} with delay {} ms", msg, delayInMs);
getScheduler().scheduleOnce(Duration.create(delayInMs, TimeUnit.MILLISECONDS), target, msg, getSystemDispatcher(), null);
}
- @Data
- @AllArgsConstructor
- private static class ComponentConfiguration {
- private final String clazz;
- private final String name;
- private final String configuration;
- }
}
diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
index 347483a..721d828 100644
--- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
@@ -108,7 +108,7 @@ public class TenantActor extends RuleChainManagerActor {
@Override
protected void broadcast(Object msg) {
super.broadcast(msg);
- deviceActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
+// deviceActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender()));
}
private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) {
@@ -127,7 +127,6 @@ public class TenantActor extends RuleChainManagerActor {
ruleChainManager.getOrCreateActor(context(), msg.getRuleChainId()).tell(msg, self());
}
-
private void onToDeviceActorMsg(DeviceAwareMsg msg) {
getOrCreateDeviceActor(msg.getDeviceId()).tell(msg, ActorRef.noSender());
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index 582bbed..be1b6db 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -48,6 +48,7 @@ import org.thingsboard.server.common.data.widget.WidgetsBundle;
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.cluster.SendToClusterMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.asset.AssetService;
@@ -673,7 +674,7 @@ public abstract class BaseController {
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), msgType, entityId, metaData, TbMsgDataType.JSON
, json.writeValueAsString(entityNode)
, null, null, 0L);
- actorService.onMsg(new ServiceToRuleEngineMsg(user.getTenantId(), tbMsg));
+ actorService.onMsg(new SendToClusterMsg(entityId, new ServiceToRuleEngineMsg(user.getTenantId(), tbMsg)));
} catch (Exception e) {
log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e);
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
index 9b7ffd2..2a9c073 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
@@ -29,7 +29,6 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
-import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntitySubtype;
@@ -39,7 +38,6 @@ import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
-import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -47,7 +45,6 @@ import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
-import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.service.security.model.SecurityUser;
@@ -174,7 +171,7 @@ public class EntityViewController extends BaseController {
EntityView entityView = checkEntityViewId(entityViewId);
entityViewService.deleteEntityView(entityViewId);
logEntityAction(entityViewId, entityView, entityView.getCustomerId(),
- ActionType.DELETED,null, strEntityViewId);
+ ActionType.DELETED, null, strEntityViewId);
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ENTITY_VIEW),
null,
@@ -185,10 +182,23 @@ public class EntityViewController extends BaseController {
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
+ @RequestMapping(value = "/tenant/entityViews", params = {"entityViewName"}, method = RequestMethod.GET)
+ @ResponseBody
+ public EntityView getTenantEntityView(
+ @RequestParam String entityViewName) throws ThingsboardException {
+ try {
+ TenantId tenantId = getCurrentUser().getTenantId();
+ return checkNotNull(entityViewService.findEntityViewByTenantIdAndName(tenantId, entityViewName));
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
+ @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/entityView/{entityViewId}", method = RequestMethod.POST)
@ResponseBody
public EntityView assignEntityViewToCustomer(@PathVariable(CUSTOMER_ID) String strCustomerId,
- @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
+ @PathVariable(ENTITY_VIEW_ID) String strEntityViewId) throws ThingsboardException {
checkParameter(CUSTOMER_ID, strCustomerId);
checkParameter(ENTITY_VIEW_ID, strEntityViewId);
try {
diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
index 6c37614..ef09a7a 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
@@ -49,9 +49,11 @@ import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.AttributeKey;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
+import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
+import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
@@ -60,12 +62,10 @@ import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
-import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.service.security.AccessValidator;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.telemetry.AttributeData;
-import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.service.telemetry.TsData;
import org.thingsboard.server.service.telemetry.exception.InvalidParametersException;
import org.thingsboard.server.service.telemetry.exception.UncheckedApiException;
@@ -250,6 +250,60 @@ public class TelemetryController extends BaseController {
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/{entityType}/{entityId}/timeseries/delete", method = RequestMethod.DELETE)
+ @ResponseBody
+ public DeferredResult<ResponseEntity> deleteEntityTimeseries(@PathVariable("entityType") String entityType, @PathVariable("entityId") String entityIdStr,
+ @RequestParam(name = "keys") String keysStr,
+ @RequestParam(name = "deleteAllDataForKeys", defaultValue = "false") boolean deleteAllDataForKeys,
+ @RequestParam(name = "startTs", required = false) Long startTs,
+ @RequestParam(name = "endTs", required = false) Long endTs,
+ @RequestParam(name = "rewriteLatestIfDeleted", defaultValue = "false") boolean rewriteLatestIfDeleted) throws ThingsboardException {
+ EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
+ return deleteTimeseries(entityId, keysStr, deleteAllDataForKeys, startTs, endTs, rewriteLatestIfDeleted);
+ }
+
+ private DeferredResult<ResponseEntity> deleteTimeseries(EntityId entityIdStr, String keysStr, boolean deleteAllDataForKeys,
+ Long startTs, Long endTs, boolean rewriteLatestIfDeleted) throws ThingsboardException {
+ List<String> keys = toKeysList(keysStr);
+ if (keys.isEmpty()) {
+ return getImmediateDeferredResult("Empty keys: " + keysStr, HttpStatus.BAD_REQUEST);
+ }
+ SecurityUser user = getCurrentUser();
+
+ long deleteFromTs;
+ long deleteToTs;
+ if (deleteAllDataForKeys) {
+ deleteFromTs = 0L;
+ deleteToTs = System.currentTimeMillis();
+ } else {
+ deleteFromTs = startTs;
+ deleteToTs = endTs;
+ }
+
+ return accessValidator.validateEntityAndCallback(user, entityIdStr, (result, entityId) -> {
+ List<DeleteTsKvQuery> deleteTsKvQueries = new ArrayList<>();
+ for (String key : keys) {
+ deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted));
+ }
+
+ ListenableFuture<List<Void>> future = tsService.remove(entityId, deleteTsKvQueries);
+ Futures.addCallback(future, new FutureCallback<List<Void>>() {
+ @Override
+ public void onSuccess(@Nullable List<Void> tmp) {
+ logTimeseriesDeleted(user, entityId, keys, null);
+ result.setResult(new ResponseEntity<>(HttpStatus.OK));
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ logTimeseriesDeleted(user, entityId, keys, t);
+ result.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR));
+ }
+ }, executor);
+ });
+ }
+
+ @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/{deviceId}/{scope}", method = RequestMethod.DELETE)
@ResponseBody
public DeferredResult<ResponseEntity> deleteEntityAttributes(@PathVariable("deviceId") String deviceIdStr,
@@ -506,6 +560,15 @@ public class TelemetryController extends BaseController {
};
}
+ private void logTimeseriesDeleted(SecurityUser user, EntityId entityId, List<String> keys, Throwable e) {
+ try {
+ logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.TIMESERIES_DELETED, toException(e),
+ keys);
+ } catch (ThingsboardException te) {
+ log.warn("Failed to log timeseries delete", te);
+ }
+ }
+
private void logAttributesDeleted(SecurityUser user, EntityId entityId, String scope, List<String> keys, Throwable e) {
try {
logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.ATTRIBUTES_DELETED, toException(e),
diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
index 45eb66a..2486cd8 100644
--- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
+++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
@@ -101,6 +101,10 @@ public class ThingsboardInstallService {
log.info("Upgrading ThingsBoard from version 2.1.1 to 2.1.2 ...");
databaseUpgradeService.upgradeDatabase("2.1.1");
+ case "2.1.3":
+ log.info("Upgrading ThingsBoard from version 2.1.3 to 2.2.0 ...");
+
+ databaseUpgradeService.upgradeDatabase("2.1.3");
log.info("Updating system data...");
diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSession.java b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSession.java
index 55cf52c..dd0a995 100644
--- a/application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSession.java
+++ b/application/src/main/java/org/thingsboard/server/service/cluster/rpc/GrpcSession.java
@@ -15,6 +15,8 @@
*/
package org.thingsboard.server.service.cluster.rpc;
+import io.grpc.Channel;
+import io.grpc.ManagedChannel;
import io.grpc.stub.StreamObserver;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@@ -34,6 +36,7 @@ public final class GrpcSession implements Closeable {
private final UUID sessionId;
private final boolean client;
private final GrpcSessionListener listener;
+ private final ManagedChannel channel;
private StreamObserver<ClusterAPIProtos.ClusterMessage> inputStream;
private StreamObserver<ClusterAPIProtos.ClusterMessage> outputStream;
@@ -41,10 +44,10 @@ public final class GrpcSession implements Closeable {
private ServerAddress remoteServer;
public GrpcSession(GrpcSessionListener listener) {
- this(null, listener);
+ this(null, listener, null);
}
- public GrpcSession(ServerAddress remoteServer, GrpcSessionListener listener) {
+ public GrpcSession(ServerAddress remoteServer, GrpcSessionListener listener, ManagedChannel channel) {
this.sessionId = UUID.randomUUID();
this.listener = listener;
if (remoteServer != null) {
@@ -54,6 +57,7 @@ public final class GrpcSession implements Closeable {
} else {
this.client = false;
}
+ this.channel = channel;
}
public void initInputStream() {
@@ -105,5 +109,8 @@ public final class GrpcSession implements Closeable {
} catch (IllegalStateException e) {
log.debug("[{}] Failed to close output stream: {}", sessionId, e.getMessage());
}
+ if (channel != null) {
+ channel.shutdownNow();
+ }
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
index f3ceed2..545baf8 100644
--- a/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
+++ b/application/src/main/java/org/thingsboard/server/service/component/AnnotationComponentDiscoveryService.java
@@ -30,7 +30,6 @@ import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.rule.engine.api.NodeDefinition;
import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbRelationTypes;
-import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.plugin.ComponentDescriptor;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.dao.component.ComponentDescriptorService;
@@ -52,6 +51,7 @@ import java.util.Set;
@Slf4j
public class AnnotationComponentDiscoveryService implements ComponentDiscoveryService {
+ public static final int MAX_OPTIMISITC_RETRIES = 3;
@Value("${plugins.scan_packages}")
private String[] scanPackages;
@@ -81,17 +81,32 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe
private void registerRuleNodeComponents() {
Set<BeanDefinition> ruleNodeBeanDefinitions = getBeanDefinitions(RuleNode.class);
for (BeanDefinition def : ruleNodeBeanDefinitions) {
- try {
- String clazzName = def.getBeanClassName();
- Class<?> clazz = Class.forName(clazzName);
- RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class);
- ComponentType type = ruleNodeAnnotation.type();
- ComponentDescriptor component = scanAndPersistComponent(def, type);
- components.put(component.getClazz(), component);
- componentsMap.computeIfAbsent(type, k -> new ArrayList<>()).add(component);
- } catch (Exception e) {
- log.error("Can't initialize component {}, due to {}", def.getBeanClassName(), e.getMessage(), e);
- throw new RuntimeException(e);
+ int retryCount = 0;
+ Exception cause = null;
+ while (retryCount < MAX_OPTIMISITC_RETRIES) {
+ try {
+ String clazzName = def.getBeanClassName();
+ Class<?> clazz = Class.forName(clazzName);
+ RuleNode ruleNodeAnnotation = clazz.getAnnotation(RuleNode.class);
+ ComponentType type = ruleNodeAnnotation.type();
+ ComponentDescriptor component = scanAndPersistComponent(def, type);
+ components.put(component.getClazz(), component);
+ componentsMap.computeIfAbsent(type, k -> new ArrayList<>()).add(component);
+ break;
+ } catch (Exception e) {
+ log.trace("Can't initialize component {}, due to {}", def.getBeanClassName(), e.getMessage(), e);
+ cause = e;
+ retryCount++;
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e1) {
+ throw new RuntimeException(e1);
+ }
+ }
+ }
+ if (cause != null && retryCount == MAX_OPTIMISITC_RETRIES) {
+ log.error("Can't initialize component {}, due to {}", def.getBeanClassName(), cause.getMessage(), cause);
+ throw new RuntimeException(cause);
}
}
}
diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
index 5526953..a2aeefa 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
@@ -251,7 +251,8 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
log.info("Entity views restored.");
break;
-
+ case "2.1.3":
+ break;
default:
throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
index 2c7f89c..b4a725d 100644
--- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
+++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
@@ -149,7 +149,14 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
log.info("Entity views restored.");
}
break;
-
+ case "2.1.3":
+ try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
+ log.info("Updating schema ...");
+ schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.2.0", SCHEMA_UPDATE_SQL);
+ loadSql(schemaUpdateFile, conn);
+ log.info("Schema updated.");
+ }
+ break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java
index 64f5213..a65c1a5 100644
--- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java
+++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java
@@ -90,7 +90,7 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
@Override
public void processRestAPIRpcRequestToRuleEngine(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer) {
- log.trace("[{}] Processing local rpc call to rule engine [{}]", request.getTenantId(), request.getDeviceId());
+ log.trace("[{}][{}] Processing REST API call to rule engine [{}]", request.getTenantId(), request.getId(), request.getDeviceId());
UUID requestId = request.getId();
localToRuleEngineRpcRequests.put(requestId, responseConsumer);
sendRpcRequestToRuleEngine(request);
@@ -98,31 +98,11 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
}
@Override
- public void processRestAPIRpcResponseFromRuleEngine(FromDeviceRpcResponse response) {
- UUID requestId = response.getId();
- Consumer<FromDeviceRpcResponse> consumer = localToRuleEngineRpcRequests.remove(requestId);
- if (consumer != null) {
- consumer.accept(response);
- } else {
- log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response);
- }
- }
-
- @Override
- public void processRpcRequestToDevice(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer) {
- log.trace("[{}] Processing local rpc call to device [{}]", request.getTenantId(), request.getDeviceId());
- UUID requestId = request.getId();
- localToDeviceRpcRequests.put(requestId, responseConsumer);
- sendRpcRequestToDevice(request);
- scheduleTimeout(request, requestId, localToDeviceRpcRequests);
- }
-
- @Override
- public void processRpcResponseFromDevice(FromDeviceRpcResponse response) {
- log.trace("[{}] response to request: [{}]", this.hashCode(), response.getId());
- if (routingService.getCurrentServer().equals(response.getServerAddress())) {
+ public void processResponseToServerSideRPCRequestFromRuleEngine(ServerAddress requestOriginAddress, FromDeviceRpcResponse response) {
+ log.trace("[{}] Received response to server-side RPC request from rule engine: [{}]", response.getId(), requestOriginAddress);
+ if (routingService.getCurrentServer().equals(requestOriginAddress)) {
UUID requestId = response.getId();
- Consumer<FromDeviceRpcResponse> consumer = localToDeviceRpcRequests.remove(requestId);
+ Consumer<FromDeviceRpcResponse> consumer = localToRuleEngineRpcRequests.remove(requestId);
if (consumer != null) {
consumer.accept(response);
} else {
@@ -138,12 +118,33 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
} else {
builder.setError(-1);
}
- rpcService.tell(response.getServerAddress(), ClusterAPIProtos.MessageType.CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE, builder.build().toByteArray());
+ rpcService.tell(requestOriginAddress, ClusterAPIProtos.MessageType.CLUSTER_RPC_FROM_DEVICE_RESPONSE_MESSAGE, builder.build().toByteArray());
+ }
+ }
+
+ @Override
+ public void forwardServerSideRPCRequestToDeviceActor(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer) {
+ log.trace("[{}][{}] Processing local rpc call to device actor [{}]", request.getTenantId(), request.getId(), request.getDeviceId());
+ UUID requestId = request.getId();
+ localToDeviceRpcRequests.put(requestId, responseConsumer);
+ sendRpcRequestToDevice(request);
+ scheduleTimeout(request, requestId, localToDeviceRpcRequests);
+ }
+
+ @Override
+ public void processResponseToServerSideRPCRequestFromDeviceActor(FromDeviceRpcResponse response) {
+ log.trace("[{}] Received response to server-side RPC request from device actor.", response.getId());
+ UUID requestId = response.getId();
+ Consumer<FromDeviceRpcResponse> consumer = localToDeviceRpcRequests.remove(requestId);
+ if (consumer != null) {
+ consumer.accept(response);
+ } else {
+ log.trace("[{}] Unknown or stale rpc response received [{}]", requestId, response);
}
}
@Override
- public void processRemoteResponseFromDevice(ServerAddress serverAddress, byte[] data) {
+ public void processResponseToServerSideRPCRequestFromRemoteServer(ServerAddress serverAddress, byte[] data) {
ClusterAPIProtos.FromDeviceRPCResponseProto proto;
try {
proto = ClusterAPIProtos.FromDeviceRPCResponseProto.parseFrom(data);
@@ -151,13 +152,12 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
throw new RuntimeException(e);
}
RpcError error = proto.getError() > 0 ? RpcError.values()[proto.getError()] : null;
- FromDeviceRpcResponse response = new FromDeviceRpcResponse(new UUID(proto.getRequestIdMSB(), proto.getRequestIdLSB()), routingService.getCurrentServer(),
- proto.getResponse(), error);
- processRpcResponseFromDevice(response);
+ FromDeviceRpcResponse response = new FromDeviceRpcResponse(new UUID(proto.getRequestIdMSB(), proto.getRequestIdLSB()), proto.getResponse(), error);
+ processResponseToServerSideRPCRequestFromRuleEngine(routingService.getCurrentServer(), response);
}
@Override
- public void sendRpcReplyToDevice(TenantId tenantId, DeviceId deviceId, int requestId, String body) {
+ public void sendReplyToRpcCallFromDevice(TenantId tenantId, DeviceId deviceId, int requestId, String body) {
ToServerRpcResponseActorMsg rpcMsg = new ToServerRpcResponseActorMsg(tenantId, deviceId, new ToServerRpcResponseMsg(requestId, body));
forward(deviceId, rpcMsg);
}
@@ -166,6 +166,8 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
ObjectNode entityNode = json.createObjectNode();
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("requestUUID", msg.getId().toString());
+ metaData.putValue("originHost", routingService.getCurrentServer().getHost());
+ metaData.putValue("originPort", Integer.toString(routingService.getCurrentServer().getPort()));
metaData.putValue("expirationTime", Long.toString(msg.getExpirationTime()));
metaData.putValue("oneway", Boolean.toString(msg.isOneway()));
@@ -176,7 +178,7 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE, msg.getDeviceId(), metaData, TbMsgDataType.JSON
, json.writeValueAsString(entityNode)
, null, null, 0L);
- actorService.onMsg(new ServiceToRuleEngineMsg(msg.getTenantId(), tbMsg));
+ actorService.onMsg(new SendToClusterMsg(msg.getDeviceId(), new ServiceToRuleEngineMsg(msg.getTenantId(), tbMsg)));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
@@ -199,7 +201,7 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
log.trace("[{}] timeout the request: [{}]", this.hashCode(), requestId);
Consumer<FromDeviceRpcResponse> consumer = requestsMap.remove(requestId);
if (consumer != null) {
- consumer.accept(new FromDeviceRpcResponse(requestId, null, null, RpcError.TIMEOUT));
+ consumer.accept(new FromDeviceRpcResponse(requestId, null, RpcError.TIMEOUT));
}
}, timeout, TimeUnit.MILLISECONDS);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DeviceRpcService.java
index 4cee96e..bf12ec6 100644
--- a/application/src/main/java/org/thingsboard/server/service/rpc/DeviceRpcService.java
+++ b/application/src/main/java/org/thingsboard/server/service/rpc/DeviceRpcService.java
@@ -29,13 +29,13 @@ public interface DeviceRpcService {
void processRestAPIRpcRequestToRuleEngine(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer);
- void processRestAPIRpcResponseFromRuleEngine(FromDeviceRpcResponse response);
+ void processResponseToServerSideRPCRequestFromRuleEngine(ServerAddress requestOriginAddress, FromDeviceRpcResponse response);
- void processRpcRequestToDevice(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer);
+ void forwardServerSideRPCRequestToDeviceActor(ToDeviceRpcRequest request, Consumer<FromDeviceRpcResponse> responseConsumer);
- void processRpcResponseFromDevice(FromDeviceRpcResponse response);
+ void processResponseToServerSideRPCRequestFromDeviceActor(FromDeviceRpcResponse response);
- void sendRpcReplyToDevice(TenantId tenantId, DeviceId deviceId, int requestId, String body);
+ void processResponseToServerSideRPCRequestFromRemoteServer(ServerAddress serverAddress, byte[] data);
- void processRemoteResponseFromDevice(ServerAddress serverAddress, byte[] bytes);
+ void sendReplyToRpcCallFromDevice(TenantId tenantId, DeviceId deviceId, int requestId, String body);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponse.java b/application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponse.java
index 9c3ce9a..75506df 100644
--- a/application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponse.java
+++ b/application/src/main/java/org/thingsboard/server/service/rpc/FromDeviceRpcResponse.java
@@ -32,8 +32,6 @@ import java.util.UUID;
public class FromDeviceRpcResponse {
@Getter
private final UUID id;
- @Getter
- private final ServerAddress serverAddress;
private final String response;
private final RpcError error;
diff --git a/application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java b/application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java
new file mode 100644
index 0000000..6201dab
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java
@@ -0,0 +1,50 @@
+/**
+ * Copyright © 2016-2018 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.session;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.cache.annotation.CachePut;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+import static org.thingsboard.server.common.data.CacheConstants.SESSIONS_CACHE;
+
+/**
+ * Created by ashvayka on 29.10.18.
+ */
+@Service
+@Slf4j
+public class DefaultDeviceSessionCacheService implements DeviceSessionCacheService {
+
+ @Override
+ @Cacheable(cacheNames = SESSIONS_CACHE, key = "#deviceId")
+ public DeviceSessionsCacheEntry get(DeviceId deviceId) {
+ log.debug("[{}] Fetching session data from cache", deviceId);
+ return DeviceSessionsCacheEntry.newBuilder().addAllSessions(Collections.emptyList()).build();
+ }
+
+ @Override
+ @CachePut(cacheNames = SESSIONS_CACHE, key = "#deviceId")
+ public DeviceSessionsCacheEntry put(DeviceId deviceId, DeviceSessionsCacheEntry sessions) {
+ log.debug("[{}] Pushing session data from cache: {}", deviceId, sessions);
+ return sessions;
+ }
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/session/DeviceSessionCacheService.java b/application/src/main/java/org/thingsboard/server/service/session/DeviceSessionCacheService.java
new file mode 100644
index 0000000..0a2e6a5
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/service/session/DeviceSessionCacheService.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright © 2016-2018 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.session;
+
+import org.thingsboard.server.common.data.id.DeviceId;
+import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry;
+
+/**
+ * Created by ashvayka on 29.10.18.
+ */
+public interface DeviceSessionCacheService {
+
+ DeviceSessionsCacheEntry get(DeviceId deviceId);
+
+ DeviceSessionsCacheEntry put(DeviceId deviceId, DeviceSessionsCacheEntry sessions);
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
index f4e37db..610cd3b 100644
--- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
+++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
@@ -43,6 +43,7 @@ import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
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.cluster.SendToClusterMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.dao.attributes.AttributesService;
@@ -457,7 +458,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
TbMsg tbMsg = new TbMsg(UUIDs.timeBased(), msgType, stateData.getDeviceId(), stateData.getMetaData().copy(), TbMsgDataType.JSON
, json.writeValueAsString(state)
, null, null, 0L);
- actorService.onMsg(new ServiceToRuleEngineMsg(stateData.getTenantId(), tbMsg));
+ actorService.onMsg(new SendToClusterMsg(stateData.getDeviceId(), new ServiceToRuleEngineMsg(stateData.getTenantId(), tbMsg)));
} catch (Exception e) {
log.warn("[{}] Failed to push inactivity alarm: {}", stateData.getDeviceId(), state, e);
}
diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
index 3b0deea..7c77242 100644
--- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
+++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
@@ -166,7 +166,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
private SubscriptionState getUpdatedSubscriptionState(EntityId entityId, SubscriptionState sub, EntityView entityView) {
Map<String, Long> keyStates;
- if(sub.isAllKeys()) {
+ if (sub.isAllKeys()) {
keyStates = entityView.getKeys().getTimeseries().stream().collect(Collectors.toMap(k -> k, k -> 0L));
} else {
keyStates = sub.getKeyStates().entrySet()
@@ -618,7 +618,9 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
builder.setEntityId(sub.getEntityId().getId().toString());
builder.setType(sub.getType().name());
builder.setAllKeys(sub.isAllKeys());
- builder.setScope(sub.getScope());
+ if (sub.getScope() != null) {
+ builder.setScope(sub.getScope());
+ }
sub.getKeyStates().entrySet().forEach(e -> builder.addKeyStates(
ClusterAPIProtos.SubscriptionKetStateProto.newBuilder().setKey(e.getKey()).setTs(e.getValue()).build()));
rpcService.tell(address, ClusterAPIProtos.MessageType.CLUSTER_TELEMETRY_SUBSCRIPTION_CREATE_MESSAGE, builder.build().toByteArray());
diff --git a/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportService.java b/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportService.java
index 0f72230..3d12938 100644
--- a/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportService.java
+++ b/application/src/main/java/org/thingsboard/server/service/transport/LocalTransportService.java
@@ -133,59 +133,48 @@ public class LocalTransportService extends AbstractTransportService implements R
}
@Override
- public void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback<Void> callback) {
- if (checkLimits(sessionInfo, callback)) {
- forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSessionEvent(msg).build(), callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback<Void> callback) {
+ forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSessionEvent(msg).build(), callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback<Void> callback) {
- if (checkLimits(sessionInfo, callback)) {
- forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setPostTelemetry(msg).build(), callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback<Void> callback) {
+ forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setPostTelemetry(msg).build(), callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback<Void> callback) {
- if (checkLimits(sessionInfo, callback)) {
- forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setPostAttributes(msg).build(), callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback<Void> callback) {
+ forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setPostAttributes(msg).build(), callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback<Void> callback) {
- if (checkLimits(sessionInfo, callback)) {
- forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setGetAttributes(msg).build(), callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback<Void> callback) {
+ forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setGetAttributes(msg).build(), callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback<Void> callback) {
- if (checkLimits(sessionInfo, callback)) {
- forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSubscribeToAttributes(msg).build(), callback);
- }
+ public void process(SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto msg, TransportServiceCallback<Void> callback) {
+ forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSubscriptionInfo(msg).build(), callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, SubscribeToRPCMsg msg, TransportServiceCallback<Void> callback) {
- if (checkLimits(sessionInfo, callback)) {
- forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSubscribeToRPC(msg).build(), callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback<Void> callback) {
+ forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSubscribeToAttributes(msg).build(), callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg msg, TransportServiceCallback<Void> callback) {
- if (checkLimits(sessionInfo, callback)) {
- forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setToDeviceRPCCallResponse(msg).build(), callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, SubscribeToRPCMsg msg, TransportServiceCallback<Void> callback) {
+ forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSubscribeToRPC(msg).build(), callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback<Void> callback) {
- if (checkLimits(sessionInfo, callback)) {
- forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setToServerRPCCallRequest(msg).build(), callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg msg, TransportServiceCallback<Void> callback) {
+ forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setToDeviceRPCCallResponse(msg).build(), callback);
+ }
+
+ @Override
+ protected void doProcess(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback<Void> callback) {
+ forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setToServerRPCCallRequest(msg).build(), callback);
}
@Override
diff --git a/application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java b/application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java
index 38db1a4..6c903cc 100644
--- a/application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java
+++ b/application/src/main/java/org/thingsboard/server/service/transport/RemoteRuleEngineTransportService.java
@@ -17,6 +17,11 @@ package org.thingsboard.server.service.transport;
import akka.actor.ActorRef;
import com.fasterxml.jackson.databind.ObjectMapper;
+import io.github.bucket4j.Bandwidth;
+import io.github.bucket4j.BlockingBucket;
+import io.github.bucket4j.Bucket4j;
+import io.github.bucket4j.local.LocalBucket;
+import io.github.bucket4j.local.LocalBucketBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.producer.Callback;
@@ -49,6 +54,7 @@ import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
/**
@@ -68,6 +74,13 @@ public class RemoteRuleEngineTransportService implements RuleEngineTransportServ
@Value("${transport.remote.rule_engine.auto_commit_interval}")
private int autoCommitInterval;
+ @Value("${transport.remote.rule_engine.poll_records_pack_size}")
+ private int pollRecordsPackSize;
+ @Value("${transport.remote.rule_engine.max_poll_records_per_second}")
+ private long pollRecordsPerSecond;
+ @Value("${transport.remote.rule_engine.max_poll_records_per_minute}")
+ private long pollRecordsPerMinute;
+
@Autowired
private TbKafkaSettings kafkaSettings;
@@ -109,15 +122,30 @@ public class RemoteRuleEngineTransportService implements RuleEngineTransportServ
ruleEngineConsumerBuilder.groupId("tb-node");
ruleEngineConsumerBuilder.autoCommit(true);
ruleEngineConsumerBuilder.autoCommitIntervalMs(autoCommitInterval);
+ ruleEngineConsumerBuilder.maxPollRecords(pollRecordsPackSize);
ruleEngineConsumerBuilder.decoder(new ToRuleEngineMsgDecoder());
ruleEngineConsumer = ruleEngineConsumerBuilder.build();
ruleEngineConsumer.subscribe();
+ LocalBucketBuilder builder = Bucket4j.builder();
+ builder.addLimit(Bandwidth.simple(pollRecordsPerSecond, Duration.ofSeconds(1)));
+ builder.addLimit(Bandwidth.simple(pollRecordsPerMinute, Duration.ofMinutes(1)));
+ LocalBucket pollRateBucket = builder.build();
+ BlockingBucket blockingPollRateBucket = pollRateBucket.asScheduler();
+
mainConsumerExecutor.execute(() -> {
while (!stopped) {
try {
ConsumerRecords<String, byte[]> records = ruleEngineConsumer.poll(Duration.ofMillis(pollDuration));
+ int recordsCount = records.count();
+ if (recordsCount > 0) {
+ while (!blockingPollRateBucket.tryConsume(recordsCount, TimeUnit.SECONDS.toNanos(5))) {
+ log.info("Rule Engine consumer is busy. Required tokens: [{}]. Available tokens: [{}].", recordsCount, pollRateBucket.getAvailableTokens());
+ Thread.sleep(TimeUnit.SECONDS.toMillis(1));
+ }
+ log.trace("Processing {} records", recordsCount);
+ }
records.forEach(record -> {
try {
ToRuleEngineMsg toRuleEngineMsg = ruleEngineConsumer.decode(record);
diff --git a/application/src/main/proto/cluster.proto b/application/src/main/proto/cluster.proto
index 21c963b..1940b36 100644
--- a/application/src/main/proto/cluster.proto
+++ b/application/src/main/proto/cluster.proto
@@ -22,6 +22,7 @@ option java_outer_classname = "ClusterAPIProtos";
service ClusterRpcService {
rpc handleMsgs(stream ClusterMessage) returns (stream ClusterMessage) {}
}
+
message ClusterMessage {
MessageType messageType = 1;
MessageMataInfo messageMetaInfo = 2;
@@ -139,4 +140,4 @@ message DeviceStateServiceMsgProto {
bool added = 5;
bool updated = 6;
bool deleted = 7;
-}
\ No newline at end of file
+}
diff --git a/application/src/main/resources/actor-system.conf b/application/src/main/resources/actor-system.conf
index 3c68775..763319e 100644
--- a/application/src/main/resources/actor-system.conf
+++ b/application/src/main/resources/actor-system.conf
@@ -92,7 +92,7 @@ core-dispatcher {
throughput = 5
}
-# This dispatcher is used for system rule actors
+# This dispatcher is used for system rule chains and rule node actors
system-rule-dispatcher {
type = Dispatcher
executor = "fork-join-executor"
@@ -115,30 +115,7 @@ system-rule-dispatcher {
throughput = 5
}
-# This dispatcher is used for system plugin actors
-system-plugin-dispatcher {
- type = Dispatcher
- executor = "fork-join-executor"
- fork-join-executor {
- # Min number of threads to cap factor-based parallelism number to
- parallelism-min = 2
- # Max number of threads to cap factor-based parallelism number to
- parallelism-max = 12
-
- # The parallelism factor is used to determine thread pool size using the
- # following formula: ceil(available processors * factor). Resulting size
- # is then bounded by the parallelism-min and parallelism-max values.
- parallelism-factor = 0.25
- }
- # How long time the dispatcher will wait for new actors until it shuts down
- shutdown-timeout = 1s
-
- # Throughput defines the number of messages that are processed in a batch
- # before the thread is returned to the pool. Set to 1 for as fair as possible.
- throughput = 5
-}
-
-# This dispatcher is used for tenant rule actors
+# This dispatcher is used for tenant rule chains and rule node actors
rule-dispatcher {
type = Dispatcher
executor = "fork-join-executor"
@@ -160,50 +137,3 @@ rule-dispatcher {
# before the thread is returned to the pool. Set to 1 for as fair as possible.
throughput = 5
}
-
-# This dispatcher is used for tenant plugin actors
-plugin-dispatcher {
- type = Dispatcher
- executor = "fork-join-executor"
- fork-join-executor {
- # Min number of threads to cap factor-based parallelism number to
- parallelism-min = 2
- # Max number of threads to cap factor-based parallelism number to
- parallelism-max = 12
-
- # The parallelism factor is used to determine thread pool size using the
- # following formula: ceil(available processors * factor). Resulting size
- # is then bounded by the parallelism-min and parallelism-max values.
- parallelism-factor = 0.25
- }
- # How long time the dispatcher will wait for new actors until it shuts down
- shutdown-timeout = 1s
-
- # Throughput defines the number of messages that are processed in a batch
- # before the thread is returned to the pool. Set to 1 for as fair as possible.
- throughput = 5
-}
-
-
-# This dispatcher is used for rule actors
-session-dispatcher {
- type = Dispatcher
- executor = "fork-join-executor"
- fork-join-executor {
- # Min number of threads to cap factor-based parallelism number to
- parallelism-min = 2
- # Max number of threads to cap factor-based parallelism number to
- parallelism-max = 12
-
- # The parallelism factor is used to determine thread pool size using the
- # following formula: ceil(available processors * factor). Resulting size
- # is then bounded by the parallelism-min and parallelism-max values.
- parallelism-factor = 0.25
- }
- # How long time the dispatcher will wait for new actors until it shuts down
- shutdown-timeout = 1s
-
- # Throughput defines the number of messages that are processed in a batch
- # before the thread is returned to the pool. Set to 1 for as fair as possible.
- throughput = 5
-}
\ No newline at end of file
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index da6db70..51ddaa9 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -140,7 +140,7 @@ cassandra:
buffer_size: "${CASSANDRA_QUERY_BUFFER_SIZE:200000}"
concurrent_limit: "${CASSANDRA_QUERY_CONCURRENT_LIMIT:1000}"
permit_max_wait_time: "${PERMIT_MAX_WAIT_TIME:120000}"
- rate_limit_print_interval_ms: "${CASSANDRA_QUERY_RATE_LIMIT_PRINT_MS:30000}"
+ rate_limit_print_interval_ms: "${CASSANDRA_QUERY_RATE_LIMIT_PRINT_MS:10000}"
# SQL configuration parameters
sql:
@@ -202,6 +202,9 @@ caffeine:
devices:
timeToLiveInMinutes: 1440
maxSize: 100000
+ sessions:
+ timeToLiveInMinutes: 1440
+ maxSize: 100000
assets:
timeToLiveInMinutes: 1440
maxSize: 100000
@@ -222,7 +225,7 @@ redis:
updates:
# Enable/disable updates checking.
enabled: "${UPDATES_ENABLED:true}"
-
+
# spring CORS configuration
spring.mvc.cors:
mappings:
@@ -322,8 +325,8 @@ audit_log:
password: "${AUDIT_LOG_SINK_PASSWORD:}"
state:
- defaultInactivityTimeoutInSec: 10
- defaultStateCheckIntervalInSec: 10
+ defaultInactivityTimeoutInSec: "${DEFAULT_INACTIVITY_TIMEOUT:10}"
+ defaultStateCheckIntervalInSec: "${DEFAULT_STATE_CHECK_INTERVAL:10}"
kafka:
enabled: true
@@ -390,8 +393,14 @@ transport:
topic: "${TB_RULE_ENGINE_TOPIC:tb.rule-engine}"
poll_interval: "${TB_RULE_ENGINE_POLL_INTERVAL_MS:25}"
auto_commit_interval: "${TB_RULE_ENGINE_AUTO_COMMIT_INTERVAL_MS:100}"
+ poll_records_pack_size: "${TB_RULE_ENGINE_MAX_POLL_RECORDS:1000}"
+ max_poll_records_per_second: "${TB_RULE_ENGINE_MAX_POLL_RECORDS_PER_SECOND:10000}"
+ max_poll_records_per_minute: "${TB_RULE_ENGINE_MAX_POLL_RECORDS_PER_MINUTE:120000}"
notifications:
topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}"
+ sessions:
+ inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
+ report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}"
rate_limits:
enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}"
tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}"
diff --git a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
index f050f3b..1de2787 100644
--- a/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
+++ b/application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
@@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
+import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.controller.AbstractRuleEngineControllerTest;
import org.thingsboard.server.dao.attributes.AttributesService;
@@ -155,7 +156,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
device.getId(),
new TbMsgMetaData(),
"{}", null, null, 0L);
- actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg));
+ actorService.onMsg(new SendToClusterMsg(device.getId(), new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg)));
Thread.sleep(3000);
@@ -270,7 +271,7 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
device.getId(),
new TbMsgMetaData(),
"{}", null, null, 0L);
- actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg));
+ actorService.onMsg(new SendToClusterMsg(device.getId(), new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg)));
Thread.sleep(3000);
diff --git a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
index 24db457..f59dd63 100644
--- a/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
+++ b/application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
@@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
+import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.controller.AbstractRuleEngineControllerTest;
import org.thingsboard.server.dao.attributes.AttributesService;
@@ -142,7 +143,7 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
new TbMsgMetaData(),
"{}",
null, null, 0L);
- actorService.onMsg(new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg));
+ actorService.onMsg(new SendToClusterMsg(device.getId(), new ServiceToRuleEngineMsg(savedTenant.getId(), tbMsg)));
Thread.sleep(3000);
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
index c37d460..822387c 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
@@ -24,6 +24,7 @@ public enum ActionType {
UPDATED(false), // log entity
ATTRIBUTES_UPDATED(false), // log attributes/values
ATTRIBUTES_DELETED(false), // log attributes
+ TIMESERIES_DELETED(false), // log timeseries
RPC_CALL(false), // log method and params
CREDENTIALS_UPDATED(false), // log new credentials
ASSIGNED_TO_CUSTOMER(false), // log customer name
@@ -32,11 +33,11 @@ public enum ActionType {
SUSPENDED(false), // log string id
CREDENTIALS_READ(true), // log device id
ATTRIBUTES_READ(true), // log attributes
- RELATION_ADD_OR_UPDATE (false),
- RELATION_DELETED (false),
- RELATIONS_DELETED (false),
- ALARM_ACK (false),
- ALARM_CLEAR (false);
+ RELATION_ADD_OR_UPDATE(false),
+ RELATION_DELETED(false),
+ RELATIONS_DELETED(false),
+ ALARM_ACK(false),
+ ALARM_CLEAR(false);
private final boolean isRead;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
index 698a69e..853caff 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
@@ -19,6 +19,7 @@ public class CacheConstants {
public static final String DEVICE_CREDENTIALS_CACHE = "deviceCredentials";
public static final String RELATIONS_CACHE = "relations";
public static final String DEVICE_CACHE = "devices";
+ public static final String SESSIONS_CACHE = "sessions";
public static final String ASSET_CACHE = "assets";
public static final String ENTITY_VIEW_CACHE = "entityViews";
}
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ServerType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ServerType.java
index f9b057c..08f74fb 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ServerType.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ServerType.java
@@ -19,6 +19,5 @@ package org.thingsboard.server.common.msg.cluster;
* Created by ashvayka on 23.09.18.
*/
public enum ServerType {
- //Should match content of enum in discovery.proto.
- CORE, JS_EVALUATOR
+ CORE
}
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
index 24758b5..44a98d6 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
@@ -96,13 +96,8 @@ public enum MsgType {
*/
DEVICE_ACTOR_TO_RULE_ENGINE_MSG,
- /**
- * Message that is sent from Rule Engine to the Device Actor when message is successfully pushed to queue.
- */
- ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG,
- TRANSPORT_TO_DEVICE_SESSION_ACTOR_MSG,
SESSION_TIMEOUT_MSG,
- SESSION_CTRL_MSG,
+
STATS_PERSIST_TICK_MSG,
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/system/ServiceToRuleEngineMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/system/ServiceToRuleEngineMsg.java
index 0792b63..7859623 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/system/ServiceToRuleEngineMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/system/ServiceToRuleEngineMsg.java
@@ -21,11 +21,13 @@ import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
+import java.io.Serializable;
+
/**
* Created by ashvayka on 15.03.18.
*/
@Data
-public final class ServiceToRuleEngineMsg implements TbActorMsg {
+public final class ServiceToRuleEngineMsg implements TbActorMsg, Serializable {
private final TenantId tenantId;
private final TbMsg tbMsg;
diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaConsumerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaConsumerTemplate.java
index 86be3a3..c8a1706 100644
--- a/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaConsumerTemplate.java
+++ b/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaConsumerTemplate.java
@@ -46,7 +46,8 @@ public class TBKafkaConsumerTemplate<T> {
private TBKafkaConsumerTemplate(TbKafkaSettings settings, TbKafkaDecoder<T> decoder,
TbKafkaRequestIdExtractor<T> requestIdExtractor,
String clientId, String groupId, String topic,
- boolean autoCommit, int autoCommitIntervalMs) {
+ boolean autoCommit, int autoCommitIntervalMs,
+ int maxPollRecords) {
Properties props = settings.toProps();
props.put(ConsumerConfig.CLIENT_ID_CONFIG, clientId);
if (groupId != null) {
@@ -56,6 +57,9 @@ public class TBKafkaConsumerTemplate<T> {
props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, autoCommitIntervalMs);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArrayDeserializer");
+ if (maxPollRecords > 0) {
+ props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, maxPollRecords);
+ }
this.consumer = new KafkaConsumer<>(props);
this.decoder = decoder;
this.requestIdExtractor = requestIdExtractor;
diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaProducerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaProducerTemplate.java
index 610a490..bd42f31 100644
--- a/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaProducerTemplate.java
+++ b/common/queue/src/main/java/org/thingsboard/server/kafka/TBKafkaProducerTemplate.java
@@ -26,6 +26,7 @@ import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.PartitionInfo;
+import org.apache.kafka.common.errors.TopicExistsException;
import org.apache.kafka.common.header.Header;
import java.util.List;
@@ -75,7 +76,11 @@ public class TBKafkaProducerTemplate<T> {
CreateTopicsResult result = admin.createTopic(new NewTopic(defaultTopic, 100, (short) 1));
result.all().get();
} catch (Exception e) {
- log.trace("Failed to create topic: {}", e.getMessage(), e);
+ if ((e instanceof TopicExistsException) || (e.getCause() != null && e.getCause() instanceof TopicExistsException)) {
+ log.trace("[{}] Topic already exists: ", defaultTopic);
+ } else {
+ log.trace("[{}] Failed to create topic: {}", defaultTopic, e.getMessage(), e);
+ }
}
//Maybe this should not be cached, but we don't plan to change size of partitions
this.partitionInfoMap = new ConcurrentHashMap<>();
diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
index da8b3a6..a178bdf 100644
--- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
+++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
@@ -43,10 +43,10 @@ import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.msg.EncryptionUtil;
+import org.thingsboard.server.common.transport.service.AbstractTransportService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent;
-import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg;
import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
@@ -141,9 +141,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
processUnsubscribe(ctx, (MqttUnsubscribeMessage) msg);
break;
case PINGREQ:
- //TODO: should we push the notification to the rule engine?
if (checkConnected(ctx)) {
ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0)));
+ transportService.reportActivity(sessionInfo);
+ if (gatewaySessionHandler != null) {
+ gatewaySessionHandler.reportActivity();
+ }
}
break;
case DISCONNECT:
@@ -394,7 +397,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
private void processDisconnect(ChannelHandlerContext ctx) {
ctx.close();
if (deviceSessionCtx.isConnected()) {
- transportService.process(sessionInfo, getSessionEventMsg(SessionEvent.CLOSED), null);
+ transportService.process(sessionInfo, AbstractTransportService.getSessionEventMsg(SessionEvent.CLOSED), null);
transportService.deregisterSession(sessionInfo);
if (gatewaySessionHandler != null) {
gatewaySessionHandler.onGatewayDisconnect();
@@ -466,16 +469,10 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
}
}
- public static SessionEventMsg getSessionEventMsg(SessionEvent event) {
- return SessionEventMsg.newBuilder()
- .setSessionType(TransportProtos.SessionType.ASYNC)
- .setEvent(event).build();
- }
-
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
if (deviceSessionCtx.isConnected()) {
- transportService.process(sessionInfo, getSessionEventMsg(SessionEvent.CLOSED), null);
+ transportService.process(sessionInfo, AbstractTransportService.getSessionEventMsg(SessionEvent.CLOSED), null);
transportService.deregisterSession(sessionInfo);
}
}
@@ -495,7 +492,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
.setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB())
.setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB())
.build();
- transportService.process(sessionInfo, getSessionEventMsg(SessionEvent.OPEN), null);
+ transportService.process(sessionInfo, AbstractTransportService.getSessionEventMsg(SessionEvent.OPEN), null);
transportService.registerAsyncSession(sessionInfo, this);
checkGatewaySession();
ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED));
diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java
index d600059..ac33ba6 100644
--- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java
+++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java
@@ -34,6 +34,7 @@ import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.adaptor.AdaptorException;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
+import org.thingsboard.server.common.transport.service.AbstractTransportService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg;
@@ -118,7 +119,7 @@ public class GatewaySessionHandler {
GatewayDeviceSessionCtx deviceSessionCtx = new GatewayDeviceSessionCtx(GatewaySessionHandler.this, msg.getDeviceInfo(), mqttQoSMap);
if (devices.putIfAbsent(deviceName, deviceSessionCtx) == null) {
SessionInfoProto deviceSessionInfo = deviceSessionCtx.getSessionInfo();
- transportService.process(deviceSessionInfo, MqttTransportHandler.getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null);
+ transportService.process(deviceSessionInfo, AbstractTransportService.getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null);
transportService.process(deviceSessionInfo, TransportProtos.SubscribeToRPCMsg.getDefaultInstance(), null);
transportService.process(deviceSessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), null);
transportService.registerAsyncSession(deviceSessionInfo, deviceSessionCtx);
@@ -334,7 +335,7 @@ public class GatewaySessionHandler {
private void deregisterSession(String deviceName, GatewayDeviceSessionCtx deviceSessionCtx) {
transportService.deregisterSession(deviceSessionCtx.getSessionInfo());
- transportService.process(deviceSessionCtx.getSessionInfo(), MqttTransportHandler.getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null);
+ transportService.process(deviceSessionCtx.getSessionInfo(), AbstractTransportService.getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null);
log.debug("[{}] Removed device [{}] from the gateway session", sessionId, deviceName);
}
@@ -360,11 +361,15 @@ public class GatewaySessionHandler {
return context;
}
- public MqttTransportAdaptor getAdaptor() {
+ MqttTransportAdaptor getAdaptor() {
return context.getAdaptor();
}
- public int nextMsgId() {
+ int nextMsgId() {
return deviceSessionCtx.nextMsgId();
}
+
+ public void reportActivity() {
+ devices.forEach((id, deviceCtx) -> transportService.reportActivity(deviceCtx.getSessionInfo()));
+ }
}
diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java
index 265dacb..fad1954 100644
--- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java
+++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java
@@ -47,9 +47,14 @@ public abstract class AbstractTransportService implements TransportService {
private String perTenantLimitsConf;
@Value("${transport.rate_limits.tenant}")
private String perDevicesLimitsConf;
+ @Value("${transport.sessions.inactivity_timeout}")
+ private long sessionInactivityTimeout;
+ @Value("${transport.sessions.report_timeout}")
+ private long sessionReportTimeout;
protected ScheduledExecutorService schedulerExecutor;
protected ExecutorService transportCallbackExecutor;
+
private ConcurrentMap<UUID, SessionMetaData> sessions = new ConcurrentHashMap<>();
//TODO: Implement cleanup of this maps.
@@ -59,7 +64,121 @@ public abstract class AbstractTransportService implements TransportService {
@Override
public void registerAsyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener) {
sessions.putIfAbsent(toId(sessionInfo), new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listener));
- //TODO: monitor sessions periodically: PING REQ/RESP, etc.
+ }
+
+ @Override
+ public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SessionEventMsg msg, TransportServiceCallback<Void> callback) {
+ if (checkLimits(sessionInfo, callback)) {
+ reportActivityInternal(sessionInfo);
+ doProcess(sessionInfo, msg, callback);
+ }
+ }
+
+ @Override
+ public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback<Void> callback) {
+ if (checkLimits(sessionInfo, callback)) {
+ reportActivityInternal(sessionInfo);
+ doProcess(sessionInfo, msg, callback);
+ }
+ }
+
+ @Override
+ public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback<Void> callback) {
+ if (checkLimits(sessionInfo, callback)) {
+ reportActivityInternal(sessionInfo);
+ doProcess(sessionInfo, msg, callback);
+ }
+ }
+
+ @Override
+ public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.GetAttributeRequestMsg msg, TransportServiceCallback<Void> callback) {
+ if (checkLimits(sessionInfo, callback)) {
+ reportActivityInternal(sessionInfo);
+ doProcess(sessionInfo, msg, callback);
+ }
+ }
+
+ @Override
+ public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback<Void> callback) {
+ if (checkLimits(sessionInfo, callback)) {
+ SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo);
+ sessionMetaData.setSubscribedToAttributes(!msg.getUnsubscribe());
+ doProcess(sessionInfo, msg, callback);
+ }
+ }
+
+ @Override
+ public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToRPCMsg msg, TransportServiceCallback<Void> callback) {
+ if (checkLimits(sessionInfo, callback)) {
+ SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo);
+ sessionMetaData.setSubscribedToRPC(!msg.getUnsubscribe());
+ doProcess(sessionInfo, msg, callback);
+ }
+ }
+
+ @Override
+ public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDeviceRpcResponseMsg msg, TransportServiceCallback<Void> callback) {
+ if (checkLimits(sessionInfo, callback)) {
+ reportActivityInternal(sessionInfo);
+ doProcess(sessionInfo, msg, callback);
+ }
+ }
+
+ @Override
+ public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToServerRpcRequestMsg msg, TransportServiceCallback<Void> callback) {
+ if (checkLimits(sessionInfo, callback)) {
+ reportActivityInternal(sessionInfo);
+ doProcess(sessionInfo, msg, callback);
+ }
+ }
+
+ @Override
+ public void reportActivity(TransportProtos.SessionInfoProto sessionInfo) {
+ reportActivityInternal(sessionInfo);
+ }
+
+ protected abstract void doProcess(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SessionEventMsg msg, TransportServiceCallback<Void> callback);
+
+ protected abstract void doProcess(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback<Void> callback);
+
+ protected abstract void doProcess(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback<Void> callback);
+
+ protected abstract void doProcess(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.GetAttributeRequestMsg msg, TransportServiceCallback<Void> callback);
+
+ protected abstract void doProcess(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback<Void> callback);
+
+ protected abstract void doProcess(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToRPCMsg msg, TransportServiceCallback<Void> callback);
+
+ protected abstract void doProcess(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDeviceRpcResponseMsg msg, TransportServiceCallback<Void> callback);
+
+ protected abstract void doProcess(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToServerRpcRequestMsg msg, TransportServiceCallback<Void> callback);
+
+ private SessionMetaData reportActivityInternal(TransportProtos.SessionInfoProto sessionInfo) {
+ UUID sessionId = toId(sessionInfo);
+ SessionMetaData sessionMetaData = sessions.get(sessionId);
+ if (sessionMetaData != null) {
+ sessionMetaData.updateLastActivityTime();
+ }
+ return sessionMetaData;
+ }
+
+ private void checkInactivityAndReportActivity() {
+ long expTime = System.currentTimeMillis() - sessionInactivityTimeout;
+ sessions.forEach((uuid, sessionMD) -> {
+ if (sessionMD.getLastActivityTime() < expTime) {
+ if (log.isDebugEnabled()) {
+ log.debug("[{}] Session has expired due to last activity time: {}", toId(sessionMD.getSessionInfo()), sessionMD.getLastActivityTime());
+ }
+ process(sessionMD.getSessionInfo(), getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null);
+ sessions.remove(uuid);
+ sessionMD.getListener().onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto.getDefaultInstance());
+ } else {
+ process(sessionMD.getSessionInfo(), TransportProtos.SubscriptionInfoProto.newBuilder()
+ .setAttributeSubscription(sessionMD.isSubscribedToAttributes())
+ .setRpcSubscription(sessionMD.isSubscribedToRPC())
+ .setLastActivityTime(sessionMD.getLastActivityTime()).build(), null);
+ }
+ });
}
@Override
@@ -131,7 +250,7 @@ public abstract class AbstractTransportService implements TransportService {
}
}
- protected UUID toId(TransportProtos.SessionInfoProto sessionInfo) {
+ private UUID toId(TransportProtos.SessionInfoProto sessionInfo) {
return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB());
}
@@ -147,6 +266,7 @@ public abstract class AbstractTransportService implements TransportService {
}
this.schedulerExecutor = Executors.newSingleThreadScheduledExecutor();
this.transportCallbackExecutor = new ThreadPoolExecutor(0, 20, 60L, TimeUnit.SECONDS, new SynchronousQueue<>());
+ this.schedulerExecutor.scheduleAtFixedRate(this::checkInactivityAndReportActivity, sessionReportTimeout, sessionReportTimeout, TimeUnit.MILLISECONDS);
}
public void destroy() {
@@ -161,4 +281,10 @@ public abstract class AbstractTransportService implements TransportService {
transportCallbackExecutor.shutdownNow();
}
}
+
+ public static TransportProtos.SessionEventMsg getSessionEventMsg(TransportProtos.SessionEvent event) {
+ return TransportProtos.SessionEventMsg.newBuilder()
+ .setSessionType(TransportProtos.SessionType.ASYNC)
+ .setEvent(event).build();
+ }
}
diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/RemoteTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/RemoteTransportService.java
index 6774942..4b11bf5 100644
--- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/RemoteTransportService.java
+++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/RemoteTransportService.java
@@ -217,91 +217,84 @@ public class RemoteTransportService extends AbstractTransportService {
}
@Override
- public void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback<Void> callback) {
- if (checkLimits(sessionInfo, callback)) {
- ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
- TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
- .setSessionEvent(msg).build()
- ).build();
- send(sessionInfo, toRuleEngineMsg, callback);
- }
+ public void process(SessionInfoProto sessionInfo, SubscriptionInfoProto msg, TransportServiceCallback<Void> callback) {
+ ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
+ TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
+ .setSubscriptionInfo(msg).build()
+ ).build();
+ send(sessionInfo, toRuleEngineMsg, callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback<Void> callback) {
- if (checkLimits(sessionInfo, callback)) {
- ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
- TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
- .setPostTelemetry(msg).build()
- ).build();
- send(sessionInfo, toRuleEngineMsg, callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback<Void> callback) {
+ ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
+ TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
+ .setSessionEvent(msg).build()
+ ).build();
+ send(sessionInfo, toRuleEngineMsg, callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback<Void> callback) {
- if (checkLimits(sessionInfo, callback)) {
- ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
- TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
- .setPostAttributes(msg).build()
- ).build();
- send(sessionInfo, toRuleEngineMsg, callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback<Void> callback) {
+ ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
+ TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
+ .setPostTelemetry(msg).build()
+ ).build();
+ send(sessionInfo, toRuleEngineMsg, callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback<Void> callback) {
- if (checkLimits(sessionInfo, callback)) {
- ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
- TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
- .setGetAttributes(msg).build()
- ).build();
- send(sessionInfo, toRuleEngineMsg, callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback<Void> callback) {
+ ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
+ TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
+ .setPostAttributes(msg).build()
+ ).build();
+ send(sessionInfo, toRuleEngineMsg, callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback<Void> callback) {
- if (checkLimits(sessionInfo, callback)) {
- ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
- TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
- .setSubscribeToAttributes(msg).build()
- ).build();
- send(sessionInfo, toRuleEngineMsg, callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback<Void> callback) {
+ ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
+ TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
+ .setGetAttributes(msg).build()
+ ).build();
+ send(sessionInfo, toRuleEngineMsg, callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, SubscribeToRPCMsg msg, TransportServiceCallback<Void> callback) {
- if (checkLimits(sessionInfo, callback)) {
- ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
- TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
- .setSubscribeToRPC(msg).build()
- ).build();
- send(sessionInfo, toRuleEngineMsg, callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback<Void> callback) {
+ ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
+ TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
+ .setSubscribeToAttributes(msg).build()
+ ).build();
+ send(sessionInfo, toRuleEngineMsg, callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg msg, TransportServiceCallback<Void> callback) {
- if (checkLimits(sessionInfo, callback)) {
- ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
- TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
- .setToDeviceRPCCallResponse(msg).build()
- ).build();
- send(sessionInfo, toRuleEngineMsg, callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, SubscribeToRPCMsg msg, TransportServiceCallback<Void> callback) {
+ ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
+ TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
+ .setSubscribeToRPC(msg).build()
+ ).build();
+ send(sessionInfo, toRuleEngineMsg, callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback<Void> callback) {
- if (checkLimits(sessionInfo, callback)) {
- ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
- TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
- .setToServerRPCCallRequest(msg).build()
- ).build();
- send(sessionInfo, toRuleEngineMsg, callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg msg, TransportServiceCallback<Void> callback) {
+ ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
+ TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
+ .setToDeviceRPCCallResponse(msg).build()
+ ).build();
+ send(sessionInfo, toRuleEngineMsg, callback);
+ }
+
+ @Override
+ protected void doProcess(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback<Void> callback) {
+ ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg(
+ TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo)
+ .setToServerRPCCallRequest(msg).build()
+ ).build();
+ send(sessionInfo, toRuleEngineMsg, callback);
}
private static class TransportCallbackAdaptor implements Callback {
diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java
index 1de5711..8642e93 100644
--- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java
+++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/SessionMetaData.java
@@ -23,10 +23,25 @@ import org.thingsboard.server.gen.transport.TransportProtos;
* Created by ashvayka on 15.10.18.
*/
@Data
-public class SessionMetaData {
+class SessionMetaData {
private final TransportProtos.SessionInfoProto sessionInfo;
private final TransportProtos.SessionType sessionType;
private final SessionMsgListener listener;
+ private volatile long lastActivityTime;
+ private volatile boolean subscribedToAttributes;
+ private volatile boolean subscribedToRPC;
+
+ SessionMetaData(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SessionType sessionType, SessionMsgListener listener) {
+ this.sessionInfo = sessionInfo;
+ this.sessionType = sessionType;
+ this.listener = listener;
+ this.lastActivityTime = System.currentTimeMillis();
+ }
+
+ void updateLastActivityTime() {
+ this.lastActivityTime = System.currentTimeMillis();
+ }
+
}
diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
index a47438f..8944e94 100644
--- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
+++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
@@ -61,10 +61,14 @@ public interface TransportService {
void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback<Void> callback);
+ void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto msg, TransportServiceCallback<Void> callback);
+
void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener);
void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout);
+ void reportActivity(SessionInfoProto sessionInfo);
+
void deregisterSession(SessionInfoProto sessionInfo);
}
diff --git a/common/transport/transport-api/src/main/proto/transport.proto b/common/transport/transport-api/src/main/proto/transport.proto
index 0e36dab..ff740d4 100644
--- a/common/transport/transport-api/src/main/proto/transport.proto
+++ b/common/transport/transport-api/src/main/proto/transport.proto
@@ -172,6 +172,22 @@ message ToServerRpcResponseMsg {
string error = 3;
}
+//Used to report session state to tb-node and persist this state in the cache on the tb-node level.
+message SubscriptionInfoProto {
+ int64 lastActivityTime = 1;
+ bool attributeSubscription = 2;
+ bool rpcSubscription = 3;
+}
+
+message SessionSubscriptionInfoProto {
+ SessionInfoProto sessionInfo = 1;
+ SubscriptionInfoProto subscriptionInfo = 2;
+}
+
+message DeviceSessionsCacheEntry {
+ repeated SessionSubscriptionInfoProto sessions = 1;
+}
+
message TransportToDeviceActorMsg {
SessionInfoProto sessionInfo = 1;
SessionEventMsg sessionEvent = 2;
@@ -182,6 +198,7 @@ message TransportToDeviceActorMsg {
SubscribeToRPCMsg subscribeToRPC = 7;
ToDeviceRpcResponseMsg toDeviceRPCCallResponse = 8;
ToServerRpcRequestMsg toServerRPCCallRequest = 9;
+ SubscriptionInfoProto subscriptionInfo = 10;
}
message DeviceActorToTransportMsg {
@@ -214,4 +231,4 @@ message TransportApiRequestMsg {
message TransportApiResponseMsg {
ValidateDeviceCredentialsResponseMsg validateTokenResponseMsg = 1;
GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2;
-}
\ No newline at end of file
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java
index da87b44..9c82866 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewService.java
@@ -43,6 +43,8 @@ public interface EntityViewService {
EntityView findEntityViewById(EntityViewId entityViewId);
+ EntityView findEntityViewByTenantIdAndName(TenantId tenantId, String name);
+
TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink);
TextPageData<EntityView> findEntityViewByTenantIdAndType(TenantId tenantId, TextPageLink pageLink, String type);
diff --git a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
index 2d94cc2..9f8949d 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/entityview/EntityViewServiceImpl.java
@@ -29,8 +29,6 @@ import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Customer;
-import org.thingsboard.server.common.data.DataConstants;
-import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
@@ -40,12 +38,10 @@ import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
-import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
-import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
@@ -56,15 +52,13 @@ import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import java.util.concurrent.ExecutionException;
+import java.util.Optional;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.CacheConstants.ENTITY_VIEW_CACHE;
-import static org.thingsboard.server.common.data.CacheConstants.RELATIONS_CACHE;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@@ -96,6 +90,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
@Caching(evict = {
@CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.tenantId, #entityView.entityId}"),
+ @CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.tenantId, #entityView.name}"),
@CacheEvict(cacheNames = ENTITY_VIEW_CACHE, key = "{#entityView.id}")})
@Override
public EntityView saveEntityView(EntityView entityView) {
@@ -137,6 +132,15 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
return entityViewDao.findById(entityViewId.getId());
}
+ @Cacheable(cacheNames = ENTITY_VIEW_CACHE, key = "{#tenantId, #name}")
+ @Override
+ public EntityView findEntityViewByTenantIdAndName(TenantId tenantId, String name) {
+ log.trace("Executing findEntityViewByTenantIdAndName [{}][{}]", tenantId, name);
+ validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
+ Optional<EntityView> entityViewOpt = entityViewDao.findEntityViewByTenantIdAndName(tenantId.getId(), name);
+ return entityViewOpt.orElse(null);
+ }
+
@Override
public TextPageData<EntityView> findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink) {
log.trace("Executing findEntityViewsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
@@ -255,6 +259,7 @@ public class EntityViewServiceImpl extends AbstractEntityService implements Enti
deleteEntityRelations(entityViewId);
EntityView entityView = entityViewDao.findById(entityViewId.getId());
cacheManager.getCache(ENTITY_VIEW_CACHE).evict(Arrays.asList(entityView.getTenantId(), entityView.getEntityId()));
+ cacheManager.getCache(ENTITY_VIEW_CACHE).evict(Arrays.asList(entityView.getTenantId(), entityView.getName()));
entityViewDao.removeById(entityViewId.getId());
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ComponentDescriptorEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ComponentDescriptorEntity.java
index 66fbcc3..e4c6bb5 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ComponentDescriptorEntity.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ComponentDescriptorEntity.java
@@ -34,6 +34,7 @@ import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
@Data
@EqualsAndHashCode(callSuper = true)
@@ -53,7 +54,7 @@ public class ComponentDescriptorEntity extends BaseSqlEntity<ComponentDescriptor
@Column(name = ModelConstants.COMPONENT_DESCRIPTOR_NAME_PROPERTY)
private String name;
- @Column(name = ModelConstants.COMPONENT_DESCRIPTOR_CLASS_PROPERTY)
+ @Column(name = ModelConstants.COMPONENT_DESCRIPTOR_CLASS_PROPERTY, unique = true)
private String clazz;
@Type(type = "json")
diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java
index d1af167..b38110b 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java
@@ -35,7 +35,6 @@ import org.thingsboard.server.dao.model.type.ComponentTypeCodec;
import org.thingsboard.server.dao.model.type.DeviceCredentialsTypeCodec;
import org.thingsboard.server.dao.model.type.EntityTypeCodec;
import org.thingsboard.server.dao.model.type.JsonCodec;
-import org.thingsboard.server.dao.util.BufferedRateLimiter;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -49,7 +48,7 @@ public abstract class CassandraAbstractDao {
private ConcurrentMap<String, PreparedStatement> preparedStatementMap = new ConcurrentHashMap<>();
@Autowired
- private BufferedRateLimiter rateLimiter;
+ private CassandraBufferedRateExecutor rateLimiter;
private Session session;
@@ -115,12 +114,12 @@ public abstract class CassandraAbstractDao {
if (statement.getConsistencyLevel() == null) {
statement.setConsistencyLevel(level);
}
- return new RateLimitedResultSetFuture(getSession(), rateLimiter, statement);
+ return rateLimiter.submit(new CassandraStatementTask(getSession(), statement));
}
private static String statementToString(Statement statement) {
if (statement instanceof BoundStatement) {
- return ((BoundStatement)statement).preparedStatement().getQueryString();
+ return ((BoundStatement) statement).preparedStatement().getQueryString();
} else {
return statement.toString();
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateExecutor.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateExecutor.java
new file mode 100644
index 0000000..a3490bf
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraBufferedRateExecutor.java
@@ -0,0 +1,79 @@
+/**
+ * Copyright © 2016-2018 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.dao.nosql;
+
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.ResultSetFuture;
+import com.google.common.util.concurrent.SettableFuture;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.thingsboard.server.dao.util.AbstractBufferedRateExecutor;
+import org.thingsboard.server.dao.util.AsyncTaskContext;
+import org.thingsboard.server.dao.util.NoSqlAnyDao;
+
+import javax.annotation.PreDestroy;
+
+/**
+ * Created by ashvayka on 24.10.18.
+ */
+@Component
+@Slf4j
+@NoSqlAnyDao
+public class CassandraBufferedRateExecutor extends AbstractBufferedRateExecutor<CassandraStatementTask, ResultSetFuture, ResultSet> {
+
+ public CassandraBufferedRateExecutor(
+ @Value("${cassandra.query.buffer_size}") int queueLimit,
+ @Value("${cassandra.query.concurrent_limit}") int concurrencyLimit,
+ @Value("${cassandra.query.permit_max_wait_time}") long maxWaitTime,
+ @Value("${cassandra.query.dispatcher_threads:2}") int dispatcherThreads,
+ @Value("${cassandra.query.callback_threads:2}") int callbackThreads,
+ @Value("${cassandra.query.poll_ms:50}") long pollMs) {
+ super(queueLimit, concurrencyLimit, maxWaitTime, dispatcherThreads, callbackThreads, pollMs);
+ }
+
+ @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}")
+ public void printStats() {
+ log.info("Permits queueSize [{}] totalAdded [{}] totalLaunched [{}] totalReleased [{}] totalFailed [{}] totalExpired [{}] totalRejected [{}] currBuffer [{}] ",
+ getQueueSize(),
+ totalAdded.getAndSet(0), totalLaunched.getAndSet(0), totalReleased.getAndSet(0),
+ totalFailed.getAndSet(0), totalExpired.getAndSet(0), totalRejected.getAndSet(0),
+ concurrencyLevel.get());
+ }
+
+ @PreDestroy
+ public void stop() {
+ super.stop();
+ }
+
+ @Override
+ protected SettableFuture<ResultSet> create() {
+ return SettableFuture.create();
+ }
+
+ @Override
+ protected ResultSetFuture wrap(CassandraStatementTask task, SettableFuture<ResultSet> future) {
+ return new TbResultSetFuture(future);
+ }
+
+ @Override
+ protected ResultSetFuture execute(AsyncTaskContext<CassandraStatementTask, ResultSet> taskCtx) {
+ CassandraStatementTask task = taskCtx.getTask();
+ return task.getSession().executeAsync(task.getStatement());
+ }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraStatementTask.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraStatementTask.java
new file mode 100644
index 0000000..19e5640
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraStatementTask.java
@@ -0,0 +1,32 @@
+/**
+ * Copyright © 2016-2018 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.dao.nosql;
+
+import com.datastax.driver.core.Session;
+import com.datastax.driver.core.Statement;
+import lombok.Data;
+import org.thingsboard.server.dao.util.AsyncTask;
+
+/**
+ * Created by ashvayka on 24.10.18.
+ */
+@Data
+public class CassandraStatementTask implements AsyncTask {
+
+ private final Session session;
+ private final Statement statement;
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/nosql/TbResultSetFuture.java b/dao/src/main/java/org/thingsboard/server/dao/nosql/TbResultSetFuture.java
new file mode 100644
index 0000000..574a5f5
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/nosql/TbResultSetFuture.java
@@ -0,0 +1,94 @@
+/**
+ * Copyright © 2016-2018 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.dao.nosql;
+
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.ResultSetFuture;
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Created by ashvayka on 24.10.18.
+ */
+public class TbResultSetFuture implements ResultSetFuture {
+
+ private final SettableFuture<ResultSet> mainFuture;
+
+ public TbResultSetFuture(SettableFuture<ResultSet> mainFuture) {
+ this.mainFuture = mainFuture;
+ }
+
+ @Override
+ public ResultSet getUninterruptibly() {
+ return getSafe();
+ }
+
+ @Override
+ public ResultSet getUninterruptibly(long timeout, TimeUnit unit) throws TimeoutException {
+ return getSafe(timeout, unit);
+ }
+
+ @Override
+ public boolean cancel(boolean mayInterruptIfRunning) {
+ return mainFuture.cancel(mayInterruptIfRunning);
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return mainFuture.isCancelled();
+ }
+
+ @Override
+ public boolean isDone() {
+ return mainFuture.isDone();
+ }
+
+ @Override
+ public ResultSet get() throws InterruptedException, ExecutionException {
+ return mainFuture.get();
+ }
+
+ @Override
+ public ResultSet get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+ return mainFuture.get(timeout, unit);
+ }
+
+ @Override
+ public void addListener(Runnable listener, Executor executor) {
+ mainFuture.addListener(listener, executor);
+ }
+
+ private ResultSet getSafe() {
+ try {
+ return mainFuture.get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private ResultSet getSafe(long timeout, TimeUnit unit) throws TimeoutException {
+ try {
+ return mainFuture.get(timeout, unit);
+ } catch (InterruptedException | ExecutionException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java
index 5bd9175..04227a3 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.sql.timeseries;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
+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.ListeningExecutorService;
@@ -31,6 +32,7 @@ import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.Aggregation;
+import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
@@ -41,9 +43,9 @@ import org.thingsboard.server.dao.model.sql.TsKvEntity;
import org.thingsboard.server.dao.model.sql.TsKvLatestCompositeKey;
import org.thingsboard.server.dao.model.sql.TsKvLatestEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService;
+import org.thingsboard.server.dao.timeseries.SimpleListenableFuture;
import org.thingsboard.server.dao.timeseries.TimeseriesDao;
import org.thingsboard.server.dao.timeseries.TsInsertExecutorType;
-import org.thingsboard.server.dao.util.SqlDao;
import org.thingsboard.server.dao.util.SqlTsDao;
import javax.annotation.Nullable;
@@ -53,6 +55,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
@@ -64,6 +67,8 @@ import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
@SqlTsDao
public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService implements TimeseriesDao {
+ private static final String DESC_ORDER = "DESC";
+
@Value("${sql.ts_inserts_executor_type}")
private String insertExecutorType;
@@ -326,14 +331,72 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
@Override
public ListenableFuture<Void> removeLatest(EntityId entityId, DeleteTsKvQuery query) {
- TsKvLatestEntity latestEntity = new TsKvLatestEntity();
- latestEntity.setEntityType(entityId.getEntityType());
- latestEntity.setEntityId(fromTimeUUID(entityId.getId()));
- latestEntity.setKey(query.getKey());
- return service.submit(() -> {
- tsKvLatestRepository.delete(latestEntity);
- return null;
+ ListenableFuture<TsKvEntry> latestFuture = findLatest(entityId, query.getKey());
+
+ ListenableFuture<Boolean> booleanFuture = Futures.transform(latestFuture, tsKvEntry -> {
+ long ts = tsKvEntry.getTs();
+ return ts > query.getStartTs() && ts <= query.getEndTs();
+ }, service);
+
+ ListenableFuture<Void> removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
+ if (isRemove) {
+ TsKvLatestEntity latestEntity = new TsKvLatestEntity();
+ latestEntity.setEntityType(entityId.getEntityType());
+ latestEntity.setEntityId(fromTimeUUID(entityId.getId()));
+ latestEntity.setKey(query.getKey());
+ return service.submit(() -> {
+ tsKvLatestRepository.delete(latestEntity);
+ return null;
+ });
+ }
+ return Futures.immediateFuture(null);
+ }, service);
+
+ final SimpleListenableFuture<Void> resultFuture = new SimpleListenableFuture<>();
+ Futures.addCallback(removedLatestFuture, new FutureCallback<Void>() {
+ @Override
+ public void onSuccess(@Nullable Void result) {
+ if (query.getRewriteLatestIfDeleted()) {
+ ListenableFuture<Void> savedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
+ if (isRemove) {
+ return getNewLatestEntryFuture(entityId, query);
+ }
+ return Futures.immediateFuture(null);
+ }, service);
+
+ try {
+ resultFuture.set(savedLatestFuture.get());
+ } catch (InterruptedException | ExecutionException e) {
+ log.warn("Could not get latest saved value for [{}], {}", entityId, query.getKey(), e);
+ }
+ } else {
+ resultFuture.set(null);
+ }
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ log.warn("[{}] Failed to process remove of the latest value", entityId, t);
+ }
});
+ return resultFuture;
+ }
+
+ private ListenableFuture<Void> getNewLatestEntryFuture(EntityId entityId, DeleteTsKvQuery query) {
+ long startTs = 0;
+ long endTs = query.getStartTs() - 1;
+ ReadTsKvQuery findNewLatestQuery = new BaseReadTsKvQuery(query.getKey(), startTs, endTs, endTs - startTs, 1,
+ Aggregation.NONE, DESC_ORDER);
+ ListenableFuture<List<TsKvEntry>> future = findAllAsync(entityId, findNewLatestQuery);
+
+ return Futures.transformAsync(future, entryList -> {
+ if (entryList.size() == 1) {
+ return saveLatest(entityId, entryList.get(0));
+ } else {
+ log.trace("Could not find new latest value for [{}], key - {}", entityId, query.getKey());
+ }
+ return Futures.immediateFuture(null);
+ }, service);
}
@Override
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java
index 4c743e5..296d173 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java
@@ -47,7 +47,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
@Modifying
@Query("DELETE FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " +
"AND tskv.entityType = :entityType AND tskv.key = :entityKey " +
- "AND tskv.ts > :startTs AND tskv.ts < :endTs")
+ "AND tskv.ts > :startTs AND tskv.ts <= :endTs")
void delete(@Param("entityId") String entityId,
@Param("entityType") EntityType entityType,
@Param("entityKey") String key,
diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
index 709bfd5..fdc69f9 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
@@ -48,7 +48,6 @@ import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.nosql.CassandraAbstractAsyncDao;
-import org.thingsboard.server.dao.util.NoSqlDao;
import org.thingsboard.server.dao.util.NoSqlTsDao;
import javax.annotation.Nullable;
@@ -62,6 +61,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
+import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
@@ -434,14 +434,14 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
public ListenableFuture<Void> removeLatest(EntityId entityId, DeleteTsKvQuery query) {
ListenableFuture<TsKvEntry> latestEntryFuture = findLatest(entityId, query.getKey());
- ListenableFuture<Boolean> booleanFuture = Futures.transformAsync(latestEntryFuture, latestEntry -> {
+ ListenableFuture<Boolean> booleanFuture = Futures.transform(latestEntryFuture, latestEntry -> {
long ts = latestEntry.getTs();
- if (ts >= query.getStartTs() && ts <= query.getEndTs()) {
- return Futures.immediateFuture(true);
+ if (ts > query.getStartTs() && ts <= query.getEndTs()) {
+ return true;
} else {
log.trace("Won't be deleted latest value for [{}], key - {}", entityId, query.getKey());
}
- return Futures.immediateFuture(false);
+ return false;
}, readResultsProcessingExecutor);
ListenableFuture<Void> removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
@@ -451,18 +451,34 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
return Futures.immediateFuture(null);
}, readResultsProcessingExecutor);
- if (query.getRewriteLatestIfDeleted()) {
- ListenableFuture<Void> savedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
- if (isRemove) {
- return getNewLatestEntryFuture(entityId, query);
+ final SimpleListenableFuture<Void> resultFuture = new SimpleListenableFuture<>();
+ Futures.addCallback(removedLatestFuture, new FutureCallback<Void>() {
+ @Override
+ public void onSuccess(@Nullable Void result) {
+ if (query.getRewriteLatestIfDeleted()) {
+ ListenableFuture<Void> savedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
+ if (isRemove) {
+ return getNewLatestEntryFuture(entityId, query);
+ }
+ return Futures.immediateFuture(null);
+ }, readResultsProcessingExecutor);
+
+ try {
+ resultFuture.set(savedLatestFuture.get());
+ } catch (InterruptedException | ExecutionException e) {
+ log.warn("Could not get latest saved value for [{}], {}", entityId, query.getKey(), e);
+ }
+ } else {
+ resultFuture.set(null);
}
- return Futures.immediateFuture(null);
- }, readResultsProcessingExecutor);
+ }
- return Futures.transformAsync(Futures.allAsList(Arrays.asList(savedLatestFuture, removedLatestFuture)),
- list -> Futures.immediateFuture(null), readResultsProcessingExecutor);
- }
- return removedLatestFuture;
+ @Override
+ public void onFailure(Throwable t) {
+ log.warn("[{}] Failed to process remove of the latest value", entityId, t);
+ }
+ });
+ return resultFuture;
}
private ListenableFuture<Void> getNewLatestEntryFuture(EntityId entityId, DeleteTsKvQuery query) {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java b/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java
new file mode 100644
index 0000000..96d3870
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java
@@ -0,0 +1,175 @@
+/**
+ * Copyright © 2016-2018 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.dao.util;
+
+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.SettableFuture;
+import lombok.extern.slf4j.Slf4j;
+
+import javax.annotation.Nullable;
+import java.util.UUID;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Created by ashvayka on 24.10.18.
+ */
+@Slf4j
+public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extends ListenableFuture<V>, V> implements BufferedRateExecutor<T, F> {
+
+ private final long maxWaitTime;
+ private final long pollMs;
+ private final BlockingQueue<AsyncTaskContext<T, V>> queue;
+ private final ExecutorService dispatcherExecutor;
+ private final ExecutorService callbackExecutor;
+ private final ScheduledExecutorService timeoutExecutor;
+ private final int concurrencyLimit;
+
+ protected final AtomicInteger concurrencyLevel = new AtomicInteger();
+ protected final AtomicInteger totalAdded = new AtomicInteger();
+ protected final AtomicInteger totalLaunched = new AtomicInteger();
+ protected final AtomicInteger totalReleased = new AtomicInteger();
+ protected final AtomicInteger totalFailed = new AtomicInteger();
+ protected final AtomicInteger totalExpired = new AtomicInteger();
+ protected final AtomicInteger totalRejected = new AtomicInteger();
+
+ public AbstractBufferedRateExecutor(int queueLimit, int concurrencyLimit, long maxWaitTime, int dispatcherThreads, int callbackThreads, long pollMs) {
+ this.maxWaitTime = maxWaitTime;
+ this.pollMs = pollMs;
+ this.concurrencyLimit = concurrencyLimit;
+ this.queue = new LinkedBlockingDeque<>(queueLimit);
+ this.dispatcherExecutor = Executors.newFixedThreadPool(dispatcherThreads);
+ this.callbackExecutor = Executors.newFixedThreadPool(callbackThreads);
+ this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor();
+ for (int i = 0; i < dispatcherThreads; i++) {
+ dispatcherExecutor.submit(this::dispatch);
+ }
+ }
+
+ @Override
+ public F submit(T task) {
+ SettableFuture<V> settableFuture = create();
+ F result = wrap(task, settableFuture);
+ try {
+ totalAdded.incrementAndGet();
+ queue.add(new AsyncTaskContext<>(UUID.randomUUID(), task, settableFuture, System.currentTimeMillis()));
+ } catch (IllegalStateException e) {
+ totalRejected.incrementAndGet();
+ settableFuture.setException(e);
+ }
+ return result;
+ }
+
+ public void stop() {
+ if (dispatcherExecutor != null) {
+ dispatcherExecutor.shutdownNow();
+ }
+ if (callbackExecutor != null) {
+ callbackExecutor.shutdownNow();
+ }
+ if (timeoutExecutor != null) {
+ timeoutExecutor.shutdownNow();
+ }
+ }
+
+ protected abstract SettableFuture<V> create();
+
+ protected abstract F wrap(T task, SettableFuture<V> future);
+
+ protected abstract ListenableFuture<V> execute(AsyncTaskContext<T, V> taskCtx);
+
+ private void dispatch() {
+ log.info("Buffered rate executor thread started");
+ while (!Thread.interrupted()) {
+ int curLvl = concurrencyLevel.get();
+ AsyncTaskContext<T, V> taskCtx = null;
+ try {
+ if (curLvl <= concurrencyLimit) {
+ taskCtx = queue.take();
+ final AsyncTaskContext<T, V> finalTaskCtx = taskCtx;
+ logTask("Processing", finalTaskCtx);
+ concurrencyLevel.incrementAndGet();
+ long timeout = finalTaskCtx.getCreateTime() + maxWaitTime - System.currentTimeMillis();
+ if (timeout > 0) {
+ totalLaunched.incrementAndGet();
+ ListenableFuture<V> result = execute(finalTaskCtx);
+ result = Futures.withTimeout(result, timeout, TimeUnit.MILLISECONDS, timeoutExecutor);
+ Futures.addCallback(result, new FutureCallback<V>() {
+ @Override
+ public void onSuccess(@Nullable V result) {
+ logTask("Releasing", finalTaskCtx);
+ totalReleased.incrementAndGet();
+ concurrencyLevel.decrementAndGet();
+ finalTaskCtx.getFuture().set(result);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ if (t instanceof TimeoutException) {
+ logTask("Expired During Execution", finalTaskCtx);
+ } else {
+ logTask("Failed", finalTaskCtx);
+ }
+ totalFailed.incrementAndGet();
+ concurrencyLevel.decrementAndGet();
+ finalTaskCtx.getFuture().setException(t);
+ log.debug("[{}] Failed to execute task: {}", finalTaskCtx.getId(), finalTaskCtx.getTask(), t);
+ }
+ }, callbackExecutor);
+ } else {
+ logTask("Expired Before Execution", finalTaskCtx);
+ totalExpired.incrementAndGet();
+ concurrencyLevel.decrementAndGet();
+ taskCtx.getFuture().setException(new TimeoutException());
+ }
+ } else {
+ Thread.sleep(pollMs);
+ }
+ } catch (InterruptedException e) {
+ break;
+ } catch (Throwable e) {
+ if (taskCtx != null) {
+ log.debug("[{}] Failed to execute task: {}", taskCtx.getId(), taskCtx, e);
+ totalFailed.incrementAndGet();
+ concurrencyLevel.decrementAndGet();
+ } else {
+ log.debug("Failed to queue task:", e);
+ }
+ }
+ }
+ log.info("Buffered rate executor thread stopped");
+ }
+
+ private void logTask(String action, AsyncTaskContext<T, V> taskCtx) {
+ if (log.isTraceEnabled()) {
+ log.trace("[{}] {} task: {}", taskCtx.getId(), action, taskCtx);
+ } else {
+ log.debug("[{}] {} task", taskCtx.getId(), action);
+ }
+ }
+
+ protected int getQueueSize() {
+ return queue.size();
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/AsyncTask.java b/dao/src/main/java/org/thingsboard/server/dao/util/AsyncTask.java
new file mode 100644
index 0000000..672e4f4
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/util/AsyncTask.java
@@ -0,0 +1,22 @@
+/**
+ * Copyright © 2016-2018 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.dao.util;
+
+/**
+ * Created by ashvayka on 24.10.18.
+ */
+public interface AsyncTask {
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/AsyncTaskContext.java b/dao/src/main/java/org/thingsboard/server/dao/util/AsyncTaskContext.java
new file mode 100644
index 0000000..593b3fb
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/util/AsyncTaskContext.java
@@ -0,0 +1,34 @@
+/**
+ * Copyright © 2016-2018 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.dao.util;
+
+import com.google.common.util.concurrent.SettableFuture;
+import lombok.Data;
+
+import java.util.UUID;
+
+/**
+ * Created by ashvayka on 24.10.18.
+ */
+@Data
+public class AsyncTaskContext<T extends AsyncTask, V> {
+
+ private final UUID id;
+ private final T task;
+ private final SettableFuture<V> future;
+ private final long createTime;
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateExecutor.java b/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateExecutor.java
new file mode 100644
index 0000000..21f4cd0
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateExecutor.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright © 2016-2018 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.dao.util;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Created by ashvayka on 24.10.18.
+ */
+public interface BufferedRateExecutor<T extends AsyncTask, F extends ListenableFuture> {
+
+ F submit(T task);
+
+}
diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql
index 0b9c853..fc23832 100644
--- a/dao/src/main/resources/sql/schema-entities.sql
+++ b/dao/src/main/resources/sql/schema-entities.sql
@@ -78,7 +78,7 @@ CREATE TABLE IF NOT EXISTS attribute_kv (
CREATE TABLE IF NOT EXISTS component_descriptor (
id varchar(31) NOT NULL CONSTRAINT component_descriptor_pkey PRIMARY KEY,
actions varchar(255),
- clazz varchar,
+ clazz varchar UNIQUE,
configuration_descriptor varchar,
name varchar(255),
scope varchar(255),
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java
index 88f4d84..81de40a 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java
@@ -152,7 +152,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
}
@Test
- public void testDeleteDeviceTsData() throws Exception {
+ public void testDeleteDeviceTsDataWithoutOverwritingLatest() throws Exception {
DeviceId deviceId = new DeviceId(UUIDs.timeBased());
saveEntries(deviceId, 10000);
@@ -172,6 +172,26 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
}
@Test
+ public void testDeleteDeviceTsDataWithOverwritingLatest() throws Exception {
+ DeviceId deviceId = new DeviceId(UUIDs.timeBased());
+
+ saveEntries(deviceId, 10000);
+ saveEntries(deviceId, 20000);
+ saveEntries(deviceId, 30000);
+ saveEntries(deviceId, 40000);
+
+ tsService.remove(deviceId, Collections.singletonList(
+ new BaseDeleteTsKvQuery(STRING_KEY, 25000, 45000, true))).get();
+
+ List<TsKvEntry> list = tsService.findAll(deviceId, Collections.singletonList(
+ new BaseReadTsKvQuery(STRING_KEY, 5000, 45000, 10000, 10, Aggregation.NONE))).get();
+ Assert.assertEquals(2, list.size());
+
+ List<TsKvEntry> latest = tsService.findLatest(deviceId, Collections.singletonList(STRING_KEY)).get();
+ Assert.assertEquals(20000, latest.get(0).getTs());
+ }
+
+ @Test
public void testFindDeviceTsData() throws Exception {
DeviceId deviceId = new DeviceId(UUIDs.timeBased());
List<TsKvEntry> entries = new ArrayList<>();
docker/.env 25(+15 -10)
diff --git a/docker/.env b/docker/.env
index e330632..c03845d 100644
--- a/docker/.env
+++ b/docker/.env
@@ -1,13 +1,18 @@
-# cassandra environment variables
-CASSANDRA_DATA_DIR=/home/docker/cassandra_volume
-# postgres environment variables
-POSTGRES_DATA_DIR=/home/docker/postgres_volume
-POSTGRES_DB=thingsboard
+DOCKER_REPO=thingsboard
-# hsqldb environment variables
-HSQLDB_DATA_DIR=/home/docker/hsqldb_volume
+JS_EXECUTOR_DOCKER_NAME=tb-js-executor
+TB_NODE_DOCKER_NAME=tb-node
+WEB_UI_DOCKER_NAME=tb-web-ui
+MQTT_TRANSPORT_DOCKER_NAME=tb-mqtt-transport
+HTTP_TRANSPORT_DOCKER_NAME=tb-http-transport
+COAP_TRANSPORT_DOCKER_NAME=tb-coap-transport
-# environment variables for schema init and insert system and demo data
-ADD_SCHEMA_AND_SYSTEM_DATA=false
-ADD_DEMO_DATA=false
\ No newline at end of file
+TB_VERSION=latest
+
+# Database used by ThingsBoard, can be either postgres (PostgreSQL) or cassandra (Cassandra).
+# According to the database type corresponding docker service will be deployed (see docker-compose.postgres.yml, docker-compose.cassandra.yml for details).
+
+DATABASE=postgres
+
+KAFKA_TOPICS="js.eval.requests:100:1:delete --config=retention.ms=60000 --config=segment.bytes=26214400 --config=retention.bytes=104857600,tb.transport.api.requests:30:1:delete --config=retention.ms=60000 --config=segment.bytes=26214400 --config=retention.bytes=104857600,tb.rule-engine:30:1"
docker/compose-utils.sh 50(+50 -0)
diff --git a/docker/compose-utils.sh b/docker/compose-utils.sh
new file mode 100755
index 0000000..d1dc20e
--- /dev/null
+++ b/docker/compose-utils.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+#
+# Copyright © 2016-2018 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.
+#
+
+function additionalComposeArgs() {
+ source .env
+ ADDITIONAL_COMPOSE_ARGS=""
+ case $DATABASE in
+ postgres)
+ ADDITIONAL_COMPOSE_ARGS="-f docker-compose.postgres.yml"
+ ;;
+ cassandra)
+ ADDITIONAL_COMPOSE_ARGS="-f docker-compose.cassandra.yml"
+ ;;
+ *)
+ echo "Unknown DATABASE value specified: '${DATABASE}'. Should be either postgres or cassandra." >&2
+ exit 1
+ esac
+ echo $ADDITIONAL_COMPOSE_ARGS
+}
+
+function additionalStartupServices() {
+ source .env
+ ADDITIONAL_STARTUP_SERVICES=""
+ case $DATABASE in
+ postgres)
+ ADDITIONAL_STARTUP_SERVICES=postgres
+ ;;
+ cassandra)
+ ADDITIONAL_STARTUP_SERVICES=cassandra
+ ;;
+ *)
+ echo "Unknown DATABASE value specified: '${DATABASE}'. Should be either postgres or cassandra." >&2
+ exit 1
+ esac
+ echo $ADDITIONAL_STARTUP_SERVICES
+}
docker/docker-compose.yml 173(+148 -25)
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 89a8369..1741db3 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -14,40 +14,163 @@
# limitations under the License.
#
-version: '2'
+
+version: '2.2'
services:
- tb:
- image: "thingsboard/application:2.1.0"
+ zookeeper:
+ restart: always
+ image: "zookeeper:3.5"
ports:
- - "8080:8080"
- - "1883:1883"
- - "5683:5683/udp"
+ - "2181"
+ kafka:
+ restart: always
+ image: "wurstmeister/kafka"
+ ports:
+ - "9092:9092"
env_file:
- - tb.env
+ - kafka.env
+ depends_on:
+ - zookeeper
+ redis:
+ image: redis:4.0
+ ports:
+ - "6379"
+ tb-js-executor:
+ restart: always
+ image: "${DOCKER_REPO}/${JS_EXECUTOR_DOCKER_NAME}:${TB_VERSION}"
+ scale: 20
+ env_file:
+ - tb-js-executor.env
+ depends_on:
+ - kafka
+ tb1:
+ restart: always
+ image: "${DOCKER_REPO}/${TB_NODE_DOCKER_NAME}:${TB_VERSION}"
+ ports:
+ - "8080"
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "200m"
+ max-file: "30"
environment:
- - ADD_SCHEMA_AND_SYSTEM_DATA=${ADD_SCHEMA_AND_SYSTEM_DATA}
- - ADD_DEMO_DATA=${ADD_DEMO_DATA}
+ TB_HOST: tb1
+ env_file:
+ - tb-node.env
volumes:
- - "${HSQLDB_DATA_DIR}:/usr/share/thingsboard/data/sql"
- entrypoint: /run-application.sh
- cassandra:
- image: "cassandra:3.11.2"
+ - ./tb-node/conf:/config
+ - ./tb-node/log:/var/log/thingsboard
+ depends_on:
+ - kafka
+ - redis
+ tb2:
+ restart: always
+ image: "${DOCKER_REPO}/${TB_NODE_DOCKER_NAME}:${TB_VERSION}"
ports:
- - "9042"
- - "9160"
+ - "8080"
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "200m"
+ max-file: "30"
+ environment:
+ TB_HOST: tb2
+ env_file:
+ - tb-node.env
volumes:
- - "${CASSANDRA_DATA_DIR}:/var/lib/cassandra"
- zk:
- image: "zookeeper:3.4.10"
+ - ./tb-node/conf:/config
+ - ./tb-node/log:/var/log/thingsboard
+ depends_on:
+ - kafka
+ - redis
+ tb-mqtt-transport1:
+ restart: always
+ image: "${DOCKER_REPO}/${MQTT_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
ports:
- - "2181"
+ - "1883"
+ env_file:
+ - tb-mqtt-transport.env
+ depends_on:
+ - kafka
+ tb-mqtt-transport2:
restart: always
- postgres:
- image: "postgres:9.6"
+ image: "${DOCKER_REPO}/${MQTT_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
ports:
- - "5432"
- environment:
- - POSTGRES_DB=${POSTGRES_DB}
+ - "1883"
+ env_file:
+ - tb-mqtt-transport.env
+ depends_on:
+ - kafka
+ tb-http-transport1:
+ restart: always
+ image: "${DOCKER_REPO}/${HTTP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
+ ports:
+ - "8081"
+ env_file:
+ - tb-http-transport.env
+ depends_on:
+ - kafka
+ tb-http-transport2:
+ restart: always
+ image: "${DOCKER_REPO}/${HTTP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
+ ports:
+ - "8081"
+ env_file:
+ - tb-http-transport.env
+ depends_on:
+ - kafka
+ tb-coap-transport:
+ restart: always
+ image: "${DOCKER_REPO}/${COAP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
+ ports:
+ - "5683:5683/udp"
+ env_file:
+ - tb-coap-transport.env
+ depends_on:
+ - kafka
+ tb-web-ui1:
+ restart: always
+ image: "${DOCKER_REPO}/${WEB_UI_DOCKER_NAME}:${TB_VERSION}"
+ ports:
+ - "8080"
+ env_file:
+ - tb-web-ui.env
+ tb-web-ui2:
+ restart: always
+ image: "${DOCKER_REPO}/${WEB_UI_DOCKER_NAME}:${TB_VERSION}"
+ ports:
+ - "8080"
+ env_file:
+ - tb-web-ui.env
+ haproxy:
+ restart: always
+ container_name: haproxy-certbot
+ image: xalauc/haproxy-certbot:1.7.9
volumes:
- - "${POSTGRES_DATA_DIR}:/var/lib/postgresql/data"
+ - ./haproxy/config:/config
+ - ./haproxy/letsencrypt:/etc/letsencrypt
+ - ./haproxy/certs.d:/usr/local/etc/haproxy/certs.d
+ ports:
+ - "80:80"
+ - "8080"
+ - "443:443"
+ - "1883:1883"
+ - "9999:9999"
+ cap_add:
+ - NET_ADMIN
+ environment:
+ HTTP_PORT: 80
+ HTTPS_PORT: 443
+ MQTT_PORT: 1883
+ TB_API_PORT: 8080
+ FORCE_HTTPS_REDIRECT: "false"
+ links:
+ - tb1
+ - tb2
+ - tb-web-ui1
+ - tb-web-ui2
+ - tb-mqtt-transport1
+ - tb-mqtt-transport2
+ - tb-http-transport1
+ - tb-http-transport2
docker/kafka.env 12(+12 -0)
diff --git a/docker/kafka.env b/docker/kafka.env
new file mode 100644
index 0000000..69fbdf6
--- /dev/null
+++ b/docker/kafka.env
@@ -0,0 +1,12 @@
+
+KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181
+KAFKA_LISTENERS=INSIDE://:9093,OUTSIDE://:9092
+KAFKA_ADVERTISED_LISTENERS=INSIDE://:9093,OUTSIDE://kafka:9092
+KAFKA_LISTENER_SECURITY_PROTOCOL_MAP=INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT
+KAFKA_INTER_BROKER_LISTENER_NAME=INSIDE
+KAFKA_CREATE_TOPICS=${KAFKA_TOPICS}
+KAFKA_AUTO_CREATE_TOPICS_ENABLE=false
+KAFKA_LOG_RETENTION_BYTES=1073741824
+KAFKA_LOG_SEGMENT_BYTES=268435456
+KAFKA_LOG_RETENTION_MS=300000
+KAFKA_LOG_CLEANUP_POLICY=delete
docker/README.md 95(+95 -0)
diff --git a/docker/README.md b/docker/README.md
new file mode 100644
index 0000000..c43a136
--- /dev/null
+++ b/docker/README.md
@@ -0,0 +1,95 @@
+# Docker configuration for ThingsBoard Microservices
+
+This folder containing scripts and Docker Compose configurations to run ThingsBoard in Microservices mode.
+
+## Prerequisites
+
+ThingsBoard Microservices are running in dockerized environment.
+Before starting please make sure [Docker CE](https://docs.docker.com/install/) and [Docker Compose](https://docs.docker.com/compose/install/) are installed in your system.
+
+## Installation
+
+Before performing initial installation you can configure the type of database to be used with ThinsBoard.
+In order to set database type change the value of `DATABASE` variable in `.env` file to one of the following:
+
+- `postgres` - use PostgreSQL database;
+- `cassandra` - use Cassandra database;
+
+**NOTE**: According to the database type corresponding docker service will be deployed (see `docker-compose.postgres.yml`, `docker-compose.cassandra.yml` for details).
+
+Execute the following command to run installation:
+
+`
+$ ./docker-install-tb.sh --loadDemo
+`
+
+Where:
+
+- `--loadDemo` - optional argument. Whether to load additional demo data.
+
+## Running
+
+Execute the following command to start services:
+
+`
+$ ./docker-start-services.sh
+`
+
+After a while when all services will be successfully started you can open `http://{your-host-ip}` in you browser (for ex. `http://localhost`).
+You should see ThingsBoard login page.
+
+Use the following default credentials:
+
+- **Systen Administrator**: sysadmin@thingsboard.org / sysadmin
+
+If you installed DataBase with demo data (using `--loadDemo` flag) you can also use the following credentials:
+
+- **Tenant Administrator**: tenant@thingsboard.org / tenant
+- **Customer User**: customer@thingsboard.org / customer
+
+In case of any issues you can examine service logs for errors.
+For example to see ThingsBoard node logs execute the following command:
+
+`
+$ docker-compose logs -f tb1
+`
+
+Or use `docker-compose ps` to see the state of all the containers.
+Use `docker-compose logs --f` to inspect the logs of all running services.
+See [docker-compose logs](https://docs.docker.com/compose/reference/logs/) command reference for details.
+
+Execute the following command to stop services:
+
+`
+$ ./docker-stop-services.sh
+`
+
+Execute the following command to stop and completely remove deployed docker containers:
+
+`
+$ ./docker-remove-services.sh
+`
+
+Execute the following command to update particular or all services (pull newer docker image and rebuild container):
+
+`
+$ ./docker-update-service.sh [SERVICE...]
+`
+
+Where:
+
+- `[SERVICE...]` - list of services to update (defined in docker-compose configurations). If not specified all services will be updated.
+
+## Upgrading
+
+In case when database upgrade is needed, execute the following commands:
+
+```
+$ ./docker-stop-services.sh
+$ ./docker-upgrade-tb.sh --fromVersion=[FROM_VERSION]
+$ ./docker-start-services.sh
+```
+
+Where:
+
+- `FROM_VERSION` - from which version upgrade should be started. See [Upgrade Instructions](https://thingsboard.io/docs/user-guide/install/upgrade-instructions) for valid `fromVersion` values.
docker/tb-node.cassandra.env 5(+5 -0)
diff --git a/docker/tb-node.cassandra.env b/docker/tb-node.cassandra.env
new file mode 100644
index 0000000..8d813b9
--- /dev/null
+++ b/docker/tb-node.cassandra.env
@@ -0,0 +1,5 @@
+# ThingsBoard server configuration for Cassandra database
+
+DATABASE_TS_TYPE=cassandra
+DATABASE_ENTITIES_TYPE=cassandra
+CASSANDRA_URL=cassandra:9042
docker/tb-node.env 10(+10 -0)
diff --git a/docker/tb-node.env b/docker/tb-node.env
new file mode 100644
index 0000000..ca945ab
--- /dev/null
+++ b/docker/tb-node.env
@@ -0,0 +1,10 @@
+# ThingsBoard server configuration
+
+ZOOKEEPER_ENABLED=true
+ZOOKEEPER_URL=zookeeper:2181
+RPC_HOST=${TB_HOST}
+TB_KAFKA_SERVERS=kafka:9092
+JS_EVALUATOR=remote
+TRANSPORT_TYPE=remote
+CACHE_TYPE=redis
+REDIS_HOST=redis
docker/tb-node.postgres.env 9(+9 -0)
diff --git a/docker/tb-node.postgres.env b/docker/tb-node.postgres.env
new file mode 100644
index 0000000..9fa79e3
--- /dev/null
+++ b/docker/tb-node.postgres.env
@@ -0,0 +1,9 @@
+# ThingsBoard server configuration for PostgreSQL database
+
+DATABASE_TS_TYPE=sql
+DATABASE_ENTITIES_TYPE=sql
+SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.PostgreSQLDialect
+SPRING_DRIVER_CLASS_NAME=org.postgresql.Driver
+SPRING_DATASOURCE_URL=jdbc:postgresql://postgres:5432/thingsboard
+SPRING_DATASOURCE_USERNAME=postgres
+SPRING_DATASOURCE_PASSWORD=postgres
docker/tb-node/conf/logback.xml 51(+51 -0)
diff --git a/docker/tb-node/conf/logback.xml b/docker/tb-node/conf/logback.xml
new file mode 100644
index 0000000..1c69f53
--- /dev/null
+++ b/docker/tb-node/conf/logback.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+
+ Copyright © 2016-2018 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.
+
+-->
+<!DOCTYPE configuration>
+<configuration scan="true" scanPeriod="10 seconds">
+
+ <appender name="fileLogAppender"
+ class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>/var/log/thingsboard/${TB_HOST}/thingsboard.log</file>
+ <rollingPolicy
+ class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+ <fileNamePattern>/var/log/thingsboard/thingsboard.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
+ <maxFileSize>100MB</maxFileSize>
+ <maxHistory>30</maxHistory>
+ <totalSizeCap>3GB</totalSizeCap>
+ </rollingPolicy>
+ <encoder>
+ <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <encoder>
+ <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
+ </encoder>
+ </appender>
+
+ <logger name="org.thingsboard.server" level="INFO" />
+ <logger name="akka" level="INFO" />
+
+ <root level="INFO">
+ <appender-ref ref="fileLogAppender"/>
+ <appender-ref ref="STDOUT"/>
+ </root>
+
+</configuration>
\ No newline at end of file
docker/tb-node/conf/thingsboard.conf 24(+24 -0)
diff --git a/docker/tb-node/conf/thingsboard.conf b/docker/tb-node/conf/thingsboard.conf
new file mode 100644
index 0000000..aa430b4
--- /dev/null
+++ b/docker/tb-node/conf/thingsboard.conf
@@ -0,0 +1,24 @@
+#
+# Copyright © 2016-2018 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.
+#
+
+export JAVA_OPTS="$JAVA_OPTS -Dplatform=deb -Dinstall.data_dir=/usr/share/thingsboard/data"
+export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/thingsboard/${TB_HOST}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
+export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"
+export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
+export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"
+export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError"
+export LOG_FILENAME=thingsboard.out
+export LOADER_PATH=/usr/share/thingsboard/conf,/usr/share/thingsboard/extensions
msa/js-executor/pom.xml 67(+59 -8)
diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml
index 6f11df1..e673a53 100644
--- a/msa/js-executor/pom.xml
+++ b/msa/js-executor/pom.xml
@@ -287,16 +287,27 @@
<goals>
<goal>build</goal>
</goals>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>${docker.repo}/${docker.name}</repository>
+ <verbose>true</verbose>
+ <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
+ <contextDirectory>${project.build.directory}</contextDirectory>
+ </configuration>
+ </execution>
+ <execution>
+ <id>tag-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>tag</goal>
+ </goals>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>${docker.repo}/${docker.name}</repository>
+ <tag>${project.version}</tag>
+ </configuration>
</execution>
</executions>
- <configuration>
- <skip>${dockerfile.skip}</skip>
- <repository>${docker.repo}/${docker.name}</repository>
- <tag>${project.version}</tag>
- <verbose>true</verbose>
- <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
- <contextDirectory>${project.build.directory}</contextDirectory>
- </configuration>
</plugin>
</plugins>
</build>
@@ -334,6 +345,46 @@
</plugins>
</build>
</profile>
+ <profile>
+ <id>push-docker-image</id>
+ <activation>
+ <property>
+ <name>push-docker-image</name>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.spotify</groupId>
+ <artifactId>dockerfile-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>push-latest-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <tag>latest</tag>
+ <repository>${docker.repo}/${docker.name}</repository>
+ </configuration>
+ </execution>
+ <execution>
+ <id>push-version-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <tag>${project.version}</tag>
+ <repository>${docker.repo}/${docker.name}</repository>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
</profiles>
<repositories>
<repository>
msa/pom.xml 4(+2 -2)
diff --git a/msa/pom.xml b/msa/pom.xml
index d357677..4192660 100644
--- a/msa/pom.xml
+++ b/msa/pom.xml
@@ -23,7 +23,6 @@
<version>2.2.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
- <groupId>org.thingsboard</groupId>
<artifactId>msa</artifactId>
<packaging>pom</packaging>
@@ -32,11 +31,12 @@
<properties>
<main.dir>${basedir}/..</main.dir>
- <docker.repo>local-maven-build</docker.repo>
+ <docker.repo>thingsboard</docker.repo>
<dockerfile.skip>true</dockerfile.skip>
</properties>
<modules>
+ <module>tb</module>
<module>js-executor</module>
<module>web-ui</module>
<module>tb-node</module>
msa/tb/docker/install-tb.sh 56(+56 -0)
diff --git a/msa/tb/docker/install-tb.sh b/msa/tb/docker/install-tb.sh
new file mode 100644
index 0000000..e22ec58
--- /dev/null
+++ b/msa/tb/docker/install-tb.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+#
+# Copyright © 2016-2018 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.
+#
+
+while [[ $# -gt 0 ]]
+do
+key="$1"
+
+case $key in
+ --loadDemo)
+ LOAD_DEMO=true
+ shift # past argument
+ ;;
+ *)
+ # unknown option
+ ;;
+esac
+shift # past argument or value
+done
+
+if [ "$LOAD_DEMO" == "true" ]; then
+ loadDemo=true
+else
+ loadDemo=false
+fi
+
+CONF_FOLDER="${pkg.installFolder}/conf"
+jarfile=${pkg.installFolder}/bin/${pkg.name}.jar
+configfile=${pkg.name}.conf
+upgradeversion=${DATA_FOLDER}/.upgradeversion
+
+source "${CONF_FOLDER}/${configfile}"
+
+echo "Starting ThingsBoard installation ..."
+
+java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \
+ -Dinstall.load_demo=${loadDemo} \
+ -Dspring.jpa.hibernate.ddl-auto=none \
+ -Dinstall.upgrade=false \
+ -Dlogging.config=/usr/share/thingsboard/bin/install/logback.xml \
+ org.springframework.boot.loader.PropertiesLauncher
+
+echo "${pkg.upgradeVersion}" > ${upgradeversion}
msa/tb/docker/start-tb.sh 39(+39 -0)
diff --git a/msa/tb/docker/start-tb.sh b/msa/tb/docker/start-tb.sh
new file mode 100755
index 0000000..37f5e3a
--- /dev/null
+++ b/msa/tb/docker/start-tb.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+#
+# Copyright © 2016-2018 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.
+#
+
+start-db.sh
+
+CONF_FOLDER="${pkg.installFolder}/conf"
+jarfile=${pkg.installFolder}/bin/${pkg.name}.jar
+configfile=${pkg.name}.conf
+firstlaunch=${DATA_FOLDER}/.firstlaunch
+
+source "${CONF_FOLDER}/${configfile}"
+
+if [ ! -f ${firstlaunch} ]; then
+ install-tb.sh --loadDemo
+ touch ${firstlaunch}
+fi
+
+echo "Starting ThingsBoard ..."
+
+java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardServerApplication \
+ -Dspring.jpa.hibernate.ddl-auto=none \
+ -Dlogging.config=${CONF_FOLDER}/logback.xml \
+ org.springframework.boot.loader.PropertiesLauncher
+
+stop-db.sh
\ No newline at end of file
msa/tb/docker-cassandra/Dockerfile 59(+59 -0)
diff --git a/msa/tb/docker-cassandra/Dockerfile b/msa/tb/docker-cassandra/Dockerfile
new file mode 100644
index 0000000..a15408d
--- /dev/null
+++ b/msa/tb/docker-cassandra/Dockerfile
@@ -0,0 +1,59 @@
+#
+# Copyright © 2016-2018 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.
+#
+
+FROM openjdk:8-jdk
+
+RUN apt-get update
+RUN apt-get install -y curl nmap procps
+RUN echo 'deb http://www.apache.org/dist/cassandra/debian 311x main' | tee --append /etc/apt/sources.list.d/cassandra.list > /dev/null
+RUN curl https://www.apache.org/dist/cassandra/KEYS | apt-key add -
+RUN apt-get update
+RUN apt-get install -y cassandra cassandra-tools
+RUN update-rc.d cassandra disable
+RUN sed -i.old '/ulimit/d' /etc/init.d/cassandra
+
+COPY logback.xml ${pkg.name}.conf start-db.sh stop-db.sh start-tb.sh upgrade-tb.sh install-tb.sh ${pkg.name}.deb /tmp/
+
+RUN chmod a+x /tmp/*.sh \
+ && mv /tmp/start-tb.sh /usr/bin \
+ && mv /tmp/upgrade-tb.sh /usr/bin \
+ && mv /tmp/install-tb.sh /usr/bin \
+ && mv /tmp/start-db.sh /usr/bin \
+ && mv /tmp/stop-db.sh /usr/bin
+
+RUN dpkg -i /tmp/${pkg.name}.deb
+
+RUN update-rc.d ${pkg.name} disable
+
+RUN mv /tmp/logback.xml ${pkg.installFolder}/conf \
+ && mv /tmp/${pkg.name}.conf ${pkg.installFolder}/conf
+
+ENV DATA_FOLDER=/data
+
+ENV HTTP_BIND_PORT=9090
+ENV DATABASE_TS_TYPE=cassandra
+ENV DATABASE_ENTITIES_TYPE=cassandra
+
+ENV CASSANDRA_HOST=localhost
+ENV CASSANDRA_PORT=9042
+
+EXPOSE 9090
+EXPOSE 1883
+EXPOSE 5683/udp
+
+VOLUME ["/data"]
+
+CMD ["start-tb.sh"]
msa/tb/docker-postgres/Dockerfile 62(+62 -0)
diff --git a/msa/tb/docker-postgres/Dockerfile b/msa/tb/docker-postgres/Dockerfile
new file mode 100644
index 0000000..c1b3f02
--- /dev/null
+++ b/msa/tb/docker-postgres/Dockerfile
@@ -0,0 +1,62 @@
+#
+# Copyright © 2016-2018 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.
+#
+
+FROM openjdk:8-jdk
+
+RUN apt-get update
+RUN apt-get install -y postgresql postgresql-contrib
+RUN update-rc.d postgresql disable
+
+RUN mkdir -p /var/log/postgres
+RUN chown -R postgres:postgres /var/log/postgres
+
+COPY logback.xml ${pkg.name}.conf start-db.sh stop-db.sh start-tb.sh upgrade-tb.sh install-tb.sh ${pkg.name}.deb /tmp/
+
+RUN chmod a+x /tmp/*.sh \
+ && mv /tmp/start-tb.sh /usr/bin \
+ && mv /tmp/upgrade-tb.sh /usr/bin \
+ && mv /tmp/install-tb.sh /usr/bin \
+ && mv /tmp/start-db.sh /usr/bin \
+ && mv /tmp/stop-db.sh /usr/bin
+
+RUN dpkg -i /tmp/${pkg.name}.deb
+
+RUN update-rc.d ${pkg.name} disable
+
+RUN mv /tmp/logback.xml ${pkg.installFolder}/conf \
+ && mv /tmp/${pkg.name}.conf ${pkg.installFolder}/conf
+
+ENV DATA_FOLDER=/data
+
+ENV HTTP_BIND_PORT=9090
+ENV DATABASE_TS_TYPE=sql
+ENV DATABASE_ENTITIES_TYPE=sql
+
+ENV PGDATA=/data/db
+
+ENV SPRING_JPA_DATABASE_PLATFORM=org.hibernate.dialect.PostgreSQLDialect
+ENV SPRING_DRIVER_CLASS_NAME=org.postgresql.Driver
+ENV SPRING_DATASOURCE_URL=jdbc:postgresql://localhost:5432/thingsboard
+ENV SPRING_DATASOURCE_USERNAME=postgres
+ENV SPRING_DATASOURCE_PASSWORD=postgres
+
+EXPOSE 9090
+EXPOSE 1883
+EXPOSE 5683/udp
+
+VOLUME ["/data"]
+
+CMD ["start-tb.sh"]
msa/tb/docker-tb/Dockerfile 48(+48 -0)
diff --git a/msa/tb/docker-tb/Dockerfile b/msa/tb/docker-tb/Dockerfile
new file mode 100644
index 0000000..497a03c
--- /dev/null
+++ b/msa/tb/docker-tb/Dockerfile
@@ -0,0 +1,48 @@
+#
+# Copyright © 2016-2018 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.
+#
+
+FROM openjdk:8-jdk
+
+COPY logback.xml ${pkg.name}.conf start-db.sh stop-db.sh start-tb.sh upgrade-tb.sh install-tb.sh ${pkg.name}.deb /tmp/
+
+RUN chmod a+x /tmp/*.sh \
+ && mv /tmp/start-tb.sh /usr/bin \
+ && mv /tmp/upgrade-tb.sh /usr/bin \
+ && mv /tmp/install-tb.sh /usr/bin \
+ && mv /tmp/start-db.sh /usr/bin \
+ && mv /tmp/stop-db.sh /usr/bin
+
+RUN dpkg -i /tmp/${pkg.name}.deb
+
+RUN update-rc.d ${pkg.name} disable
+
+RUN mv /tmp/logback.xml ${pkg.installFolder}/conf \
+ && mv /tmp/${pkg.name}.conf ${pkg.installFolder}/conf
+
+ENV DATA_FOLDER=/data
+
+ENV HTTP_BIND_PORT=9090
+ENV DATABASE_TS_TYPE=sql
+ENV DATABASE_ENTITIES_TYPE=sql
+ENV SQL_DATA_FOLDER=/data/db
+
+EXPOSE 9090
+EXPOSE 1883
+EXPOSE 5683/udp
+
+VOLUME ["/data"]
+
+CMD ["start-tb.sh"]
msa/tb/pom.xml 370(+370 -0)
diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml
new file mode 100644
index 0000000..6ea7beb
--- /dev/null
+++ b/msa/tb/pom.xml
@@ -0,0 +1,370 @@
+<!--
+
+ Copyright © 2016-2018 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.
+
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.thingsboard</groupId>
+ <version>2.2.0-SNAPSHOT</version>
+ <artifactId>msa</artifactId>
+ </parent>
+ <groupId>org.thingsboard.msa</groupId>
+ <artifactId>tb</artifactId>
+ <packaging>pom</packaging>
+
+ <name>ThingsBoard Docker Images</name>
+ <url>https://thingsboard.io</url>
+ <description>ThingsBoard Docker Images</description>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <main.dir>${basedir}/../..</main.dir>
+ <pkg.name>thingsboard</pkg.name>
+ <tb.docker.name>tb</tb.docker.name>
+ <tb-postgres.docker.name>tb-postgres</tb-postgres.docker.name>
+ <tb-cassandra.docker.name>tb-cassandra</tb-cassandra.docker.name>
+ <pkg.user>thingsboard</pkg.user>
+ <pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
+ <pkg.upgradeVersion>2.1.1</pkg.upgradeVersion>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.thingsboard</groupId>
+ <artifactId>application</artifactId>
+ <version>${project.version}</version>
+ <classifier>deb</classifier>
+ <type>deb</type>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-tb-deb</id>
+ <phase>package</phase>
+ <goals>
+ <goal>copy</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.thingsboard</groupId>
+ <artifactId>application</artifactId>
+ <classifier>deb</classifier>
+ <type>deb</type>
+ <destFileName>${pkg.name}.deb</destFileName>
+ <outputDirectory>${project.build.directory}/docker-tb</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
+ <execution>
+ <id>copy-tb-postgres-deb</id>
+ <phase>package</phase>
+ <goals>
+ <goal>copy</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.thingsboard</groupId>
+ <artifactId>application</artifactId>
+ <classifier>deb</classifier>
+ <type>deb</type>
+ <destFileName>${pkg.name}.deb</destFileName>
+ <outputDirectory>${project.build.directory}/docker-postgres</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
+ <execution>
+ <id>copy-tb-cassandra-deb</id>
+ <phase>package</phase>
+ <goals>
+ <goal>copy</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.thingsboard</groupId>
+ <artifactId>application</artifactId>
+ <classifier>deb</classifier>
+ <type>deb</type>
+ <destFileName>${pkg.name}.deb</destFileName>
+ <outputDirectory>${project.build.directory}/docker-cassandra</outputDirectory>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-docker-tb-config</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.directory}/docker-tb</outputDirectory>
+ <resources>
+ <resource>
+ <directory>docker</directory>
+ <filtering>true</filtering>
+ </resource>
+ <resource>
+ <directory>docker-tb</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ <execution>
+ <id>copy-docker-tb-postgres-config</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.directory}/docker-postgres</outputDirectory>
+ <resources>
+ <resource>
+ <directory>docker</directory>
+ <filtering>true</filtering>
+ </resource>
+ <resource>
+ <directory>docker-postgres</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ <execution>
+ <id>copy-docker-tb-cassandra-config</id>
+ <phase>process-resources</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${project.build.directory}/docker-cassandra</outputDirectory>
+ <resources>
+ <resource>
+ <directory>docker</directory>
+ <filtering>true</filtering>
+ </resource>
+ <resource>
+ <directory>docker-cassandra</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>com.spotify</groupId>
+ <artifactId>dockerfile-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>build-docker-tb-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>build</goal>
+ </goals>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>${docker.repo}/${tb.docker.name}</repository>
+ <verbose>true</verbose>
+ <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
+ <contextDirectory>${project.build.directory}/docker-tb</contextDirectory>
+ </configuration>
+ </execution>
+ <execution>
+ <id>tag-docker-tb-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>tag</goal>
+ </goals>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>${docker.repo}/${tb.docker.name}</repository>
+ <tag>${project.version}</tag>
+ </configuration>
+ </execution>
+ <execution>
+ <id>build-docker-tb-postgres-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>build</goal>
+ </goals>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>${docker.repo}/${tb-postgres.docker.name}</repository>
+ <verbose>true</verbose>
+ <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
+ <contextDirectory>${project.build.directory}/docker-postgres</contextDirectory>
+ </configuration>
+ </execution>
+ <execution>
+ <id>tag-docker-tb-postgres-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>tag</goal>
+ </goals>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>${docker.repo}/${tb-postgres.docker.name}</repository>
+ <tag>${project.version}</tag>
+ </configuration>
+ </execution>
+ <execution>
+ <id>build-docker-tb-cassandra-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>build</goal>
+ </goals>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>${docker.repo}/${tb-cassandra.docker.name}</repository>
+ <verbose>true</verbose>
+ <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
+ <contextDirectory>${project.build.directory}/docker-cassandra</contextDirectory>
+ </configuration>
+ </execution>
+ <execution>
+ <id>tag-docker-tb-cassandra-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>tag</goal>
+ </goals>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>${docker.repo}/${tb-cassandra.docker.name}</repository>
+ <tag>${project.version}</tag>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ <profiles>
+ <profile>
+ <id>push-docker-image</id>
+ <activation>
+ <property>
+ <name>push-docker-image</name>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.spotify</groupId>
+ <artifactId>dockerfile-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>push-latest-docker-tb-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <tag>latest</tag>
+ <repository>${docker.repo}/${tb.docker.name}</repository>
+ </configuration>
+ </execution>
+ <execution>
+ <id>push-version-docker-tb-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <tag>${project.version}</tag>
+ <repository>${docker.repo}/${tb.docker.name}</repository>
+ </configuration>
+ </execution>
+ <execution>
+ <id>push-latest-docker-tb-postgres-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <tag>latest</tag>
+ <repository>${docker.repo}/${tb-postgres.docker.name}</repository>
+ </configuration>
+ </execution>
+ <execution>
+ <id>push-version-docker-tb-postgres-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <tag>${project.version}</tag>
+ <repository>${docker.repo}/${tb-postgres.docker.name}</repository>
+ </configuration>
+ </execution>
+ <execution>
+ <id>push-latest-docker-tb-cassandra-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <tag>latest</tag>
+ <repository>${docker.repo}/${tb-cassandra.docker.name}</repository>
+ </configuration>
+ </execution>
+ <execution>
+ <id>push-version-docker-tb-cassandra-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <tag>${project.version}</tag>
+ <repository>${docker.repo}/${tb-cassandra.docker.name}</repository>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+ <repositories>
+ <repository>
+ <id>jenkins</id>
+ <name>Jenkins Repository</name>
+ <url>http://repo.jenkins-ci.org/releases</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ </repositories>
+</project>
msa/tb/README.md 83(+83 -0)
diff --git a/msa/tb/README.md b/msa/tb/README.md
new file mode 100644
index 0000000..e4b7400
--- /dev/null
+++ b/msa/tb/README.md
@@ -0,0 +1,83 @@
+# ThingsBoard single docker images
+
+This project provides the build for the ThingsBoard single docker images.
+
+* `thingsboard/tb` - single instance of ThingsBoard with embedded HSQLDB database.
+* `thingsboard/tb-postgres` - single instance of ThingsBoard with PostgreSQL database.
+* `thingsboard/tb-cassandra` - single instance of ThingsBoard with Cassandra database.
+
+## Running
+
+In this example `thingsboard/tb` image will be used. You can choose any other images with different databases (see above).
+Execute the following command to run this docker directly:
+
+`
+$ docker run -it -p 9090:9090 -p 1883:1883 -p 5683:5683/udp -v ~/.mytb-data:/data --name mytb thingsboard/tb
+`
+
+Where:
+
+- `docker run` - run this container
+- `-it` - attach a terminal session with current ThingsBoard process output
+- `-p 9090:9090` - connect local port 9090 to exposed internal HTTP port 9090
+- `-p 1883:1883` - connect local port 1883 to exposed internal MQTT port 1883
+- `-p 5683:5683` - connect local port 5683 to exposed internal COAP port 5683
+- `-v ~/.mytb-data:/data` - mounts the host's dir `~/.mytb-data` to ThingsBoard DataBase data directory
+- `--name mytb` - friendly local name of this machine
+- `thingsboard/tb` - docker image, can be also `thingsboard/tb-postgres` or `thingsboard/tb-cassandra`
+
+> **NOTE**: **Windows** users should use docker managed volume instead of host's dir. Create docker volume (for ex. `mytb-data`) before executing `docker run` command:
+> ```
+> $ docker create volume mytb-data
+> ```
+> After you can execute docker run command using `mytb-data` volume instead of `~/.mytb-data`.
+> In order to get access to necessary resources from external IP/Host on **Windows** machine, please execute the following commands:
+> ```
+> $ VBoxManage controlvm "default" natpf1 "tcp-port9090,tcp,,9090,,9090"
+> $ VBoxManage controlvm "default" natpf1 "tcp-port1883,tcp,,1883,,1883"
+> $ VBoxManage controlvm "default" natpf1 "tcp-port5683,tcp,,5683,,5683"
+> ```
+
+After executing `docker run` command you can open `http://{your-host-ip}:9090` in you browser (for ex. `http://localhost:9090`). You should see ThingsBoard login page.
+Use the following default credentials:
+
+- **System Administrator**: sysadmin@thingsboard.org / sysadmin
+- **Tenant Administrator**: tenant@thingsboard.org / tenant
+- **Customer User**: customer@thingsboard.org / customer
+
+You can always change passwords for each account in account profile page.
+
+You can detach from session terminal with `Ctrl-p` `Ctrl-q` - the container will keep running in the background.
+
+To reattach to the terminal (to see ThingsBoard logs) run:
+
+```
+$ docker attach mytb
+```
+
+To stop the container:
+
+```
+$ docker stop mytb
+```
+
+To start the container:
+
+```
+$ docker start mytb
+```
+
+## Upgrading
+
+In order to update to the latest image, execute the following commands:
+
+```
+$ docker pull thingsboard/tb
+$ docker stop mytb
+$ docker run -it -v ~/.mytb-data:/data --rm thingsboard/tb upgrade-tb.sh
+$ docker start mytb
+```
+
+**NOTE**: if you use different database change image name in all commands from `thingsboard/tb` to `thingsboard/tb-postgres` or `thingsboard/tb-cassandra` correspondingly.
+
+**NOTE**: replace host's directory `~/.mytb-data` with directory used during container creation.
msa/tb-node/docker/Dockerfile 2(+1 -1)
diff --git a/msa/tb-node/docker/Dockerfile b/msa/tb-node/docker/Dockerfile
index c86d020..57a6151 100644
--- a/msa/tb-node/docker/Dockerfile
+++ b/msa/tb-node/docker/Dockerfile
@@ -14,7 +14,7 @@
# limitations under the License.
#
-FROM openjdk:8-jre
+FROM openjdk:8-jdk
COPY start-tb-node.sh ${pkg.name}.deb /tmp/
msa/tb-node/pom.xml 69(+61 -8)
diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml
index 18a81f3..fc62189 100644
--- a/msa/tb-node/pom.xml
+++ b/msa/tb-node/pom.xml
@@ -111,19 +111,72 @@
<goals>
<goal>build</goal>
</goals>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>${docker.repo}/${docker.name}</repository>
+ <verbose>true</verbose>
+ <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
+ <contextDirectory>${project.build.directory}</contextDirectory>
+ </configuration>
+ </execution>
+ <execution>
+ <id>tag-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>tag</goal>
+ </goals>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>${docker.repo}/${docker.name}</repository>
+ <tag>${project.version}</tag>
+ </configuration>
</execution>
</executions>
- <configuration>
- <skip>${dockerfile.skip}</skip>
- <repository>${docker.repo}/${docker.name}</repository>
- <tag>${project.version}</tag>
- <verbose>true</verbose>
- <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
- <contextDirectory>${project.build.directory}</contextDirectory>
- </configuration>
</plugin>
</plugins>
</build>
+ <profiles>
+ <profile>
+ <id>push-docker-image</id>
+ <activation>
+ <property>
+ <name>push-docker-image</name>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.spotify</groupId>
+ <artifactId>dockerfile-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>push-latest-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <tag>latest</tag>
+ <repository>${docker.repo}/${docker.name}</repository>
+ </configuration>
+ </execution>
+ <execution>
+ <id>push-version-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <tag>${project.version}</tag>
+ <repository>${docker.repo}/${docker.name}</repository>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
<repositories>
<repository>
<id>jenkins</id>
msa/transport/coap/docker/Dockerfile 2(+1 -1)
diff --git a/msa/transport/coap/docker/Dockerfile b/msa/transport/coap/docker/Dockerfile
index dcaef3a..9240b2a 100644
--- a/msa/transport/coap/docker/Dockerfile
+++ b/msa/transport/coap/docker/Dockerfile
@@ -14,7 +14,7 @@
# limitations under the License.
#
-FROM openjdk:8-jre
+FROM openjdk:8-jdk
COPY logback.xml ${pkg.name}.conf start-tb-coap-transport.sh ${pkg.name}.deb /tmp/
msa/transport/coap/pom.xml 69(+61 -8)
diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml
index 94399c7..26eaa30 100644
--- a/msa/transport/coap/pom.xml
+++ b/msa/transport/coap/pom.xml
@@ -111,19 +111,72 @@
<goals>
<goal>build</goal>
</goals>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>${docker.repo}/${docker.name}</repository>
+ <verbose>true</verbose>
+ <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
+ <contextDirectory>${project.build.directory}</contextDirectory>
+ </configuration>
+ </execution>
+ <execution>
+ <id>tag-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>tag</goal>
+ </goals>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>${docker.repo}/${docker.name}</repository>
+ <tag>${project.version}</tag>
+ </configuration>
</execution>
</executions>
- <configuration>
- <skip>${dockerfile.skip}</skip>
- <repository>${docker.repo}/${docker.name}</repository>
- <tag>${project.version}</tag>
- <verbose>true</verbose>
- <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
- <contextDirectory>${project.build.directory}</contextDirectory>
- </configuration>
</plugin>
</plugins>
</build>
+ <profiles>
+ <profile>
+ <id>push-docker-image</id>
+ <activation>
+ <property>
+ <name>push-docker-image</name>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.spotify</groupId>
+ <artifactId>dockerfile-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>push-latest-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <tag>latest</tag>
+ <repository>${docker.repo}/${docker.name}</repository>
+ </configuration>
+ </execution>
+ <execution>
+ <id>push-version-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <tag>${project.version}</tag>
+ <repository>${docker.repo}/${docker.name}</repository>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
<repositories>
<repository>
<id>jenkins</id>
msa/transport/http/docker/Dockerfile 2(+1 -1)
diff --git a/msa/transport/http/docker/Dockerfile b/msa/transport/http/docker/Dockerfile
index 212047f..6c83b9c 100644
--- a/msa/transport/http/docker/Dockerfile
+++ b/msa/transport/http/docker/Dockerfile
@@ -14,7 +14,7 @@
# limitations under the License.
#
-FROM openjdk:8-jre
+FROM openjdk:8-jdk
COPY logback.xml ${pkg.name}.conf start-tb-http-transport.sh ${pkg.name}.deb /tmp/
msa/transport/http/pom.xml 69(+61 -8)
diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml
index 6bbfc3f..6d1707c 100644
--- a/msa/transport/http/pom.xml
+++ b/msa/transport/http/pom.xml
@@ -111,19 +111,72 @@
<goals>
<goal>build</goal>
</goals>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>${docker.repo}/${docker.name}</repository>
+ <verbose>true</verbose>
+ <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
+ <contextDirectory>${project.build.directory}</contextDirectory>
+ </configuration>
+ </execution>
+ <execution>
+ <id>tag-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>tag</goal>
+ </goals>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>${docker.repo}/${docker.name}</repository>
+ <tag>${project.version}</tag>
+ </configuration>
</execution>
</executions>
- <configuration>
- <skip>${dockerfile.skip}</skip>
- <repository>${docker.repo}/${docker.name}</repository>
- <tag>${project.version}</tag>
- <verbose>true</verbose>
- <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
- <contextDirectory>${project.build.directory}</contextDirectory>
- </configuration>
</plugin>
</plugins>
</build>
+ <profiles>
+ <profile>
+ <id>push-docker-image</id>
+ <activation>
+ <property>
+ <name>push-docker-image</name>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.spotify</groupId>
+ <artifactId>dockerfile-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>push-latest-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <tag>latest</tag>
+ <repository>${docker.repo}/${docker.name}</repository>
+ </configuration>
+ </execution>
+ <execution>
+ <id>push-version-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <tag>${project.version}</tag>
+ <repository>${docker.repo}/${docker.name}</repository>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
<repositories>
<repository>
<id>jenkins</id>
msa/transport/mqtt/docker/Dockerfile 2(+1 -1)
diff --git a/msa/transport/mqtt/docker/Dockerfile b/msa/transport/mqtt/docker/Dockerfile
index 5827c65..f636e2f 100644
--- a/msa/transport/mqtt/docker/Dockerfile
+++ b/msa/transport/mqtt/docker/Dockerfile
@@ -14,7 +14,7 @@
# limitations under the License.
#
-FROM openjdk:8-jre
+FROM openjdk:8-jdk
COPY logback.xml ${pkg.name}.conf start-tb-mqtt-transport.sh ${pkg.name}.deb /tmp/
msa/transport/mqtt/pom.xml 69(+61 -8)
diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml
index 0a3b077..d51c51c 100644
--- a/msa/transport/mqtt/pom.xml
+++ b/msa/transport/mqtt/pom.xml
@@ -111,19 +111,72 @@
<goals>
<goal>build</goal>
</goals>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>${docker.repo}/${docker.name}</repository>
+ <verbose>true</verbose>
+ <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
+ <contextDirectory>${project.build.directory}</contextDirectory>
+ </configuration>
+ </execution>
+ <execution>
+ <id>tag-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>tag</goal>
+ </goals>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>${docker.repo}/${docker.name}</repository>
+ <tag>${project.version}</tag>
+ </configuration>
</execution>
</executions>
- <configuration>
- <skip>${dockerfile.skip}</skip>
- <repository>${docker.repo}/${docker.name}</repository>
- <tag>${project.version}</tag>
- <verbose>true</verbose>
- <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
- <contextDirectory>${project.build.directory}</contextDirectory>
- </configuration>
</plugin>
</plugins>
</build>
+ <profiles>
+ <profile>
+ <id>push-docker-image</id>
+ <activation>
+ <property>
+ <name>push-docker-image</name>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.spotify</groupId>
+ <artifactId>dockerfile-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>push-latest-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <tag>latest</tag>
+ <repository>${docker.repo}/${docker.name}</repository>
+ </configuration>
+ </execution>
+ <execution>
+ <id>push-version-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <tag>${project.version}</tag>
+ <repository>${docker.repo}/${docker.name}</repository>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
<repositories>
<repository>
<id>jenkins</id>
msa/web-ui/pom.xml 67(+59 -8)
diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml
index 805bf8b..83124df 100644
--- a/msa/web-ui/pom.xml
+++ b/msa/web-ui/pom.xml
@@ -311,16 +311,27 @@
<goals>
<goal>build</goal>
</goals>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>${docker.repo}/${docker.name}</repository>
+ <verbose>true</verbose>
+ <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
+ <contextDirectory>${project.build.directory}</contextDirectory>
+ </configuration>
+ </execution>
+ <execution>
+ <id>tag-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>tag</goal>
+ </goals>
+ <configuration>
+ <skip>${dockerfile.skip}</skip>
+ <repository>${docker.repo}/${docker.name}</repository>
+ <tag>${project.version}</tag>
+ </configuration>
</execution>
</executions>
- <configuration>
- <skip>${dockerfile.skip}</skip>
- <repository>${docker.repo}/${docker.name}</repository>
- <tag>${project.version}</tag>
- <verbose>true</verbose>
- <googleContainerRegistryEnabled>false</googleContainerRegistryEnabled>
- <contextDirectory>${project.build.directory}</contextDirectory>
- </configuration>
</plugin>
</plugins>
</build>
@@ -358,6 +369,46 @@
</plugins>
</build>
</profile>
+ <profile>
+ <id>push-docker-image</id>
+ <activation>
+ <property>
+ <name>push-docker-image</name>
+ </property>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.spotify</groupId>
+ <artifactId>dockerfile-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>push-latest-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <tag>latest</tag>
+ <repository>${docker.repo}/${docker.name}</repository>
+ </configuration>
+ </execution>
+ <execution>
+ <id>push-version-docker-image</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>push</goal>
+ </goals>
+ <configuration>
+ <tag>${project.version}</tag>
+ <repository>${docker.repo}/${docker.name}</repository>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
</profiles>
<repositories>
<repository>
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java
index 9a93d1e..f3d57de 100644
--- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java
+++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java
@@ -31,6 +31,8 @@ public final class RuleEngineDeviceRpcRequest {
private final DeviceId deviceId;
private final int requestId;
private final UUID requestUUID;
+ private final String originHost;
+ private final int originPort;
private final boolean oneway;
private final String method;
private final String body;
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java
index c568e3d..e3766ba 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java
@@ -37,7 +37,7 @@ public class TbMsgGeneratorNodeConfiguration implements NodeConfiguration<TbMsgG
configuration.setPeriodInSeconds(1);
configuration.setJsScript("var msg = { temp: 42, humidity: 77 };\n" +
"var metadata = { data: 40 };\n" +
- "var msgType = \"DebugMsg\";\n\n" +
+ "var msgType = \"POST_TELEMETRY_REQUEST\";\n\n" +
"return { msg: msg, metadata: metadata, msgType: msgType };");
return configuration;
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java
index 5d7e124..8c95b88 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java
@@ -86,6 +86,10 @@ public class TbSendRPCRequestNode implements TbNode {
tmp = msg.getMetaData().getValue("requestUUID");
UUID requestUUID = !StringUtils.isEmpty(tmp) ? UUID.fromString(tmp) : UUIDs.timeBased();
+ tmp = msg.getMetaData().getValue("originHost");
+ String originHost = !StringUtils.isEmpty(tmp) ? tmp : null;
+ tmp = msg.getMetaData().getValue("originPort");
+ int originPort = !StringUtils.isEmpty(tmp) ? Integer.parseInt(tmp) : 0;
tmp = msg.getMetaData().getValue("expirationTime");
long expirationTime = !StringUtils.isEmpty(tmp) ? Long.parseLong(tmp) : (System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(config.getTimeoutInSeconds()));
@@ -105,6 +109,8 @@ public class TbSendRPCRequestNode implements TbNode {
.deviceId(new DeviceId(msg.getOriginator().getId()))
.requestId(requestId)
.requestUUID(requestUUID)
+ .originHost(originHost)
+ .originPort(originPort)
.expirationTime(expirationTime)
.restApiCall(restApiCall)
.build();
diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml
index 30351b0..f96bcbe 100644
--- a/transport/coap/src/main/resources/tb-coap-transport.yml
+++ b/transport/coap/src/main/resources/tb-coap-transport.yml
@@ -23,6 +23,9 @@ transport:
bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}"
bind_port: "${COAP_BIND_PORT:5683}"
timeout: "${COAP_TIMEOUT:10000}"
+ sessions:
+ inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
+ report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}"
rate_limits:
enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}"
tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}"
diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml
index 001e08a..6d593ed 100644
--- a/transport/http/src/main/resources/tb-http-transport.yml
+++ b/transport/http/src/main/resources/tb-http-transport.yml
@@ -24,6 +24,9 @@ server:
transport:
http:
request_timeout: "${HTTP_REQUEST_TIMEOUT:60000}"
+ sessions:
+ inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
+ report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}"
rate_limits:
enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}"
tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}"
diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml
index e37d14e..e7f8942 100644
--- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml
+++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml
@@ -44,8 +44,8 @@ transport:
# Type of the key store
key_store_type: "${MQTT_SSL_KEY_STORE_TYPE:JKS}"
sessions:
- max_per_tenant: "${TB_TRANSPORT_SESSIONS_MAX_PER_TENANT:1000}"
- max_per_device: "${TB_TRANSPORT_SESSIONS_MAX_PER_DEVICE:2}"
+ inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}"
+ report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}"
rate_limits:
enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}"
tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}"
ui/package.json 4(+2 -2)
diff --git a/ui/package.json b/ui/package.json
index 7874170..9fb0cc5 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -26,7 +26,7 @@
"angular-gridster": "^0.13.14",
"angular-hotkeys": "^1.7.0",
"angular-jwt": "^0.1.6",
- "angular-material": "1.1.1",
+ "angular-material": "1.1.9",
"angular-material-data-table": "^0.10.9",
"angular-material-icons": "^0.7.1",
"angular-material-expansion-panel": "^0.7.2",
@@ -63,7 +63,7 @@
"leaflet-providers": "^1.1.17",
"material-ui": "^0.16.1",
"material-ui-number-input": "^5.0.16",
- "md-color-picker": "^0.2.6",
+ "md-color-picker": "0.2.6",
"mdPickers": "git://github.com/alenaksu/mdPickers.git#0.7.5",
"moment": "^2.15.0",
"ngclipboard": "^1.1.1",
diff --git a/ui/src/app/alarm/alarm-row.directive.js b/ui/src/app/alarm/alarm-row.directive.js
index 37eec7c..681498f 100644
--- a/ui/src/app/alarm/alarm-row.directive.js
+++ b/ui/src/app/alarm/alarm-row.directive.js
@@ -50,7 +50,7 @@ export default function AlarmRowDirective($compile, $templateCache, types, $mdDi
parent: angular.element($document[0].body),
targetEvent: $event,
fullscreen: true,
- skipHide: true,
+ multiple: true,
onShowing: function(scope, element) {
onShowingCallback.onShowing(scope, element);
}
ui/src/app/app.js 32(+18 -14)
diff --git a/ui/src/app/app.js b/ui/src/app/app.js
index a3a179e..31b53a0 100644
--- a/ui/src/app/app.js
+++ b/ui/src/app/app.js
@@ -31,6 +31,7 @@ import 'angular-translate-interpolation-messageformat';
import 'md-color-picker';
import mdPickers from 'mdPickers';
import ngSanitize from 'angular-sanitize';
+import FBAngular from 'angular-fullscreen';
import vAccordion from 'v-accordion';
import ngAnimate from 'angular-animate';
import 'angular-websocket';
@@ -51,6 +52,21 @@ import react from 'ngreact';
import '@flowjs/ng-flow/dist/ng-flow-standalone.min';
import 'ngFlowchart/dist/ngFlowchart';
+import 'typeface-roboto';
+import 'font-awesome/css/font-awesome.min.css';
+import 'angular-material/angular-material.min.css';
+import 'angular-material-icons/angular-material-icons.css';
+import 'angular-gridster/dist/angular-gridster.min.css';
+import 'v-accordion/dist/v-accordion.min.css';
+import 'md-color-picker/dist/mdColorPicker.min.css';
+import 'mdPickers/dist/mdPickers.min.css';
+import 'angular-hotkeys/build/hotkeys.min.css';
+import 'angular-carousel/dist/angular-carousel.min.css';
+import 'angular-material-expansion-panel/dist/md-expansion-panel.min.css';
+import 'ngFlowchart/dist/flowchart.css';
+import '../scss/main.scss';
+
+import thingsboardThirdpartyFix from './common/thirdparty-fix';
import thingsboardTranslateHandler from './locale/translate-handler';
import thingsboardLogin from './login';
import thingsboardDialogs from './components/datakey-config-dialog.controller';
@@ -78,20 +94,6 @@ import thingsboardApiAuditLog from './api/audit-log.service';
import thingsboardApiComponentDescriptor from './api/component-descriptor.service';
import thingsboardApiRuleChain from './api/rule-chain.service';
-import 'typeface-roboto';
-import 'font-awesome/css/font-awesome.min.css';
-import 'angular-material/angular-material.min.css';
-import 'angular-material-icons/angular-material-icons.css';
-import 'angular-gridster/dist/angular-gridster.min.css';
-import 'v-accordion/dist/v-accordion.min.css';
-import 'md-color-picker/dist/mdColorPicker.min.css';
-import 'mdPickers/dist/mdPickers.min.css';
-import 'angular-hotkeys/build/hotkeys.min.css';
-import 'angular-carousel/dist/angular-carousel.min.css';
-import 'angular-material-expansion-panel/dist/md-expansion-panel.min.css';
-import 'ngFlowchart/dist/flowchart.css';
-import '../scss/main.scss';
-
import AppConfig from './app.config';
import GlobalInterceptor from './global-interceptor.service';
import AppRun from './app.run';
@@ -105,6 +107,7 @@ angular.module('thingsboard', [
'mdColorPicker',
mdPickers,
ngSanitize,
+ FBAngular.name,
vAccordion,
ngAnimate,
'ngWebSocket',
@@ -118,6 +121,7 @@ angular.module('thingsboard', [
react.name,
'flow',
'flowchart',
+ thingsboardThirdpartyFix,
thingsboardTranslateHandler,
thingsboardLogin,
thingsboardDialogs,
diff --git a/ui/src/app/audit/audit-log-row.directive.js b/ui/src/app/audit/audit-log-row.directive.js
index 2c2e170..f13a0d2 100644
--- a/ui/src/app/audit/audit-log-row.directive.js
+++ b/ui/src/app/audit/audit-log-row.directive.js
@@ -48,7 +48,7 @@ export default function AuditLogRowDirective($compile, $templateCache, types, $m
parent: angular.element($document[0].body),
targetEvent: $event,
fullscreen: true,
- skipHide: true,
+ multiple: true,
onShowing: function(scope, element) {
onShowingCallback.onShowing(scope, element);
}
ui/src/app/common/thirdparty-fix.js 459(+459 -0)
diff --git a/ui/src/app/common/thirdparty-fix.js b/ui/src/app/common/thirdparty-fix.js
new file mode 100644
index 0000000..af4222c
--- /dev/null
+++ b/ui/src/app/common/thirdparty-fix.js
@@ -0,0 +1,459 @@
+/*
+ * Copyright © 2016-2018 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.
+ */
+
+import tinycolor from 'tinycolor2';
+import moment from 'moment';
+
+export default angular.module('thingsboard.thirdpartyFix', [])
+ .factory('Fullscreen', Fullscreen)
+ .factory('$mdColorPicker', mdColorPicker)
+ .provider('$mdpDatePicker', mdpDatePicker)
+ .provider('$mdpTimePicker', mdpTimePicker)
+ .name;
+
+/*@ngInject*/
+function Fullscreen($document, $rootScope) {
+
+ /* eslint-disable */
+
+ var document = $document[0];
+
+ // ensure ALLOW_KEYBOARD_INPUT is available and enabled
+ var isKeyboardAvailbleOnFullScreen = (typeof Element !== 'undefined' && 'ALLOW_KEYBOARD_INPUT' in Element) && Element.ALLOW_KEYBOARD_INPUT;
+
+ var emitter = $rootScope.$new();
+
+ // listen event on document instead of element to avoid firefox limitation
+ // see https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Using_full_screen_mode
+ $document.on('fullscreenchange webkitfullscreenchange mozfullscreenchange MSFullscreenChange', function(){
+ emitter.$emit('FBFullscreen.change', serviceInstance.isEnabled());
+ });
+
+ var serviceInstance = {
+ $on: angular.bind(emitter, emitter.$on),
+ all: function() {
+ serviceInstance.enable( document.documentElement );
+ },
+ enable: function(element) {
+ if(element.requestFullScreen) {
+ element.requestFullScreen();
+ } else if(element.mozRequestFullScreen) {
+ element.mozRequestFullScreen();
+ } else if(element.webkitRequestFullscreen) {
+ // Safari temporary fix
+ //if (/Version\/[\d]{1,2}(\.[\d]{1,2}){1}(\.(\d){1,2}){0,1} Safari/.test(navigator.userAgent)) {
+ if (/Safari/.test(navigator.userAgent)) {
+ element.webkitRequestFullscreen();
+ } else {
+ element.webkitRequestFullscreen(isKeyboardAvailbleOnFullScreen);
+ }
+ } else if (element.msRequestFullscreen) {
+ element.msRequestFullscreen();
+ }
+ },
+ cancel: function() {
+ if(document.cancelFullScreen) {
+ document.cancelFullScreen();
+ } else if(document.mozCancelFullScreen) {
+ document.mozCancelFullScreen();
+ } else if(document.webkitExitFullscreen) {
+ document.webkitExitFullscreen();
+ } else if (document.msExitFullscreen) {
+ document.msExitFullscreen();
+ }
+ },
+ isEnabled: function(){
+ var fullscreenElement = document.fullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement;
+ return fullscreenElement ? true : false;
+ },
+ toggleAll: function(){
+ serviceInstance.isEnabled() ? serviceInstance.cancel() : serviceInstance.all();
+ },
+ isSupported: function(){
+ var docElm = document.documentElement;
+ var requestFullscreen = docElm.requestFullScreen || docElm.mozRequestFullScreen || docElm.webkitRequestFullscreen || docElm.msRequestFullscreen;
+ return requestFullscreen ? true : false;
+ }
+ };
+
+ /* eslint-enable */
+
+ return serviceInstance;
+}
+
+/*@ngInject*/
+function mdColorPicker($q, $mdDialog, mdColorPickerHistory) {
+ var dialog;
+
+ /* eslint-disable angular/definedundefined */
+
+ return {
+ show: function (options)
+ {
+ if ( options === undefined ) {
+ options = {};
+ }
+ //console.log( 'DIALOG OPTIONS', options );
+ // Defaults
+ // Dialog Properties
+ options.hasBackdrop = options.hasBackdrop === undefined ? true : options.hasBackdrop;
+ options.clickOutsideToClose = options.clickOutsideToClose === undefined ? true : options.clickOutsideToClose;
+ options.defaultValue = options.defaultValue === undefined ? '#FFFFFF' : options.defaultValue;
+ options.focusOnOpen = options.focusOnOpen === undefined ? false : options.focusOnOpen;
+ options.preserveScope = options.preserveScope === undefined ? true : options.preserveScope;
+ if (options.skipHide !== undefined) {
+ options.multiple = options.skipHide;
+ }
+ if (options.multiple === undefined) {
+ options.multiple = true;
+ }
+
+ // mdColorPicker Properties
+ options.mdColorAlphaChannel = options.mdColorAlphaChannel === undefined ? false : options.mdColorAlphaChannel;
+ options.mdColorSpectrum = options.mdColorSpectrum === undefined ? true : options.mdColorSpectrum;
+ options.mdColorSliders = options.mdColorSliders === undefined ? true : options.mdColorSliders;
+ options.mdColorGenericPalette = options.mdColorGenericPalette === undefined ? true : options.mdColorGenericPalette;
+ options.mdColorMaterialPalette = options.mdColorMaterialPalette === undefined ? true : options.mdColorMaterialPalette;
+ options.mdColorHistory = options.mdColorHistory === undefined ? true : options.mdColorHistory;
+
+
+ dialog = $mdDialog.show({
+ templateUrl: 'mdColorPickerDialog.tpl.html',
+ hasBackdrop: options.hasBackdrop,
+ clickOutsideToClose: options.clickOutsideToClose,
+
+ controller: ['$scope', 'options', function( $scope, options ) {
+ //console.log( "DIALOG CONTROLLER OPEN", Date.now() - dateClick );
+ $scope.close = function close()
+ {
+ $mdDialog.cancel();
+ };
+ $scope.ok = function ok()
+ {
+ $mdDialog.hide( $scope.value );
+ };
+ $scope.hide = $scope.ok;
+
+
+
+ $scope.value = options.value;
+ $scope.default = options.defaultValue;
+ $scope.random = options.random;
+
+ $scope.mdColorAlphaChannel = options.mdColorAlphaChannel;
+ $scope.mdColorSpectrum = options.mdColorSpectrum;
+ $scope.mdColorSliders = options.mdColorSliders;
+ $scope.mdColorGenericPalette = options.mdColorGenericPalette;
+ $scope.mdColorMaterialPalette = options.mdColorMaterialPalette;
+ $scope.mdColorHistory = options.mdColorHistory;
+ $scope.mdColorDefaultTab = options.mdColorDefaultTab;
+
+ }],
+
+ locals: {
+ options: options,
+ },
+ preserveScope: options.preserveScope,
+ multiple: options.multiple,
+
+ targetEvent: options.$event,
+ focusOnOpen: options.focusOnOpen,
+ autoWrap: false,
+ onShowing: function() {
+ // console.log( "DIALOG OPEN START", Date.now() - dateClick );
+ },
+ onComplete: function() {
+ // console.log( "DIALOG OPEN COMPLETE", Date.now() - dateClick );
+ }
+ });
+
+ dialog.then(function (value) {
+ mdColorPickerHistory.add(new tinycolor(value));
+ }, function () { });
+
+ return dialog;
+ },
+ hide: function() {
+ return dialog.hide();
+ },
+ cancel: function() {
+ return dialog.cancel();
+ }
+ };
+
+ /* eslint-enable angular/definedundefined */
+}
+
+function DatePickerCtrl($scope, $mdDialog, $mdMedia, $timeout, currentDate, options) {
+ var self = this;
+
+ this.date = moment(currentDate);
+ this.minDate = options.minDate && moment(options.minDate).isValid() ? moment(options.minDate) : null;
+ this.maxDate = options.maxDate && moment(options.maxDate).isValid() ? moment(options.maxDate) : null;
+ this.displayFormat = options.displayFormat || "ddd, MMM DD";
+ this.dateFilter = angular.isFunction(options.dateFilter) ? options.dateFilter : null;
+ this.selectingYear = false;
+
+ // validate min and max date
+ if (this.minDate && this.maxDate) {
+ if (this.maxDate.isBefore(this.minDate)) {
+ this.maxDate = moment(this.minDate).add(1, 'days');
+ }
+ }
+
+ if (this.date) {
+ // check min date
+ if (this.minDate && this.date.isBefore(this.minDate)) {
+ this.date = moment(this.minDate);
+ }
+
+ // check max date
+ if (this.maxDate && this.date.isAfter(this.maxDate)) {
+ this.date = moment(this.maxDate);
+ }
+ }
+
+ this.yearItems = {
+ currentIndex_: 0,
+ PAGE_SIZE: 5,
+ START: (self.minDate ? self.minDate.year() : 1900),
+ END: (self.maxDate ? self.maxDate.year() : 0),
+ getItemAtIndex: function(index) {
+ if(this.currentIndex_ < index)
+ this.currentIndex_ = index;
+
+ return this.START + index;
+ },
+ getLength: function() {
+ return Math.min(
+ this.currentIndex_ + Math.floor(this.PAGE_SIZE / 2),
+ Math.abs(this.START - this.END) + 1
+ );
+ }
+ };
+
+ $scope.$mdMedia = $mdMedia;
+ $scope.year = this.date.year();
+
+ this.selectYear = function(year) {
+ self.date.year(year);
+ $scope.year = year;
+ self.selectingYear = false;
+ self.animate();
+ };
+
+ this.showYear = function() {
+ self.yearTopIndex = (self.date.year() - self.yearItems.START) + Math.floor(self.yearItems.PAGE_SIZE / 2);
+ self.yearItems.currentIndex_ = (self.date.year() - self.yearItems.START) + 1;
+ self.selectingYear = true;
+ };
+
+ this.showCalendar = function() {
+ self.selectingYear = false;
+ };
+
+ this.cancel = function() {
+ $mdDialog.cancel();
+ };
+
+ this.confirm = function() {
+ var date = this.date;
+
+ if (this.minDate && this.date.isBefore(this.minDate)) {
+ date = moment(this.minDate);
+ }
+
+ if (this.maxDate && this.date.isAfter(this.maxDate)) {
+ date = moment(this.maxDate);
+ }
+
+ $mdDialog.hide(date.toDate());
+ };
+
+ this.animate = function() {
+ self.animating = true;
+ $timeout(angular.noop).then(function() {
+ self.animating = false;
+ })
+ };
+}
+
+/*@ngInject*/
+function mdpDatePicker() {
+ var LABEL_OK = "OK",
+ LABEL_CANCEL = "Cancel",
+ DISPLAY_FORMAT = "ddd, MMM DD";
+
+ this.setDisplayFormat = function(format) {
+ DISPLAY_FORMAT = format;
+ };
+
+ this.setOKButtonLabel = function(label) {
+ LABEL_OK = label;
+ };
+
+ this.setCancelButtonLabel = function(label) {
+ LABEL_CANCEL = label;
+ };
+
+ /*@ngInject*/
+ this.$get = function($mdDialog) {
+ var datePicker = function(currentDate, options) {
+ if (!angular.isDate(currentDate)) currentDate = Date.now();
+ if (!angular.isObject(options)) options = {};
+
+ options.displayFormat = DISPLAY_FORMAT;
+
+ return $mdDialog.show({
+ controller: ['$scope', '$mdDialog', '$mdMedia', '$timeout', 'currentDate', 'options', DatePickerCtrl],
+ controllerAs: 'datepicker',
+ clickOutsideToClose: true,
+ template: '<md-dialog aria-label="" class="mdp-datepicker" ng-class="{ \'portrait\': !$mdMedia(\'gt-xs\') }">' +
+ '<md-dialog-content layout="row" layout-wrap>' +
+ '<div layout="column" layout-align="start center">' +
+ '<md-toolbar layout-align="start start" flex class="mdp-datepicker-date-wrapper md-hue-1 md-primary" layout="column">' +
+ '<span class="mdp-datepicker-year" ng-click="datepicker.showYear()" ng-class="{ \'active\': datepicker.selectingYear }">{{ datepicker.date.format(\'YYYY\') }}</span>' +
+ '<span class="mdp-datepicker-date" ng-click="datepicker.showCalendar()" ng-class="{ \'active\': !datepicker.selectingYear }">{{ datepicker.date.format(datepicker.displayFormat) }}</span> ' +
+ '</md-toolbar>' +
+ '</div>' +
+ '<div>' +
+ '<div class="mdp-datepicker-select-year mdp-animation-zoom" layout="column" layout-align="center start" ng-if="datepicker.selectingYear">' +
+ '<md-virtual-repeat-container md-auto-shrink md-top-index="datepicker.yearTopIndex">' +
+ '<div flex md-virtual-repeat="item in datepicker.yearItems" md-on-demand class="repeated-year">' +
+ '<span class="md-button" ng-click="datepicker.selectYear(item)" md-ink-ripple ng-class="{ \'md-primary current\': item == year }">{{ item }}</span>' +
+ '</div>' +
+ '</md-virtual-repeat-container>' +
+ '</div>' +
+ '<mdp-calendar ng-if="!datepicker.selectingYear" class="mdp-animation-zoom" date="datepicker.date" min-date="datepicker.minDate" date-filter="datepicker.dateFilter" max-date="datepicker.maxDate"></mdp-calendar>' +
+ '<md-dialog-actions layout="row">' +
+ '<span flex></span>' +
+ '<md-button ng-click="datepicker.cancel()" aria-label="' + LABEL_CANCEL + '">' + LABEL_CANCEL + '</md-button>' +
+ '<md-button ng-click="datepicker.confirm()" class="md-primary" aria-label="' + LABEL_OK + '">' + LABEL_OK + '</md-button>' +
+ '</md-dialog-actions>' +
+ '</div>' +
+ '</md-dialog-content>' +
+ '</md-dialog>',
+ targetEvent: options.targetEvent,
+ locals: {
+ currentDate: currentDate,
+ options: options
+ },
+ multiple: true
+ });
+ };
+
+ return datePicker;
+ };
+
+}
+
+function TimePickerCtrl($scope, $mdDialog, time, autoSwitch, $mdMedia) {
+ var self = this;
+ this.VIEW_HOURS = 1;
+ this.VIEW_MINUTES = 2;
+ this.currentView = this.VIEW_HOURS;
+ this.time = moment(time);
+ this.autoSwitch = !!autoSwitch;
+
+ this.clockHours = parseInt(this.time.format("h"));
+ this.clockMinutes = parseInt(this.time.minutes());
+
+ $scope.$mdMedia = $mdMedia;
+
+ this.switchView = function() {
+ self.currentView = self.currentView == self.VIEW_HOURS ? self.VIEW_MINUTES : self.VIEW_HOURS;
+ };
+
+ this.setAM = function() {
+ if(self.time.hours() >= 12)
+ self.time.hour(self.time.hour() - 12);
+ };
+
+ this.setPM = function() {
+ if(self.time.hours() < 12)
+ self.time.hour(self.time.hour() + 12);
+ };
+
+ this.cancel = function() {
+ $mdDialog.cancel();
+ };
+
+ this.confirm = function() {
+ $mdDialog.hide(this.time.toDate());
+ };
+}
+
+/*@ngInject*/
+function mdpTimePicker() {
+ var LABEL_OK = "OK",
+ LABEL_CANCEL = "Cancel";
+
+ this.setOKButtonLabel = function(label) {
+ LABEL_OK = label;
+ };
+
+ this.setCancelButtonLabel = function(label) {
+ LABEL_CANCEL = label;
+ };
+
+ /*@ngInject*/
+ this.$get = function($mdDialog) {
+ var timePicker = function(time, options) {
+ if(!angular.isDate(time)) time = Date.now();
+ if (!angular.isObject(options)) options = {};
+
+ return $mdDialog.show({
+ controller: ['$scope', '$mdDialog', 'time', 'autoSwitch', '$mdMedia', TimePickerCtrl],
+ controllerAs: 'timepicker',
+ clickOutsideToClose: true,
+ template: '<md-dialog aria-label="" class="mdp-timepicker" ng-class="{ \'portrait\': !$mdMedia(\'gt-xs\') }">' +
+ '<md-dialog-content layout-gt-xs="row" layout-wrap>' +
+ '<md-toolbar layout-gt-xs="column" layout-xs="row" layout-align="center center" flex class="mdp-timepicker-time md-hue-1 md-primary">' +
+ '<div class="mdp-timepicker-selected-time">' +
+ '<span ng-class="{ \'active\': timepicker.currentView == timepicker.VIEW_HOURS }" ng-click="timepicker.currentView = timepicker.VIEW_HOURS">{{ timepicker.time.format("h") }}</span>:' +
+ '<span ng-class="{ \'active\': timepicker.currentView == timepicker.VIEW_MINUTES }" ng-click="timepicker.currentView = timepicker.VIEW_MINUTES">{{ timepicker.time.format("mm") }}</span>' +
+ '</div>' +
+ '<div layout="column" class="mdp-timepicker-selected-ampm">' +
+ '<span ng-click="timepicker.setAM()" ng-class="{ \'active\': timepicker.time.hours() < 12 }">AM</span>' +
+ '<span ng-click="timepicker.setPM()" ng-class="{ \'active\': timepicker.time.hours() >= 12 }">PM</span>' +
+ '</div>' +
+ '</md-toolbar>' +
+ '<div>' +
+ '<div class="mdp-clock-switch-container" ng-switch="timepicker.currentView" layout layout-align="center center">' +
+ '<mdp-clock class="mdp-animation-zoom" auto-switch="timepicker.autoSwitch" time="timepicker.time" type="hours" ng-switch-when="1"></mdp-clock>' +
+ '<mdp-clock class="mdp-animation-zoom" auto-switch="timepicker.autoSwitch" time="timepicker.time" type="minutes" ng-switch-when="2"></mdp-clock>' +
+ '</div>' +
+
+ '<md-dialog-actions layout="row">' +
+ '<span flex></span>' +
+ '<md-button ng-click="timepicker.cancel()" aria-label="' + LABEL_CANCEL + '">' + LABEL_CANCEL + '</md-button>' +
+ '<md-button ng-click="timepicker.confirm()" class="md-primary" aria-label="' + LABEL_OK + '">' + LABEL_OK + '</md-button>' +
+ '</md-dialog-actions>' +
+ '</div>' +
+ '</md-dialog-content>' +
+ '</md-dialog>',
+ targetEvent: options.targetEvent,
+ locals: {
+ time: time,
+ autoSwitch: options.autoSwitch
+ },
+ multiple: true
+ });
+ };
+
+ return timePicker;
+ };
+}
diff --git a/ui/src/app/components/datasource-entity.directive.js b/ui/src/app/components/datasource-entity.directive.js
index b02a30d..6bc1932 100644
--- a/ui/src/app/components/datasource-entity.directive.js
+++ b/ui/src/app/components/datasource-entity.directive.js
@@ -186,7 +186,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
random: tinycolor.random(),
clickOutsideToClose: false,
hasBackdrop: false,
- skipHide: true,
+ multiple: true,
preserveScope: false,
mdColorAlphaChannel: true,
@@ -220,7 +220,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc
parent: angular.element($document[0].body),
fullscreen: true,
targetEvent: event,
- skipHide: true,
+ multiple: true,
onComplete: function () {
var w = angular.element($window);
w.triggerHandler('resize');
diff --git a/ui/src/app/components/datasource-entity.tpl.html b/ui/src/app/components/datasource-entity.tpl.html
index db6fd3b..8f25787 100644
--- a/ui/src/app/components/datasource-entity.tpl.html
+++ b/ui/src/app/components/datasource-entity.tpl.html
@@ -26,7 +26,6 @@
<section flex layout='column' layout-align="center" style="padding-left: 4px;">
<md-chips flex ng-if="widgetType != types.widgetType.alarm.value"
id="timeseries_datakey_chips"
- ng-required="true"
ng-model="timeseriesDataKeys" md-autocomplete-snap
md-transform-chip="transformTimeseriesDataKeyChip($chip)"
md-require-match="false">
@@ -78,7 +77,6 @@
</md-chips>
<md-chips flex ng-if="widgetType === types.widgetType.latest.value"
id="attribute_datakey_chips"
- ng-required="true"
ng-model="attributeDataKeys" md-autocomplete-snap
md-transform-chip="transformAttributeDataKeyChip($chip)"
md-require-match="false">
diff --git a/ui/src/app/components/datasource-func.directive.js b/ui/src/app/components/datasource-func.directive.js
index 7515b61..982685f 100644
--- a/ui/src/app/components/datasource-func.directive.js
+++ b/ui/src/app/components/datasource-func.directive.js
@@ -139,7 +139,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
random: tinycolor.random(),
clickOutsideToClose: false,
hasBackdrop: false,
- skipHide: true,
+ multiple: true,
preserveScope: false,
mdColorAlphaChannel: true,
@@ -173,7 +173,7 @@ function DatasourceFunc($compile, $templateCache, $mdDialog, $window, $document,
parent: angular.element($document[0].body),
fullscreen: true,
targetEvent: event,
- skipHide: true,
+ multiple: true,
onComplete: function () {
var w = angular.element($window);
w.triggerHandler('resize');
diff --git a/ui/src/app/components/json-form.directive.js b/ui/src/app/components/json-form.directive.js
index b016f57..97b7bfb 100644
--- a/ui/src/app/components/json-form.directive.js
+++ b/ui/src/app/components/json-form.directive.js
@@ -96,7 +96,7 @@ function JsonForm($compile, $templateCache, $mdColorPicker) {
random: tinycolor.random(),
clickOutsideToClose: false,
hasBackdrop: false,
- skipHide: true,
+ multiple: true,
preserveScope: false,
mdColorAlphaChannel: true,
diff --git a/ui/src/app/components/material-icon-select.directive.js b/ui/src/app/components/material-icon-select.directive.js
index 67caff2..f3efe63 100644
--- a/ui/src/app/components/material-icon-select.directive.js
+++ b/ui/src/app/components/material-icon-select.directive.js
@@ -67,7 +67,7 @@ function MaterialIconSelect($compile, $templateCache, $document, $mdDialog) {
templateUrl: materialIconsDialogTemplate,
parent: angular.element($document[0].body),
locals: {icon: scope.icon},
- skipHide: true,
+ multiple: true,
fullscreen: true,
targetEvent: $event
}).then(function (icon) {
diff --git a/ui/src/app/components/widget/action/manage-widget-actions.directive.js b/ui/src/app/components/widget/action/manage-widget-actions.directive.js
index 8110322..9597204 100644
--- a/ui/src/app/components/widget/action/manage-widget-actions.directive.js
+++ b/ui/src/app/components/widget/action/manage-widget-actions.directive.js
@@ -164,7 +164,7 @@ function ManageWidgetActionsController($rootScope, $scope, $document, $mdDialog,
.cancel($translate.instant('action.no'))
.ok($translate.instant('action.yes'));
- confirm._options.skipHide = true;
+ confirm._options.multiple = true;
confirm._options.fullscreen = true;
$mdDialog.show(confirm).then(function () {
@@ -212,7 +212,7 @@ function ManageWidgetActionsController($rootScope, $scope, $document, $mdDialog,
locals: {isAdd: isAdd, fetchDashboardStates: vm.fetchDashboardStates,
actionSources: availableActionSources, widgetActions: vm.widgetActions,
action: angular.copy(action)},
- skipHide: true,
+ multiple: true,
fullscreen: true,
targetEvent: $event
}).then(function (action) {
diff --git a/ui/src/app/dashboard/add-widget.controller.js b/ui/src/app/dashboard/add-widget.controller.js
index caa69d6..fd665fd 100644
--- a/ui/src/app/dashboard/add-widget.controller.js
+++ b/ui/src/app/dashboard/add-widget.controller.js
@@ -166,7 +166,7 @@ export default function AddWidgetController($scope, widgetService, entityService
},
parent: angular.element($document[0].body),
fullscreen: true,
- skipHide: true,
+ multiple: true,
targetEvent: event
}).then(function (singleEntityAlias) {
vm.dashboard.configuration.entityAliases[singleEntityAlias.id] = singleEntityAlias;
diff --git a/ui/src/app/dashboard/dashboard.controller.js b/ui/src/app/dashboard/dashboard.controller.js
index 6df48df..3f32e1b 100644
--- a/ui/src/app/dashboard/dashboard.controller.js
+++ b/ui/src/app/dashboard/dashboard.controller.js
@@ -463,7 +463,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget
}
},
parent: angular.element($document[0].body),
- skipHide: true,
+ multiple: true,
fullscreen: true,
targetEvent: $event
}).then(function (entityAliases) {
@@ -488,7 +488,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget
gridSettings: gridSettings
},
parent: angular.element($document[0].body),
- skipHide: true,
+ multiple: true,
fullscreen: true,
targetEvent: $event
}).then(function (data) {
@@ -510,7 +510,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget
layouts: angular.copy(vm.dashboard.configuration.states[vm.dashboardCtx.state].layouts)
},
parent: angular.element($document[0].body),
- skipHide: true,
+ multiple: true,
fullscreen: true,
targetEvent: $event
}).then(function (layouts) {
@@ -531,7 +531,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget
states: states
},
parent: angular.element($document[0].body),
- skipHide: true,
+ multiple: true,
fullscreen: true,
targetEvent: $event
}).then(function (states) {
@@ -873,7 +873,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget
templateUrl: selectTargetLayoutTemplate,
parent: angular.element($document[0].body),
fullscreen: true,
- skipHide: true,
+ multiple: true,
targetEvent: $event
}).then(
function success(layoutId) {
@@ -941,7 +941,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget
},
parent: angular.element($document[0].body),
fullscreen: true,
- skipHide: true,
+ multiple: true,
targetEvent: event,
onComplete: function () {
var w = angular.element($window);
diff --git a/ui/src/app/dashboard/dashboard.tpl.html b/ui/src/app/dashboard/dashboard.tpl.html
index 79653a4..a1bb268 100644
--- a/ui/src/app/dashboard/dashboard.tpl.html
+++ b/ui/src/app/dashboard/dashboard.tpl.html
@@ -74,6 +74,7 @@
<md-icon aria-label="{{ 'dashboard.settings' | translate }}" class="material-icons">settings</md-icon>
</md-button>
<tb-dashboard-select ng-show="!vm.isEdit && !vm.widgetEditMode && vm.displayDashboardsSelect()"
+ md-theme="tb-dark"
ng-model="vm.currentDashboardId"
dashboards-scope="{{vm.currentDashboardScope}}"
customer-id="vm.currentCustomerId">
diff --git a/ui/src/app/dashboard/edit-widget.directive.js b/ui/src/app/dashboard/edit-widget.directive.js
index e6858b4..f8aee69 100644
--- a/ui/src/app/dashboard/edit-widget.directive.js
+++ b/ui/src/app/dashboard/edit-widget.directive.js
@@ -131,7 +131,7 @@ export default function EditWidgetDirective($compile, $templateCache, types, wid
},
parent: angular.element($document[0].body),
fullscreen: true,
- skipHide: true,
+ multiple: true,
targetEvent: event
}).then(function (singleEntityAlias) {
scope.dashboard.configuration.entityAliases[singleEntityAlias.id] = singleEntityAlias;
diff --git a/ui/src/app/dashboard/layouts/manage-dashboard-layouts.controller.js b/ui/src/app/dashboard/layouts/manage-dashboard-layouts.controller.js
index 5ce8a6c..828bd6f 100644
--- a/ui/src/app/dashboard/layouts/manage-dashboard-layouts.controller.js
+++ b/ui/src/app/dashboard/layouts/manage-dashboard-layouts.controller.js
@@ -51,7 +51,7 @@ export default function ManageDashboardLayoutsController($scope, $mdDialog, $doc
gridSettings: gridSettings
},
parent: angular.element($document[0].body),
- skipHide: true,
+ multiple: true,
fullscreen: true,
targetEvent: $event
}).then(function (data) {
diff --git a/ui/src/app/dashboard/states/manage-dashboard-states.controller.js b/ui/src/app/dashboard/states/manage-dashboard-states.controller.js
index 98df466..298b2ab 100644
--- a/ui/src/app/dashboard/states/manage-dashboard-states.controller.js
+++ b/ui/src/app/dashboard/states/manage-dashboard-states.controller.js
@@ -111,7 +111,7 @@ export default function ManageDashboardStatesController($scope, $mdDialog, $filt
templateUrl: dashboardStateDialogTemplate,
parent: angular.element($document[0].body),
locals: {isAdd: isAdd, allStates: vm.allStates, state: angular.copy(state)},
- skipHide: true,
+ multiple: true,
fullscreen: true,
targetEvent: $event
}).then(function (state) {
@@ -163,7 +163,7 @@ export default function ManageDashboardStatesController($scope, $mdDialog, $filt
.cancel($translate.instant('action.no'))
.ok($translate.instant('action.yes'));
- confirm._options.skipHide = true;
+ confirm._options.multiple = true;
confirm._options.fullscreen = true;
$mdDialog.show(confirm).then(function () {
diff --git a/ui/src/app/entity/alias/entity-aliases.controller.js b/ui/src/app/entity/alias/entity-aliases.controller.js
index 732f10f..49f28c9 100644
--- a/ui/src/app/entity/alias/entity-aliases.controller.js
+++ b/ui/src/app/entity/alias/entity-aliases.controller.js
@@ -126,7 +126,7 @@ export default function EntityAliasesController(utils, entityService, toast, $sc
},
parent: angular.element($document[0].body),
fullscreen: true,
- skipHide: true,
+ multiple: true,
targetEvent: $event
}).then(function (alias) {
if (isAdd) {
@@ -158,7 +158,7 @@ export default function EntityAliasesController(utils, entityService, toast, $sc
.ariaLabel($translate.instant('entity.unable-delete-entity-alias-title'))
.ok($translate.instant('action.close'))
.targetEvent($event);
- alert._options.skipHide = true;
+ alert._options.multiple = true;
alert._options.fullscreen = true;
$mdDialog.show(alert);
diff --git a/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js b/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js
index 5346e03..f39593c 100644
--- a/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js
+++ b/ui/src/app/entity/attribute/add-widget-to-dashboard-dialog.controller.js
@@ -53,7 +53,7 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
states: states
},
fullscreen: true,
- skipHide: true,
+ multiple: true,
targetEvent: $event
}).then(
function success(stateId) {
@@ -81,7 +81,7 @@ export default function AddWidgetToDashboardDialogController($scope, $mdDialog,
templateUrl: selectTargetLayoutTemplate,
parent: angular.element($document[0].body),
fullscreen: true,
- skipHide: true,
+ multiple: true,
targetEvent: $event
}).then(
function success(layoutId) {
diff --git a/ui/src/app/entity/relation/relation-table.directive.js b/ui/src/app/entity/relation/relation-table.directive.js
index 872042c..d2586c4 100644
--- a/ui/src/app/entity/relation/relation-table.directive.js
+++ b/ui/src/app/entity/relation/relation-table.directive.js
@@ -160,7 +160,7 @@ function RelationTableController($scope, $q, $mdDialog, $document, $translate, $
showingCallback: onShowingCallback},
targetEvent: $event,
fullscreen: true,
- skipHide: true,
+ multiple: true,
onShowing: function(scope, element) {
onShowingCallback.onShowing(scope, element);
}
diff --git a/ui/src/app/entity-view/entity-view.directive.js b/ui/src/app/entity-view/entity-view.directive.js
index 25377f4..761930e 100644
--- a/ui/src/app/entity-view/entity-view.directive.js
+++ b/ui/src/app/entity-view/entity-view.directive.js
@@ -98,8 +98,8 @@ export default function EntityViewDirective($q, $compile, $templateCache, $filte
if (newDate.getTime() > scope.maxStartTimeMs) {
scope.startTimeMs = angular.copy(scope.maxStartTimeMs);
}
- updateMinMaxDates();
}
+ updateMinMaxDates();
});
scope.$watch('endTimeMs', function (newDate) {
@@ -107,18 +107,24 @@ export default function EntityViewDirective($q, $compile, $templateCache, $filte
if (newDate.getTime() < scope.minEndTimeMs) {
scope.endTimeMs = angular.copy(scope.minEndTimeMs);
}
- updateMinMaxDates();
}
+ updateMinMaxDates();
});
function updateMinMaxDates() {
- if (scope.endTimeMs) {
- scope.maxStartTimeMs = angular.copy(new Date(scope.endTimeMs.getTime()));
- scope.entityView.endTimeMs = scope.endTimeMs.getTime();
- }
- if (scope.startTimeMs) {
- scope.minEndTimeMs = angular.copy(new Date(scope.startTimeMs.getTime()));
- scope.entityView.startTimeMs = scope.startTimeMs.getTime();
+ if (scope.entityView) {
+ if (scope.endTimeMs) {
+ scope.maxStartTimeMs = angular.copy(new Date(scope.endTimeMs.getTime()));
+ scope.entityView.endTimeMs = scope.endTimeMs.getTime();
+ } else {
+ scope.entityView.endTimeMs = 0;
+ }
+ if (scope.startTimeMs) {
+ scope.minEndTimeMs = angular.copy(new Date(scope.startTimeMs.getTime()));
+ scope.entityView.startTimeMs = scope.startTimeMs.getTime();
+ } else {
+ scope.entityView.startTimeMs = 0;
+ }
}
}
diff --git a/ui/src/app/event/event-row.directive.js b/ui/src/app/event/event-row.directive.js
index b808fb8..5f84187 100644
--- a/ui/src/app/event/event-row.directive.js
+++ b/ui/src/app/event/event-row.directive.js
@@ -79,7 +79,7 @@ export default function EventRowDirective($compile, $templateCache, $mdDialog, $
parent: angular.element($document[0].body),
fullscreen: true,
targetEvent: $event,
- skipHide: true,
+ multiple: true,
onShowing: function(scope, element) {
onShowingCallback.onShowing(scope, element);
}
diff --git a/ui/src/app/extension/extension-table.directive.js b/ui/src/app/extension/extension-table.directive.js
index 18d281c..73c5d93 100644
--- a/ui/src/app/extension/extension-table.directive.js
+++ b/ui/src/app/extension/extension-table.directive.js
@@ -208,7 +208,7 @@ function ExtensionTableController($scope, $filter, $document, $translate, $timeo
bindToController: true,
targetEvent: $event,
fullscreen: true,
- skipHide: true
+ multiple: true
}).then(function() {
reloadExtensions();
}, function () {
diff --git a/ui/src/app/import-export/import-export.service.js b/ui/src/app/import-export/import-export.service.js
index d64441f..e08fd24 100644
--- a/ui/src/app/import-export/import-export.service.js
+++ b/ui/src/app/import-export/import-export.service.js
@@ -721,7 +721,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
}
},
parent: angular.element($document[0].body),
- skipHide: true,
+ multiple: true,
fullscreen: true,
targetEvent: $event
}).then(function (updatedEntityAliases) {
@@ -796,7 +796,7 @@ export default function ImportExport($log, $translate, $q, $mdDialog, $document,
importFileLabel: importFileLabel
},
parent: angular.element($document[0].body),
- skipHide: true,
+ multiple: true,
fullscreen: true,
targetEvent: $event
}).then(function (importData) {
ui/src/app/layout/index.js 2(+0 -2)
diff --git a/ui/src/app/layout/index.js b/ui/src/app/layout/index.js
index 192400a..7c7e870 100644
--- a/ui/src/app/layout/index.js
+++ b/ui/src/app/layout/index.js
@@ -17,7 +17,6 @@ import './home.scss';
import uiRouter from 'angular-ui-router';
import ngSanitize from 'angular-sanitize';
-import FBAngular from 'angular-fullscreen';
import 'angular-breadcrumb';
import thingsboardMenu from '../services/menu.service';
@@ -63,7 +62,6 @@ import BreadcrumbIcon from './breadcrumb-icon.filter';
export default angular.module('thingsboard.home', [
uiRouter,
ngSanitize,
- FBAngular.name,
'ncy-angular-breadcrumb',
thingsboardMenu,
thingsboardHomeLinks,
diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json
index 9f326c7..30e1eb1 100644
--- a/ui/src/app/locale/locale.constant-en_US.json
+++ b/ui/src/app/locale/locale.constant-en_US.json
@@ -774,6 +774,7 @@
},
"entity-view": {
"entity-view": "Entity View",
+ "entity-view-required": "Entity view is required.",
"entity-views": "Entity Views",
"management": "Entity View management",
"view-entity-views": "View Entity Views",
@@ -1573,7 +1574,7 @@
"ru_RU": "Russian",
"es_ES": "Spanish",
"ja_JA": "Japanese",
- "TR": "Turkish"
+ "tr_TR": "Turkish"
}
}
}
diff --git a/ui/src/app/locale/locale.constant-es_ES.json b/ui/src/app/locale/locale.constant-es_ES.json
index 81cc52e..7b02141 100644
--- a/ui/src/app/locale/locale.constant-es_ES.json
+++ b/ui/src/app/locale/locale.constant-es_ES.json
@@ -1552,7 +1552,7 @@
"ru_RU": "Ruso",
"es_ES": "Español",
"ja_JA": "Japonés",
- "TR": "Turco"
+ "tr_TR": "Turco"
}
}
}
diff --git a/ui/src/app/locale/locale.constant-fr_FR.json b/ui/src/app/locale/locale.constant-fr_FR.json
index 8b270aa..dd2fa0d 100644
--- a/ui/src/app/locale/locale.constant-fr_FR.json
+++ b/ui/src/app/locale/locale.constant-fr_FR.json
@@ -1010,7 +1010,7 @@
"ko_KR": "Coréen",
"ru_RU": "Russe",
"zh_CN": "Chinois",
- "TR": "Turc"
+ "tr_TR": "Turc"
}
},
"layout": {
diff --git a/ui/src/app/locale/locale.constant-it_IT.json b/ui/src/app/locale/locale.constant-it_IT.json
index 79843ab..49f9b48 100644
--- a/ui/src/app/locale/locale.constant-it_IT.json
+++ b/ui/src/app/locale/locale.constant-it_IT.json
@@ -1442,7 +1442,7 @@
"ru_RU": "Russo",
"es_ES": "Spagnolo",
"ja_JA": "Giapponese",
- "TR": "Turco"
+ "tr_TR": "Turco"
}
}
}
diff --git a/ui/src/app/locale/locale.constant-ja_JA.json b/ui/src/app/locale/locale.constant-ja_JA.json
index 6c48894..2ab2bcb 100644
--- a/ui/src/app/locale/locale.constant-ja_JA.json
+++ b/ui/src/app/locale/locale.constant-ja_JA.json
@@ -1458,7 +1458,7 @@
"ru_RU": "ロシア",
"es_ES": "スペイン語",
"ja_JA": "日本語",
- "TR": "トルコ語"
+ "tr_TR": "トルコ語"
}
}
}
diff --git a/ui/src/app/locale/locale.constant-ko_KR.json b/ui/src/app/locale/locale.constant-ko_KR.json
index 07e7dd6..62c0054 100644
--- a/ui/src/app/locale/locale.constant-ko_KR.json
+++ b/ui/src/app/locale/locale.constant-ko_KR.json
@@ -1336,7 +1336,7 @@
"es_ES": "스페인어",
"it_IT": "이탈리아 사람",
"ja_JA": "일본어",
- "TR": "터키어"
+ "tr_TR": "터키어"
}
}
}
diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json
index 893bbcd..eb9996d 100644
--- a/ui/src/app/locale/locale.constant-ru_RU.json
+++ b/ui/src/app/locale/locale.constant-ru_RU.json
@@ -1361,7 +1361,7 @@
"it_IT": "Итальянский",
"ru_RU": "Русский",
"ja_JA": "Японский",
- "TR": "Турецкий"
+ "tr_TR": "Турецкий"
}
}
diff --git a/ui/src/app/locale/locale.constant-zh_CN.json b/ui/src/app/locale/locale.constant-zh_CN.json
index c952f9c..bac1706 100644
--- a/ui/src/app/locale/locale.constant-zh_CN.json
+++ b/ui/src/app/locale/locale.constant-zh_CN.json
@@ -1445,7 +1445,7 @@
"es_ES": "西班牙语",
"it_IT": "意大利",
"ja_JA": "日本",
- "TR": "土耳其"
+ "tr_TR": "土耳其"
}
}
}
diff --git a/ui/src/app/rulechain/script/node-script-test.service.js b/ui/src/app/rulechain/script/node-script-test.service.js
index 81d81c1..d4e6df8 100644
--- a/ui/src/app/rulechain/script/node-script-test.service.js
+++ b/ui/src/app/rulechain/script/node-script-test.service.js
@@ -121,7 +121,7 @@ export default function NodeScriptTest($q, $mdDialog, $document, ruleChainServic
onShowingCallback: onShowingCallback
},
fullscreen: true,
- skipHide: true,
+ multiple: true,
targetEvent: $event,
onComplete: () => {
onShowingCallback.onShowed();
diff --git a/ui/src/app/rulechain/script/node-script-test.tpl.html b/ui/src/app/rulechain/script/node-script-test.tpl.html
index e5fb9c3..e7f50cc 100644
--- a/ui/src/app/rulechain/script/node-script-test.tpl.html
+++ b/ui/src/app/rulechain/script/node-script-test.tpl.html
@@ -98,7 +98,7 @@
validate-content="false"
ng-readonly="true"
fill-height="true">
- </tb-json-content>generateReport
+ </tb-json-content>
</div>
</div>
</div>
diff --git a/ui/src/app/user/add-user.controller.js b/ui/src/app/user/add-user.controller.js
index 20ad0be..c81cd67 100644
--- a/ui/src/app/user/add-user.controller.js
+++ b/ui/src/app/user/add-user.controller.js
@@ -100,7 +100,7 @@ export default function AddUserController($scope, $mdDialog, $state, $stateParam
},
parent: angular.element($document[0].body),
fullscreen: true,
- skipHide: true,
+ multiple: true,
targetEvent: $event
}).then(function () {
deferred.resolve();
ui/src/app/user/user.controller.js 2(+1 -1)
diff --git a/ui/src/app/user/user.controller.js b/ui/src/app/user/user.controller.js
index 8d7f214..ef7948d 100644
--- a/ui/src/app/user/user.controller.js
+++ b/ui/src/app/user/user.controller.js
@@ -186,7 +186,7 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d
},
parent: angular.element($document[0].body),
fullscreen: true,
- skipHide: true,
+ multiple: true,
targetEvent: event
});
}
diff --git a/ui/src/app/widget/lib/alarms-table-widget.js b/ui/src/app/widget/lib/alarms-table-widget.js
index 6ae17e0..3d91e9a 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.js
+++ b/ui/src/app/widget/lib/alarms-table-widget.js
@@ -402,7 +402,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
parent: angular.element($document[0].body),
targetEvent: $event,
fullscreen: true,
- skipHide: true,
+ multiple: true,
onShowing: function(scope, element) {
onShowingCallback.onShowing(scope, element);
}
diff --git a/ui/src/app/widget/lib/canvas-digital-gauge.js b/ui/src/app/widget/lib/canvas-digital-gauge.js
index 283a426..274cc97 100644
--- a/ui/src/app/widget/lib/canvas-digital-gauge.js
+++ b/ui/src/app/widget/lib/canvas-digital-gauge.js
@@ -209,8 +209,8 @@ export default class TbCanvasDigitalGauge {
}
var value = tvPair[1];
if(value !== this.gauge.value) {
- this.gauge.value = value;
this.gauge._value = value;
+ this.gauge.value = value;
} else if (this.localSettings.showTimestamp && this.gauge.timestamp != timestamp) {
this.gauge.timestamp = timestamp;
}