diff --git a/.gitignore b/.gitignore
index 9039e8ec4d..da8569baa3 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 1ccfdc9071..6550f58da4 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 \"
\"\n );\n\n var datasourceContainer = $('#' + datasourceId,\n self.ctx.$container);\n\n datasourceContainer.append(\n \"\" +\n tbDatasource.name + \"
\"\n );\n \n var datasourceTitleCell = $('.tbDatasource-title', datasourceContainer);\n self.ctx.datasourceTitleCells.push(datasourceTitleCell);\n \n var tableId = 'table' + i;\n datasourceContainer.append(\n \"\"\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(\"| \" + dataKey.label +\n \" | |
\");\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 \"\"\n );\n\n var datasourceContainer = $('#' + datasourceId,\n self.ctx.$container);\n\n datasourceContainer.append(\n \"\" +\n tbDatasource.name + \"
\"\n );\n \n var datasourceTitleCell = $('.tbDatasource-title', datasourceContainer);\n self.ctx.datasourceTitleCells.push(datasourceTitleCell);\n \n var tableId = 'table' + i;\n datasourceContainer.append(\n \"\"\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(\"| \" + dataKey.label +\n \" | |
\");\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": "\n",
"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 0000000000..1832b7901b
--- /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 5e19d690c4..091b504ea7 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 bd2a0f4486..7b412b1328 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
@@ -43,12 +43,20 @@ public class DeviceActor extends ContextAwareActor {
this.processor = new DeviceActorMessageProcessor(systemContext, logger, tenantId, deviceId);
}
+ @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 05902a6146..a16bd78a25 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 sessions;
+ final TenantId tenantId;
+ final DeviceId deviceId;
+ private final Map sessions;
private final Map attributeSubscriptions;
private final Map rpcSubscriptions;
private final Map 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> processPendingRpc(ActorContext context, UUID sessionId, String nodeId, Set 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> 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 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 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 43ae592cee..fe09077af1 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 0000000000..dd0839486f
--- /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 0000000000..d9172ae825
--- /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 14bb636830..1eba066960 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 608a77fd7a..31320ce5d2 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 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 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 c9cf8696e5..86509ca08e 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 4e779d452b..0baeceabc3 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 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 f7e80e4baa..08d1dd8a11 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 c2e8fd048a..85b8943569 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 8864486150..c8097821d9 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 347483a185..721d828fd6 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 582bbedebd..be1b6db1d9 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 9b7ffd2d11..2a9c073d34 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,
@@ -184,11 +181,24 @@ 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 6c37614147..ef09a7a62c 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;
@@ -249,6 +249,60 @@ public class TelemetryController extends BaseController {
return saveTelemetry(entityId, requestBody, ttl);
}
+ @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/{entityType}/{entityId}/timeseries/delete", method = RequestMethod.DELETE)
+ @ResponseBody
+ public DeferredResult 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 deleteTimeseries(EntityId entityIdStr, String keysStr, boolean deleteAllDataForKeys,
+ Long startTs, Long endTs, boolean rewriteLatestIfDeleted) throws ThingsboardException {
+ List 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 deleteTsKvQueries = new ArrayList<>();
+ for (String key : keys) {
+ deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted));
+ }
+
+ ListenableFuture> future = tsService.remove(entityId, deleteTsKvQueries);
+ Futures.addCallback(future, new FutureCallback>() {
+ @Override
+ public void onSuccess(@Nullable List 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
@@ -506,6 +560,15 @@ public class TelemetryController extends BaseController {
};
}
+ private void logTimeseriesDeleted(SecurityUser user, EntityId entityId, List 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 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 45eb66a332..2486cd8d41 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 55cf52c37b..dd0a995c84 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 inputStream;
private StreamObserver 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 f3ceed2069..545baf8e11 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 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 552695314e..a2aeefaecf 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 2c7f89c75a..b4a725d941 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 64f5213d65..a65c1a5771 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 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 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 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 consumer = localToDeviceRpcRequests.remove(requestId);
+ Consumer 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 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 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 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 4cee96ee71..bf12ec664c 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 responseConsumer);
- void processRestAPIRpcResponseFromRuleEngine(FromDeviceRpcResponse response);
+ void processResponseToServerSideRPCRequestFromRuleEngine(ServerAddress requestOriginAddress, FromDeviceRpcResponse response);
- void processRpcRequestToDevice(ToDeviceRpcRequest request, Consumer responseConsumer);
+ void forwardServerSideRPCRequestToDeviceActor(ToDeviceRpcRequest request, Consumer 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 9c3ce9a834..75506dfacc 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 0000000000..6201dabdda
--- /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 0000000000..0a2e6a52e5
--- /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 f4e37db971..610cd3b72a 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 3b0deea78a..7c772420ba 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 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 0f722306ff..3d12938903 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 callback) {
- if (checkLimits(sessionInfo, callback)) {
- forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSessionEvent(msg).build(), callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback callback) {
+ forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSessionEvent(msg).build(), callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback callback) {
- if (checkLimits(sessionInfo, callback)) {
- forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setPostTelemetry(msg).build(), callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback callback) {
+ forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setPostTelemetry(msg).build(), callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback callback) {
- if (checkLimits(sessionInfo, callback)) {
- forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setPostAttributes(msg).build(), callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback callback) {
+ forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setPostAttributes(msg).build(), callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback callback) {
- if (checkLimits(sessionInfo, callback)) {
- forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setGetAttributes(msg).build(), callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback callback) {
+ forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setGetAttributes(msg).build(), callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback callback) {
- if (checkLimits(sessionInfo, callback)) {
- forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSubscribeToAttributes(msg).build(), callback);
- }
+ public void process(SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto msg, TransportServiceCallback callback) {
+ forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSubscriptionInfo(msg).build(), callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, SubscribeToRPCMsg msg, TransportServiceCallback callback) {
- if (checkLimits(sessionInfo, callback)) {
- forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSubscribeToRPC(msg).build(), callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback callback) {
+ forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSubscribeToAttributes(msg).build(), callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg msg, TransportServiceCallback callback) {
- if (checkLimits(sessionInfo, callback)) {
- forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setToDeviceRPCCallResponse(msg).build(), callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, SubscribeToRPCMsg msg, TransportServiceCallback callback) {
+ forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSubscribeToRPC(msg).build(), callback);
}
@Override
- public void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback callback) {
- if (checkLimits(sessionInfo, callback)) {
- forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setToServerRPCCallRequest(msg).build(), callback);
- }
+ protected void doProcess(SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg msg, TransportServiceCallback callback) {
+ forwardToDeviceActor(TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setToDeviceRPCCallResponse(msg).build(), callback);
+ }
+
+ @Override
+ protected void doProcess(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback 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 38db1a415b..6c903cc955 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 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 21c963b15e..1940b36c12 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 3c687759e2..763319e52c 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 da6db707f1..51ddaa9181 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 f050f3b505..1de2787af6 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 24db4570bd..f59dd63614 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/CacheConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
index 698a69ef6b..853caff8d5 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/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 c37d4607c3..822387cfcb 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/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 24758b5b36..44a98d6ee4 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/cluster/ServerType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/cluster/ServerType.java
index f9b057c463..08f74fbe54 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/system/ServiceToRuleEngineMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/system/ServiceToRuleEngineMsg.java
index 0792b63c28..7859623d52 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 86be3a3c38..c8a1706c3b 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 {
private TBKafkaConsumerTemplate(TbKafkaSettings settings, TbKafkaDecoder decoder,
TbKafkaRequestIdExtractor 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 {
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 610a49007d..bd42f310a6 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 {
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 da8b3a62ca..a178bdf891 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 d6000591a8..ac33ba677e 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/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
index a47438f30d..8944e94a86 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 callback);
+ void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto msg, TransportServiceCallback 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/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 265dacb5de..fad19541b4 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 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 callback) {
+ if (checkLimits(sessionInfo, callback)) {
+ reportActivityInternal(sessionInfo);
+ doProcess(sessionInfo, msg, callback);
+ }
+ }
+
+ @Override
+ public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback callback) {
+ if (checkLimits(sessionInfo, callback)) {
+ reportActivityInternal(sessionInfo);
+ doProcess(sessionInfo, msg, callback);
+ }
+ }
+
+ @Override
+ public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback callback) {
+ if (checkLimits(sessionInfo, callback)) {
+ reportActivityInternal(sessionInfo);
+ doProcess(sessionInfo, msg, callback);
+ }
+ }
+
+ @Override
+ public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.GetAttributeRequestMsg msg, TransportServiceCallback callback) {
+ if (checkLimits(sessionInfo, callback)) {
+ reportActivityInternal(sessionInfo);
+ doProcess(sessionInfo, msg, callback);
+ }
+ }
+
+ @Override
+ public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback 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 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 callback) {
+ if (checkLimits(sessionInfo, callback)) {
+ reportActivityInternal(sessionInfo);
+ doProcess(sessionInfo, msg, callback);
+ }
+ }
+
+ @Override
+ public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToServerRpcRequestMsg msg, TransportServiceCallback 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 callback);
+
+ protected abstract void doProcess(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback callback);
+
+ protected abstract void doProcess(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback callback);
+
+ protected abstract void doProcess(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.GetAttributeRequestMsg msg, TransportServiceCallback callback);
+
+ protected abstract void doProcess(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback callback);
+
+ protected abstract void doProcess(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToRPCMsg msg, TransportServiceCallback callback);
+
+ protected abstract void doProcess(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDeviceRpcResponseMsg msg, TransportServiceCallback callback);
+
+ protected abstract void doProcess(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToServerRpcRequestMsg msg, TransportServiceCallback 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 6774942de3..4b11bf58c9 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 1de57114e4..8642e93a97 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/proto/transport.proto b/common/transport/transport-api/src/main/proto/transport.proto
index 0e36dabbf3..ff740d4ce4 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 da87b44322..9c828663c1 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 findEntityViewByTenantId(TenantId tenantId, TextPageLink pageLink);
TextPageData 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 2d94cc2eab..9f8949dc4e 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 entityViewOpt = entityViewDao.findEntityViewByTenantIdAndName(tenantId.getId(), name);
+ return entityViewOpt.orElse(null);
+ }
+
@Override
public TextPageData 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 66fbcc3382..e4c6bb5318 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 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 0000000000..a3490bffdd
--- /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 {
+
+ 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 create() {
+ return SettableFuture.create();
+ }
+
+ @Override
+ protected ResultSetFuture wrap(CassandraStatementTask task, SettableFuture future) {
+ return new TbResultSetFuture(future);
+ }
+
+ @Override
+ protected ResultSetFuture execute(AsyncTaskContext 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 0000000000..19e56402db
--- /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 0000000000..574a5f5e69
--- /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 mainFuture;
+
+ public TbResultSetFuture(SettableFuture 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 5bd9175628..04227a3825 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 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 latestFuture = findLatest(entityId, query.getKey());
+
+ ListenableFuture booleanFuture = Futures.transform(latestFuture, tsKvEntry -> {
+ long ts = tsKvEntry.getTs();
+ return ts > query.getStartTs() && ts <= query.getEndTs();
+ }, service);
+
+ ListenableFuture 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 resultFuture = new SimpleListenableFuture<>();
+ Futures.addCallback(removedLatestFuture, new FutureCallback() {
+ @Override
+ public void onSuccess(@Nullable Void result) {
+ if (query.getRewriteLatestIfDeleted()) {
+ ListenableFuture 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 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> 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 4c743e5ab6..296d173cf7 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 :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 709bfd5432..fdc69f9262 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 removeLatest(EntityId entityId, DeleteTsKvQuery query) {
ListenableFuture latestEntryFuture = findLatest(entityId, query.getKey());
- ListenableFuture booleanFuture = Futures.transformAsync(latestEntryFuture, latestEntry -> {
+ ListenableFuture 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 removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
@@ -451,18 +451,34 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
return Futures.immediateFuture(null);
}, readResultsProcessingExecutor);
- if (query.getRewriteLatestIfDeleted()) {
- ListenableFuture savedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
- if (isRemove) {
- return getNewLatestEntryFuture(entityId, query);
+ final SimpleListenableFuture resultFuture = new SimpleListenableFuture<>();
+ Futures.addCallback(removedLatestFuture, new FutureCallback() {
+ @Override
+ public void onSuccess(@Nullable Void result) {
+ if (query.getRewriteLatestIfDeleted()) {
+ ListenableFuture 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 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 0000000000..96d3870dcd
--- /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, V> implements BufferedRateExecutor {
+
+ private final long maxWaitTime;
+ private final long pollMs;
+ private final BlockingQueue> 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 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 create();
+
+ protected abstract F wrap(T task, SettableFuture future);
+
+ protected abstract ListenableFuture execute(AsyncTaskContext taskCtx);
+
+ private void dispatch() {
+ log.info("Buffered rate executor thread started");
+ while (!Thread.interrupted()) {
+ int curLvl = concurrencyLevel.get();
+ AsyncTaskContext taskCtx = null;
+ try {
+ if (curLvl <= concurrencyLimit) {
+ taskCtx = queue.take();
+ final AsyncTaskContext finalTaskCtx = taskCtx;
+ logTask("Processing", finalTaskCtx);
+ concurrencyLevel.incrementAndGet();
+ long timeout = finalTaskCtx.getCreateTime() + maxWaitTime - System.currentTimeMillis();
+ if (timeout > 0) {
+ totalLaunched.incrementAndGet();
+ ListenableFuture result = execute(finalTaskCtx);
+ result = Futures.withTimeout(result, timeout, TimeUnit.MILLISECONDS, timeoutExecutor);
+ Futures.addCallback(result, new FutureCallback() {
+ @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 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 0000000000..672e4f4656
--- /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 0000000000..593b3fb04c
--- /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 {
+
+ private final UUID id;
+ private final T task;
+ private final SettableFuture 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 0000000000..21f4cd0493
--- /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 {
+
+ F submit(T task);
+
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java b/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java
deleted file mode 100644
index eab05b2fdb..0000000000
--- a/dao/src/main/java/org/thingsboard/server/dao/util/BufferedRateLimiter.java
+++ /dev/null
@@ -1,182 +0,0 @@
-/**
- * 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.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-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.exception.BufferLimitException;
-
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executors;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-@Component
-@Slf4j
-@NoSqlAnyDao
-public class BufferedRateLimiter implements AsyncRateLimiter {
-
- private final ListeningExecutorService pool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
-
- private final int permitsLimit;
- private final int maxPermitWaitTime;
- private final AtomicInteger permits;
- private final BlockingQueue queue;
-
- private final AtomicInteger maxQueueSize = new AtomicInteger();
- private final AtomicInteger maxGrantedPermissions = new AtomicInteger();
- private final AtomicInteger totalGranted = new AtomicInteger();
- private final AtomicInteger totalReleased = new AtomicInteger();
- private final AtomicInteger totalRequested = new AtomicInteger();
-
- public BufferedRateLimiter(@Value("${cassandra.query.buffer_size}") int queueLimit,
- @Value("${cassandra.query.concurrent_limit}") int permitsLimit,
- @Value("${cassandra.query.permit_max_wait_time}") int maxPermitWaitTime) {
- this.permitsLimit = permitsLimit;
- this.maxPermitWaitTime = maxPermitWaitTime;
- this.permits = new AtomicInteger();
- this.queue = new LinkedBlockingQueue<>(queueLimit);
- }
-
- @Override
- public ListenableFuture acquireAsync() {
- totalRequested.incrementAndGet();
- if (queue.isEmpty()) {
- if (permits.incrementAndGet() <= permitsLimit) {
- if (permits.get() > maxGrantedPermissions.get()) {
- maxGrantedPermissions.set(permits.get());
- }
- totalGranted.incrementAndGet();
- return Futures.immediateFuture(null);
- }
- permits.decrementAndGet();
- }
-
- return putInQueue();
- }
-
- @Override
- public void release() {
- permits.decrementAndGet();
- totalReleased.incrementAndGet();
- reprocessQueue();
- }
-
- private void reprocessQueue() {
- while (permits.get() < permitsLimit) {
- if (permits.incrementAndGet() <= permitsLimit) {
- if (permits.get() > maxGrantedPermissions.get()) {
- maxGrantedPermissions.set(permits.get());
- }
- LockedFuture lockedFuture = queue.poll();
- if (lockedFuture != null) {
- totalGranted.incrementAndGet();
- lockedFuture.latch.countDown();
- } else {
- permits.decrementAndGet();
- break;
- }
- } else {
- permits.decrementAndGet();
- }
- }
- }
-
- private LockedFuture createLockedFuture() {
- CountDownLatch latch = new CountDownLatch(1);
- ListenableFuture future = pool.submit(() -> {
- latch.await();
- return null;
- });
- return new LockedFuture(latch, future, System.currentTimeMillis());
- }
-
- private ListenableFuture putInQueue() {
-
- int size = queue.size();
- if (size > maxQueueSize.get()) {
- maxQueueSize.set(size);
- }
-
- if (queue.remainingCapacity() > 0) {
- try {
- LockedFuture lockedFuture = createLockedFuture();
- if (!queue.offer(lockedFuture, 1, TimeUnit.SECONDS)) {
- lockedFuture.cancelFuture();
- return Futures.immediateFailedFuture(new BufferLimitException());
- }
- if(permits.get() < permitsLimit) {
- reprocessQueue();
- }
- if(permits.get() < permitsLimit) {
- reprocessQueue();
- }
- return lockedFuture.future;
- } catch (InterruptedException e) {
- return Futures.immediateFailedFuture(new BufferLimitException());
- }
- }
- return Futures.immediateFailedFuture(new BufferLimitException());
- }
-
- @Scheduled(fixedDelayString = "${cassandra.query.rate_limit_print_interval_ms}")
- public void printStats() {
- int expiredCount = 0;
- for (LockedFuture lockedFuture : queue) {
- if (lockedFuture.isExpired()) {
- lockedFuture.cancelFuture();
- expiredCount++;
- }
- }
- log.info("Permits maxBuffer [{}] maxPermits [{}] expired [{}] currPermits [{}] currBuffer [{}] " +
- "totalPermits [{}] totalRequests [{}] totalReleased [{}]",
- maxQueueSize.getAndSet(0), maxGrantedPermissions.getAndSet(0), expiredCount,
- permits.get(), queue.size(),
- totalGranted.getAndSet(0), totalRequested.getAndSet(0), totalReleased.getAndSet(0));
- }
-
- private class LockedFuture {
- final CountDownLatch latch;
- final ListenableFuture future;
- final long createTime;
-
- public LockedFuture(CountDownLatch latch, ListenableFuture future, long createTime) {
- this.latch = latch;
- this.future = future;
- this.createTime = createTime;
- }
-
- void cancelFuture() {
- future.cancel(false);
- latch.countDown();
- }
-
- boolean isExpired() {
- return (System.currentTimeMillis() - createTime) > maxPermitWaitTime;
- }
-
- }
-
-
-}
diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql
index 0b9c8535fb..fc23832d56 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 88f4d84eb6..81de40ac82 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);
@@ -171,6 +171,26 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
Assert.assertEquals(null, latest.get(0).getValueAsString());
}
+ @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 list = tsService.findAll(deviceId, Collections.singletonList(
+ new BaseReadTsKvQuery(STRING_KEY, 5000, 45000, 10000, 10, Aggregation.NONE))).get();
+ Assert.assertEquals(2, list.size());
+
+ List 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());
diff --git a/dao/src/test/java/org/thingsboard/server/dao/util/BufferedRateLimiterTest.java b/dao/src/test/java/org/thingsboard/server/dao/util/BufferedRateLimiterTest.java
deleted file mode 100644
index 366cefd5d8..0000000000
--- a/dao/src/test/java/org/thingsboard/server/dao/util/BufferedRateLimiterTest.java
+++ /dev/null
@@ -1,142 +0,0 @@
-/**
- * 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.ListeningExecutorService;
-import com.google.common.util.concurrent.MoreExecutors;
-import org.junit.Test;
-import org.thingsboard.server.dao.exception.BufferLimitException;
-
-import javax.annotation.Nullable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-
-public class BufferedRateLimiterTest {
-
- @Test
- public void finishedFutureReturnedIfPermitsAreGranted() {
- BufferedRateLimiter limiter = new BufferedRateLimiter(10, 10, 100);
- ListenableFuture actual = limiter.acquireAsync();
- assertTrue(actual.isDone());
- }
-
- @Test
- public void notFinishedFutureReturnedIfPermitsAreNotGranted() {
- BufferedRateLimiter limiter = new BufferedRateLimiter(10, 1, 100);
- ListenableFuture actual1 = limiter.acquireAsync();
- ListenableFuture actual2 = limiter.acquireAsync();
- assertTrue(actual1.isDone());
- assertFalse(actual2.isDone());
- }
-
- @Test
- public void failedFutureReturnedIfQueueIsfull() {
- BufferedRateLimiter limiter = new BufferedRateLimiter(1, 1, 100);
- ListenableFuture actual1 = limiter.acquireAsync();
- ListenableFuture actual2 = limiter.acquireAsync();
- ListenableFuture actual3 = limiter.acquireAsync();
-
- assertTrue(actual1.isDone());
- assertFalse(actual2.isDone());
- assertTrue(actual3.isDone());
- try {
- actual3.get();
- fail();
- } catch (Exception e) {
- assertTrue(e instanceof ExecutionException);
- Throwable actualCause = e.getCause();
- assertTrue(actualCause instanceof BufferLimitException);
- assertEquals("Rate Limit Buffer is full", actualCause.getMessage());
- }
- }
-
- @Test
- public void releasedPermitTriggerTasksFromQueue() throws InterruptedException {
- BufferedRateLimiter limiter = new BufferedRateLimiter(10, 2, 100);
- ListenableFuture actual1 = limiter.acquireAsync();
- ListenableFuture actual2 = limiter.acquireAsync();
- ListenableFuture actual3 = limiter.acquireAsync();
- ListenableFuture actual4 = limiter.acquireAsync();
- assertTrue(actual1.isDone());
- assertTrue(actual2.isDone());
- assertFalse(actual3.isDone());
- assertFalse(actual4.isDone());
- limiter.release();
- TimeUnit.MILLISECONDS.sleep(100L);
- assertTrue(actual3.isDone());
- assertFalse(actual4.isDone());
- limiter.release();
- TimeUnit.MILLISECONDS.sleep(100L);
- assertTrue(actual4.isDone());
- }
-
- @Test
- public void permitsReleasedInConcurrentMode() throws InterruptedException {
- BufferedRateLimiter limiter = new BufferedRateLimiter(10, 2, 100);
- AtomicInteger actualReleased = new AtomicInteger();
- AtomicInteger actualRejected = new AtomicInteger();
- ListeningExecutorService pool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(5));
- for (int i = 0; i < 100; i++) {
- ListenableFuture> submit = pool.submit(limiter::acquireAsync);
- Futures.addCallback(submit, new FutureCallback>() {
- @Override
- public void onSuccess(@Nullable ListenableFuture result) {
- Futures.addCallback(result, new FutureCallback() {
- @Override
- public void onSuccess(@Nullable Void result) {
- try {
- TimeUnit.MILLISECONDS.sleep(100);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- limiter.release();
- actualReleased.incrementAndGet();
- }
-
- @Override
- public void onFailure(Throwable t) {
- actualRejected.incrementAndGet();
- }
- });
- }
-
- @Override
- public void onFailure(Throwable t) {
- }
- });
- }
-
- TimeUnit.SECONDS.sleep(2);
- assertTrue("Unexpected released count " + actualReleased.get(),
- actualReleased.get() > 10 && actualReleased.get() < 20);
- assertTrue("Unexpected rejected count " + actualRejected.get(),
- actualRejected.get() > 80 && actualRejected.get() < 90);
-
- }
-
-
-}
\ No newline at end of file
diff --git a/docker/.env b/docker/.env
index e3306324d4..c03845dfe0 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"
diff --git a/msa/docker/.gitignore b/docker/.gitignore
similarity index 65%
rename from msa/docker/.gitignore
rename to docker/.gitignore
index c9843dc5a5..bc09e846b0 100644
--- a/msa/docker/.gitignore
+++ b/docker/.gitignore
@@ -2,4 +2,6 @@ haproxy/certs.d/**
haproxy/letsencrypt/**
tb-node/log/**
tb-node/db/**
+tb-node/postgres/**
+tb-node/cassandra/**
!.env
diff --git a/docker/README.md b/docker/README.md
new file mode 100644
index 0000000000..c43a136d09
--- /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.
diff --git a/docker/cassandra-setup/Dockerfile b/docker/cassandra-setup/Dockerfile
deleted file mode 100644
index c3f7ddb725..0000000000
--- a/docker/cassandra-setup/Dockerfile
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# 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-jre
-
-ADD install.sh /install.sh
-ADD thingsboard.deb /thingsboard.deb
-
-RUN apt-get update \
- && apt-get install -y nmap \
- && chmod +x /install.sh
diff --git a/docker/cassandra-setup/Makefile b/docker/cassandra-setup/Makefile
deleted file mode 100644
index 7990a3e7e8..0000000000
--- a/docker/cassandra-setup/Makefile
+++ /dev/null
@@ -1,12 +0,0 @@
-VERSION=2.1.0
-PROJECT=thingsboard
-APP=cassandra-setup
-
-build:
- cp ../../application/target/thingsboard.deb .
- docker build --pull -t ${PROJECT}/${APP}:${VERSION} -t ${PROJECT}/${APP}:latest .
- rm thingsboard.deb
-
-push: build
- docker push ${PROJECT}/${APP}:${VERSION}
- docker push ${PROJECT}/${APP}:latest
diff --git a/docker/cassandra-upgrade/Dockerfile b/docker/cassandra-upgrade/Dockerfile
deleted file mode 100644
index 312db0d4e3..0000000000
--- a/docker/cassandra-upgrade/Dockerfile
+++ /dev/null
@@ -1,24 +0,0 @@
-#
-# 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-jre
-
-ADD upgrade.sh /upgrade.sh
-ADD thingsboard.deb /thingsboard.deb
-
-RUN apt-get update \
- && apt-get install -y nmap \
- && chmod +x /upgrade.sh
diff --git a/docker/cassandra-upgrade/Makefile b/docker/cassandra-upgrade/Makefile
deleted file mode 100644
index b2e8501a2b..0000000000
--- a/docker/cassandra-upgrade/Makefile
+++ /dev/null
@@ -1,12 +0,0 @@
-VERSION=2.1.0
-PROJECT=thingsboard
-APP=cassandra-upgrade
-
-build:
- cp ../../application/target/thingsboard.deb .
- docker build --pull -t ${PROJECT}/${APP}:${VERSION} -t ${PROJECT}/${APP}:latest .
- rm thingsboard.deb
-
-push: build
- docker push ${PROJECT}/${APP}:${VERSION}
- docker push ${PROJECT}/${APP}:latest
diff --git a/docker/cassandra/Makefile b/docker/cassandra/Makefile
deleted file mode 100644
index d1fb67705f..0000000000
--- a/docker/cassandra/Makefile
+++ /dev/null
@@ -1,10 +0,0 @@
-VERSION=2.1.0
-PROJECT=thingsboard
-APP=cassandra
-
-build:
- docker build --pull -t ${PROJECT}/${APP}:${VERSION} -t ${PROJECT}/${APP}:latest .
-
-push: build
- docker push ${PROJECT}/${APP}:${VERSION}
- docker push ${PROJECT}/${APP}:latest
diff --git a/docker/cassandra/ready-probe.sh b/docker/cassandra/ready-probe.sh
deleted file mode 100644
index 8533b6a6d1..0000000000
--- a/docker/cassandra/ready-probe.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/usr/bin/env 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.
-#
-
-if [[ $(nodetool status | grep $POD_IP) == *"UN"* ]]; then
- if [[ $DEBUG ]]; then
- echo "UN";
- fi
- exit 0;
-else
- if [[ $DEBUG ]]; then
- echo "Not Up";
- fi
- exit 1;
-fi
\ No newline at end of file
diff --git a/msa/docker/check-dirs.sh b/docker/check-dirs.sh
similarity index 91%
rename from msa/docker/check-dirs.sh
rename to docker/check-dirs.sh
index 9c683e6a51..2042a44aea 100755
--- a/msa/docker/check-dirs.sh
+++ b/docker/check-dirs.sh
@@ -15,7 +15,7 @@
# limitations under the License.
#
-dirsArray=("./haproxy/certs.d" "./haproxy/letsencrypt" "./tb-node/db" "./tb-node/log")
+dirsArray=("./haproxy/certs.d" "./haproxy/letsencrypt" "./tb-node/postgres" "./tb-node/cassandra" "./tb-node/log/tb1" "./tb-node/log/tb2")
for dir in ${dirsArray[@]}
do
diff --git a/docker/compose-utils.sh b/docker/compose-utils.sh
new file mode 100755
index 0000000000..d1dc20e540
--- /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
+}
diff --git a/docker/docker-compose-tests.yml b/docker/docker-compose-tests.yml
deleted file mode 100644
index 64868b5a1f..0000000000
--- a/docker/docker-compose-tests.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-#
-# 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.
-#
-
-version: '3.3'
-services:
- redis:
- image: redis:4.0
- networks:
- - core
- ports:
- - "6379:6379"
-
-networks:
- core:
diff --git a/docker/docker-compose.static.yml b/docker/docker-compose.cassandra.yml
similarity index 63%
rename from docker/docker-compose.static.yml
rename to docker/docker-compose.cassandra.yml
index 4a0688a2da..721ea3ef42 100644
--- a/docker/docker-compose.static.yml
+++ b/docker/docker-compose.cassandra.yml
@@ -14,16 +14,27 @@
# limitations under the License.
#
-version: '2'
+version: '2.2'
services:
cassandra:
+ restart: always
+ image: "cassandra:3.11.3"
ports:
- - "9042:9042"
- - "9160:9160"
- zk:
- ports:
- - "2181:2181"
- postgres:
- ports:
- - "5432:5432"
\ No newline at end of file
+ - "9042"
+ volumes:
+ - ./tb-node/cassandra:/var/lib/cassandra
+ tb1:
+ env_file:
+ - tb-node.cassandra.env
+ depends_on:
+ - kafka
+ - redis
+ - cassandra
+ tb2:
+ env_file:
+ - tb-node.cassandra.env
+ depends_on:
+ - kafka
+ - redis
+ - cassandra
diff --git a/docker/cluster-mode-thirdparty.yml b/docker/docker-compose.postgres.yml
similarity index 59%
rename from docker/cluster-mode-thirdparty.yml
rename to docker/docker-compose.postgres.yml
index 3aa5bb5959..c81359aac2 100644
--- a/docker/cluster-mode-thirdparty.yml
+++ b/docker/docker-compose.postgres.yml
@@ -14,32 +14,29 @@
# limitations under the License.
#
-version: '3.3'
-services:
- zookeeper:
- image: wurstmeister/zookeeper
- networks:
- - core
- ports:
- - "2181:2181"
-
- cassandra:
- image: cassandra:3.11.2
- networks:
- - core
- ports:
- - "7199:7199"
- - "9160:9160"
- - "9042:9042"
+version: '2.2'
- redis:
- image: redis:4.0
- networks:
- - core
- command: redis-server --maxclients 2000
+services:
+ postgres:
+ restart: always
+ image: "postgres:9.6"
ports:
- - "6379:6379"
-
-networks:
- core:
-
+ - "5432"
+ environment:
+ POSTGRES_DB: thingsboard
+ volumes:
+ - ./tb-node/postgres:/var/lib/postgresql/data
+ tb1:
+ env_file:
+ - tb-node.postgres.env
+ depends_on:
+ - kafka
+ - redis
+ - postgres
+ tb2:
+ env_file:
+ - tb-node.postgres.env
+ depends_on:
+ - kafka
+ - redis
+ - postgres
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 89a83696f3..1741db3694 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
diff --git a/msa/docker/docker-install-tb.sh b/docker/docker-install-tb.sh
similarity index 66%
rename from msa/docker/docker-install-tb.sh
rename to docker/docker-install-tb.sh
index 5f34cc345f..9032e2fd15 100755
--- a/msa/docker/docker-install-tb.sh
+++ b/docker/docker-install-tb.sh
@@ -39,6 +39,18 @@ else
loadDemo=false
fi
-docker-compose run --no-deps --rm -e INSTALL_TB=true -e LOAD_DEMO=${loadDemo} tb
+set -e
+
+source compose-utils.sh
+
+ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $?
+
+ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $?
+
+if [ ! -z "${ADDITIONAL_STARTUP_SERVICES// }" ]; then
+ docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS up -d redis $ADDITIONAL_STARTUP_SERVICES
+fi
+
+docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS run --no-deps --rm -e INSTALL_TB=true -e LOAD_DEMO=${loadDemo} tb1
diff --git a/docker/docker-remove-services.sh b/docker/docker-remove-services.sh
new file mode 100755
index 0000000000..f80a73f9cd
--- /dev/null
+++ b/docker/docker-remove-services.sh
@@ -0,0 +1,24 @@
+#!/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.
+#
+
+set -e
+
+source compose-utils.sh
+
+ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $?
+
+docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS down -v
diff --git a/docker/docker-start-services.sh b/docker/docker-start-services.sh
new file mode 100755
index 0000000000..8437695a1a
--- /dev/null
+++ b/docker/docker-start-services.sh
@@ -0,0 +1,26 @@
+#!/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.
+#
+
+./check-dirs.sh
+
+set -e
+
+source compose-utils.sh
+
+ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $?
+
+docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS up -d
diff --git a/docker/docker-stop-services.sh b/docker/docker-stop-services.sh
new file mode 100755
index 0000000000..63dc6cbebc
--- /dev/null
+++ b/docker/docker-stop-services.sh
@@ -0,0 +1,24 @@
+#!/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.
+#
+
+set -e
+
+source compose-utils.sh
+
+ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $?
+
+docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS stop
diff --git a/docker/docker-update-service.sh b/docker/docker-update-service.sh
new file mode 100755
index 0000000000..e8702760ab
--- /dev/null
+++ b/docker/docker-update-service.sh
@@ -0,0 +1,25 @@
+#!/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.
+#
+
+set -e
+
+source compose-utils.sh
+
+ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $?
+
+docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS pull $@
+docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS up -d --no-deps --build $@
diff --git a/msa/docker/docker-upgrade-tb.sh b/docker/docker-upgrade-tb.sh
similarity index 65%
rename from msa/docker/docker-upgrade-tb.sh
rename to docker/docker-upgrade-tb.sh
index 51202a6b0b..d83b1b7579 100755
--- a/msa/docker/docker-upgrade-tb.sh
+++ b/docker/docker-upgrade-tb.sh
@@ -38,4 +38,18 @@ else
fromVersion="${FROM_VERSION// }"
fi
-docker-compose run --no-deps --rm -e UPGRADE_TB=true -e FROM_VERSION=${fromVersion} tb
+set -e
+
+source compose-utils.sh
+
+ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $?
+
+ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $?
+
+docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS pull tb1
+
+if [ ! -z "${ADDITIONAL_STARTUP_SERVICES// }" ]; then
+ docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS up -d redis $ADDITIONAL_STARTUP_SERVICES
+fi
+
+docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS run --no-deps --rm -e UPGRADE_TB=true -e FROM_VERSION=${fromVersion} tb1
diff --git a/msa/docker/haproxy/config/haproxy.cfg b/docker/haproxy/config/haproxy.cfg
similarity index 59%
rename from msa/docker/haproxy/config/haproxy.cfg
rename to docker/haproxy/config/haproxy.cfg
index cb70552a73..8ba0ce50c7 100644
--- a/msa/docker/haproxy/config/haproxy.cfg
+++ b/docker/haproxy/config/haproxy.cfg
@@ -23,6 +23,13 @@ defaults
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
+ timeout tunnel 1h # timeout to use with WebSocket and CONNECT
+
+ default-server init-addr none
+
+#enable resolving throught docker dns and avoid crashing if service is down while proxy is starting
+resolvers docker_resolver
+ nameserver dns 127.0.0.11:53
listen stats
bind *:9999
@@ -39,8 +46,8 @@ listen mqtt-in
timeout server 3h
option tcplog
balance leastconn
- server tbMqtt1 tb-mqtt-transport1:1883 check
- server tbMqtt2 tb-mqtt-transport2:1883 check
+ server tbMqtt1 tb-mqtt-transport1:1883 check inter 5s resolvers docker_resolver resolve-prefer ipv4
+ server tbMqtt2 tb-mqtt-transport2:1883 check inter 5s resolvers docker_resolver resolve-prefer ipv4
frontend http-in
bind *:${HTTP_PORT}
@@ -51,7 +58,7 @@ frontend http-in
acl transport_http_acl path_beg /api/v1/
acl letsencrypt_http_acl path_beg /.well-known/acme-challenge/
- redirect scheme https if !letsencrypt_http_acl !transport_http_acl
+ redirect scheme https if !letsencrypt_http_acl !transport_http_acl { env(FORCE_HTTPS_REDIRECT) -m str true }
use_backend letsencrypt_http if letsencrypt_http_acl
use_backend tb-http-backend if transport_http_acl
@@ -69,6 +76,11 @@ frontend https_in
default_backend tb-web-backend
+frontend http-api-in
+ bind *:${TB_API_PORT}
+
+ default_backend tb-api-backend
+
backend letsencrypt_http
server letsencrypt_http_srv 127.0.0.1:8080
@@ -76,13 +88,20 @@ backend tb-web-backend
balance leastconn
option tcp-check
option log-health-checks
- server tbWeb1 tb-web-ui1:8080 check
- server tbWeb2 tb-web-ui2:8080 check
+ server tbWeb1 tb-web-ui1:8080 check inter 5s resolvers docker_resolver resolve-prefer ipv4
+ server tbWeb2 tb-web-ui2:8080 check inter 5s resolvers docker_resolver resolve-prefer ipv4
http-request set-header X-Forwarded-Port %[dst_port]
backend tb-http-backend
balance leastconn
option tcp-check
option log-health-checks
- server tbHttp1 tb-http-transport1:8081 check
- server tbHttp2 tb-http-transport2:8081 check
+ server tbHttp1 tb-http-transport1:8081 check inter 5s resolvers docker_resolver resolve-prefer ipv4
+ server tbHttp2 tb-http-transport2:8081 check inter 5s resolvers docker_resolver resolve-prefer ipv4
+
+backend tb-api-backend
+ balance leastconn
+ option tcp-check
+ option log-health-checks
+ server tbApi1 tb1:8080 check inter 5s resolvers docker_resolver resolve-prefer ipv4
+ server tbApi2 tb2:8080 check inter 5s resolvers docker_resolver resolve-prefer ipv4
diff --git a/docker/k8s/cassandra-setup.yaml b/docker/k8s/cassandra-setup.yaml
deleted file mode 100644
index 03a2739446..0000000000
--- a/docker/k8s/cassandra-setup.yaml
+++ /dev/null
@@ -1,43 +0,0 @@
-#
-# 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.
-#
-
-apiVersion: v1
-kind: Pod
-metadata:
- name: cassandra-setup
-spec:
- containers:
- - name: cassandra-setup
- imagePullPolicy: Always
- image: thingsboard/cassandra-setup:2.1.0
- env:
- - name: ADD_DEMO_DATA
- value: "true"
- - name : CASSANDRA_HOST
- value: "cassandra-headless"
- - name : CASSANDRA_PORT
- value: "9042"
- - name : DATABASE_ENTITIES_TYPE
- value: "cassandra"
- - name : DATABASE_TS_TYPE
- value: "cassandra"
- - name : CASSANDRA_URL
- value: "cassandra-headless:9042"
- command:
- - sh
- - -c
- - /install.sh
- restartPolicy: Never
diff --git a/docker/k8s/cassandra-upgrade.yaml b/docker/k8s/cassandra-upgrade.yaml
deleted file mode 100644
index 9276776678..0000000000
--- a/docker/k8s/cassandra-upgrade.yaml
+++ /dev/null
@@ -1,45 +0,0 @@
-#
-# 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.
-#
-
-apiVersion: v1
-kind: Pod
-metadata:
- name: cassandra-upgrade
-spec:
- containers:
- - name: cassandra-upgrade
- imagePullPolicy: Always
- image: thingsboard/cassandra-upgrade:2.1.0
- env:
- - name: ADD_DEMO_DATA
- value: "true"
- - name : CASSANDRA_HOST
- value: "cassandra-headless"
- - name : CASSANDRA_PORT
- value: "9042"
- - name : DATABASE_ENTITIES_TYPE
- value: "cassandra"
- - name : DATABASE_TS_TYPE
- value: "cassandra"
- - name : CASSANDRA_URL
- value: "cassandra-headless:9042"
- - name : UPGRADE_FROM_VERSION
- value: "1.4.0"
- command:
- - sh
- - -c
- - /upgrade.sh
- restartPolicy: Never
diff --git a/docker/k8s/cassandra.yaml b/docker/k8s/cassandra.yaml
deleted file mode 100644
index 3165bb1a7a..0000000000
--- a/docker/k8s/cassandra.yaml
+++ /dev/null
@@ -1,132 +0,0 @@
-#
-# 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.
-#
-
-apiVersion: v1
-kind: Service
-metadata:
- name: cassandra-headless
- labels:
- app: cassandra-headless
-spec:
- ports:
- - port: 9042
- name: cql
- clusterIP: None
- selector:
- app: cassandra
----
-apiVersion: "apps/v1beta1"
-kind: StatefulSet
-metadata:
- name: cassandra
-spec:
- serviceName: cassandra-headless
- replicas: 2
- template:
- metadata:
- labels:
- app: cassandra
- spec:
- nodeSelector:
- machinetype: other
- affinity:
- podAntiAffinity:
- requiredDuringSchedulingIgnoredDuringExecution:
- - labelSelector:
- matchExpressions:
- - key: "app"
- operator: In
- values:
- - cassandra-headless
- topologyKey: "kubernetes.io/hostname"
- containers:
- - name: cassandra
- image: thingsboard/cassandra:2.1.0
- imagePullPolicy: Always
- ports:
- - containerPort: 7000
- name: intra-node
- - containerPort: 7001
- name: tls-intra-node
- - containerPort: 7199
- name: jmx
- - containerPort: 9042
- name: cql
- - containerPort: 9160
- name: thrift
- securityContext:
- capabilities:
- add:
- - IPC_LOCK
- lifecycle:
- preStop:
- exec:
- command: ["/bin/sh", "-c", "PID=$(pidof java) && kill $PID && while ps -p $PID > /dev/null; do sleep 1; done"]
- env:
- - name: MAX_HEAP_SIZE
- value: 2048M
- - name: HEAP_NEWSIZE
- value: 100M
- - name: CASSANDRA_SEEDS
- value: "cassandra-0.cassandra-headless.default.svc.cluster.local"
- - name: CASSANDRA_CLUSTER_NAME
- value: "Thingsboard-Cluster"
- - name: CASSANDRA_DC
- value: "DC1-Thingsboard-Cluster"
- - name: CASSANDRA_RACK
- value: "Rack-Thingsboard-Cluster"
- - name: CASSANDRA_AUTO_BOOTSTRAP
- value: "false"
- - name: POD_IP
- valueFrom:
- fieldRef:
- fieldPath: status.podIP
- - name: POD_NAMESPACE
- valueFrom:
- fieldRef:
- fieldPath: metadata.namespace
- readinessProbe:
- exec:
- command:
- - /bin/bash
- - -c
- - /ready-probe.sh
- initialDelaySeconds: 15
- timeoutSeconds: 5
- volumeMounts:
- - name: cassandra-data
- mountPath: /var/lib/cassandra/data
- - name: cassandra-commitlog
- mountPath: /var/lib/cassandra/commitlog
- volumeClaimTemplates:
- - metadata:
- name: cassandra-data
- annotations:
- volume.beta.kubernetes.io/storage-class: fast
- spec:
- accessModes: [ "ReadWriteOnce" ]
- resources:
- requests:
- storage: 3Gi
- - metadata:
- name: cassandra-commitlog
- annotations:
- volume.beta.kubernetes.io/storage-class: fast
- spec:
- accessModes: [ "ReadWriteOnce" ]
- resources:
- requests:
- storage: 2Gi
\ No newline at end of file
diff --git a/docker/k8s/common.yaml b/docker/k8s/common.yaml
deleted file mode 100644
index 75a938a0ff..0000000000
--- a/docker/k8s/common.yaml
+++ /dev/null
@@ -1,33 +0,0 @@
-#
-# 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.
-#
-
----
-apiVersion: storage.k8s.io/v1beta1
-kind: StorageClass
-metadata:
- name: slow
-provisioner: kubernetes.io/gce-pd
-parameters:
- type: pd-standard
----
-apiVersion: storage.k8s.io/v1beta1
-kind: StorageClass
-metadata:
- name: fast
-provisioner: kubernetes.io/gce-pd
-parameters:
- type: pd-ssd
----
\ No newline at end of file
diff --git a/docker/k8s/redis.yaml b/docker/k8s/redis.yaml
deleted file mode 100644
index 3af810e1e3..0000000000
--- a/docker/k8s/redis.yaml
+++ /dev/null
@@ -1,93 +0,0 @@
-#
-# 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.
-#
-
----
-apiVersion: v1
-kind: Service
-metadata:
- labels:
- name: redis-service
- name: redis-service
-spec:
- ports:
- - name: redis-service
- protocol: TCP
- port: 6379
- targetPort: 6379
- selector:
- app: redis
----
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: redis-conf
-data:
- redis.conf: |
- appendonly yes
- protected-mode no
- bind 0.0.0.0
- port 6379
- dir /var/lib/redis
----
-apiVersion: apps/v1beta1
-kind: StatefulSet
-metadata:
- name: redis
-spec:
- serviceName: redis-service
- replicas: 1
- template:
- metadata:
- labels:
- app: redis
- spec:
- terminationGracePeriodSeconds: 10
- containers:
- - name: redis
- image: redis:4.0.9
- command:
- - redis-server
- args:
- - /etc/redis/redis.conf
- resources:
- requests:
- cpu: 100m
- memory: 100Mi
- ports:
- - containerPort: 6379
- name: redis
- volumeMounts:
- - name: redis-data
- mountPath: /var/lib/redis
- - name: redis-conf
- mountPath: /etc/redis
- volumes:
- - name: redis-conf
- configMap:
- name: redis-conf
- items:
- - key: redis.conf
- path: redis.conf
- volumeClaimTemplates:
- - metadata:
- name: redis-data
- annotations:
- volume.beta.kubernetes.io/storage-class: fast
- spec:
- accessModes: [ "ReadWriteOnce" ]
- resources:
- requests:
- storage: 1Gi
\ No newline at end of file
diff --git a/docker/k8s/tb.yaml b/docker/k8s/tb.yaml
deleted file mode 100644
index 741bbe0f26..0000000000
--- a/docker/k8s/tb.yaml
+++ /dev/null
@@ -1,156 +0,0 @@
-#
-# 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.
-#
-
----
-apiVersion: v1
-kind: Service
-metadata:
- name: tb-service
- labels:
- app: tb-service
-spec:
- ports:
- - port: 8080
- name: ui
- - port: 1883
- name: mqtt
- - port: 5683
- name: coap
- selector:
- app: tb
- type: LoadBalancer
----
-apiVersion: policy/v1beta1
-kind: PodDisruptionBudget
-metadata:
- name: tb-budget
-spec:
- selector:
- matchLabels:
- app: tb
- minAvailable: 3
----
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: tb-config
-data:
- zookeeper.enabled: "true"
- zookeeper.url: "zk-headless"
- cassandra.url: "cassandra-headless:9042"
- cassandra.host: "cassandra-headless"
- cassandra.port: "9042"
- database.type: "cassandra"
- cache.type: "redis"
- redis.host: "redis-service"
----
-apiVersion: apps/v1beta1
-kind: StatefulSet
-metadata:
- name: tb
-spec:
- serviceName: "tb-service"
- replicas: 3
- template:
- metadata:
- labels:
- app: tb
- spec:
- nodeSelector:
- machinetype: tb
- affinity:
- podAntiAffinity:
- requiredDuringSchedulingIgnoredDuringExecution:
- - labelSelector:
- matchExpressions:
- - key: "app"
- operator: In
- values:
- - tb-service
- topologyKey: "kubernetes.io/hostname"
- containers:
- - name: tb
- imagePullPolicy: Always
- image: thingsboard/application:2.1.0
- ports:
- - containerPort: 8080
- name: ui
- - containerPort: 1883
- name: mqtt
- - containerPort: 5683
- name: coap
- - containerPort: 9001
- name: rpc
- env:
- - name: ZOOKEEPER_ENABLED
- valueFrom:
- configMapKeyRef:
- name: tb-config
- key: zookeeper.enabled
- - name: ZOOKEEPER_URL
- valueFrom:
- configMapKeyRef:
- name: tb-config
- key: zookeeper.url
- - name : CASSANDRA_HOST
- valueFrom:
- configMapKeyRef:
- name: tb-config
- key: cassandra.host
- - name : CASSANDRA_PORT
- valueFrom:
- configMapKeyRef:
- name: tb-config
- key: cassandra.port
- - name : CASSANDRA_URL
- valueFrom:
- configMapKeyRef:
- name: tb-config
- key: cassandra.url
- - name: DATABASE_ENTITIES_TYPE
- valueFrom:
- configMapKeyRef:
- name: tb-config
- key: database.type
- - name: DATABASE_TS_TYPE
- valueFrom:
- configMapKeyRef:
- name: tb-config
- key: database.type
- - name : RPC_HOST
- valueFrom:
- fieldRef:
- fieldPath: status.podIP
- - name: CACHE_TYPE
- valueFrom:
- configMapKeyRef:
- name: tb-config
- key: cache.type
- - name: REDIS_HOST
- valueFrom:
- configMapKeyRef:
- name: tb-config
- key: redis.host
- command:
- - sh
- - -c
- - /run-application.sh
- livenessProbe:
- httpGet:
- path: /login
- port: ui-port
- initialDelaySeconds: 120
- timeoutSeconds: 10
\ No newline at end of file
diff --git a/docker/k8s/zookeeper.yaml b/docker/k8s/zookeeper.yaml
deleted file mode 100644
index ae33ea2efa..0000000000
--- a/docker/k8s/zookeeper.yaml
+++ /dev/null
@@ -1,190 +0,0 @@
-#
-# 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.
-#
-
-apiVersion: v1
-kind: Service
-metadata:
- name: zk-headless
- labels:
- app: zk-headless
-spec:
- ports:
- - port: 2888
- name: server
- - port: 3888
- name: leader-election
- clusterIP: None
- selector:
- app: zk
----
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: zk-config
-data:
- ensemble: "zk-0;zk-1;zk-2"
- replicas: "3"
- jvm.heap: "500m"
- tick: "2000"
- init: "10"
- sync: "5"
- client.cnxns: "60"
- snap.retain: "3"
- purge.interval: "1"
- client.port: "2181"
- server.port: "2888"
- election.port: "3888"
----
-apiVersion: policy/v1beta1
-kind: PodDisruptionBudget
-metadata:
- name: zk-budget
-spec:
- selector:
- matchLabels:
- app: zk
- minAvailable: 3
----
-apiVersion: apps/v1beta1
-kind: StatefulSet
-metadata:
- name: zk
-spec:
- serviceName: zk-headless
- replicas: 3
- template:
- metadata:
- labels:
- app: zk
- annotations:
- pod.alpha.kubernetes.io/initialized: "true"
- spec:
- nodeSelector:
- machinetype: other
- affinity:
- podAntiAffinity:
- requiredDuringSchedulingIgnoredDuringExecution:
- - labelSelector:
- matchExpressions:
- - key: "app"
- operator: In
- values:
- - zk-headless
- topologyKey: "kubernetes.io/hostname"
- containers:
- - name: zk
- imagePullPolicy: Always
- image: thingsboard/zk:2.1.0
- ports:
- - containerPort: 2181
- name: client
- - containerPort: 2888
- name: server
- - containerPort: 3888
- name: leader-election
- env:
- - name : ZK_ENSEMBLE
- valueFrom:
- configMapKeyRef:
- name: zk-config
- key: ensemble
- - name : ZK_REPLICAS
- valueFrom:
- configMapKeyRef:
- name: zk-config
- key: replicas
- - name : ZK_HEAP_SIZE
- valueFrom:
- configMapKeyRef:
- name: zk-config
- key: jvm.heap
- - name : ZK_TICK_TIME
- valueFrom:
- configMapKeyRef:
- name: zk-config
- key: tick
- - name : ZK_INIT_LIMIT
- valueFrom:
- configMapKeyRef:
- name: zk-config
- key: init
- - name : ZK_SYNC_LIMIT
- valueFrom:
- configMapKeyRef:
- name: zk-config
- key: tick
- - name : ZK_MAX_CLIENT_CNXNS
- valueFrom:
- configMapKeyRef:
- name: zk-config
- key: client.cnxns
- - name: ZK_SNAP_RETAIN_COUNT
- valueFrom:
- configMapKeyRef:
- name: zk-config
- key: snap.retain
- - name: ZK_PURGE_INTERVAL
- valueFrom:
- configMapKeyRef:
- name: zk-config
- key: purge.interval
- - name: ZK_CLIENT_PORT
- valueFrom:
- configMapKeyRef:
- name: zk-config
- key: client.port
- - name: ZK_SERVER_PORT
- valueFrom:
- configMapKeyRef:
- name: zk-config
- key: server.port
- - name: ZK_ELECTION_PORT
- valueFrom:
- configMapKeyRef:
- name: zk-config
- key: election.port
- command:
- - sh
- - -c
- - zk-gen-config.sh && zkServer.sh start-foreground
- readinessProbe:
- exec:
- command:
- - "zk-ok.sh"
- initialDelaySeconds: 15
- timeoutSeconds: 5
- livenessProbe:
- exec:
- command:
- - "zk-ok.sh"
- initialDelaySeconds: 15
- timeoutSeconds: 5
- volumeMounts:
- - name: zkdatadir
- mountPath: /var/lib/zookeeper
- securityContext:
- runAsUser: 1000
- fsGroup: 1000
- volumeClaimTemplates:
- - metadata:
- name: zkdatadir
- annotations:
- volume.beta.kubernetes.io/storage-class: slow
- spec:
- accessModes: [ "ReadWriteOnce" ]
- resources:
- requests:
- storage: 1Gi
\ No newline at end of file
diff --git a/docker/kafka.env b/docker/kafka.env
new file mode 100644
index 0000000000..69fbdf6116
--- /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
diff --git a/msa/docker/tb-coap-transport.env b/docker/tb-coap-transport.env
similarity index 68%
rename from msa/docker/tb-coap-transport.env
rename to docker/tb-coap-transport.env
index b3331fe849..ed8f78d116 100644
--- a/msa/docker/tb-coap-transport.env
+++ b/docker/tb-coap-transport.env
@@ -3,4 +3,4 @@ COAP_BIND_ADDRESS=0.0.0.0
COAP_BIND_PORT=5683
COAP_TIMEOUT=10000
-TB_KAFKA_SERVERS=localhost:9092
\ No newline at end of file
+TB_KAFKA_SERVERS=kafka:9092
\ No newline at end of file
diff --git a/msa/docker/tb-http-transport.env b/docker/tb-http-transport.env
similarity index 70%
rename from msa/docker/tb-http-transport.env
rename to docker/tb-http-transport.env
index 33c479104e..c8363230a2 100644
--- a/msa/docker/tb-http-transport.env
+++ b/docker/tb-http-transport.env
@@ -3,4 +3,4 @@ HTTP_BIND_ADDRESS=0.0.0.0
HTTP_BIND_PORT=8081
HTTP_REQUEST_TIMEOUT=60000
-TB_KAFKA_SERVERS=localhost:9092
\ No newline at end of file
+TB_KAFKA_SERVERS=kafka:9092
\ No newline at end of file
diff --git a/msa/docker/tb-js-executor.env b/docker/tb-js-executor.env
similarity index 71%
rename from msa/docker/tb-js-executor.env
rename to docker/tb-js-executor.env
index cf455243ee..f7f24acabf 100644
--- a/msa/docker/tb-js-executor.env
+++ b/docker/tb-js-executor.env
@@ -1,6 +1,6 @@
REMOTE_JS_EVAL_REQUEST_TOPIC=js.eval.requests
-TB_KAFKA_SERVERS=localhost:9092
+TB_KAFKA_SERVERS=kafka:9092
LOGGER_LEVEL=info
LOG_FOLDER=logs
LOGGER_FILENAME=tb-js-executor-%DATE%.log
diff --git a/msa/docker/tb-mqtt-transport.env b/docker/tb-mqtt-transport.env
similarity index 68%
rename from msa/docker/tb-mqtt-transport.env
rename to docker/tb-mqtt-transport.env
index 5ada3f786a..b024c7a1a5 100644
--- a/msa/docker/tb-mqtt-transport.env
+++ b/docker/tb-mqtt-transport.env
@@ -3,4 +3,4 @@ MQTT_BIND_ADDRESS=0.0.0.0
MQTT_BIND_PORT=1883
MQTT_TIMEOUT=10000
-TB_KAFKA_SERVERS=localhost:9092
\ No newline at end of file
+TB_KAFKA_SERVERS=kafka:9092
\ No newline at end of file
diff --git a/docker/tb-node.cassandra.env b/docker/tb-node.cassandra.env
new file mode 100644
index 0000000000..8d813b9059
--- /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
diff --git a/docker/tb-node.env b/docker/tb-node.env
new file mode 100644
index 0000000000..ca945ab6ed
--- /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
diff --git a/docker/tb-node.postgres.env b/docker/tb-node.postgres.env
new file mode 100644
index 0000000000..9fa79e369c
--- /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
diff --git a/docker/tb-node/conf/logback.xml b/docker/tb-node/conf/logback.xml
new file mode 100644
index 0000000000..1c69f537bc
--- /dev/null
+++ b/docker/tb-node/conf/logback.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+ /var/log/thingsboard/${TB_HOST}/thingsboard.log
+
+ /var/log/thingsboard/thingsboard.%d{yyyy-MM-dd}.%i.log
+ 100MB
+ 30
+ 3GB
+
+
+ %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+ %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docker/tb-node/conf/thingsboard.conf b/docker/tb-node/conf/thingsboard.conf
new file mode 100644
index 0000000000..aa430b4a79
--- /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
diff --git a/msa/docker/tb-web-ui.env b/docker/tb-web-ui.env
similarity index 79%
rename from msa/docker/tb-web-ui.env
rename to docker/tb-web-ui.env
index 5c52681798..dbf8120cbf 100644
--- a/msa/docker/tb-web-ui.env
+++ b/docker/tb-web-ui.env
@@ -1,7 +1,7 @@
HTTP_BIND_ADDRESS=0.0.0.0
HTTP_BIND_PORT=8080
-TB_HOST=localhost
+TB_HOST=haproxy
TB_PORT=8080
LOGGER_LEVEL=info
LOG_FOLDER=logs
diff --git a/docker/tb.env b/docker/tb.env
deleted file mode 100644
index bc92de5c8b..0000000000
--- a/docker/tb.env
+++ /dev/null
@@ -1,26 +0,0 @@
-#Thingsboard server configuration
-MQTT_BIND_ADDRESS=0.0.0.0
-MQTT_BIND_PORT=1883
-COAP_BIND_ADDRESS=0.0.0.0
-COAP_BIND_PORT=5683
-
-# zk config
-ZOOKEEPER_URL=zk:2181
-
-# type of database to use: sql[DEFAULT] or cassandra
-DATABASE_TS_TYPE=sql
-DATABASE_ENTITIES_TYPE=sql
-
-# cassandra db config
-CASSANDRA_URL=cassandra:9042
-CASSANDRA_HOST=cassandra
-CASSANDRA_PORT=9042
-
-# postgres db config
-POSTGRES_HOST=postgres
-POSTGRES_PORT=5432
-# 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
diff --git a/docker/tb/Dockerfile b/docker/tb/Dockerfile
deleted file mode 100644
index 24281f9f3e..0000000000
--- a/docker/tb/Dockerfile
+++ /dev/null
@@ -1,26 +0,0 @@
-#
-# 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-jre
-
-ADD run-application.sh /run-application.sh
-ADD thingsboard.deb /thingsboard.deb
-
-RUN apt-get update \
- && apt-get install --no-install-recommends -y nmap \
- && apt-get clean \
- && rm -r /var/lib/apt/lists/* \
- && chmod +x /run-application.sh
diff --git a/docker/tb/Makefile b/docker/tb/Makefile
deleted file mode 100644
index 96803abf58..0000000000
--- a/docker/tb/Makefile
+++ /dev/null
@@ -1,12 +0,0 @@
-VERSION=2.1.0
-PROJECT=thingsboard
-APP=application
-
-build:
- cp ../../application/target/thingsboard.deb .
- docker build --pull -t ${PROJECT}/${APP}:${VERSION} -t ${PROJECT}/${APP}:latest .
- rm thingsboard.deb
-
-push: build
- docker push ${PROJECT}/${APP}:${VERSION}
- docker push ${PROJECT}/${APP}:latest
diff --git a/docker/tb/run-application.sh b/docker/tb/run-application.sh
deleted file mode 100755
index e6b59f9aee..0000000000
--- a/docker/tb/run-application.sh
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/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.
-#
-
-
-dpkg -i /thingsboard.deb
-
-# Copying env variables into conf files
-printenv | awk -F "=" '{print "export " $1 "='\''" $2 "'\''"}' >> /usr/share/thingsboard/conf/thingsboard.conf
-
-cat /usr/share/thingsboard/conf/thingsboard.conf
-
-if [ "$DATABASE_ENTITIES_TYPE" == "cassandra" ]; then
- until nmap $CASSANDRA_HOST -p $CASSANDRA_PORT | grep "$CASSANDRA_PORT/tcp open\|filtered"
- do
- echo "Wait for cassandra db to start..."
- sleep 10
- done
-fi
-
-if [ "$DATABASE_ENTITIES_TYPE" == "sql" ]; then
- if [ "$SPRING_DRIVER_CLASS_NAME" == "org.postgresql.Driver" ]; then
- until nmap $POSTGRES_HOST -p $POSTGRES_PORT | grep "$POSTGRES_PORT/tcp open"
- do
- echo "Waiting for postgres db to start..."
- sleep 10
- done
- fi
-fi
-
-if [ "$ADD_SCHEMA_AND_SYSTEM_DATA" == "true" ]; then
- echo "Creating 'Thingsboard' schema and system data..."
- if [ "$ADD_DEMO_DATA" == "true" ]; then
- echo "plus demo data..."
- /usr/share/thingsboard/bin/install/install.sh --loadDemo
- elif [ "$ADD_DEMO_DATA" == "false" ]; then
- /usr/share/thingsboard/bin/install/install.sh
- fi
-fi
-
-echo "Starting 'Thingsboard' service..."
-service thingsboard start
-
-# Wait until log file is created
-sleep 10
-tail -f /var/log/thingsboard/thingsboard.log
diff --git a/docker/zookeeper/Dockerfile b/docker/zookeeper/Dockerfile
deleted file mode 100644
index 38f13fca14..0000000000
--- a/docker/zookeeper/Dockerfile
+++ /dev/null
@@ -1,71 +0,0 @@
-#
-# 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 ubuntu:16.04
-ENV ZK_USER=zookeeper \
-ZK_DATA_DIR=/var/lib/zookeeper/data \
-ZK_DATA_LOG_DIR=/var/lib/zookeeper/log \
-ZK_LOG_DIR=/var/log/zookeeper \
-JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
-
-ARG GPG_KEY=C823E3E5B12AF29C67F81976F5CECB3CB5E9BD2D
-ARG ZK_DIST=zookeeper-3.4.10
-RUN set -x \
- && apt-get update \
- && apt-get install -y openjdk-8-jre-headless wget netcat-openbsd \
- && wget -q "http://www.apache.org/dist/zookeeper/$ZK_DIST/$ZK_DIST.tar.gz" \
- && wget -q "http://www.apache.org/dist/zookeeper/$ZK_DIST/$ZK_DIST.tar.gz.asc" \
- && export GNUPGHOME="$(mktemp -d)" \
- && gpg --keyserver ha.pool.sks-keyservers.net --recv-key "$GPG_KEY" \
- && gpg --batch --verify "$ZK_DIST.tar.gz.asc" "$ZK_DIST.tar.gz" \
- && tar -xzf "$ZK_DIST.tar.gz" -C /opt \
- && rm -r "$GNUPGHOME" "$ZK_DIST.tar.gz" "$ZK_DIST.tar.gz.asc" \
- && ln -s /opt/$ZK_DIST /opt/zookeeper \
- && rm -rf /opt/zookeeper/CHANGES.txt \
- /opt/zookeeper/README.txt \
- /opt/zookeeper/NOTICE.txt \
- /opt/zookeeper/CHANGES.txt \
- /opt/zookeeper/README_packaging.txt \
- /opt/zookeeper/build.xml \
- /opt/zookeeper/config \
- /opt/zookeeper/contrib \
- /opt/zookeeper/dist-maven \
- /opt/zookeeper/docs \
- /opt/zookeeper/ivy.xml \
- /opt/zookeeper/ivysettings.xml \
- /opt/zookeeper/recipes \
- /opt/zookeeper/src \
- /opt/zookeeper/$ZK_DIST.jar.asc \
- /opt/zookeeper/$ZK_DIST.jar.md5 \
- /opt/zookeeper/$ZK_DIST.jar.sha1 \
- && apt-get autoremove -y wget \
- && rm -rf /var/lib/apt/lists/*
-
-#Copy configuration generator script to bin
-COPY zk-gen-config.sh zk-ok.sh /opt/zookeeper/bin/
-
-# Create a user for the zookeeper process and configure file system ownership
-# for nessecary directories and symlink the distribution as a user executable
-RUN set -x \
- && useradd $ZK_USER \
- && [ `id -u $ZK_USER` -eq 1000 ] \
- && [ `id -g $ZK_USER` -eq 1000 ] \
- && mkdir -p $ZK_DATA_DIR $ZK_DATA_LOG_DIR $ZK_LOG_DIR /usr/share/zookeeper /tmp/zookeeper /usr/etc/ \
- && chown -R "$ZK_USER:$ZK_USER" /opt/$ZK_DIST $ZK_DATA_DIR $ZK_LOG_DIR $ZK_DATA_LOG_DIR /tmp/zookeeper \
- && ln -s /opt/zookeeper/conf/ /usr/etc/zookeeper \
- && ln -s /opt/zookeeper/bin/* /usr/bin \
- && ln -s /opt/zookeeper/$ZK_DIST.jar /usr/share/zookeeper/ \
- && ln -s /opt/zookeeper/lib/* /usr/share/zookeeper
diff --git a/docker/zookeeper/Makefile b/docker/zookeeper/Makefile
deleted file mode 100644
index 5c58a74cf2..0000000000
--- a/docker/zookeeper/Makefile
+++ /dev/null
@@ -1,10 +0,0 @@
-VERSION=2.1.0
-PROJECT=thingsboard
-APP=zk
-
-build:
- docker build --pull -t ${PROJECT}/${APP}:${VERSION} -t ${PROJECT}/${APP}:latest .
-
-push: build
- docker push ${PROJECT}/${APP}:${VERSION}
- docker push ${PROJECT}/${APP}:latest
diff --git a/docker/zookeeper/zk-gen-config.sh b/docker/zookeeper/zk-gen-config.sh
deleted file mode 100755
index 5516f570c1..0000000000
--- a/docker/zookeeper/zk-gen-config.sh
+++ /dev/null
@@ -1,153 +0,0 @@
-#!/usr/bin/env 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.
-#
-
-ZK_USER=${ZK_USER:-"zookeeper"}
-ZK_LOG_LEVEL=${ZK_LOG_LEVEL:-"INFO"}
-ZK_DATA_DIR=${ZK_DATA_DIR:-"/var/lib/zookeeper/data"}
-ZK_DATA_LOG_DIR=${ZK_DATA_LOG_DIR:-"/var/lib/zookeeper/log"}
-ZK_LOG_DIR=${ZK_LOG_DIR:-"var/log/zookeeper"}
-ZK_CONF_DIR=${ZK_CONF_DIR:-"/opt/zookeeper/conf"}
-ZK_CLIENT_PORT=${ZK_CLIENT_PORT:-2181}
-ZK_SERVER_PORT=${ZK_SERVER_PORT:-2888}
-ZK_ELECTION_PORT=${ZK_ELECTION_PORT:-3888}
-ZK_TICK_TIME=${ZK_TICK_TIME:-2000}
-ZK_INIT_LIMIT=${ZK_INIT_LIMIT:-10}
-ZK_SYNC_LIMIT=${ZK_SYNC_LIMIT:-5}
-ZK_HEAP_SIZE=${ZK_HEAP_SIZE:-2G}
-ZK_MAX_CLIENT_CNXNS=${ZK_MAX_CLIENT_CNXNS:-60}
-ZK_MIN_SESSION_TIMEOUT=${ZK_MIN_SESSION_TIMEOUT:- $((ZK_TICK_TIME*2))}
-ZK_MAX_SESSION_TIMEOUT=${ZK_MAX_SESSION_TIMEOUT:- $((ZK_TICK_TIME*20))}
-ZK_SNAP_RETAIN_COUNT=${ZK_SNAP_RETAIN_COUNT:-3}
-ZK_PURGE_INTERVAL=${ZK_PURGE_INTERVAL:-0}
-ID_FILE="$ZK_DATA_DIR/myid"
-ZK_CONFIG_FILE="$ZK_CONF_DIR/zoo.cfg"
-LOGGER_PROPS_FILE="$ZK_CONF_DIR/log4j.properties"
-JAVA_ENV_FILE="$ZK_CONF_DIR/java.env"
-HOST=`hostname -s`
-DOMAIN=`hostname -d`
-
-function print_servers() {
- for (( i=1; i<=$ZK_REPLICAS; i++ ))
- do
- echo "server.$i=$NAME-$((i-1)).$DOMAIN:$ZK_SERVER_PORT:$ZK_ELECTION_PORT"
- done
-}
-
-function validate_env() {
- echo "Validating environment"
- if [ -z $ZK_REPLICAS ]; then
- echo "ZK_REPLICAS is a mandatory environment variable"
- exit 1
- fi
-
- if [[ $HOST =~ (.*)-([0-9]+)$ ]]; then
- NAME=${BASH_REMATCH[1]}
- ORD=${BASH_REMATCH[2]}
- else
- echo "Failed to extract ordinal from hostname $HOST"
- exit 1
- fi
- MY_ID=$((ORD+1))
- echo "ZK_REPLICAS=$ZK_REPLICAS"
- echo "MY_ID=$MY_ID"
- echo "ZK_LOG_LEVEL=$ZK_LOG_LEVEL"
- echo "ZK_DATA_DIR=$ZK_DATA_DIR"
- echo "ZK_DATA_LOG_DIR=$ZK_DATA_LOG_DIR"
- echo "ZK_LOG_DIR=$ZK_LOG_DIR"
- echo "ZK_CLIENT_PORT=$ZK_CLIENT_PORT"
- echo "ZK_SERVER_PORT=$ZK_SERVER_PORT"
- echo "ZK_ELECTION_PORT=$ZK_ELECTION_PORT"
- echo "ZK_TICK_TIME=$ZK_TICK_TIME"
- echo "ZK_INIT_LIMIT=$ZK_INIT_LIMIT"
- echo "ZK_SYNC_LIMIT=$ZK_SYNC_LIMIT"
- echo "ZK_MAX_CLIENT_CNXNS=$ZK_MAX_CLIENT_CNXNS"
- echo "ZK_MIN_SESSION_TIMEOUT=$ZK_MIN_SESSION_TIMEOUT"
- echo "ZK_MAX_SESSION_TIMEOUT=$ZK_MAX_SESSION_TIMEOUT"
- echo "ZK_HEAP_SIZE=$ZK_HEAP_SIZE"
- echo "ZK_SNAP_RETAIN_COUNT=$ZK_SNAP_RETAIN_COUNT"
- echo "ZK_PURGE_INTERVAL=$ZK_PURGE_INTERVAL"
- echo "ENSEMBLE"
- print_servers
- echo "Environment validation successful"
-}
-
-function create_config() {
- rm -f $ZK_CONFIG_FILE
- echo "Creating ZooKeeper configuration"
- echo "#This file was autogenerated by zk DO NOT EDIT" >> $ZK_CONFIG_FILE
- echo "clientPort=$ZK_CLIENT_PORT" >> $ZK_CONFIG_FILE
- echo "dataDir=$ZK_DATA_DIR" >> $ZK_CONFIG_FILE
- echo "dataLogDir=$ZK_DATA_LOG_DIR" >> $ZK_CONFIG_FILE
- echo "tickTime=$ZK_TICK_TIME" >> $ZK_CONFIG_FILE
- echo "initLimit=$ZK_INIT_LIMIT" >> $ZK_CONFIG_FILE
- echo "syncLimit=$ZK_SYNC_LIMIT" >> $ZK_CONFIG_FILE
- echo "maxClientCnxns=$ZK_MAX_CLIENT_CNXNS" >> $ZK_CONFIG_FILE
- echo "minSessionTimeout=$ZK_MIN_SESSION_TIMEOUT" >> $ZK_CONFIG_FILE
- echo "maxSessionTimeout=$ZK_MAX_SESSION_TIMEOUT" >> $ZK_CONFIG_FILE
- echo "autopurge.snapRetainCount=$ZK_SNAP_RETAIN_COUNT" >> $ZK_CONFIG_FILE
- echo "autopurge.purgeInteval=$ZK_PURGE_INTERVAL" >> $ZK_CONFIG_FILE
-
- if [ $ZK_REPLICAS -gt 1 ]; then
- print_servers >> $ZK_CONFIG_FILE
- fi
- echo "Wrote ZooKeeper configuration file to $ZK_CONFIG_FILE"
-}
-
-function create_data_dirs() {
- echo "Creating ZooKeeper data directories and setting permissions"
- if [ ! -d $ZK_DATA_DIR ]; then
- mkdir -p $ZK_DATA_DIR
- chown -R $ZK_USER:$ZK_USER $ZK_DATA_DIR
- fi
-
- if [ ! -d $ZK_DATA_LOG_DIR ]; then
- mkdir -p $ZK_DATA_LOG_DIR
- chown -R $ZK_USER:$ZK_USER $ZK_DATA_LOG_DIR
- fi
-
- if [ ! -d $ZK_LOG_DIR ]; then
- mkdir -p $ZK_LOG_DIR
- chown -R $ZK_USER:$ZK_USER $ZK_LOG_DIR
- fi
- if [ ! -f $ID_FILE ]; then
- echo $MY_ID >> $ID_FILE
- fi
- echo "Created ZooKeeper data directories and set permissions in $ZK_DATA_DIR"
-}
-
-function create_log_props () {
- rm -f $LOGGER_PROPS_FILE
- echo "Creating ZooKeeper log4j configuration"
- echo "zookeeper.root.logger=CONSOLE" >> $LOGGER_PROPS_FILE
- echo "zookeeper.console.threshold="$ZK_LOG_LEVEL >> $LOGGER_PROPS_FILE
- echo "log4j.rootLogger=\${zookeeper.root.logger}" >> $LOGGER_PROPS_FILE
- echo "log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender" >> $LOGGER_PROPS_FILE
- echo "log4j.appender.CONSOLE.Threshold=\${zookeeper.console.threshold}" >> $LOGGER_PROPS_FILE
- echo "log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout" >> $LOGGER_PROPS_FILE
- echo "log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} [myid:%X{myid}] - %-5p [%t:%C{1}@%L] - %m%n" >> $LOGGER_PROPS_FILE
- echo "Wrote log4j configuration to $LOGGER_PROPS_FILE"
-}
-
-function create_java_env() {
- rm -f $JAVA_ENV_FILE
- echo "Creating JVM configuration file"
- echo "ZOO_LOG_DIR=$ZK_LOG_DIR" >> $JAVA_ENV_FILE
- echo "JVMFLAGS=\"-Xmx$ZK_HEAP_SIZE -Xms$ZK_HEAP_SIZE\"" >> $JAVA_ENV_FILE
- echo "Wrote JVM configuration to $JAVA_ENV_FILE"
-}
-
-validate_env && create_config && create_log_props && create_data_dirs && create_java_env
diff --git a/docker/zookeeper/zk-ok.sh b/docker/zookeeper/zk-ok.sh
deleted file mode 100755
index 9f45db259d..0000000000
--- a/docker/zookeeper/zk-ok.sh
+++ /dev/null
@@ -1,28 +0,0 @@
-#!/usr/bin/env 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.
-#
-
-# zk-ok.sh uses the ruok ZooKeeper four letter work to determine if the instance
-# is health. The $? variable will be set to 0 if server responds that it is
-# healthy, or 1 if the server fails to respond.
-
-ZK_CLIENT_PORT=${ZK_CLIENT_PORT:-2181}
-OK=$(echo ruok | nc 127.0.0.1 $ZK_CLIENT_PORT)
-if [ "$OK" == "imok" ]; then
- exit 0
-else
- exit 1
-fi
diff --git a/img/dashboard.gif b/img/dashboard.gif
deleted file mode 100644
index 9d792709a9..0000000000
Binary files a/img/dashboard.gif and /dev/null differ
diff --git a/msa/docker/.env b/msa/docker/.env
deleted file mode 100644
index afcd91ced5..0000000000
--- a/msa/docker/.env
+++ /dev/null
@@ -1,13 +0,0 @@
-
-DOCKER_REPO=local-maven-build
-
-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
-
-TB_VERSION=2.2.0-SNAPSHOT
-
-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
diff --git a/msa/docker/docker-compose.yml b/msa/docker/docker-compose.yml
deleted file mode 100644
index 5410533822..0000000000
--- a/msa/docker/docker-compose.yml
+++ /dev/null
@@ -1,177 +0,0 @@
-#
-# 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.
-#
-
-
-version: '2.2'
-
-services:
- zookeeper:
- restart: always
- image: "wurstmeister/zookeeper"
- ports:
- - "2181"
- kafka:
- restart: always
- image: "wurstmeister/kafka"
- ports:
- - "9092:9092"
- environment:
- 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
- depends_on:
- - zookeeper
- tb-js-executor:
- restart: always
- image: "${DOCKER_REPO}/${JS_EXECUTOR_DOCKER_NAME}:${TB_VERSION}"
- scale: 20
- environment:
- TB_KAFKA_SERVERS: kafka:9092
- env_file:
- - tb-js-executor.env
- depends_on:
- - kafka
- tb:
- restart: always
- image: "${DOCKER_REPO}/${TB_NODE_DOCKER_NAME}:${TB_VERSION}"
- ports:
- - "8080"
- logging:
- driver: "json-file"
- options:
- max-size: "200m"
- max-file: "30"
- env_file:
- - tb-node.env
- environment:
- ZOOKEEPER_URL: zk:2181
- TB_KAFKA_SERVERS: kafka:9092
- JS_EVALUATOR: remote
- volumes:
- - ./tb-node/db:/usr/share/thingsboard/data/db
- - ./tb-node/conf:/config
- - ./tb-node/log:/var/log/thingsboard
- depends_on:
- - kafka
- tb-mqtt-transport1:
- restart: always
- image: "${DOCKER_REPO}/${MQTT_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
- ports:
- - "1883"
- environment:
- TB_KAFKA_SERVERS: kafka:9092
- env_file:
- - tb-mqtt-transport.env
- depends_on:
- - kafka
- tb-mqtt-transport2:
- restart: always
- image: "${DOCKER_REPO}/${MQTT_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
- ports:
- - "1883"
- environment:
- TB_KAFKA_SERVERS: kafka:9092
- 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"
- environment:
- TB_KAFKA_SERVERS: kafka:9092
- 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"
- environment:
- TB_KAFKA_SERVERS: kafka:9092
- 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"
- environment:
- TB_KAFKA_SERVERS: kafka:9092
- 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"
- environment:
- TB_HOST: tb
- TB_PORT: 8080
- env_file:
- - tb-web-ui.env
- tb-web-ui2:
- restart: always
- image: "${DOCKER_REPO}/${WEB_UI_DOCKER_NAME}:${TB_VERSION}"
- ports:
- - "8080"
- environment:
- TB_HOST: tb
- TB_PORT: 8080
- env_file:
- - tb-web-ui.env
- web:
- restart: always
- container_name: haproxy-certbot
- image: nmarus/haproxy-certbot
- volumes:
- - ./haproxy/config:/config
- - ./haproxy/letsencrypt:/etc/letsencrypt
- - ./haproxy/certs.d:/usr/local/etc/haproxy/certs.d
- ports:
- - "80:80"
- - "443:443"
- - "1883:1883"
- - "9999:9999"
- cap_add:
- - NET_ADMIN
- environment:
- HTTP_PORT: 80
- HTTPS_PORT: 443
- MQTT_PORT: 1883
- links:
- - tb-web-ui1
- - tb-web-ui2
- - tb-mqtt-transport1
- - tb-mqtt-transport2
- - tb-http-transport1
- - tb-http-transport2
diff --git a/msa/docker/tb-node.env b/msa/docker/tb-node.env
deleted file mode 100644
index abeccb12eb..0000000000
--- a/msa/docker/tb-node.env
+++ /dev/null
@@ -1,22 +0,0 @@
-# ThingsBoard server configuration
-
-TRANSPORT_TYPE=remote
-
-# type of database to use: sql[DEFAULT] or cassandra
-DATABASE_TS_TYPE=sql
-DATABASE_ENTITIES_TYPE=sql
-SQL_DATA_FOLDER=/usr/share/thingsboard/data/db
-
-# cassandra db config
-CASSANDRA_URL=cassandra:9042
-CASSANDRA_HOST=cassandra
-CASSANDRA_PORT=9042
-
-# postgres db config
-POSTGRES_HOST=postgres
-POSTGRES_PORT=5432
-# 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
diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml
index 6f11df1452..e673a5389c 100644
--- a/msa/js-executor/pom.xml
+++ b/msa/js-executor/pom.xml
@@ -287,16 +287,27 @@
build
+
+ ${dockerfile.skip}
+ ${docker.repo}/${docker.name}
+ true
+ false
+ ${project.build.directory}
+
+
+
+ tag-docker-image
+ pre-integration-test
+
+ tag
+
+
+ ${dockerfile.skip}
+ ${docker.repo}/${docker.name}
+ ${project.version}
+
-
- ${dockerfile.skip}
- ${docker.repo}/${docker.name}
- ${project.version}
- true
- false
- ${project.build.directory}
-
@@ -334,6 +345,46 @@
+
+ push-docker-image
+
+
+ push-docker-image
+
+
+
+
+
+ com.spotify
+ dockerfile-maven-plugin
+
+
+ push-latest-docker-image
+ pre-integration-test
+
+ push
+
+
+ latest
+ ${docker.repo}/${docker.name}
+
+
+
+ push-version-docker-image
+ pre-integration-test
+
+ push
+
+
+ ${project.version}
+ ${docker.repo}/${docker.name}
+
+
+
+
+
+
+
diff --git a/msa/pom.xml b/msa/pom.xml
index d357677a7e..41926608be 100644
--- a/msa/pom.xml
+++ b/msa/pom.xml
@@ -23,7 +23,6 @@
2.2.0-SNAPSHOT
thingsboard
- org.thingsboard
msa
pom
@@ -32,11 +31,12 @@
${basedir}/..
- local-maven-build
+ thingsboard
true
+ tb
js-executor
web-ui
tb-node
diff --git a/msa/tb-node/docker/Dockerfile b/msa/tb-node/docker/Dockerfile
index c86d020d20..57a6151e37 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/
diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml
index 18a81f3970..fc62189e62 100644
--- a/msa/tb-node/pom.xml
+++ b/msa/tb-node/pom.xml
@@ -111,19 +111,72 @@
build
+
+ ${dockerfile.skip}
+ ${docker.repo}/${docker.name}
+ true
+ false
+ ${project.build.directory}
+
+
+
+ tag-docker-image
+ pre-integration-test
+
+ tag
+
+
+ ${dockerfile.skip}
+ ${docker.repo}/${docker.name}
+ ${project.version}
+
-
- ${dockerfile.skip}
- ${docker.repo}/${docker.name}
- ${project.version}
- true
- false
- ${project.build.directory}
-
+
+
+ push-docker-image
+
+
+ push-docker-image
+
+
+
+
+
+ com.spotify
+ dockerfile-maven-plugin
+
+
+ push-latest-docker-image
+ pre-integration-test
+
+ push
+
+
+ latest
+ ${docker.repo}/${docker.name}
+
+
+
+ push-version-docker-image
+ pre-integration-test
+
+ push
+
+
+ ${project.version}
+ ${docker.repo}/${docker.name}
+
+
+
+
+
+
+
+
jenkins
diff --git a/msa/tb/README.md b/msa/tb/README.md
new file mode 100644
index 0000000000..e4b7400b50
--- /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.
diff --git a/msa/tb/docker-cassandra/Dockerfile b/msa/tb/docker-cassandra/Dockerfile
new file mode 100644
index 0000000000..a15408dddb
--- /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"]
diff --git a/docker/cassandra-setup/install.sh b/msa/tb/docker-cassandra/start-db.sh
old mode 100755
new mode 100644
similarity index 62%
rename from docker/cassandra-setup/install.sh
rename to msa/tb/docker-cassandra/start-db.sh
index 8578b2e769..c089bf65a6
--- a/docker/cassandra-setup/install.sh
+++ b/msa/tb/docker-cassandra/start-db.sh
@@ -15,19 +15,24 @@
# limitations under the License.
#
+cassandra_data_dir=${DATA_FOLDER}/db
+cassandra_data_link=/var/lib/cassandra
-dpkg -i /thingsboard.deb
+if [ ! -L ${cassandra_data_link} ]; then
+ if [ -d ${cassandra_data_link} ]; then
+ rm -rf ${cassandra_data_link}
+ fi
+ if [ ! -d ${cassandra_data_dir} ]; then
+ mkdir -p ${cassandra_data_dir}
+ chown -R cassandra:cassandra ${cassandra_data_dir}
+ fi
+ ln -s ${cassandra_data_dir} ${cassandra_data_link}
+fi
+
+service cassandra start
until nmap $CASSANDRA_HOST -p $CASSANDRA_PORT | grep "$CASSANDRA_PORT/tcp open"
do
echo "Wait for cassandra db to start..."
- sleep 10
+ sleep 5
done
-
-echo "Creating 'Thingsboard' schema and system data..."
-if [ "$ADD_DEMO_DATA" == "true" ]; then
- echo "plus demo data..."
- /usr/share/thingsboard/bin/install/install.sh --loadDemo
-elif [ "$ADD_DEMO_DATA" == "false" ]; then
- /usr/share/thingsboard/bin/install/install.sh
-fi
diff --git a/msa/docker/docker-start-services.sh b/msa/tb/docker-cassandra/stop-db.sh
old mode 100755
new mode 100644
similarity index 94%
rename from msa/docker/docker-start-services.sh
rename to msa/tb/docker-cassandra/stop-db.sh
index 44093934cd..8cb4b1210b
--- a/msa/docker/docker-start-services.sh
+++ b/msa/tb/docker-cassandra/stop-db.sh
@@ -15,6 +15,4 @@
# limitations under the License.
#
-./check-dirs.sh
-
-docker-compose up -d
+service cassandra stop
diff --git a/msa/tb/docker-postgres/Dockerfile b/msa/tb/docker-postgres/Dockerfile
new file mode 100644
index 0000000000..c1b3f029b0
--- /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"]
diff --git a/docker/cassandra-upgrade/upgrade.sh b/msa/tb/docker-postgres/start-db.sh
old mode 100755
new mode 100644
similarity index 59%
rename from docker/cassandra-upgrade/upgrade.sh
rename to msa/tb/docker-postgres/start-db.sh
index dac49191b2..4b973e6527
--- a/docker/cassandra-upgrade/upgrade.sh
+++ b/msa/tb/docker-postgres/start-db.sh
@@ -15,14 +15,16 @@
# limitations under the License.
#
+firstlaunch=${DATA_FOLDER}/.firstlaunch
-dpkg -i /thingsboard.deb
+if [ ! -d ${PGDATA} ]; then
+ mkdir -p ${PGDATA}
+ chown -R postgres:postgres ${PGDATA}
+ su postgres -c '/usr/lib/postgresql/9.6/bin/pg_ctl initdb -U postgres'
+fi
-until nmap $CASSANDRA_HOST -p $CASSANDRA_PORT | grep "$CASSANDRA_PORT/tcp open"
-do
- echo "Wait for cassandra db to start..."
- sleep 10
-done
+su postgres -c '/usr/lib/postgresql/9.6/bin/pg_ctl -l /var/log/postgres/postgres.log -w start'
-echo "Upgrading 'Thingsboard' schema..."
-/usr/share/thingsboard/bin/install/upgrade.sh --fromVersion=$UPGRADE_FROM_VERSION
+if [ ! -f ${firstlaunch} ]; then
+ su postgres -c 'psql -U postgres -d postgres -c "CREATE DATABASE thingsboard"'
+fi
diff --git a/msa/docker/docker-update-service.sh b/msa/tb/docker-postgres/stop-db.sh
old mode 100755
new mode 100644
similarity index 90%
rename from msa/docker/docker-update-service.sh
rename to msa/tb/docker-postgres/stop-db.sh
index f698f9b015..b1db7caa9e
--- a/msa/docker/docker-update-service.sh
+++ b/msa/tb/docker-postgres/stop-db.sh
@@ -15,5 +15,4 @@
# limitations under the License.
#
-docker-compose pull $@
-docker-compose up -d --no-deps --build $@
+su postgres -c '/usr/lib/postgresql/9.6/bin/pg_ctl stop'
diff --git a/msa/tb/docker-tb/Dockerfile b/msa/tb/docker-tb/Dockerfile
new file mode 100644
index 0000000000..497a03c959
--- /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"]
diff --git a/msa/docker/docker-stop-services.sh b/msa/tb/docker-tb/start-db.sh
old mode 100755
new mode 100644
similarity index 96%
rename from msa/docker/docker-stop-services.sh
rename to msa/tb/docker-tb/start-db.sh
index 673a0b2dfc..779e69c3eb
--- a/msa/docker/docker-stop-services.sh
+++ b/msa/tb/docker-tb/start-db.sh
@@ -15,4 +15,4 @@
# limitations under the License.
#
-docker-compose stop
+# Do nothing
\ No newline at end of file
diff --git a/docker/cassandra/Dockerfile b/msa/tb/docker-tb/stop-db.sh
similarity index 84%
rename from docker/cassandra/Dockerfile
rename to msa/tb/docker-tb/stop-db.sh
index a4e5cc049b..779e69c3eb 100644
--- a/docker/cassandra/Dockerfile
+++ b/msa/tb/docker-tb/stop-db.sh
@@ -1,3 +1,4 @@
+#!/bin/bash
#
# Copyright © 2016-2018 The Thingsboard Authors
#
@@ -14,10 +15,4 @@
# limitations under the License.
#
-FROM cassandra:3.11.2
-
-ADD ready-probe.sh /ready-probe.sh
-
-RUN chmod +x /ready-probe.sh
-
-CMD ["cassandra", "-f"]
\ No newline at end of file
+# Do nothing
\ No newline at end of file
diff --git a/msa/tb/docker/install-tb.sh b/msa/tb/docker/install-tb.sh
new file mode 100644
index 0000000000..e22ec58ec2
--- /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}
diff --git a/msa/docker/tb-node/conf/logback.xml b/msa/tb/docker/logback.xml
similarity index 100%
rename from msa/docker/tb-node/conf/logback.xml
rename to msa/tb/docker/logback.xml
diff --git a/msa/tb/docker/start-tb.sh b/msa/tb/docker/start-tb.sh
new file mode 100755
index 0000000000..37f5e3a72a
--- /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
diff --git a/msa/docker/tb-node/conf/thingsboard.conf b/msa/tb/docker/thingsboard.conf
similarity index 100%
rename from msa/docker/tb-node/conf/thingsboard.conf
rename to msa/tb/docker/thingsboard.conf
diff --git a/msa/tb/docker/upgrade-tb.sh b/msa/tb/docker/upgrade-tb.sh
new file mode 100644
index 0000000000..ab77cddf4c
--- /dev/null
+++ b/msa/tb/docker/upgrade-tb.sh
@@ -0,0 +1,47 @@
+#!/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
+upgradeversion=${DATA_FOLDER}/.upgradeversion
+
+source "${CONF_FOLDER}/${configfile}"
+
+FROM_VERSION=`cat ${upgradeversion}`
+
+echo "Starting ThingsBoard upgrade ..."
+
+if [[ -z "${FROM_VERSION// }" ]]; then
+ echo "FROM_VERSION variable is invalid or unspecified!"
+ exit 1
+else
+ fromVersion="${FROM_VERSION// }"
+fi
+
+java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \
+ -Dspring.jpa.hibernate.ddl-auto=none \
+ -Dinstall.upgrade=true \
+ -Dinstall.upgrade.from_version=${fromVersion} \
+ -Dlogging.config=/usr/share/thingsboard/bin/install/logback.xml \
+ org.springframework.boot.loader.PropertiesLauncher
+
+echo "${pkg.upgradeVersion}" > ${upgradeversion}
+
+stop-db.sh
\ No newline at end of file
diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml
new file mode 100644
index 0000000000..6ea7bebae2
--- /dev/null
+++ b/msa/tb/pom.xml
@@ -0,0 +1,370 @@
+
+
+ 4.0.0
+
+ org.thingsboard
+ 2.2.0-SNAPSHOT
+ msa
+
+ org.thingsboard.msa
+ tb
+ pom
+
+ ThingsBoard Docker Images
+ https://thingsboard.io
+ ThingsBoard Docker Images
+
+
+ UTF-8
+ ${basedir}/../..
+ thingsboard
+ tb
+ tb-postgres
+ tb-cassandra
+ thingsboard
+ /usr/share/${pkg.name}
+ 2.1.1
+
+
+
+
+ org.thingsboard
+ application
+ ${project.version}
+ deb
+ deb
+ provided
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+
+
+ copy-tb-deb
+ package
+
+ copy
+
+
+
+
+ org.thingsboard
+ application
+ deb
+ deb
+ ${pkg.name}.deb
+ ${project.build.directory}/docker-tb
+
+
+
+
+
+ copy-tb-postgres-deb
+ package
+
+ copy
+
+
+
+
+ org.thingsboard
+ application
+ deb
+ deb
+ ${pkg.name}.deb
+ ${project.build.directory}/docker-postgres
+
+
+
+
+
+ copy-tb-cassandra-deb
+ package
+
+ copy
+
+
+
+
+ org.thingsboard
+ application
+ deb
+ deb
+ ${pkg.name}.deb
+ ${project.build.directory}/docker-cassandra
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+
+
+ copy-docker-tb-config
+ process-resources
+
+ copy-resources
+
+
+ ${project.build.directory}/docker-tb
+
+
+ docker
+ true
+
+
+ docker-tb
+ true
+
+
+
+
+
+ copy-docker-tb-postgres-config
+ process-resources
+
+ copy-resources
+
+
+ ${project.build.directory}/docker-postgres
+
+
+ docker
+ true
+
+
+ docker-postgres
+ true
+
+
+
+
+
+ copy-docker-tb-cassandra-config
+ process-resources
+
+ copy-resources
+
+
+ ${project.build.directory}/docker-cassandra
+
+
+ docker
+ true
+
+
+ docker-cassandra
+ true
+
+
+
+
+
+
+
+ com.spotify
+ dockerfile-maven-plugin
+
+
+ build-docker-tb-image
+ pre-integration-test
+
+ build
+
+
+ ${dockerfile.skip}
+ ${docker.repo}/${tb.docker.name}
+ true
+ false
+ ${project.build.directory}/docker-tb
+
+
+
+ tag-docker-tb-image
+ pre-integration-test
+
+ tag
+
+
+ ${dockerfile.skip}
+ ${docker.repo}/${tb.docker.name}
+ ${project.version}
+
+
+
+ build-docker-tb-postgres-image
+ pre-integration-test
+
+ build
+
+
+ ${dockerfile.skip}
+ ${docker.repo}/${tb-postgres.docker.name}
+ true
+ false
+ ${project.build.directory}/docker-postgres
+
+
+
+ tag-docker-tb-postgres-image
+ pre-integration-test
+
+ tag
+
+
+ ${dockerfile.skip}
+ ${docker.repo}/${tb-postgres.docker.name}
+ ${project.version}
+
+
+
+ build-docker-tb-cassandra-image
+ pre-integration-test
+
+ build
+
+
+ ${dockerfile.skip}
+ ${docker.repo}/${tb-cassandra.docker.name}
+ true
+ false
+ ${project.build.directory}/docker-cassandra
+
+
+
+ tag-docker-tb-cassandra-image
+ pre-integration-test
+
+ tag
+
+
+ ${dockerfile.skip}
+ ${docker.repo}/${tb-cassandra.docker.name}
+ ${project.version}
+
+
+
+
+
+
+
+
+ push-docker-image
+
+
+ push-docker-image
+
+
+
+
+
+ com.spotify
+ dockerfile-maven-plugin
+
+
+ push-latest-docker-tb-image
+ pre-integration-test
+
+ push
+
+
+ latest
+ ${docker.repo}/${tb.docker.name}
+
+
+
+ push-version-docker-tb-image
+ pre-integration-test
+
+ push
+
+
+ ${project.version}
+ ${docker.repo}/${tb.docker.name}
+
+
+
+ push-latest-docker-tb-postgres-image
+ pre-integration-test
+
+ push
+
+
+ latest
+ ${docker.repo}/${tb-postgres.docker.name}
+
+
+
+ push-version-docker-tb-postgres-image
+ pre-integration-test
+
+ push
+
+
+ ${project.version}
+ ${docker.repo}/${tb-postgres.docker.name}
+
+
+
+ push-latest-docker-tb-cassandra-image
+ pre-integration-test
+
+ push
+
+
+ latest
+ ${docker.repo}/${tb-cassandra.docker.name}
+
+
+
+ push-version-docker-tb-cassandra-image
+ pre-integration-test
+
+ push
+
+
+ ${project.version}
+ ${docker.repo}/${tb-cassandra.docker.name}
+
+
+
+
+
+
+
+
+
+
+ jenkins
+ Jenkins Repository
+ http://repo.jenkins-ci.org/releases
+
+ false
+
+
+
+
diff --git a/msa/transport/coap/docker/Dockerfile b/msa/transport/coap/docker/Dockerfile
index dcaef3a358..9240b2a4f0 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/
diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml
index 94399c7715..26eaa30e0a 100644
--- a/msa/transport/coap/pom.xml
+++ b/msa/transport/coap/pom.xml
@@ -111,19 +111,72 @@
build
+
+ ${dockerfile.skip}
+ ${docker.repo}/${docker.name}
+ true
+ false
+ ${project.build.directory}
+
+
+
+ tag-docker-image
+ pre-integration-test
+
+ tag
+
+
+ ${dockerfile.skip}
+ ${docker.repo}/${docker.name}
+ ${project.version}
+
-
- ${dockerfile.skip}
- ${docker.repo}/${docker.name}
- ${project.version}
- true
- false
- ${project.build.directory}
-
+
+
+ push-docker-image
+
+
+ push-docker-image
+
+
+
+
+
+ com.spotify
+ dockerfile-maven-plugin
+
+
+ push-latest-docker-image
+ pre-integration-test
+
+ push
+
+
+ latest
+ ${docker.repo}/${docker.name}
+
+
+
+ push-version-docker-image
+ pre-integration-test
+
+ push
+
+
+ ${project.version}
+ ${docker.repo}/${docker.name}
+
+
+
+
+
+
+
+
jenkins
diff --git a/msa/transport/http/docker/Dockerfile b/msa/transport/http/docker/Dockerfile
index 212047fa42..6c83b9c76b 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/
diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml
index 6bbfc3f782..6d1707c4f7 100644
--- a/msa/transport/http/pom.xml
+++ b/msa/transport/http/pom.xml
@@ -111,19 +111,72 @@
build
+
+ ${dockerfile.skip}
+ ${docker.repo}/${docker.name}
+ true
+ false
+ ${project.build.directory}
+
+
+
+ tag-docker-image
+ pre-integration-test
+
+ tag
+
+
+ ${dockerfile.skip}
+ ${docker.repo}/${docker.name}
+ ${project.version}
+
-
- ${dockerfile.skip}
- ${docker.repo}/${docker.name}
- ${project.version}
- true
- false
- ${project.build.directory}
-
+
+
+ push-docker-image
+
+
+ push-docker-image
+
+
+
+
+
+ com.spotify
+ dockerfile-maven-plugin
+
+
+ push-latest-docker-image
+ pre-integration-test
+
+ push
+
+
+ latest
+ ${docker.repo}/${docker.name}
+
+
+
+ push-version-docker-image
+ pre-integration-test
+
+ push
+
+
+ ${project.version}
+ ${docker.repo}/${docker.name}
+
+
+
+
+
+
+
+
jenkins
diff --git a/msa/transport/mqtt/docker/Dockerfile b/msa/transport/mqtt/docker/Dockerfile
index 5827c65229..f636e2f3a7 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/
diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml
index 0a3b07771b..d51c51c035 100644
--- a/msa/transport/mqtt/pom.xml
+++ b/msa/transport/mqtt/pom.xml
@@ -111,19 +111,72 @@
build
+
+ ${dockerfile.skip}
+ ${docker.repo}/${docker.name}
+ true
+ false
+ ${project.build.directory}
+
+
+
+ tag-docker-image
+ pre-integration-test
+
+ tag
+
+
+ ${dockerfile.skip}
+ ${docker.repo}/${docker.name}
+ ${project.version}
+
-
- ${dockerfile.skip}
- ${docker.repo}/${docker.name}
- ${project.version}
- true
- false
- ${project.build.directory}
-
+
+
+ push-docker-image
+
+
+ push-docker-image
+
+
+
+
+
+ com.spotify
+ dockerfile-maven-plugin
+
+
+ push-latest-docker-image
+ pre-integration-test
+
+ push
+
+
+ latest
+ ${docker.repo}/${docker.name}
+
+
+
+ push-version-docker-image
+ pre-integration-test
+
+ push
+
+
+ ${project.version}
+ ${docker.repo}/${docker.name}
+
+
+
+
+
+
+
+
jenkins
diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml
index 805bf8b821..83124dff06 100644
--- a/msa/web-ui/pom.xml
+++ b/msa/web-ui/pom.xml
@@ -311,16 +311,27 @@
build
+
+ ${dockerfile.skip}
+ ${docker.repo}/${docker.name}
+ true
+ false
+ ${project.build.directory}
+
+
+
+ tag-docker-image
+ pre-integration-test
+
+ tag
+
+
+ ${dockerfile.skip}
+ ${docker.repo}/${docker.name}
+ ${project.version}
+
-
- ${dockerfile.skip}
- ${docker.repo}/${docker.name}
- ${project.version}
- true
- false
- ${project.build.directory}
-
@@ -358,6 +369,46 @@
+
+ push-docker-image
+
+
+ push-docker-image
+
+
+
+
+
+ com.spotify
+ dockerfile-maven-plugin
+
+
+ push-latest-docker-image
+ pre-integration-test
+
+ push
+
+
+ latest
+ ${docker.repo}/${docker.name}
+
+
+
+ push-version-docker-image
+ pre-integration-test
+
+ push
+
+
+ ${project.version}
+ ${docker.repo}/${docker.name}
+
+
+
+
+
+
+
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 9a93d1e281..f3d57de602 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 c568e3dd94..e3766baf7b 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' +
+ '' +
+ '' +
+ '' +
+ '{{ datepicker.date.format(\'YYYY\') }}' +
+ '{{ datepicker.date.format(datepicker.displayFormat) }} ' +
+ '' +
+ '
' +
+ '' +
+ '
' +
+ '
' +
+ '' +
+ '{{ item }}' +
+ '
' +
+ '' +
+ '
' +
+ '
' +
+ '
' +
+ '' +
+ '' + LABEL_CANCEL + '' +
+ '' + LABEL_OK + '' +
+ '' +
+ '
' +
+ '' +
+ '',
+ 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: '' +
+ '' +
+ '' +
+ '' +
+ '{{ timepicker.time.format("h") }}:' +
+ '{{ timepicker.time.format("mm") }}' +
+ '
' +
+ '' +
+ 'AM' +
+ 'PM' +
+ '
' +
+ '' +
+ '' +
+ '
' +
+ '' +
+ '' +
+ '
' +
+
+ '
' +
+ '' +
+ '' + LABEL_CANCEL + '' +
+ '' + LABEL_OK + '' +
+ '' +
+ '
' +
+ '' +
+ '',
+ 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 b02a30d206..6bc1932be6 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 db6fd3be96..8f257874c5 100644
--- a/ui/src/app/components/datasource-entity.tpl.html
+++ b/ui/src/app/components/datasource-entity.tpl.html
@@ -26,7 +26,6 @@
@@ -78,7 +77,6 @@
diff --git a/ui/src/app/components/datasource-func.directive.js b/ui/src/app/components/datasource-func.directive.js
index 7515b61cc7..982685ffe1 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 b016f57d89..97b7bfb05f 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 67caff203f..f3efe63eba 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 81103227ba..959720410e 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 caa69d6f98..fd665fd1a6 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 6df48df032..3f32e1b92c 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 79653a42c5..a1bb2688c0 100644
--- a/ui/src/app/dashboard/dashboard.tpl.html
+++ b/ui/src/app/dashboard/dashboard.tpl.html
@@ -74,6 +74,7 @@
settings
diff --git a/ui/src/app/dashboard/edit-widget.directive.js b/ui/src/app/dashboard/edit-widget.directive.js
index e6858b4227..f8aee69224 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 5ce8a6ca23..828bd6f8a7 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 98df466599..298b2ab5e7 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-view/entity-view.directive.js b/ui/src/app/entity-view/entity-view.directive.js
index 25377f4cb4..761930e615 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/entity/alias/entity-aliases.controller.js b/ui/src/app/entity/alias/entity-aliases.controller.js
index 732f10f456..49f28c9122 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 5346e038f8..f39593c2be 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 872042c2fa..d2586c4845 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/event/event-row.directive.js b/ui/src/app/event/event-row.directive.js
index b808fb8fff..5f84187e2c 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 18d281c0b0..73c5d93e80 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 d64441fdef..e08fd24d5a 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) {
diff --git a/ui/src/app/layout/index.js b/ui/src/app/layout/index.js
index 192400a646..7c7e870205 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 9f326c7612..30e1eb119b 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 81cc52ed6f..7b0214190b 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 8b270aa7af..dd2fa0d864 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 79843ab09b..49f9b4880d 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 6c4889460d..2ab2bcb909 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 07e7dd6070..62c0054d53 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 893bbcd86c..eb9996d274 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-TR.json b/ui/src/app/locale/locale.constant-tr_TR.json
similarity index 94%
rename from ui/src/app/locale/locale.constant-TR.json
rename to ui/src/app/locale/locale.constant-tr_TR.json
index de88116fe5..54176d83a9 100644
--- a/ui/src/app/locale/locale.constant-TR.json
+++ b/ui/src/app/locale/locale.constant-tr_TR.json
@@ -126,15 +126,15 @@
"acknowledge": "Onayla",
"clear": "Temizle",
"search": "Alarm ara",
- "selected-alarms": "{ count, select, 1 {1 alarm} other {# alarm} } seçildi",
+ "selected-alarms": "{ count, plural, 1 {1 alarm} other {# alarm} } seçildi",
"no-data": "Görüntülenecek veri bulunmuyor",
"polling-interval": "Alarm yoklama aralığı (saniye)",
"polling-interval-required": "Alarm yoklama aralığı gerekli.",
"min-polling-interval-message": "Alarm yoklama aralığı en az 1 saniye olmalıdır.",
- "aknowledge-alarms-title": "{ count, select, 1 {1 alarmı} other {# alarmı} } onayla",
- "aknowledge-alarms-text": "{ count, select, 1 {1 alarmı} other {# alarmı} } onaylamak istediğinize emin misiniz?",
- "clear-alarms-title": "{ count, select, 1 {1 alarmı} other {# alarmı} } temizle",
- "clear-alarms-text": "{ count, select, 1 {1 alarmı} other {# alarmı} } temizlemek istediğinize emin misiniz?"
+ "aknowledge-alarms-title": "{ count, plural, 1 {1 alarmı} other {# alarmı} } onayla",
+ "aknowledge-alarms-text": "{ count, plural, 1 {1 alarmı} other {# alarmı} } onaylamak istediğinize emin misiniz?",
+ "clear-alarms-title": "{ count, plural, 1 {1 alarmı} other {# alarmı} } temizle",
+ "clear-alarms-text": "{ count, plural, 1 {1 alarmı} other {# alarmı} } temizlemek istediğinize emin misiniz?"
},
"alias": {
"add": "Kısa ad ekle",
@@ -212,15 +212,15 @@
"add-asset-text": "Yeni varlık ekle",
"asset-details": "Varlık detayları",
"assign-assets": "Varlıkları ata",
- "assign-assets-text": "{ count, select, 1 {1 varlığı} other {# varlığı} } kullanıcı grubuna ata",
+ "assign-assets-text": "{ count, plural, 1 {1 varlığı} other {# varlığı} } kullanıcı grubuna ata",
"delete-assets": "Varlıkları sil",
"unassign-assets": "Varlıkların atamalarını kaldır",
- "unassign-assets-action-title": "{ count, select, 1 {1 varlığın} other {# varlığın} } atamalarını kullanıcı grubundan kaldır",
+ "unassign-assets-action-title": "{ count, plural, 1 {1 varlığın} other {# varlığın} } atamalarını kullanıcı grubundan kaldır",
"assign-new-asset": "Yeni varlık ata",
"delete-asset-title": "'{{assetName}}' isimli varlığı silmek istediğinize emin misiniz?",
"delete-asset-text": "UYARI: Onaylandıktan sonra varlık ve ilgili tüm veriler geri yüklenemeyecek şekilde silinecek.",
- "delete-assets-title": "{ count, select, 1 {1 varlığı} other {# varlığı} } silmek istediğinize emin misiniz?",
- "delete-assets-action-title": "{ count, select, 1 {1 varlığı} other {# varlığı} } sil",
+ "delete-assets-title": "{ count, plural, 1 {1 varlığı} other {# varlığı} } silmek istediğinize emin misiniz?",
+ "delete-assets-action-title": "{ count, plural, 1 {1 varlığı} other {# varlığı} } sil",
"delete-assets-text": "UYARI: Onaylandıktan sonra tüm seçili varlıklar ver ilgili tüm veriler geri yüklenemyeck şekilde silinecek.",
"make-public-asset-title": "'{{assetName}}' isimli varlığı açık hale getirmek istediğinize emin misiniz?",
"make-public-asset-text": "Onaylandıktan sonra varlık ve ilgili veriler açık hale gelecek ve başkaları tarafından erişilebilir olacaktır.",
@@ -229,7 +229,7 @@
"unassign-asset-title": "'{{assetName}}' isimli varlığın atamasını kaldırmak istediğinize emin misiniz?",
"unassign-asset-text": "Onaylandıktan sonra varlığın ataması kaldırılacak ve kullanıcı grubu tarafından erişilemez olacaktır.",
"unassign-asset": "Varlık atamasını kaldır",
- "unassign-assets-title": " { count, select, 1 {1 varlık} other {# varlık} } atamasını kaldırmak istediğinize emin misiniz?",
+ "unassign-assets-title": " { count, plural, 1 {1 varlık} other {# varlık} } atamasını kaldırmak istediğinize emin misiniz?",
"unassign-assets-text": "Onaylandıktan sonra tüm seçili varlıkların ataması kaldırılacak ve kullanıcı grubu tarafından erişilemez olacaktır.",
"copyId": "Varlık kimliğini kopyala",
"idCopiedMessage": "Varlık kimliği panoya kopyalandı",
@@ -252,7 +252,7 @@
"key-required": "Öznitelik anahtarı gerekli.",
"value": "Değer",
"value-required": "Öznitelik değeri gerekli.",
- "delete-attributes-title": "Silmek istediğinize emin misiniz { count, select, 1 {1 öznitelik} other {# öznitelik} }?",
+ "delete-attributes-title": "Silmek istediğinize emin misiniz { count, plural, 1 {1 öznitelik} other {# öznitelik} }?",
"delete-attributes-text": "UYARI: Onaylandıktan sonra tüm seçili öznitelikler kaldırılacak.",
"delete-attributes": "Öznitelikleri sil",
"enter-attribute-value": "Öznitelik değeri gir",
@@ -262,8 +262,8 @@
"prev-widget": "Önceki gösterge",
"add-to-dashboard": "Kontrol paneline ekle",
"add-widget-to-dashboard": "Göstergeyi kontrol paneline ekle",
- "selected-attributes": "{ count, select, 1 {1 öznitelik} other {# öznitelik} } seçildi",
- "selected-telemetry": "{ count, select, 1 {1 telemetri birimi} other {# telemetri birimi} } seçildi"
+ "selected-attributes": "{ count, plural, 1 {1 öznitelik} other {# öznitelik} } seçildi",
+ "selected-telemetry": "{ count, plural, 1 {1 telemetri birimi} other {# telemetri birimi} } seçildi"
},
"audit-log": {
"audit": "Log ve Hata Yönetimi",
@@ -358,8 +358,8 @@
"customer-details": "Kullanıcı Grubu detayları",
"delete-customer-title": "'{{customerTitle}}' isimli kullanıcı grubunu silmek istediğinize emin misiniz?",
"delete-customer-text": "UYARI: Onaylandıktan sonra kullanıcı grubu ve tüm ilişkili veriler geri yüklenemeyecek şekilde silinecek.",
- "delete-customers-title": "{ count, select, 1 {1 kullanıcı grubunu} other {# kullanıcı grubunu} } silmek istediğinize emin misiniz?",
- "delete-customers-action-title": "{ count, select, 1 {1 kullanıcı grubunu} other {# kullanıcı grubunu} } sil",
+ "delete-customers-title": "{ count, plural, 1 {1 kullanıcı grubunu} other {# kullanıcı grubunu} } silmek istediğinize emin misiniz?",
+ "delete-customers-action-title": "{ count, plural, 1 {1 kullanıcı grubunu} other {# kullanıcı grubunu} } sil",
"delete-customers-text": "UYARI: Onaylandıktan sonra tüm seçili kullanıcı grupları ve ilişkili veriler geri yüklenemez şekilde silinecek.",
"manage-users": "Kullanıcıları yönet",
"manage-assets": "Varlıkları yönet",
@@ -418,20 +418,20 @@
"add-dashboard-text": "Yeni kontrol paneli ekle",
"assign-dashboards": "Kontrol panelleri ata",
"assign-new-dashboard": "Yeni kontrol paneli ata",
- "assign-dashboards-text": "{ count, select, 1 {1 kontrol panelini} other {# kontrol panelini} } kullanıcı grubuna ata",
- "unassign-dashboards-action-text": "Müşterilerden atama sayısı, çoğul, 1 {1 gösterge tablosu} diğer {# panolar}}",
+ "assign-dashboards-text": "{ count, plural, 1 {1 kontrol panelini} other {# kontrol panelini} } kullanıcı grubuna ata",
+ "unassign-dashboards-action-text": "Müşterilerden atama { count, plural, 1 {1 gösterge tablosu} other {# panolar}}",
"delete-dashboards": "Kontrol panellerini sil",
"unassign-dashboards": "Kontrol panellerinden atamayı kaldır",
- "unassign-dashboards-action-title": "{ count, select, 1 {1 kontrol panelinin} other {# kontrol panelinin} } atamaları kullanıcı grubundan kaldır",
+ "unassign-dashboards-action-title": "{ count, plural, 1 {1 kontrol panelinin} other {# kontrol panelinin} } atamaları kullanıcı grubundan kaldır",
"delete-dashboard-title": "'{{dashboardTitle}}' isimli kontrol panelini silmek istediğinize emin misiniz?",
"delete-dashboard-text": "UYARI: Onaylandıktan sonra kontrol paneli ve ilişkili verileri geri yüklenemez şekilde silinecek.",
- "delete-dashboards-title": "{ count, select, 1 {1 kontrol panelini} other {# kontrol panelini} } silmek istediğinize emin misiniz?",
- "delete-dashboards-action-title": "{ count, select, 1 {1 kontrol panelini} other {# kontrol panelini} } sil",
+ "delete-dashboards-title": "{ count, plural, 1 {1 kontrol panelini} other {# kontrol panelini} } silmek istediğinize emin misiniz?",
+ "delete-dashboards-action-title": "{ count, plural, 1 {1 kontrol panelini} other {# kontrol panelini} } sil",
"delete-dashboards-text": "UYARI: Onaylandıktan sonra tüm seçili kontrol panelleri ve ilişkili verileri geri yüklenemez şekilde silinecek.",
"unassign-dashboard-title": "'{{dashboardTitle}}' isimli kontrol panelindeki atamayı kaldırmak istediğinize emin misiniz?",
"unassign-dashboard-text": "Onaylandıktan sonra kontrol panelinin ataması kaldırılacak ve kullanıcı grubu tarafından erişilemez hale gelecektir.",
"unassign-dashboard": "Kontrol panelinin ataması kaldır",
- "unassign-dashboards-title": "{count, select, 1 {1 kontrol panelindeki} other {# kontrol panelindeki} } atamayı kaldırmak istediğinize emin misiniz?",
+ "unassign-dashboards-title": "{count, plural, 1 {1 kontrol panelindeki} other {# kontrol panelindeki} } atamayı kaldırmak istediğinize emin misiniz?",
"unassign-dashboards-text": "Onaylandıktan {{dashboardTitle}} açık hale getirildi ve bu bağlantıdan erişilebilir durumda",
@@ -508,7 +508,7 @@
"manage-states": "Kontrol paneli durumlarını yönet",
"states": "Kontrol paneli durumları",
"search-states": "Kontrol paneli durumu ara",
- "selected-states": "{ count, select, 1 {1 kontrol paneli durumu} other {# kontrol paneli durumu} } seçildi",
+ "selected-states": "{ count, plural, 1 {1 kontrol paneli durumu} other {# kontrol paneli durumu} } seçildi",
"edit-state": "Kontrol paneli durumu düzenle",
"delete-state": "Kontrol paneli durumunu sil",
"add-state": "Kontrol paneli durumu ekle",
@@ -541,11 +541,11 @@
"alarm": "Alarm alanları",
"timeseries-required": "Zaman serisi öğesi gerekli.",
"timeseries-or-attributes-required": "Zaman serisi/öznitelikler öğesi gerekli.",
- "maximum-timeseries-or-attributes": "Maksimum { count, select, 1 {1 zamanserisi/öznitelik kabul edilir.} other {# zamanserisi/öznitelik kabul edilir} }",
+ "maximum-timeseries-or-attributes": "Maksimum { count, plural, 1 {1 zamanserisi/öznitelik kabul edilir.} other {# zamanserisi/öznitelik kabul edilir} }",
"alarm-fields-required": "Alarm alanları gerekli.",
"function-types": "Fonksiyon türleri",
"function-types-required": "Fonksiyon türleri gerekli.",
- "maximum-function-types": "Maksimum { count, select, 1 {1 fonksiyon türü kabul edilir.} other {# fonksiyon türü kabul edilir} }"
+ "maximum-function-types": "Maksimum { count, plural, 1 {1 fonksiyon türü kabul edilir.} other {# fonksiyon türü kabul edilir} }"
},
"datasource": {
"type": "Veri kaynağı türü",
@@ -597,11 +597,11 @@
"manage-credentials": "Kimlik bilgilerini yönet",
"delete": "Aygıt sil",
"assign-devices": "Aygıt ata",
- "assign-devices-text": "{ count, select, 1 {1 aygıtı} other {# aygıtı} } kullanıcı grubuna ata",
+ "assign-devices-text": "{ count, plural, 1 {1 aygıtı} other {# aygıtı} } kullanıcı grubuna ata",
"delete-devices": "Aygıtları sil",
"unassign-from-customer": "Kullanıcı Grubundan atamayı kaldır",
"unassign-devices": "Aygıtlardan atamayı kaldır",
- "unassign-devices-action-title": "{ count, select, 1 {1 aygıtın} other {# aygıtın} } atamasını kullanıcı grubundan kaldır",
+ "unassign-devices-action-title": "{ count, plural, 1 {1 aygıtın} other {# aygıtın} } atamasını kullanıcı grubundan kaldır",
"assign-new-device": "Yeni aygıt ata",
"make-public-device-title": "'{{deviceName}}' isimli aygıtı açık hale getirmek istediğinizden emin misiniz?",
"make-public-device-text": "Onaylandıktan sonra aygıt ve verileri açık hale getirilecek ve diğerleri tarafından erişilebilir olacak.",
@@ -610,13 +610,13 @@
"view-credentials": "Kimlik bilgilerini görüntüle",
"delete-device-title": "'{{deviceName}}' isimli aygıtı silmek istediğinize emin misiniz?",
"delete-device-text": "UYARI: Onaylandıktan sonra aygıt ve ilişkili verileri geri yüklenemez şekilde silinecek.",
- "delete-devices-title": "{ count, select, 1 {1 aygıtı} other {# aygıtı} } silmek istediğinize emin misiniz?",
- "delete-devices-action-title": "{ count, select, 1 {1 aygıtı} other {# aygıtı} } sil",
+ "delete-devices-title": "{ count, plural, 1 {1 aygıtı} other {# aygıtı} } silmek istediğinize emin misiniz?",
+ "delete-devices-action-title": "{ count, plural, 1 {1 aygıtı} other {# aygıtı} } sil",
"delete-devices-text": "UYARI: Onaylandıktan sonra tüm seçili aygıtlar ve ilişkili verileri geri yüklenemez şekilde silinecek.",
"unassign-device-title": "'{{deviceName}}' isimli aygıtın atamasını kaldırmak istediğinize emin misiniz?",
"unassign-device-text": "Onaylandıktan sonra aygıtın ataması kaldırılacak ve kullanıcı grubu tarafından erişilemez olacak.",
"unassign-device": "Aygıt atamasını kaldır",
- "unassign-devices-title": "{ count, select, 1 {1 aygıtın} other {# aygıtın} } atamasını kaldırmak istediğinize emin misiniz?",
+ "unassign-devices-title": "{ count, plural, 1 {1 aygıtın} other {# aygıtın} } atamasını kaldırmak istediğinize emin misiniz?",
"unassign-devices-text": "Onaylandıktan sonra seçili aygıtların atamaları kaldırılacak ve kullanıcı grubu tarafından erişilemez olacak.",
"device-credentials": "Aygıt Kimlik Bilgileri",
"credentials-type": "Kimlik Bilgi Türü",
@@ -702,55 +702,55 @@
"type-required": "Öğe türü gerekli.",
"type-device": "Aygıt",
"type-devices": "Aygıtlar",
- "list-of-devices": "{ count, select, 1 {Bir aygıt} other {# Aygıtın Listesi} }",
+ "list-of-devices": "{ count, plural, 1 {Bir aygıt} other {# Aygıtın Listesi} }",
"device-name-starts-with": "İsimleri '{{prefix}}' ile başlayan aygıtlar",
"type-asset": "Varlık",
"type-assets": "Varlıklar",
- "list-of-assets": "{ count, select, 1 {Bir varlık} other {# Varlığın Listesi} }",
+ "list-of-assets": "{ count, plural, 1 {Bir varlık} other {# Varlığın Listesi} }",
"asset-name-starts-with": "İsmi '{{prefix}}' ile başlayan varlıklar",
"type-entity-view": "Varlık Görünümü",
"type-entity-views": "Varlık Görünümleri",
- "list-of-entity-views": "{count, çoğul, 1 {Bir varlık görünümü} diğer {# varlık görüntüleme}} listesi",
+ "list-of-entity-views": "{ count, plural, 1 {Bir varlık görünümü} other {# varlık görüntüleme}} listesi",
"entity-view-name-starts-with": "Adı {{önek}} ile başlayan varlık görünümleri",
"type-rule": "Kural",
"type-rules": "Kurallar",
- "list-of-rules": "{ count, select, 1 {Bir kural} other {# Kuralın Listesi} }",
+ "list-of-rules": "{ count, plural, 1 {Bir kural} other {# Kuralın Listesi} }",
"rule-name-starts-with": "İsmi '{{prefix}}' ile başlayan kurallar",
"type-plugin": "Eklenti",
"type-plugins": "Eklentiler",
- "list-of-plugins": "{ count, select, 1 {Bir eklenti} other {# Eklentinin Listesi} }",
+ "list-of-plugins": "{ count, plural, 1 {Bir eklenti} other {# Eklentinin Listesi} }",
"plugin-name-starts-with": "İsmi '{{prefix}}' ile başlayan eklentiler",
"type-tenant": "Tenant",
"type-tenants": "Tenantlar",
- "list-of-tenants": "{ count, select, 1 {Bir tenant} other {# Tenantın Listesi} }",
+ "list-of-tenants": "{ count, plural, 1 {Bir tenant} other {# Tenantın Listesi} }",
"tenant-name-starts-with": "İsmi '{{prefix}}' ile başlayan tenantlar",
"type-customer": "Kullanıcı Grubu",
"type-customers": "Kullanıcı Grupları",
- "list-of-customers": "{ count, select, 1 {Bir Kullanıcı Grubu} other {# Kullanıcı Grupları} }",
+ "list-of-customers": "{ count, plural, 1 {Bir Kullanıcı Grubu} other {# Kullanıcı Grupları} }",
"customer-name-starts-with": "İsmi '{{prefix}}' ile başlayan Kullanıcı Grupları",
"type-user": "Kullanıcı",
"type-users": "Kullanıcılar",
- "list-of-users": "{ count, select, 1 {Bir kullanıcı} other {# Kullanıcının Listesi} }",
+ "list-of-users": "{ count, plural, 1 {Bir kullanıcı} other {# Kullanıcının Listesi} }",
"user-name-starts-with": "İsmi '{{prefix}}' ile başlayan kullanıcılar",
"type-dashboard": "Kontrol paneli",
"type-dashboards": "Kontrol panelleri",
- "list-of-dashboards": "{ count, select, 1 {Bir kontrol paneli} other {# Kontrol Panelinin Listesi} }",
+ "list-of-dashboards": "{ count, plural, 1 {Bir kontrol paneli} other {# Kontrol Panelinin Listesi} }",
"dashboard-name-starts-with": "İsmi '{{prefix}}' ile başlayan kontrol panelleri",
"type-alarm": "Alarm",
"type-alarms": "Alarmlar",
- "list-of-alarms": "{ count, select, 1 {Bir alarm} other {# Alarmın Listesi} }",
+ "list-of-alarms": "{ count, plural, 1 {Bir alarm} other {# Alarmın Listesi} }",
"alarm-name-starts-with": "İsmi '{{prefix}}' ile başlayan alarmlar",
"type-rulechain": "Kural zinciri",
"type-rulechains": "Kural zincirleri",
- "list-of-rulechains": "{count, çoğul, 1 {Bir kural zinciri} diğer {# kural zincirinin listesi}}",
+ "list-of-rulechains": "{ count, plural, 1 {Bir kural zinciri} other {# kural zincirinin listesi}}",
"rulechain-name-starts-with": "İsimleri {{prefix}} ile başlayan kural zincirleri",
"type-rulenode": "Kural düğümü",
"type-rulenodes": "Kural düğümleri",
- "list-of-rulenodes": "{count, çoğul, 1 {Bir kural node} diğer {# kural düğümünün listesi}}",
+ "list-of-rulenodes": "{ count, plural, 1 {Bir kural node} other {# kural düğümünün listesi}}",
"rulenode-name-starts-with": "İsimleri '{{prefix}} ile başlayan kural düğümleri",
"type-current-customer": "Mevcut Müşteri",
"search": "Öğeleri ara",
- "selected-entities": "{ count, select, 1 {1 öğe} other {# öğe} } seçildi",
+ "selected-entities": "{ count, plural, 1 {1 öğe} other {# öğe} } seçildi",
"entity-name": "Öğe adı",
"details": "Öğe detayları",
"no-entities-prompt": "Hiçbir öğe bulunamadı",
@@ -792,21 +792,21 @@
"add-entity-view-text": "Yeni varlık görünümü ekle",
"delete": "Varlık görünümünü sil",
"assign-entity-views": "Varlık görünümleri atama",
- "assign-entity-views-text": "Müşteriye {count, çoğul, 1 {1 entityView} diğer {# entityViews}} atayın ",
+ "assign-entity-views-text": "Müşteriye { count, plural, 1 {1 entityView} other {# entityViews}} atayın ",
"delete-entity-views": "Varlık görünümlerini sil",
"unassign-from-customer": "Müşteriden atama",
"unassign-entity-views": "Varlık görünümlerini atama",
- "unassign-entity-views-action-title": "Müşteriden atama sayım, çoğul, 1 {1 entityView} diğer {# entityViews}}",
+ "unassign-entity-views-action-title": "Müşteriden atama { count, plural, 1 {1 entityView} other {# entityViews}}",
"assign-new-entity-view": "Yeni varlık görünümü atama",
"delete-entity-view-title": "Varlık görünümünü silmek istediğinizden emin misiniz?, {{entityViewName}} '? ",
"delete-entity-view-text": "Dikkatli olun, onaylandıktan sonra varlık görünümü ve ilgili tüm veriler kurtarılamayacak.",
- "delete-entity-views-title": "{count, çoğul, 1 {1 entityView} diğer {# entityViews}} varlık görünümüne sahip olmak istediğinizden emin misiniz?",
- "delete-entity-views-action-title": "Sil {count, çoğul, 1 {1 entityView} diğer {# entityViews}}",
+ "delete-entity-views-title": "{ count, plural, 1 {1 entityView} other {# entityViews}} varlık görünümüne sahip olmak istediğinizden emin misiniz?",
+ "delete-entity-views-action-title": "Sil { count, plural, 1 {1 entityView} other {# entityViews}}",
"delete-entity-views-text": "Dikkatli olun, onaylandıktan sonra tüm seçilen görünümler kaldırılacak ve ilgili tüm veriler kurtarılamayacaktır.",
"unassign-entity-view-title": "Varlık görünümünün atamasını kaldırmak istediğinizden emin misiniz? {{entityViewName}} '? ",
"unassign-entity-view-text": "Onaydan sonra varlık görünümü atanmamış olacak ve müşteri tarafından erişilemeyecektir.",
"unassign-entity-view": "Varlık görünümünün atamasını kaldır",
- "unassign-entity-views-title": "Sayım, çoğul, 1 {1 entityView} diğer {# entityViews}} hesabının atamasını kaldırmak istediğinizden emin misiniz?",
+ "unassign-entity-views-title": "{ count, plural, 1 {1 entityView} other {# entityViews}} hesabının atamasını kaldırmak istediğinizden emin misiniz?",
"unassign-entity-views-text": "Onaylandıktan sonra, seçilen tüm öğe görünümleri atamadan kaldırılacak ve müşteri tarafından erişilemeyecektir.",
"entity-view-type": "Varlık Görünümü türü",
"entity-view-type-required": "Varlık Görünümü türü gerekli.",
@@ -861,7 +861,7 @@
},
"extension": {
"extensions": "Uzantılar",
- "selected-extensions": "{count, çoğul, 1 {1 uzantı} diğer {# extensions}} seçildi",
+ "selected-extensions": "{ count, plural, 1 {1 uzantı} other {# extensions}} seçildi",
"type": "Tür",
"key": "Anahtar",
"value": "Değer",
@@ -875,7 +875,7 @@
"edit": "Uzantıyı düzenle",
"delete-extension-title": "{{ExtensionId}} uzantısını silmek istediğinizden emin misiniz? ",
"delete-extension-text": "Dikkatli olun, onaylamadan sonra uzantı ve ilgili tüm veriler kurtarılamaz.",
- "delete-extensions-title": "{Count, çoğul, 1 {1 uzantı} diğer {# extensions}} silmek istediğinizden emin misiniz?",
+ "delete-extensions-title": "{ count, plural, 1 {1 uzantı} other {# extensions}} silmek istediğinizden emin misiniz?",
"delete-extensions-text": "Dikkatli olun, onaylandıktan sonra tüm seçilen uzantılar kaldırılacak.",
"converters": "Dönüştürücü",
"converter-id": "Dönüştürücü kimliği",
@@ -1026,8 +1026,8 @@
"grid": {
"delete-item-title": "Bu öğeyi silmek istediğinizden emin misiniz?",
"delete-item-text": "UYARI: Onayladıktan sonra bu öğe ve ilişkili tüm verileri geri yüklenemez şekilde silinecektir.",
- "delete-items-title": "{ count, select, 1 {1 öğeyi} other {# öğeyi} } silmek istediğinizden emin misiniz?",
- "delete-items-action-title": "{ count, select, 1 {1 öğeyi} other {# öğeyi} } sil",
+ "delete-items-title": "{ count, plural, 1 {1 öğeyi} other {# öğeyi} } silmek istediğinizden emin misiniz?",
+ "delete-items-action-title": "{ count, plural, 1 {1 öğeyi} other {# öğeyi} } sil",
"delete-items-text": "UYARI: Onayladıktan sonra tüm seçili öğeler ve ilişkili tüm verileri geri yüklenemez şekilde silinecektir.",
"add-item-text": "Yeni öğe ekle",
"no-items-text": "Hiç bir öğe bulunamadı",
@@ -1128,7 +1128,7 @@
},
"from-relations": "Giden ilişkiler",
"to-relations": "Gelen ilişkiler",
- "selected-relations": "{ count, select, 1 {1 ilişki} other {# ilişki} } seçildi",
+ "selected-relations": "{ count, plural, 1 {1 ilişki} other {# ilişki} } seçildi",
"type": "Tür",
"to-entity-type": "Hedef Öğe Türü",
"to-entity-name": "Hedef Öğe Adı",
@@ -1144,11 +1144,11 @@
"edit": "İlişki düzenle",
"delete-to-relation-title": "'{{entityName}}' öğesine olan ilişkiyi silmek istediğinize emin misiniz?",
"delete-to-relation-text": "UYARI: Onaylandıktan sonra '{{entityName}}' öğesinin şimdiki öğeyle olan ilişkisi sona erecektir.",
- "delete-to-relations-title": "{ count, select, 1 {1 ilişkiyi} other {# ilişkiyi} } silmek istediğinize emin misiniz?",
+ "delete-to-relations-title": "{ count, plural, 1 {1 ilişkiyi} other {# ilişkiyi} } silmek istediğinize emin misiniz?",
"delete-to-relations-text": "UYARI: Onaylandıktan sonra tüm seçili ilişkiler kaldırılacaktır ve ilgili öğelerin şimdiki öğeyle ilişkisi sona erecektir.",
"delete-from-relation-title": "'{{entityName}}' öğesinden ilişkiyi silmek istediğinize emin misiniz?",
"delete-from-relation-text": "UYARI: Onaylandıktan sonra şimdiki öğenin '{{entityName}}' öğesiyle ilişkisi sonlandırılacaktır.",
- "delete-from-relations-title": "{ count, select, 1 {1 ilişkiyi} other {# ilişkiyi} } silmek istediğinize emin misiniz?",
+ "delete-from-relations-title": "{ count, plural, 1 {1 ilişkiyi} other {# ilişkiyi} } silmek istediğinize emin misiniz?",
"delete-from-relations-text": "UYARI: Onaylandıktan sonra tüm seçili ilişkiler kaldırılacak ve şimdiki öğenin ilgili tüm öğelerle ilişkisi sona erecektir.",
"remove-relation-filter": "İlişki filtresini kaldır",
"add-relation-filter": "İlişkisi ekle",
@@ -1171,8 +1171,8 @@
"set-root-rulechain-text": "Onaydan sonra kural zinciri kökleşecek ve gelen tüm iletilerle ilgilenecek.",
"delete-rulechain-title": "'{{ruleName}}' isimli kuralı silmek istediğinize emin misiniz?",
"delete-rulechain-text": "UYARI: Onaylandıktan sonra kural ve ilişkili tüm veriler geri yüklenemez şekilde silinecektir.",
- "delete-rulechains-title": "{ count, select, 1 {1 kuralı} other {# kuralı} } sikmek istediğinize emin misiniz?",
- "delete-rulechains-action-title": "{ count, select, 1 {1 kuralı} other {# kuralı} sil}",
+ "delete-rulechains-title": "{ count, plural, 1 {1 kuralı} other {# kuralı} } sikmek istediğinize emin misiniz?",
+ "delete-rulechains-action-title": "{ count, plural, 1 {1 kuralı} other {# kuralı} } sil",
"delete-rulechains-text": "UYARI: Onaylandıktan sonra seçili tüm kurallar ve ilişkili tüm veriler geri yüklenemez şekilde silinecektir.",
"add-rulechain-text": "Yeni kural ekle",
"no-rulechains-text":"Hiçbir kural bulunamadı",
@@ -1270,8 +1270,8 @@
"tenant-details": "Tenant detayları",
"delete-tenant-title": "'{{tenantTitle}}' isimli tenantı silmek istediğinize emin misiniz?",
"delete-tenant-text": "UYARI: Onaylandıktan sonra tenant ve ilişkili tüm veriler geri yüklenemez şekilde silinecektir.",
- "delete-tenants-title": "{ count, select, 1 {1 tenantı} other {# tenantı} } silmek istediğinize emin misiniz?",
- "delete-tenants-action-title": "{ count, select, 1 {1 tenantı} other {# tenantı} } sil",
+ "delete-tenants-title": "{ count, plural, 1 {1 tenantı} other {# tenantı} } silmek istediğinize emin misiniz?",
+ "delete-tenants-action-title": "{ count, plural, 1 {1 tenantı} other {# tenantı} } sil",
"delete-tenants-text": "UYARI: Onaylandıktan sonra seçili tüm tenantlar ve ilişkili tüm veriler geri yüklenemez şekilde silinecektir",
"title": "Başlık",
"title-required": "Başlık gerekli.",
@@ -1325,8 +1325,8 @@
"user-details": "Kullanıcı detayları",
"delete-user-title": "'{{userEmail}}' kullanıcısını silmek istediğinize emin misiniz?",
"delete-user-text": "UYARI: Onaylandıktan sonra kullanıcı ve ilişkili tüm verileri geri yüklenemez şekilde silinecektir.",
- "delete-users-title": "{ count, select, 1 {1 kullanıcıyı} other {# kullanıcıyı} } sikmek istediğinize emin misiniz?",
- "delete-users-action-title": "{ count, select, 1 {1 kullancıyı} other {# kullanıcıyı} } sil",
+ "delete-users-title": "{ count, plural, 1 {1 kullanıcıyı} other {# kullanıcıyı} } sikmek istediğinize emin misiniz?",
+ "delete-users-action-title": "{ count, plural, 1 {1 kullancıyı} other {# kullanıcıyı} } sil",
"delete-users-text": "UYARI: Onaylandıktan sonra kullanıcı ve ilişkili tüm verileri geri yüklenemez şekilde silinecektir.",
"activation-email-sent-message": "Etkinleştirme e-postası başarılı bir şekilde gönderildi!",
"resend-activation": "Etkinleştirme e-postasını yeniden gönder",
@@ -1445,8 +1445,8 @@
"widgets-bundle-details": "Gösterge demeti detayları",
"delete-widgets-bundle-title": "'{{widgetsBundleTitle}}' isimli gösterge demetini silmek istediğinize emin misiniz?",
"delete-widgets-bundle-text": "UYARI: Onaylandıktan sonra gösterge demeti ve ilişkili tüm veriler geri yüklenemez şekilde silinecektir.",
- "delete-widgets-bundles-title": "{ count, select, 1 {1 gösterge demetini} other {# gösterge demetini} } silmek istediğinize emin misiniz?",
- "delete-widgets-bundles-action-title": "{ count, select, 1 {1 gösterge demetini} other {# gösterge demetini} } sil",
+ "delete-widgets-bundles-title": "{ count, plural, 1 {1 gösterge demetini} other {# gösterge demetini} } silmek istediğinize emin misiniz?",
+ "delete-widgets-bundles-action-title": "{ count, plural, 1 {1 gösterge demetini} other {# gösterge demetini} } sil",
"delete-widgets-bundles-text": "UYARI: Onaylandıktan sonra seçili tüm gösterge demetleri ve ilişkili tüm veriler geri yüklenemez şekilde silinecektir.",
"no-widgets-bundles-matching": "'{{widgetsBundle}}' ile eşleşen gösterge demeti bulunamadı.",
"widgets-bundle-required": "Gösterge demeti gerekli.",
@@ -1482,7 +1482,7 @@
"use-dashboard-timewindow": "Kontrol paneli zaman aralığı kullan",
"display-legend": "Lejant göster",
"datasources": "Veri kaynakları",
- "maximum-datasources": "En fazla { count, select, 1 {1 veri kaynağı kullanılabilir.} other {# veri kaynağı kullanılabilir} }",
+ "maximum-datasources": "En fazla { count, plural, 1 {1 veri kaynağı kullanılabilir.} other {# veri kaynağı kullanılabilir} }",
"datasource-type": "Tür",
"datasource-parameters": "Parametreler",
"remove-datasource": "Veri kaynağını kaldır",
@@ -1539,7 +1539,7 @@
"ru_RU": "Rusça",
"es_ES": "İspanyol",
"ja_JA": "Japonca",
- "TR": "Türkçe"
+ "tr_TR": "Türkçe"
}
}
}
\ No newline at end of file
diff --git a/ui/src/app/locale/locale.constant-zh_CN.json b/ui/src/app/locale/locale.constant-zh_CN.json
index c952f9cd32..bac1706c67 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 81d81c1c54..d4e6df82d5 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 e5fb9c38de..e7f50ccc06 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">
- generateReport
+
diff --git a/ui/src/app/user/add-user.controller.js b/ui/src/app/user/add-user.controller.js
index 20ad0beaf7..c81cd67a57 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();
diff --git a/ui/src/app/user/user.controller.js b/ui/src/app/user/user.controller.js
index 8d7f214b9a..ef7948daf0 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 6ae17e0459..3d91e9abbc 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 283a42676b..274cc97901 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;
}