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 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; }