From 241653260fe18b08708ccc39994ca9861bf4e572 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 17 Sep 2018 19:10:48 +0300 Subject: [PATCH 01/35] update license-header in TbGetTelemetryNode --- .../rule/engine/metadata/TbGetTelemetryNode.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java index 5a5e9c6630..260669f9ff 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java @@ -1,12 +1,12 @@ /** * 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 - *

+ * + * 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. From 5ffd778df110f0ec36f09f60348592c140d635e3 Mon Sep 17 00:00:00 2001 From: bombay Date: Sun, 7 Oct 2018 17:38:44 +0700 Subject: [PATCH 02/35] [WIP]Help refactor code solid patterns --- .../client/tools/MqttSslClient.java | 106 +++++++++++++----- 1 file changed, 77 insertions(+), 29 deletions(-) diff --git a/tools/src/main/java/org/thingsboard/client/tools/MqttSslClient.java b/tools/src/main/java/org/thingsboard/client/tools/MqttSslClient.java index dd27e44a1d..339f26ded8 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/MqttSslClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/MqttSslClient.java @@ -50,38 +50,86 @@ public class MqttSslClient { File ksFile = new File(ksUrl.toURI()); URL tsUrl = Resources.getResource(KEY_STORE_FILE); File tsFile = new File(tsUrl.toURI()); + char[] ksPwd = setKeyPassword(); + + KeyStore trustStore = setKeyStore(tsFile, ksPwd); + + TrustManagerFactory tmf = setTrustManagerFactory(trustStore); + + KeyStore ks = setKeyStore2(ksFile, ksPwd); + + KeyManagerFactory kmf = setKeyManagerFactory(ks); + + SSLContext sslContext = setSSLContext(kmf, tmf); + + MqttConnectOptions mqttConnectOptions = setMqttConnectOptions(sslContext); + + MqttAsyncClient mqttAsyncClient = setMqttAsyncClient(mqttConnectOptions); + + setMessage(mqttAsyncClient, "v1/devices/me/telemetry", "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4}"); - TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - - KeyStore trustStore = KeyStore.getInstance(JKS); - char[] ksPwd = new char[]{0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x5F, 0x6B, 0x73, 0x5F, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64}; - trustStore.load(new FileInputStream(tsFile), ksPwd); - tmf.init(trustStore); - KeyStore ks = KeyStore.getInstance(JKS); - - ks.load(new FileInputStream(ksFile), ksPwd); - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - char[] clientPwd = new char[]{0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x5F, 0x6B, 0x65, 0x79, 0x5F, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64}; - kmf.init(ks, clientPwd); - - KeyManager[] km = kmf.getKeyManagers(); - TrustManager[] tm = tmf.getTrustManagers(); - SSLContext sslContext = SSLContext.getInstance(TLS); - sslContext.init(km, tm, null); - - MqttConnectOptions options = new MqttConnectOptions(); - options.setSocketFactory(sslContext.getSocketFactory()); - MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, CLIENT_ID); - client.connect(options); - Thread.sleep(3000); - MqttMessage message = new MqttMessage(); - message.setPayload("{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4}".getBytes()); - client.publish("v1/devices/me/telemetry", message); - client.disconnect(); - log.info("Disconnected"); - System.exit(0); } catch (Exception e) { log.error("Unexpected exception occurred in MqttSslClient", e); } } + + private static char[] setKeyPassword(){ + return new char[]{0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x5F, 0x6B, 0x73, 0x5F, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64}; + } + + private static KeyStore setKeyStore(File tsFile, char[] ksPwd) throws Exception{ + KeyStore trustStore = KeyStore.getInstance(JKS); + trustStore.load(new FileInputStream(tsFile), ksPwd); + return trustStore; + } + + private static TrustManagerFactory setTrustManagerFactory(KeyStore trustStore) throws Exception { + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(trustStore); + return tmf; + } + + private static KeyStore setKeyStore2(File ksFile, char[] ksPwd)throws Exception{ + KeyStore ks = KeyStore.getInstance(JKS); + ks.load(new FileInputStream(ksFile), ksPwd); + return ks; + } + + private static KeyManagerFactory setKeyManagerFactory(KeyStore ks) throws Exception { + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + char[] clientPwd = new char[]{0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x5F, 0x6B, 0x65, 0x79, 0x5F, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64}; + kmf.init(ks, clientPwd); + return kmf; + } + + private static SSLContext setSSLContext(KeyManagerFactory kmf, TrustManagerFactory tmf) throws Exception { + KeyManager[] km = kmf.getKeyManagers(); + TrustManager[] tm = tmf.getTrustManagers(); + SSLContext sslContext = SSLContext.getInstance(TLS); + sslContext.init(km, tm, null); + return sslContext; + } + + private static MqttConnectOptions setMqttConnectOptions(SSLContext sslContext){ + MqttConnectOptions options = new MqttConnectOptions(); + options.setSocketFactory(sslContext.getSocketFactory()); + return options; + } + + private static MqttAsyncClient setMqttAsyncClient(MqttConnectOptions options) throws Exception{ + MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, CLIENT_ID); + client.connect(options); + Thread.sleep(3000); + return client; + } + + private static void setMessage(MqttAsyncClient client, String topic, String payload) throws Exception { + MqttMessage message = new MqttMessage(); + message.setPayload(payload.getBytes()); + client.publish(topic, message); + client.disconnect(); + log.info("Disconnected"); + System.exit(0); + } + } \ No newline at end of file From 4b0554b274714657f70a03938b36e4f9551fce8a Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Tue, 16 Oct 2018 14:05:52 +0300 Subject: [PATCH 03/35] Integration-tests subproject --- msa/integration-tests/README.md | 18 +++ msa/integration-tests/pom.xml | 98 +++++++++++++++ .../server/msa/AbstractContainerTest.java | 112 ++++++++++++++++++ .../server/msa/ContainerTestSuite.java | 37 ++++++ .../org/thingsboard/server/msa/WsClient.java | 57 +++++++++ .../server/msa/WsTelemetryResponse.java | 40 +++++++ .../msa/connectivity/HttpClientTest.java | 57 +++++++++ .../msa/connectivity/MqttClientTest.java | 111 +++++++++++++++++ msa/pom.xml | 1 + 9 files changed, 531 insertions(+) create mode 100644 msa/integration-tests/README.md create mode 100644 msa/integration-tests/pom.xml create mode 100644 msa/integration-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java create mode 100644 msa/integration-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java create mode 100644 msa/integration-tests/src/test/java/org/thingsboard/server/msa/WsClient.java create mode 100644 msa/integration-tests/src/test/java/org/thingsboard/server/msa/WsTelemetryResponse.java create mode 100644 msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java create mode 100644 msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java diff --git a/msa/integration-tests/README.md b/msa/integration-tests/README.md new file mode 100644 index 0000000000..5ae6354740 --- /dev/null +++ b/msa/integration-tests/README.md @@ -0,0 +1,18 @@ + +## Integration tests execution +To run the integration tests with using Docker, the local Docker images of Thingsboard's microservices should be built.
+- Build the local Docker images in the directory with the Thingsboard's main [pom.xml](./../../pom.xml): + + mvn clean install -Ddockerfile.skip=false +- Verify that the new local images were built: + + docker image ls +As result, in REPOSITORY column, next images should be present: + + local-maven-build/tb-node + local-maven-build/tb-web-ui + local-maven-build/tb-web-ui + +- Run the integration tests in the [msa/integration-tests](../integration-tests) directory: + + mvn clean install -Dintegrationtests.skip=false \ No newline at end of file diff --git a/msa/integration-tests/pom.xml b/msa/integration-tests/pom.xml new file mode 100644 index 0000000000..a1c24f39c6 --- /dev/null +++ b/msa/integration-tests/pom.xml @@ -0,0 +1,98 @@ + + + 4.0.0 + + + org.thingsboard + 2.2.0-SNAPSHOT + msa + + org.thingsboard.msa + integration-tests + + ThingsBoard Integration Tests + https://thingsboard.io + Project for ThingsBoard integration tests with using Docker + + + UTF-8 + ${basedir}/../.. + true + 1.9.1 + 1.3.9 + + + + + org.testcontainers + testcontainers + ${testcontainers.version} + + + org.java-websocket + Java-WebSocket + ${java-websocket.version} + + + io.takari.junit + takari-cpsuite + + + ch.qos.logback + logback-classic + + + com.google.code.gson + gson + + + org.apache.commons + commons-lang3 + + + com.google.guava + guava + + + org.thingsboard + netty-mqtt + + + org.thingsboard + tools + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + **/*TestSuite.java + + ${integrationtests.skip} + + + + + + diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java new file mode 100644 index 0000000000..ab7f101f98 --- /dev/null +++ b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java @@ -0,0 +1,112 @@ +/** + * 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.msa; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomStringUtils; +import org.junit.*; +import org.thingsboard.client.tools.RestClient; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.DeviceId; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +@Slf4j +public abstract class AbstractContainerTest { + protected static String httpUrl; + protected static String wsUrl; + protected static RestClient restClient; + protected ObjectMapper mapper = new ObjectMapper(); + + @BeforeClass + public static void before() { + httpUrl = "http://localhost:" + ContainerTestSuite.composeContainer.getServicePort("tb-web-ui1", ContainerTestSuite.EXPOSED_PORT); + wsUrl = "ws://localhost:" + ContainerTestSuite.composeContainer.getServicePort("tb-web-ui1", ContainerTestSuite.EXPOSED_PORT); + restClient = new RestClient(httpUrl); + } + + protected Device createDevice(String name) { + return restClient.createDevice(name + RandomStringUtils.randomAlphanumeric(7), "DEFAULT"); + } + + protected WsClient subscribeToTelemetryWebSocket(DeviceId deviceId) throws URISyntaxException, InterruptedException { + WsClient mWs = new WsClient(new URI(wsUrl + "/api/ws/plugins/telemetry?token=" + restClient.getToken())); + mWs.connectBlocking(1, TimeUnit.SECONDS); + + JsonObject tsSubCmd = new JsonObject(); + tsSubCmd.addProperty("entityType", EntityType.DEVICE.name()); + tsSubCmd.addProperty("entityId", deviceId.toString()); + tsSubCmd.addProperty("scope", "LATEST_TELEMETRY"); + tsSubCmd.addProperty("cmdId", new Random().nextInt(100)); + tsSubCmd.addProperty("unsubscribe", false); + JsonArray wsTsSubCmds = new JsonArray(); + wsTsSubCmds.add(tsSubCmd); + JsonObject wsRequest = new JsonObject(); + wsRequest.add("tsSubCmds", wsTsSubCmds); + wsRequest.add("historyCmds", new JsonArray()); + wsRequest.add("attrSubCmds", new JsonArray()); + mWs.send(wsRequest.toString()); + return mWs; + } + + protected Map getExpectedLatestValues(long ts) { + return ImmutableMap.builder() + .put("booleanKey", ts) + .put("stringKey", ts) + .put("doubleKey", ts) + .put("longKey", ts) + .build(); + } + + protected boolean verify(WsTelemetryResponse wsTelemetryResponse, String key, Long expectedTs, String expectedValue) { + List list = wsTelemetryResponse.getDataValuesByKey(key); + return expectedTs.equals(list.get(0)) && expectedValue.equals(list.get(1)); + } + + protected boolean verify(WsTelemetryResponse wsTelemetryResponse, String key, String expectedValue) { + List list = wsTelemetryResponse.getDataValuesByKey(key); + return expectedValue.equals(list.get(1)); + } + + protected JsonObject createPayload(long ts) { + JsonObject values = createPayload(); + JsonObject payload = new JsonObject(); + payload.addProperty("ts", ts); + payload.add("values", values); + return payload; + } + + protected JsonObject createPayload() { + JsonObject values = new JsonObject(); + values.addProperty("stringKey", "value1"); + values.addProperty("booleanKey", true); + values.addProperty("doubleKey", 42.0); + values.addProperty("longKey", 73L); + + return values; + } + +} diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java new file mode 100644 index 0000000000..fd2de229d0 --- /dev/null +++ b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java @@ -0,0 +1,37 @@ +/** + * 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.msa; + +import org.junit.ClassRule; +import org.junit.extensions.cpsuite.ClasspathSuite; +import org.junit.runner.RunWith; +import org.testcontainers.containers.DockerComposeContainer; +import org.testcontainers.containers.wait.strategy.Wait; + +import java.io.File; + +@RunWith(ClasspathSuite.class) +@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*"}) +public class ContainerTestSuite { + static final int EXPOSED_PORT = 8080; + + @ClassRule + public static DockerComposeContainer composeContainer = new DockerComposeContainer(new File("./../docker/docker-compose.yml")) + .withPull(false) + .withLocalCompose(true) + .withTailChildContainers(true) + .withExposedService("tb-web-ui1", EXPOSED_PORT, Wait.forHttp("/login")); +} diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/WsClient.java b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/WsClient.java new file mode 100644 index 0000000000..2f05eee4ce --- /dev/null +++ b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/WsClient.java @@ -0,0 +1,57 @@ +/** + * 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.msa; + +import org.java_websocket.client.WebSocketClient; +import org.java_websocket.handshake.ServerHandshake; + +import java.net.URI; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +public class WsClient extends WebSocketClient { + private final BlockingQueue events; + private String message; + + public WsClient(URI serverUri) { + super(serverUri); + events = new ArrayBlockingQueue<>(100); + } + + @Override + public void onOpen(ServerHandshake serverHandshake) { + } + + @Override + public void onMessage(String message) { + events.add(message); + this.message = message; + } + + @Override + public void onClose(int code, String reason, boolean remote) { + events.clear(); + } + + @Override + public void onError(Exception ex) { + ex.printStackTrace(); + } + + public String getLastMessage() { + return this.message; + } +} \ No newline at end of file diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/WsTelemetryResponse.java b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/WsTelemetryResponse.java new file mode 100644 index 0000000000..834c5d1449 --- /dev/null +++ b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/WsTelemetryResponse.java @@ -0,0 +1,40 @@ +/** + * 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.msa; + +import lombok.Data; + +import java.io.Serializable; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Data +public class WsTelemetryResponse implements Serializable { + private int subscriptionId; + private int errorCode; + private String errorMsg; + private Map>> data; + private Map latestValues; + + public List getDataValuesByKey(String key) { + return data.entrySet().stream() + .filter(e -> e.getKey().equals(key)) + .flatMap(e -> e.getValue().stream().flatMap(Collection::stream)) + .collect(Collectors.toList()); + } +} diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java new file mode 100644 index 0000000000..7cc0a0f6e3 --- /dev/null +++ b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java @@ -0,0 +1,57 @@ +/** + * 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.msa.connectivity; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.http.ResponseEntity; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.msa.AbstractContainerTest; +import org.thingsboard.server.msa.WsClient; +import org.thingsboard.server.msa.WsTelemetryResponse; + +import java.util.concurrent.TimeUnit; + +public class HttpClientTest extends AbstractContainerTest { + + @Test + public void telemetryUpdate() throws Exception { + restClient.login("tenant@thingsboard.org", "tenant"); + + Device device = createDevice("http_"); + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); + + WsClient mWs = subscribeToTelemetryWebSocket(device.getId()); + ResponseEntity deviceTelemetryResponse = restClient.getRestTemplate() + .postForEntity(httpUrl + "/api/v1/{credentialsId}/telemetry", + mapper.readTree(createPayload().toString()), + ResponseEntity.class, + deviceCredentials.getCredentialsId()); + Assert.assertTrue(deviceTelemetryResponse.getStatusCode().is2xxSuccessful()); + TimeUnit.SECONDS.sleep(1); + WsTelemetryResponse actualLatestTelemetry = mapper.readValue(mWs.getLastMessage(), WsTelemetryResponse.class); + + Assert.assertEquals(getExpectedLatestValues(123456789L).keySet(), actualLatestTelemetry.getLatestValues().keySet()); + + Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", Boolean.TRUE.toString())); + Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", "value1")); + Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", Double.toString(42.0))); + Assert.assertTrue(verify(actualLatestTelemetry, "longKey", Long.toString(73))); + + restClient.getRestTemplate().delete(httpUrl + "/api/device/" + device.getId()); + } +} diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java new file mode 100644 index 0000000000..4ad638ed15 --- /dev/null +++ b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java @@ -0,0 +1,111 @@ +/** + * 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.msa.connectivity; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import lombok.Data; +import org.junit.*; +import org.thingsboard.mqtt.MqttClient; +import org.thingsboard.mqtt.MqttClientConfig; +import org.thingsboard.mqtt.MqttHandler; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.msa.AbstractContainerTest; +import org.thingsboard.server.msa.WsClient; +import org.thingsboard.server.msa.WsTelemetryResponse; + +import java.nio.charset.StandardCharsets; +import java.util.concurrent.*; + +public class MqttClientTest extends AbstractContainerTest { + + @Test + public void telemetryUpload() throws Exception { + restClient.login("tenant@thingsboard.org", "tenant"); + Device device = createDevice("mqtt_"); + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); + + WsClient mWs = subscribeToTelemetryWebSocket(device.getId()); + MqttClient mqttClient = getMqttClient(deviceCredentials); + mqttClient.publish("v1/devices/me/telemetry", Unpooled.wrappedBuffer(createPayload().toString().getBytes())); + TimeUnit.SECONDS.sleep(1); + WsTelemetryResponse actualLatestTelemetry = mapper.readValue(mWs.getLastMessage(), WsTelemetryResponse.class); + + Assert.assertEquals(getExpectedLatestValues(123456789L).keySet(), actualLatestTelemetry.getLatestValues().keySet()); + + Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", Boolean.TRUE.toString())); + Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", "value1")); + Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", Double.toString(42.0))); + Assert.assertTrue(verify(actualLatestTelemetry, "longKey", Long.toString(73))); + + restClient.getRestTemplate().delete(httpUrl + "/api/device/" + device.getId()); + } + + @Test + public void telemetryUploadWithTs() throws Exception { + long ts = 1451649600512L; + + restClient.login("tenant@thingsboard.org", "tenant"); + Device device = createDevice("mqtt_"); + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); + + WsClient mWs = subscribeToTelemetryWebSocket(device.getId()); + MqttClient mqttClient = getMqttClient(deviceCredentials); + mqttClient.publish("v1/devices/me/telemetry", Unpooled.wrappedBuffer(createPayload(ts).toString().getBytes())); + TimeUnit.SECONDS.sleep(1); + WsTelemetryResponse actualLatestTelemetry = mapper.readValue(mWs.getLastMessage(), WsTelemetryResponse.class); + + Assert.assertEquals(getExpectedLatestValues(ts), actualLatestTelemetry.getLatestValues()); + + Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", ts, Boolean.TRUE.toString())); + Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", ts, "value1")); + Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", ts, Double.toString(42.0))); + Assert.assertTrue(verify(actualLatestTelemetry, "longKey", ts, Long.toString(73))); + + restClient.getRestTemplate().delete(httpUrl + "/api/device/" + device.getId()); + } + + private MqttClient getMqttClient(DeviceCredentials deviceCredentials) throws InterruptedException { + MqttMessageListener queue = new MqttMessageListener(); + MqttClientConfig clientConfig = new MqttClientConfig(); + clientConfig.setClientId("MQTT client from test"); + clientConfig.setUsername(deviceCredentials.getCredentialsId()); + MqttClient mqttClient = MqttClient.create(clientConfig, queue); + mqttClient.connect("localhost", 1883).sync(); + return mqttClient; + } + + @Data + private class MqttMessageListener implements MqttHandler { + private final BlockingQueue events; + + private MqttMessageListener() { + events = new ArrayBlockingQueue<>(100); + } + + @Override + public void onMessage(String topic, ByteBuf message) { + events.add(new MqttEvent(topic, message.toString(StandardCharsets.UTF_8))); + } + } + + @Data + private class MqttEvent { + private final String topic; + private final String message; + } +} diff --git a/msa/pom.xml b/msa/pom.xml index 21ddb4dafe..dd4c3653ba 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -40,6 +40,7 @@ js-executor web-ui tb-node + integration-tests From fb098e1bae920b89358596ee274f4fd960673952 Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Tue, 23 Oct 2018 16:41:43 +0300 Subject: [PATCH 04/35] Cover MQTT API with black box tests --- msa/integration-tests/README.md | 2 +- msa/integration-tests/pom.xml | 10 +- .../server/msa/AbstractContainerTest.java | 113 +++++-- .../server/msa/ContainerTestSuite.java | 3 +- .../org/thingsboard/server/msa/WsClient.java | 8 +- .../msa/connectivity/HttpClientTest.java | 14 +- .../msa/connectivity/MqttClientTest.java | 304 +++++++++++++++++- .../server/msa/mapper/AttributesResponse.java | 26 ++ .../msa/{ => mapper}/WsTelemetryResponse.java | 2 +- .../RpcResponseRuleChainMetadata.json | 59 ++++ 10 files changed, 484 insertions(+), 57 deletions(-) create mode 100644 msa/integration-tests/src/test/java/org/thingsboard/server/msa/mapper/AttributesResponse.java rename msa/integration-tests/src/test/java/org/thingsboard/server/msa/{ => mapper}/WsTelemetryResponse.java (96%) create mode 100644 msa/integration-tests/src/test/resources/RpcResponseRuleChainMetadata.json diff --git a/msa/integration-tests/README.md b/msa/integration-tests/README.md index 5ae6354740..93df2bf77f 100644 --- a/msa/integration-tests/README.md +++ b/msa/integration-tests/README.md @@ -15,4 +15,4 @@ As result, in REPOSITORY column, next images should be present: - Run the integration tests in the [msa/integration-tests](../integration-tests) directory: - mvn clean install -Dintegrationtests.skip=false \ No newline at end of file + mvn clean install -DintegrationTests.skip=false \ No newline at end of file diff --git a/msa/integration-tests/pom.xml b/msa/integration-tests/pom.xml index a1c24f39c6..a9c67ccf93 100644 --- a/msa/integration-tests/pom.xml +++ b/msa/integration-tests/pom.xml @@ -34,9 +34,10 @@ UTF-8 ${basedir}/../.. - true + true 1.9.1 1.3.9 + 4.5.6 @@ -50,6 +51,11 @@ Java-WebSocket ${java-websocket.version} + + org.apache.httpcomponents + httpclient + ${httpclient.version} + io.takari.junit takari-cpsuite @@ -89,7 +95,7 @@ **/*TestSuite.java - ${integrationtests.skip} + ${integrationTests.skip} diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java index ab7f101f98..5ebab780be 100644 --- a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java +++ b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java @@ -21,55 +21,68 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustStrategy; +import org.apache.http.conn.ssl.X509HostnameVerifier; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.ssl.SSLContextBuilder; +import org.apache.http.ssl.SSLContexts; import org.junit.*; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.thingsboard.client.tools.RestClient; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.msa.mapper.WsTelemetryResponse; +import javax.net.ssl.*; import java.net.URI; -import java.net.URISyntaxException; +import java.security.cert.X509Certificate; import java.util.List; import java.util.Map; import java.util.Random; -import java.util.concurrent.TimeUnit; @Slf4j public abstract class AbstractContainerTest { - protected static String httpUrl; - protected static String wsUrl; + protected static final String HTTPS_URL = "https://localhost"; + protected static final String WSS_URL = "wss://localhost"; protected static RestClient restClient; protected ObjectMapper mapper = new ObjectMapper(); @BeforeClass - public static void before() { - httpUrl = "http://localhost:" + ContainerTestSuite.composeContainer.getServicePort("tb-web-ui1", ContainerTestSuite.EXPOSED_PORT); - wsUrl = "ws://localhost:" + ContainerTestSuite.composeContainer.getServicePort("tb-web-ui1", ContainerTestSuite.EXPOSED_PORT); - restClient = new RestClient(httpUrl); + public static void before() throws Exception { + restClient = new RestClient(HTTPS_URL); + restClient.getRestTemplate().setRequestFactory(getRequestFactoryForSelfSignedCert()); } protected Device createDevice(String name) { return restClient.createDevice(name + RandomStringUtils.randomAlphanumeric(7), "DEFAULT"); } - protected WsClient subscribeToTelemetryWebSocket(DeviceId deviceId) throws URISyntaxException, InterruptedException { - WsClient mWs = new WsClient(new URI(wsUrl + "/api/ws/plugins/telemetry?token=" + restClient.getToken())); - mWs.connectBlocking(1, TimeUnit.SECONDS); - - JsonObject tsSubCmd = new JsonObject(); - tsSubCmd.addProperty("entityType", EntityType.DEVICE.name()); - tsSubCmd.addProperty("entityId", deviceId.toString()); - tsSubCmd.addProperty("scope", "LATEST_TELEMETRY"); - tsSubCmd.addProperty("cmdId", new Random().nextInt(100)); - tsSubCmd.addProperty("unsubscribe", false); - JsonArray wsTsSubCmds = new JsonArray(); - wsTsSubCmds.add(tsSubCmd); + protected WsClient subscribeToWebSocket(DeviceId deviceId, String scope, CmdsType property) throws Exception { + WsClient wsClient = new WsClient(new URI(WSS_URL + "/api/ws/plugins/telemetry?token=" + restClient.getToken())); + SSLContextBuilder builder = SSLContexts.custom(); + builder.loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true); + wsClient.setSocket(builder.build().getSocketFactory().createSocket()); + wsClient.connectBlocking(); + + JsonObject cmdsObject = new JsonObject(); + cmdsObject.addProperty("entityType", EntityType.DEVICE.name()); + cmdsObject.addProperty("entityId", deviceId.toString()); + cmdsObject.addProperty("scope", scope); + cmdsObject.addProperty("cmdId", new Random().nextInt(100)); + + JsonArray cmd = new JsonArray(); + cmd.add(cmdsObject); JsonObject wsRequest = new JsonObject(); - wsRequest.add("tsSubCmds", wsTsSubCmds); - wsRequest.add("historyCmds", new JsonArray()); - wsRequest.add("attrSubCmds", new JsonArray()); - mWs.send(wsRequest.toString()); - return mWs; + wsRequest.add(property.toString(), cmd); + wsClient.send(wsRequest.toString()); + return wsClient; } protected Map getExpectedLatestValues(long ts) { @@ -109,4 +122,54 @@ public abstract class AbstractContainerTest { return values; } + protected enum CmdsType { + TS_SUB_CMDS("tsSubCmds"), + HISTORY_CMDS("historyCmds"), + ATTR_SUB_CMDS("attrSubCmds"); + + private final String text; + + CmdsType(final String text) { + this.text = text; + } + + @Override + public String toString() { + return text; + } + } + + private static HttpComponentsClientHttpRequestFactory getRequestFactoryForSelfSignedCert() throws Exception { + SSLContextBuilder builder = SSLContexts.custom(); + builder.loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true); + SSLContext sslContext = builder.build(); + SSLConnectionSocketFactory sslSelfSigned = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() { + @Override + public void verify(String host, SSLSocket ssl) { + } + + @Override + public void verify(String host, X509Certificate cert) { + } + + @Override + public void verify(String host, String[] cns, String[] subjectAlts) { + } + + @Override + public boolean verify(String s, SSLSession sslSession) { + return true; + } + }); + + Registry socketFactoryRegistry = RegistryBuilder + .create() + .register("https", sslSelfSigned) + .build(); + + PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry); + CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build(); + return new HttpComponentsClientHttpRequestFactory(httpClient); + } + } diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java index fd2de229d0..0629960b24 100644 --- a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java +++ b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java @@ -26,12 +26,11 @@ import java.io.File; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*"}) public class ContainerTestSuite { - static final int EXPOSED_PORT = 8080; @ClassRule public static DockerComposeContainer composeContainer = new DockerComposeContainer(new File("./../docker/docker-compose.yml")) .withPull(false) .withLocalCompose(true) .withTailChildContainers(true) - .withExposedService("tb-web-ui1", EXPOSED_PORT, Wait.forHttp("/login")); + .withExposedService("tb-web-ui1", 8080, Wait.forHttp("/login")); } diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/WsClient.java b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/WsClient.java index 2f05eee4ce..5ef238f8aa 100644 --- a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/WsClient.java +++ b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/WsClient.java @@ -19,16 +19,12 @@ import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; import java.net.URI; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; public class WsClient extends WebSocketClient { - private final BlockingQueue events; private String message; public WsClient(URI serverUri) { super(serverUri); - events = new ArrayBlockingQueue<>(100); } @Override @@ -37,13 +33,11 @@ public class WsClient extends WebSocketClient { @Override public void onMessage(String message) { - events.add(message); this.message = message; } @Override public void onClose(int code, String reason, boolean remote) { - events.clear(); } @Override @@ -54,4 +48,4 @@ public class WsClient extends WebSocketClient { public String getLastMessage() { return this.message; } -} \ No newline at end of file +} diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java index 7cc0a0f6e3..f8e1041252 100644 --- a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java +++ b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.msa.connectivity; +import com.google.common.collect.Sets; import org.junit.Assert; import org.junit.Test; import org.springframework.http.ResponseEntity; @@ -22,7 +23,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.msa.AbstractContainerTest; import org.thingsboard.server.msa.WsClient; -import org.thingsboard.server.msa.WsTelemetryResponse; +import org.thingsboard.server.msa.mapper.WsTelemetryResponse; import java.util.concurrent.TimeUnit; @@ -35,23 +36,24 @@ public class HttpClientTest extends AbstractContainerTest { Device device = createDevice("http_"); DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); - WsClient mWs = subscribeToTelemetryWebSocket(device.getId()); + WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); ResponseEntity deviceTelemetryResponse = restClient.getRestTemplate() - .postForEntity(httpUrl + "/api/v1/{credentialsId}/telemetry", + .postForEntity(HTTPS_URL + "/api/v1/{credentialsId}/telemetry", mapper.readTree(createPayload().toString()), ResponseEntity.class, deviceCredentials.getCredentialsId()); Assert.assertTrue(deviceTelemetryResponse.getStatusCode().is2xxSuccessful()); TimeUnit.SECONDS.sleep(1); - WsTelemetryResponse actualLatestTelemetry = mapper.readValue(mWs.getLastMessage(), WsTelemetryResponse.class); + WsTelemetryResponse actualLatestTelemetry = mapper.readValue(wsClient.getLastMessage(), WsTelemetryResponse.class); - Assert.assertEquals(getExpectedLatestValues(123456789L).keySet(), actualLatestTelemetry.getLatestValues().keySet()); + Assert.assertEquals(Sets.newHashSet("booleanKey", "stringKey", "doubleKey", "longKey"), + actualLatestTelemetry.getLatestValues().keySet()); Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", Boolean.TRUE.toString())); Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", "value1")); Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", Double.toString(42.0))); Assert.assertTrue(verify(actualLatestTelemetry, "longKey", Long.toString(73))); - restClient.getRestTemplate().delete(httpUrl + "/api/device/" + device.getId()); + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); } } diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java index 4ad638ed15..eae98b9f1e 100644 --- a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java +++ b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java @@ -15,20 +15,39 @@ */ package org.thingsboard.server.msa.connectivity; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.gson.JsonObject; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import lombok.Data; +import org.apache.commons.lang3.RandomStringUtils; import org.junit.*; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; import org.thingsboard.mqtt.MqttClient; import org.thingsboard.mqtt.MqttClientConfig; import org.thingsboard.mqtt.MqttHandler; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.rule.NodeConnectionInfo; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.msa.AbstractContainerTest; import org.thingsboard.server.msa.WsClient; -import org.thingsboard.server.msa.WsTelemetryResponse; +import org.thingsboard.server.msa.mapper.AttributesResponse; +import org.thingsboard.server.msa.mapper.WsTelemetryResponse; +import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.*; import java.util.concurrent.*; public class MqttClientTest extends AbstractContainerTest { @@ -39,20 +58,22 @@ public class MqttClientTest extends AbstractContainerTest { Device device = createDevice("mqtt_"); DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); - WsClient mWs = subscribeToTelemetryWebSocket(device.getId()); - MqttClient mqttClient = getMqttClient(deviceCredentials); + WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); + MqttClient mqttClient = getMqttClient(deviceCredentials, null); mqttClient.publish("v1/devices/me/telemetry", Unpooled.wrappedBuffer(createPayload().toString().getBytes())); TimeUnit.SECONDS.sleep(1); - WsTelemetryResponse actualLatestTelemetry = mapper.readValue(mWs.getLastMessage(), WsTelemetryResponse.class); + WsTelemetryResponse actualLatestTelemetry = mapper.readValue(wsClient.getLastMessage(), WsTelemetryResponse.class); - Assert.assertEquals(getExpectedLatestValues(123456789L).keySet(), actualLatestTelemetry.getLatestValues().keySet()); + Assert.assertEquals(4, actualLatestTelemetry.getData().size()); + Assert.assertEquals(Sets.newHashSet("booleanKey", "stringKey", "doubleKey", "longKey"), + actualLatestTelemetry.getLatestValues().keySet()); Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", Boolean.TRUE.toString())); Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", "value1")); Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", Double.toString(42.0))); Assert.assertTrue(verify(actualLatestTelemetry, "longKey", Long.toString(73))); - restClient.getRestTemplate().delete(httpUrl + "/api/device/" + device.getId()); + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); } @Test @@ -63,12 +84,13 @@ public class MqttClientTest extends AbstractContainerTest { Device device = createDevice("mqtt_"); DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); - WsClient mWs = subscribeToTelemetryWebSocket(device.getId()); - MqttClient mqttClient = getMqttClient(deviceCredentials); + WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); + MqttClient mqttClient = getMqttClient(deviceCredentials, null); mqttClient.publish("v1/devices/me/telemetry", Unpooled.wrappedBuffer(createPayload(ts).toString().getBytes())); TimeUnit.SECONDS.sleep(1); - WsTelemetryResponse actualLatestTelemetry = mapper.readValue(mWs.getLastMessage(), WsTelemetryResponse.class); + WsTelemetryResponse actualLatestTelemetry = mapper.readValue(wsClient.getLastMessage(), WsTelemetryResponse.class); + Assert.assertEquals(4, actualLatestTelemetry.getData().size()); Assert.assertEquals(getExpectedLatestValues(ts), actualLatestTelemetry.getLatestValues()); Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", ts, Boolean.TRUE.toString())); @@ -76,15 +98,271 @@ public class MqttClientTest extends AbstractContainerTest { Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", ts, Double.toString(42.0))); Assert.assertTrue(verify(actualLatestTelemetry, "longKey", ts, Long.toString(73))); - restClient.getRestTemplate().delete(httpUrl + "/api/device/" + device.getId()); + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); } - private MqttClient getMqttClient(DeviceCredentials deviceCredentials) throws InterruptedException { - MqttMessageListener queue = new MqttMessageListener(); + @Test + public void publishAttributeUpdateToServer() throws Exception { + restClient.login("tenant@thingsboard.org", "tenant"); + Device device = createDevice("mqtt_"); + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); + + WsClient wsClient = subscribeToWebSocket(device.getId(), "CLIENT_SCOPE", CmdsType.ATTR_SUB_CMDS); + MqttMessageListener listener = new MqttMessageListener(); + MqttClient mqttClient = getMqttClient(deviceCredentials, listener); + JsonObject clientAttributes = new JsonObject(); + clientAttributes.addProperty("attr1", "value1"); + clientAttributes.addProperty("attr2", true); + clientAttributes.addProperty("attr3", 42.0); + clientAttributes.addProperty("attr4", 73); + mqttClient.publish("v1/devices/me/attributes", Unpooled.wrappedBuffer(clientAttributes.toString().getBytes())); + TimeUnit.SECONDS.sleep(1); + WsTelemetryResponse actualLatestTelemetry = mapper.readValue(wsClient.getLastMessage(), WsTelemetryResponse.class); + + Assert.assertEquals(4, actualLatestTelemetry.getData().size()); + Assert.assertEquals(Sets.newHashSet("attr1", "attr2", "attr3", "attr4"), + actualLatestTelemetry.getLatestValues().keySet()); + + Assert.assertTrue(verify(actualLatestTelemetry, "attr1", "value1")); + Assert.assertTrue(verify(actualLatestTelemetry, "attr2", Boolean.TRUE.toString())); + Assert.assertTrue(verify(actualLatestTelemetry, "attr3", Double.toString(42.0))); + Assert.assertTrue(verify(actualLatestTelemetry, "attr4", Long.toString(73))); + + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); + } + + @Test + public void requestAttributeValuesFromServer() throws Exception { + restClient.login("tenant@thingsboard.org", "tenant"); + Device device = createDevice("mqtt_"); + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); + + MqttMessageListener listener = new MqttMessageListener(); + MqttClient mqttClient = getMqttClient(deviceCredentials, listener); + + // Add a new client attribute + JsonObject clientAttributes = new JsonObject(); + String clientAttributeValue = RandomStringUtils.randomAlphanumeric(8); + clientAttributes.addProperty("clientAttr", clientAttributeValue); + mqttClient.publish("v1/devices/me/attributes", Unpooled.wrappedBuffer(clientAttributes.toString().getBytes())); + + // Add a new shared attribute + JsonObject sharedAttributes = new JsonObject(); + String sharedAttributeValue = RandomStringUtils.randomAlphanumeric(8); + sharedAttributes.addProperty("sharedAttr", sharedAttributeValue); + ResponseEntity sharedAttributesResponse = restClient.getRestTemplate() + .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", + mapper.readTree(sharedAttributes.toString()), ResponseEntity.class, + device.getId()); + Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful()); + + // Subscribe to attributes response + mqttClient.on("v1/devices/me/attributes/response/+", listener); + // Request attributes + JsonObject request = new JsonObject(); + request.addProperty("clientKeys", "clientAttr"); + request.addProperty("sharedKeys", "sharedAttr"); + mqttClient.publish("v1/devices/me/attributes/request/" + new Random().nextInt(100), Unpooled.wrappedBuffer(request.toString().getBytes())); + MqttEvent event = listener.getEvents().poll(10, TimeUnit.SECONDS); + AttributesResponse attributes = mapper.readValue(Objects.requireNonNull(event).getMessage(), AttributesResponse.class); + + Assert.assertEquals(1, attributes.getClient().size()); + Assert.assertEquals(clientAttributeValue, attributes.getClient().get("clientAttr")); + + Assert.assertEquals(1, attributes.getShared().size()); + Assert.assertEquals(sharedAttributeValue, attributes.getShared().get("sharedAttr")); + + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); + } + + @Test + public void subscribeToAttributeUpdatesFromServer() throws Exception { + restClient.login("tenant@thingsboard.org", "tenant"); + Device device = createDevice("mqtt_"); + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); + + MqttMessageListener listener = new MqttMessageListener(); + MqttClient mqttClient = getMqttClient(deviceCredentials, listener); + mqttClient.on("v1/devices/me/attributes", listener); + + String sharedAttributeName = "sharedAttr"; + + // Add a new shared attribute + JsonObject sharedAttributes = new JsonObject(); + String sharedAttributeValue = RandomStringUtils.randomAlphanumeric(8); + sharedAttributes.addProperty(sharedAttributeName, sharedAttributeValue); + ResponseEntity sharedAttributesResponse = restClient.getRestTemplate() + .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", + mapper.readTree(sharedAttributes.toString()), ResponseEntity.class, + device.getId()); + Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful()); + + MqttEvent event = listener.getEvents().poll(10, TimeUnit.SECONDS); + Assert.assertEquals(sharedAttributeValue, + mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get(sharedAttributeName).asText()); + + // Update the shared attribute value + JsonObject updatedSharedAttributes = new JsonObject(); + String updatedSharedAttributeValue = RandomStringUtils.randomAlphanumeric(8); + updatedSharedAttributes.addProperty(sharedAttributeName, updatedSharedAttributeValue); + ResponseEntity updatedSharedAttributesResponse = restClient.getRestTemplate() + .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", + mapper.readTree(updatedSharedAttributes.toString()), ResponseEntity.class, + device.getId()); + Assert.assertTrue(updatedSharedAttributesResponse.getStatusCode().is2xxSuccessful()); + + event = listener.getEvents().poll(10, TimeUnit.SECONDS); + Assert.assertEquals(updatedSharedAttributeValue, + mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get(sharedAttributeName).asText()); + + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); + } + + @Test + public void serverSideRpc() throws Exception { + restClient.login("tenant@thingsboard.org", "tenant"); + Device device = createDevice("mqtt_"); + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); + + MqttMessageListener listener = new MqttMessageListener(); + MqttClient mqttClient = getMqttClient(deviceCredentials, listener); + mqttClient.on("v1/devices/me/rpc/request/+", listener); + + // Send an RPC from the server + JsonObject serverRpcPayload = new JsonObject(); + serverRpcPayload.addProperty("method", "getValue"); + serverRpcPayload.addProperty("params", true); + serverRpcPayload.addProperty("timeout", 1000); + ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); + ListenableFuture future = service.submit(() -> { + try { + return restClient.getRestTemplate() + .postForEntity(HTTPS_URL + "/api/plugins/rpc/twoway/{deviceId}", + mapper.readTree(serverRpcPayload.toString()), String.class, + device.getId()); + } catch (IOException e) { + return ResponseEntity.badRequest().build(); + } + }); + + // Wait for RPC call from the server and send the response + MqttEvent requestFromServer = listener.getEvents().poll(10, TimeUnit.SECONDS); + + Assert.assertEquals("{\"method\":\"getValue\",\"params\":true}", Objects.requireNonNull(requestFromServer).getMessage()); + + Integer requestId = Integer.valueOf(Objects.requireNonNull(requestFromServer).getTopic().substring("v1/devices/me/rpc/request/".length())); + JsonObject clientResponse = new JsonObject(); + clientResponse.addProperty("response", "someResponse"); + // Send a response to the server's RPC request + mqttClient.publish("v1/devices/me/rpc/response/" + requestId, Unpooled.wrappedBuffer(clientResponse.toString().getBytes())); + + ResponseEntity serverResponse = future.get(5, TimeUnit.SECONDS); + Assert.assertTrue(serverResponse.getStatusCode().is2xxSuccessful()); + Assert.assertEquals(clientResponse.toString(), serverResponse.getBody()); + + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); + } + + @Test + public void clientSideRpc() throws Exception { + restClient.login("tenant@thingsboard.org", "tenant"); + Device device = createDevice("mqtt_"); + DeviceCredentials deviceCredentials = restClient.getCredentials(device.getId()); + + MqttMessageListener listener = new MqttMessageListener(); + MqttClient mqttClient = getMqttClient(deviceCredentials, listener); + mqttClient.on("v1/devices/me/rpc/request/+", listener); + + // Get the default rule chain id to make it root again after test finished + RuleChainId defaultRuleChainId = getDefaultRuleChainId(); + + // Create a new root rule chain + RuleChainId ruleChainId = createRootRuleChainForRpcResponse(); + + // Send the request to the server + JsonObject clientRequest = new JsonObject(); + clientRequest.addProperty("method", "getResponse"); + clientRequest.addProperty("params", true); + Integer requestId = 42; + mqttClient.publish("v1/devices/me/rpc/request/" + requestId, Unpooled.wrappedBuffer(clientRequest.toString().getBytes())); + + // Check the response from the server + TimeUnit.SECONDS.sleep(1); + MqttEvent responseFromServer = listener.getEvents().poll(1, TimeUnit.SECONDS); + Integer responseId = Integer.valueOf(Objects.requireNonNull(responseFromServer).getTopic().substring("v1/devices/me/rpc/response/".length())); + Assert.assertEquals(requestId, responseId); + Assert.assertEquals("requestReceived", mapper.readTree(responseFromServer.getMessage()).get("response").asText()); + + // Make the default rule chain a root again + ResponseEntity rootRuleChainResponse = restClient.getRestTemplate() + .postForEntity(HTTPS_URL + "/api/ruleChain/{ruleChainId}/root", + null, + RuleChain.class, + defaultRuleChainId); + Assert.assertTrue(rootRuleChainResponse.getStatusCode().is2xxSuccessful()); + + // Delete the created rule chain + restClient.getRestTemplate().delete(HTTPS_URL + "/api/ruleChain/{ruleChainId}", ruleChainId); + restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); + } + + private RuleChainId createRootRuleChainForRpcResponse() throws Exception { + RuleChain newRuleChain = new RuleChain(); + newRuleChain.setName("testRuleChain"); + ResponseEntity ruleChainResponse = restClient.getRestTemplate() + .postForEntity(HTTPS_URL + "/api/ruleChain", + newRuleChain, + RuleChain.class); + Assert.assertTrue(ruleChainResponse.getStatusCode().is2xxSuccessful()); + RuleChain ruleChain = ruleChainResponse.getBody(); + + JsonNode configuration = mapper.readTree(this.getClass().getClassLoader().getResourceAsStream("RpcResponseRuleChainMetadata.json")); + RuleChainMetaData ruleChainMetaData = new RuleChainMetaData(); + ruleChainMetaData.setRuleChainId(ruleChain.getId()); + ruleChainMetaData.setFirstNodeIndex(configuration.get("firstNodeIndex").asInt()); + ruleChainMetaData.setNodes(Arrays.asList(mapper.treeToValue(configuration.get("nodes"), RuleNode[].class))); + ruleChainMetaData.setConnections(Arrays.asList(mapper.treeToValue(configuration.get("connections"), NodeConnectionInfo[].class))); + + ResponseEntity ruleChainMetadataResponse = restClient.getRestTemplate() + .postForEntity(HTTPS_URL + "/api/ruleChain/metadata", + ruleChainMetaData, + RuleChainMetaData.class); + Assert.assertTrue(ruleChainMetadataResponse.getStatusCode().is2xxSuccessful()); + + // Set a new rule chain as root + ResponseEntity rootRuleChainResponse = restClient.getRestTemplate() + .postForEntity(HTTPS_URL + "/api/ruleChain/{ruleChainId}/root", + null, + RuleChain.class, + ruleChain.getId()); + Assert.assertTrue(rootRuleChainResponse.getStatusCode().is2xxSuccessful()); + + return ruleChain.getId(); + } + + private RuleChainId getDefaultRuleChainId() { + ResponseEntity> ruleChains = restClient.getRestTemplate().exchange( + HTTPS_URL + "/api/ruleChains?limit=40&textSearch=", + HttpMethod.GET, + null, + new ParameterizedTypeReference>() { + }); + + Optional defaultRuleChain = ruleChains.getBody().getData() + .stream() + .filter(RuleChain::isRoot) + .findFirst(); + if (!defaultRuleChain.isPresent()) { + Assert.fail("Root rule chain wasn't found"); + } + return defaultRuleChain.get().getId(); + } + + private MqttClient getMqttClient(DeviceCredentials deviceCredentials, MqttMessageListener listener) throws InterruptedException { MqttClientConfig clientConfig = new MqttClientConfig(); clientConfig.setClientId("MQTT client from test"); clientConfig.setUsername(deviceCredentials.getCredentialsId()); - MqttClient mqttClient = MqttClient.create(clientConfig, queue); + MqttClient mqttClient = MqttClient.create(clientConfig, listener); mqttClient.connect("localhost", 1883).sync(); return mqttClient; } diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/mapper/AttributesResponse.java b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/mapper/AttributesResponse.java new file mode 100644 index 0000000000..f9774eef20 --- /dev/null +++ b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/mapper/AttributesResponse.java @@ -0,0 +1,26 @@ +/** + * 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.msa.mapper; + +import lombok.Data; + +import java.util.Map; + +@Data +public class AttributesResponse { + private Map client; + private Map shared; +} diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/WsTelemetryResponse.java b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/mapper/WsTelemetryResponse.java similarity index 96% rename from msa/integration-tests/src/test/java/org/thingsboard/server/msa/WsTelemetryResponse.java rename to msa/integration-tests/src/test/java/org/thingsboard/server/msa/mapper/WsTelemetryResponse.java index 834c5d1449..b22244f3a7 100644 --- a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/WsTelemetryResponse.java +++ b/msa/integration-tests/src/test/java/org/thingsboard/server/msa/mapper/WsTelemetryResponse.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.msa; +package org.thingsboard.server.msa.mapper; import lombok.Data; diff --git a/msa/integration-tests/src/test/resources/RpcResponseRuleChainMetadata.json b/msa/integration-tests/src/test/resources/RpcResponseRuleChainMetadata.json new file mode 100644 index 0000000000..09178ef781 --- /dev/null +++ b/msa/integration-tests/src/test/resources/RpcResponseRuleChainMetadata.json @@ -0,0 +1,59 @@ +{ + "firstNodeIndex": 0, + "nodes": [ + { + "additionalInfo": { + "layoutX": 325, + "layoutY": 150 + }, + "type": "org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode", + "name": "msgTypeSwitch", + "debugMode": true, + "configuration": { + "version": 0 + } + }, + { + "additionalInfo": { + "layoutX": 60, + "layoutY": 300 + }, + "type": "org.thingsboard.rule.engine.transform.TbTransformMsgNode", + "name": "formResponse", + "debugMode": true, + "configuration": { + "jsScript": "if (msg.method == \"getResponse\") {\n return {msg: {\"response\": \"requestReceived\"}, metadata: metadata, msgType: msgType};\n}\n\nreturn {msg: msg, metadata: metadata, msgType: msgType};" + } + }, + { + "additionalInfo": { + "layoutX": 450, + "layoutY": 300 + }, + "type": "org.thingsboard.rule.engine.rpc.TbSendRPCReplyNode", + "name": "rpcReply", + "debugMode": true, + "configuration": { + "requestIdMetaDataAttribute": "requestId" + } + } + ], + "connections": [ + { + "fromIndex": 0, + "toIndex": 1, + "type": "RPC Request from Device" + }, + { + "fromIndex": 1, + "toIndex": 2, + "type": "Success" + }, + { + "fromIndex": 1, + "toIndex": 2, + "type": "Failure" + } + ], + "ruleChainConnections": null +} \ No newline at end of file From 7d14d46edf693a25a933cc57fe1829bbce090730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20M=C3=BCcahit=20=C3=87etinkaya?= Date: Fri, 26 Oct 2018 19:31:35 +0300 Subject: [PATCH 05/35] check and recover currentServer zNode on zk. --- .../cluster/discovery/ZkDiscoveryService.java | 48 +++++++++++++++---- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java index 6f51ceed26..c1876d6b1b 100644 --- a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java @@ -29,6 +29,7 @@ import org.apache.curator.framework.state.ConnectionStateListener; import org.apache.curator.retry.RetryForever; import org.apache.curator.utils.CloseableUtils; import org.apache.zookeeper.CreateMode; +import org.apache.zookeeper.KeeperException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -49,6 +50,8 @@ import java.util.NoSuchElementException; import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; +import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED; + /** * @author Andrew Shvayka */ @@ -121,19 +124,42 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi } @Override - public void publishCurrentServer() { + public synchronized void publishCurrentServer() { + ServerInstance self = this.serverInstance.getSelf(); + if (currentServerExists()) { + log.info("[{}:{}] ZK node for current instance already exists, NOT created new one: {}", self.getHost(), self.getPort(), nodePath); + } else { + try { + log.info("[{}:{}] Creating ZK node for current instance", self.getHost(), self.getPort()); + nodePath = client.create() + .creatingParentsIfNeeded() + .withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(zkNodesDir + "/", SerializationUtils.serialize(self.getServerAddress())); + log.info("[{}:{}] Created ZK node for current instance: {}", self.getHost(), self.getPort(), nodePath); + client.getConnectionStateListenable().addListener(checkReconnect(self)); + } catch (Exception e) { + log.error("Failed to create ZK node", e); + throw new RuntimeException(e); + } + } + } + + private boolean currentServerExists() { + if (nodePath == null) { + return false; + } try { ServerInstance self = this.serverInstance.getSelf(); - log.info("[{}:{}] Creating ZK node for current instance", self.getHost(), self.getPort()); - nodePath = client.create() - .creatingParentsIfNeeded() - .withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(zkNodesDir + "/", SerializationUtils.serialize(self.getServerAddress())); - log.info("[{}:{}] Created ZK node for current instance: {}", self.getHost(), self.getPort(), nodePath); - client.getConnectionStateListenable().addListener(checkReconnect(self)); + ServerAddress registeredServerAdress = null; + registeredServerAdress = SerializationUtils.deserialize(client.getData().forPath(nodePath)); + if (self.getServerAddress() != null && self.getServerAddress().equals(registeredServerAdress)) { + return true; + } + } catch (KeeperException.NoNodeException e) { + log.info("ZK node does not exist: {}", nodePath); } catch (Exception e) { - log.error("Failed to create ZK node", e); - throw new RuntimeException(e); + log.error("Couldn't check if ZK node exists", e); } + return false; } private ConnectionStateListener checkReconnect(ServerInstance self) { @@ -221,6 +247,10 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi log.debug("Ignoring {} due to empty child's data", pathChildrenCacheEvent); return; } else if (nodePath != null && nodePath.equals(data.getPath())) { + if (pathChildrenCacheEvent.getType() == CHILD_REMOVED) { + log.info("ZK node for current instance is somehow deleted."); + publishCurrentServer(); + } log.debug("Ignoring event about current server {}", pathChildrenCacheEvent); return; } From 093eafb462cab211107761c6fa54f1aba54835cb Mon Sep 17 00:00:00 2001 From: Dima Landiak Date: Mon, 29 Oct 2018 17:48:20 +0200 Subject: [PATCH 06/35] delete timeseries fixes and controller, entityView findByName added --- .../controller/EntityViewController.java | 20 +++-- .../controller/TelemetryController.java | 67 +++++++++++++++- .../server/common/data/audit/ActionType.java | 11 +-- .../dao/entityview/EntityViewService.java | 2 + .../dao/entityview/EntityViewServiceImpl.java | 19 +++-- .../dao/sql/timeseries/JpaTimeseriesDao.java | 79 +++++++++++++++++-- .../dao/sql/timeseries/TsKvRepository.java | 2 +- .../CassandraBaseTimeseriesDao.java | 46 +++++++---- .../timeseries/BaseTimeseriesServiceTest.java | 22 +++++- 9 files changed, 224 insertions(+), 44 deletions(-) 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/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/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/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/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()); From 2553c8c57b20f3eddd50bf5080fc738836a71689 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 31 Oct 2018 13:06:04 +0200 Subject: [PATCH 07/35] Revert "[WIP]Help refactor code solid patterns" --- .../client/tools/MqttSslClient.java | 106 +++++------------- 1 file changed, 29 insertions(+), 77 deletions(-) diff --git a/tools/src/main/java/org/thingsboard/client/tools/MqttSslClient.java b/tools/src/main/java/org/thingsboard/client/tools/MqttSslClient.java index 339f26ded8..dd27e44a1d 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/MqttSslClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/MqttSslClient.java @@ -50,86 +50,38 @@ public class MqttSslClient { File ksFile = new File(ksUrl.toURI()); URL tsUrl = Resources.getResource(KEY_STORE_FILE); File tsFile = new File(tsUrl.toURI()); - char[] ksPwd = setKeyPassword(); - - KeyStore trustStore = setKeyStore(tsFile, ksPwd); - - TrustManagerFactory tmf = setTrustManagerFactory(trustStore); - - KeyStore ks = setKeyStore2(ksFile, ksPwd); - - KeyManagerFactory kmf = setKeyManagerFactory(ks); - - SSLContext sslContext = setSSLContext(kmf, tmf); - - MqttConnectOptions mqttConnectOptions = setMqttConnectOptions(sslContext); - - MqttAsyncClient mqttAsyncClient = setMqttAsyncClient(mqttConnectOptions); - - setMessage(mqttAsyncClient, "v1/devices/me/telemetry", "{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4}"); + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + + KeyStore trustStore = KeyStore.getInstance(JKS); + char[] ksPwd = new char[]{0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x5F, 0x6B, 0x73, 0x5F, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64}; + trustStore.load(new FileInputStream(tsFile), ksPwd); + tmf.init(trustStore); + KeyStore ks = KeyStore.getInstance(JKS); + + ks.load(new FileInputStream(ksFile), ksPwd); + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + char[] clientPwd = new char[]{0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x5F, 0x6B, 0x65, 0x79, 0x5F, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64}; + kmf.init(ks, clientPwd); + + KeyManager[] km = kmf.getKeyManagers(); + TrustManager[] tm = tmf.getTrustManagers(); + SSLContext sslContext = SSLContext.getInstance(TLS); + sslContext.init(km, tm, null); + + MqttConnectOptions options = new MqttConnectOptions(); + options.setSocketFactory(sslContext.getSocketFactory()); + MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, CLIENT_ID); + client.connect(options); + Thread.sleep(3000); + MqttMessage message = new MqttMessage(); + message.setPayload("{\"key1\":\"value1\", \"key2\":true, \"key3\": 3.0, \"key4\": 4}".getBytes()); + client.publish("v1/devices/me/telemetry", message); + client.disconnect(); + log.info("Disconnected"); + System.exit(0); } catch (Exception e) { log.error("Unexpected exception occurred in MqttSslClient", e); } } - - private static char[] setKeyPassword(){ - return new char[]{0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x5F, 0x6B, 0x73, 0x5F, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64}; - } - - private static KeyStore setKeyStore(File tsFile, char[] ksPwd) throws Exception{ - KeyStore trustStore = KeyStore.getInstance(JKS); - trustStore.load(new FileInputStream(tsFile), ksPwd); - return trustStore; - } - - private static TrustManagerFactory setTrustManagerFactory(KeyStore trustStore) throws Exception { - TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); - tmf.init(trustStore); - return tmf; - } - - private static KeyStore setKeyStore2(File ksFile, char[] ksPwd)throws Exception{ - KeyStore ks = KeyStore.getInstance(JKS); - ks.load(new FileInputStream(ksFile), ksPwd); - return ks; - } - - private static KeyManagerFactory setKeyManagerFactory(KeyStore ks) throws Exception { - KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); - char[] clientPwd = new char[]{0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x5F, 0x6B, 0x65, 0x79, 0x5F, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64}; - kmf.init(ks, clientPwd); - return kmf; - } - - private static SSLContext setSSLContext(KeyManagerFactory kmf, TrustManagerFactory tmf) throws Exception { - KeyManager[] km = kmf.getKeyManagers(); - TrustManager[] tm = tmf.getTrustManagers(); - SSLContext sslContext = SSLContext.getInstance(TLS); - sslContext.init(km, tm, null); - return sslContext; - } - - private static MqttConnectOptions setMqttConnectOptions(SSLContext sslContext){ - MqttConnectOptions options = new MqttConnectOptions(); - options.setSocketFactory(sslContext.getSocketFactory()); - return options; - } - - private static MqttAsyncClient setMqttAsyncClient(MqttConnectOptions options) throws Exception{ - MqttAsyncClient client = new MqttAsyncClient(MQTT_URL, CLIENT_ID); - client.connect(options); - Thread.sleep(3000); - return client; - } - - private static void setMessage(MqttAsyncClient client, String topic, String payload) throws Exception { - MqttMessage message = new MqttMessage(); - message.setPayload(payload.getBytes()); - client.publish(topic, message); - client.disconnect(); - log.info("Disconnected"); - System.exit(0); - } - } \ No newline at end of file From 32dd7f0437053fdbb42227ff939dc9f2e7be17d1 Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Wed, 31 Oct 2018 13:07:43 +0200 Subject: [PATCH 08/35] Black box tests update --- msa/black-box-tests/README.md | 21 ++++++++++++ .../pom.xml | 10 +++--- .../server/msa/AbstractContainerTest.java | 0 .../server/msa/ContainerTestSuite.java | 9 +++-- .../org/thingsboard/server/msa/WsClient.java | 33 ++++++++++++++++--- .../msa/connectivity/HttpClientTest.java | 8 ++--- .../msa/connectivity/MqttClientTest.java | 25 +++++++------- .../server/msa/mapper/AttributesResponse.java | 0 .../msa/mapper/WsTelemetryResponse.java | 0 .../RpcResponseRuleChainMetadata.json | 0 msa/integration-tests/README.md | 18 ---------- msa/pom.xml | 4 +-- 12 files changed, 80 insertions(+), 48 deletions(-) create mode 100644 msa/black-box-tests/README.md rename msa/{integration-tests => black-box-tests}/pom.xml (92%) rename msa/{integration-tests => black-box-tests}/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java (100%) rename msa/{integration-tests => black-box-tests}/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java (80%) rename msa/{integration-tests => black-box-tests}/src/test/java/org/thingsboard/server/msa/WsClient.java (52%) rename msa/{integration-tests => black-box-tests}/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java (90%) rename msa/{integration-tests => black-box-tests}/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java (95%) rename msa/{integration-tests => black-box-tests}/src/test/java/org/thingsboard/server/msa/mapper/AttributesResponse.java (100%) rename msa/{integration-tests => black-box-tests}/src/test/java/org/thingsboard/server/msa/mapper/WsTelemetryResponse.java (100%) rename msa/{integration-tests => black-box-tests}/src/test/resources/RpcResponseRuleChainMetadata.json (100%) delete mode 100644 msa/integration-tests/README.md diff --git a/msa/black-box-tests/README.md b/msa/black-box-tests/README.md new file mode 100644 index 0000000000..c26d9c5fc6 --- /dev/null +++ b/msa/black-box-tests/README.md @@ -0,0 +1,21 @@ + +## Black box tests execution +To run the black box tests with using Docker, the local Docker images of Thingsboard's microservices should be built.
+- Build the local Docker images in the directory with the Thingsboard's main [pom.xml](./../../pom.xml): + + mvn clean install -Ddockerfile.skip=false +- Verify that the new local images were built: + + docker image ls +As result, in REPOSITORY column, next images should be present: + + thingsboard/tb-coap-transport + thingsboard/tb-http-transport + thingsboard/tb-mqtt-transport + thingsboard/tb-node + thingsboard/tb-web-ui + thingsboard/tb-js-executor + +- Run the black box tests in the [msa/black-box-tests](../black-box-tests) directory: + + mvn clean install -DblackBoxTests.skip=false \ No newline at end of file diff --git a/msa/integration-tests/pom.xml b/msa/black-box-tests/pom.xml similarity index 92% rename from msa/integration-tests/pom.xml rename to msa/black-box-tests/pom.xml index a9c67ccf93..2af4d2c14e 100644 --- a/msa/integration-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -25,16 +25,16 @@ msa org.thingsboard.msa - integration-tests + black-box-tests - ThingsBoard Integration Tests + ThingsBoard Black Box Tests https://thingsboard.io - Project for ThingsBoard integration tests with using Docker + Project for ThingsBoard black box testing with using Docker UTF-8 ${basedir}/../.. - true + true 1.9.1 1.3.9 4.5.6 @@ -95,7 +95,7 @@ **/*TestSuite.java - ${integrationTests.skip} + ${blackBoxTests.skip} diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java similarity index 100% rename from msa/integration-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java similarity index 80% rename from msa/integration-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java index 0629960b24..495fd94d2e 100644 --- a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java @@ -22,15 +22,18 @@ import org.testcontainers.containers.DockerComposeContainer; import org.testcontainers.containers.wait.strategy.Wait; import java.io.File; +import java.time.Duration; @RunWith(ClasspathSuite.class) -@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*"}) +@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*Test"}) public class ContainerTestSuite { @ClassRule - public static DockerComposeContainer composeContainer = new DockerComposeContainer(new File("./../docker/docker-compose.yml")) + public static DockerComposeContainer composeContainer = new DockerComposeContainer( + new File("./../../docker/docker-compose.yml"), + new File("./../../docker/docker-compose.postgres.yml")) .withPull(false) .withLocalCompose(true) .withTailChildContainers(true) - .withExposedService("tb-web-ui1", 8080, Wait.forHttp("/login")); + .withExposedService("tb-web-ui1", 8080, Wait.forHttp("/login").withStartupTimeout(Duration.ofSeconds(120))); } diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/WsClient.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/WsClient.java similarity index 52% rename from msa/integration-tests/src/test/java/org/thingsboard/server/msa/WsClient.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/WsClient.java index 5ef238f8aa..a9835ed618 100644 --- a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/WsClient.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/WsClient.java @@ -15,13 +15,23 @@ */ package org.thingsboard.server.msa; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; import org.java_websocket.client.WebSocketClient; import org.java_websocket.handshake.ServerHandshake; +import org.thingsboard.server.msa.mapper.WsTelemetryResponse; +import java.io.IOException; import java.net.URI; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +@Slf4j public class WsClient extends WebSocketClient { - private String message; + private static final ObjectMapper mapper = new ObjectMapper(); + private WsTelemetryResponse message; + + private CountDownLatch latch = new CountDownLatch(1);; public WsClient(URI serverUri) { super(serverUri); @@ -33,11 +43,20 @@ public class WsClient extends WebSocketClient { @Override public void onMessage(String message) { - this.message = message; + try { + WsTelemetryResponse response = mapper.readValue(message, WsTelemetryResponse.class); + if (!response.getData().isEmpty()) { + this.message = response; + latch.countDown(); + } + } catch (IOException e) { + log.error("ws message can't be read"); + } } @Override public void onClose(int code, String reason, boolean remote) { + log.info("ws is closed, due to [{}]", reason); } @Override @@ -45,7 +64,13 @@ public class WsClient extends WebSocketClient { ex.printStackTrace(); } - public String getLastMessage() { - return this.message; + public WsTelemetryResponse getLastMessage() { + try { + latch.await(10, TimeUnit.SECONDS); + return this.message; + } catch (InterruptedException e) { + log.error("Timeout, ws message wasn't received"); + } + return null; } } diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java similarity index 90% rename from msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java index f8e1041252..a6e89de185 100644 --- a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java @@ -25,12 +25,10 @@ import org.thingsboard.server.msa.AbstractContainerTest; import org.thingsboard.server.msa.WsClient; import org.thingsboard.server.msa.mapper.WsTelemetryResponse; -import java.util.concurrent.TimeUnit; - public class HttpClientTest extends AbstractContainerTest { @Test - public void telemetryUpdate() throws Exception { + public void telemetryUpload() throws Exception { restClient.login("tenant@thingsboard.org", "tenant"); Device device = createDevice("http_"); @@ -43,8 +41,8 @@ public class HttpClientTest extends AbstractContainerTest { ResponseEntity.class, deviceCredentials.getCredentialsId()); Assert.assertTrue(deviceTelemetryResponse.getStatusCode().is2xxSuccessful()); - TimeUnit.SECONDS.sleep(1); - WsTelemetryResponse actualLatestTelemetry = mapper.readValue(wsClient.getLastMessage(), WsTelemetryResponse.class); + WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); + wsClient.closeBlocking(); Assert.assertEquals(Sets.newHashSet("booleanKey", "stringKey", "doubleKey", "longKey"), actualLatestTelemetry.getLatestValues().keySet()); diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java similarity index 95% rename from msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java index eae98b9f1e..d889d2c52e 100644 --- a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java @@ -23,7 +23,9 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.gson.JsonObject; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.handler.codec.mqtt.MqttQoS; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.junit.*; import org.springframework.core.ParameterizedTypeReference; @@ -50,6 +52,7 @@ import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.*; +@Slf4j public class MqttClientTest extends AbstractContainerTest { @Test @@ -61,8 +64,8 @@ public class MqttClientTest extends AbstractContainerTest { WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); MqttClient mqttClient = getMqttClient(deviceCredentials, null); mqttClient.publish("v1/devices/me/telemetry", Unpooled.wrappedBuffer(createPayload().toString().getBytes())); - TimeUnit.SECONDS.sleep(1); - WsTelemetryResponse actualLatestTelemetry = mapper.readValue(wsClient.getLastMessage(), WsTelemetryResponse.class); + WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); + wsClient.closeBlocking(); Assert.assertEquals(4, actualLatestTelemetry.getData().size()); Assert.assertEquals(Sets.newHashSet("booleanKey", "stringKey", "doubleKey", "longKey"), @@ -87,8 +90,8 @@ public class MqttClientTest extends AbstractContainerTest { WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); MqttClient mqttClient = getMqttClient(deviceCredentials, null); mqttClient.publish("v1/devices/me/telemetry", Unpooled.wrappedBuffer(createPayload(ts).toString().getBytes())); - TimeUnit.SECONDS.sleep(1); - WsTelemetryResponse actualLatestTelemetry = mapper.readValue(wsClient.getLastMessage(), WsTelemetryResponse.class); + WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); + wsClient.closeBlocking(); Assert.assertEquals(4, actualLatestTelemetry.getData().size()); Assert.assertEquals(getExpectedLatestValues(ts), actualLatestTelemetry.getLatestValues()); @@ -116,8 +119,8 @@ public class MqttClientTest extends AbstractContainerTest { clientAttributes.addProperty("attr3", 42.0); clientAttributes.addProperty("attr4", 73); mqttClient.publish("v1/devices/me/attributes", Unpooled.wrappedBuffer(clientAttributes.toString().getBytes())); - TimeUnit.SECONDS.sleep(1); - WsTelemetryResponse actualLatestTelemetry = mapper.readValue(wsClient.getLastMessage(), WsTelemetryResponse.class); + WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); + wsClient.closeBlocking(); Assert.assertEquals(4, actualLatestTelemetry.getData().size()); Assert.assertEquals(Sets.newHashSet("attr1", "attr2", "attr3", "attr4"), @@ -157,7 +160,7 @@ public class MqttClientTest extends AbstractContainerTest { Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful()); // Subscribe to attributes response - mqttClient.on("v1/devices/me/attributes/response/+", listener); + mqttClient.on("v1/devices/me/attributes/response/+", listener, MqttQoS.AT_LEAST_ONCE); // Request attributes JsonObject request = new JsonObject(); request.addProperty("clientKeys", "clientAttr"); @@ -183,7 +186,7 @@ public class MqttClientTest extends AbstractContainerTest { MqttMessageListener listener = new MqttMessageListener(); MqttClient mqttClient = getMqttClient(deviceCredentials, listener); - mqttClient.on("v1/devices/me/attributes", listener); + mqttClient.on("v1/devices/me/attributes", listener, MqttQoS.AT_LEAST_ONCE); String sharedAttributeName = "sharedAttr"; @@ -226,13 +229,12 @@ public class MqttClientTest extends AbstractContainerTest { MqttMessageListener listener = new MqttMessageListener(); MqttClient mqttClient = getMqttClient(deviceCredentials, listener); - mqttClient.on("v1/devices/me/rpc/request/+", listener); + mqttClient.on("v1/devices/me/rpc/request/+", listener, MqttQoS.AT_LEAST_ONCE); // Send an RPC from the server JsonObject serverRpcPayload = new JsonObject(); serverRpcPayload.addProperty("method", "getValue"); serverRpcPayload.addProperty("params", true); - serverRpcPayload.addProperty("timeout", 1000); ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor()); ListenableFuture future = service.submit(() -> { try { @@ -271,7 +273,7 @@ public class MqttClientTest extends AbstractContainerTest { MqttMessageListener listener = new MqttMessageListener(); MqttClient mqttClient = getMqttClient(deviceCredentials, listener); - mqttClient.on("v1/devices/me/rpc/request/+", listener); + mqttClient.on("v1/devices/me/rpc/request/+", listener, MqttQoS.AT_LEAST_ONCE); // Get the default rule chain id to make it root again after test finished RuleChainId defaultRuleChainId = getDefaultRuleChainId(); @@ -377,6 +379,7 @@ public class MqttClientTest extends AbstractContainerTest { @Override public void onMessage(String topic, ByteBuf message) { + log.info("MQTT message [{}], topic [{}]", message.toString(StandardCharsets.UTF_8), topic); events.add(new MqttEvent(topic, message.toString(StandardCharsets.UTF_8))); } } diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/mapper/AttributesResponse.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/mapper/AttributesResponse.java similarity index 100% rename from msa/integration-tests/src/test/java/org/thingsboard/server/msa/mapper/AttributesResponse.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/mapper/AttributesResponse.java diff --git a/msa/integration-tests/src/test/java/org/thingsboard/server/msa/mapper/WsTelemetryResponse.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/mapper/WsTelemetryResponse.java similarity index 100% rename from msa/integration-tests/src/test/java/org/thingsboard/server/msa/mapper/WsTelemetryResponse.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/mapper/WsTelemetryResponse.java diff --git a/msa/integration-tests/src/test/resources/RpcResponseRuleChainMetadata.json b/msa/black-box-tests/src/test/resources/RpcResponseRuleChainMetadata.json similarity index 100% rename from msa/integration-tests/src/test/resources/RpcResponseRuleChainMetadata.json rename to msa/black-box-tests/src/test/resources/RpcResponseRuleChainMetadata.json diff --git a/msa/integration-tests/README.md b/msa/integration-tests/README.md deleted file mode 100644 index 93df2bf77f..0000000000 --- a/msa/integration-tests/README.md +++ /dev/null @@ -1,18 +0,0 @@ - -## Integration tests execution -To run the integration tests with using Docker, the local Docker images of Thingsboard's microservices should be built.
-- Build the local Docker images in the directory with the Thingsboard's main [pom.xml](./../../pom.xml): - - mvn clean install -Ddockerfile.skip=false -- Verify that the new local images were built: - - docker image ls -As result, in REPOSITORY column, next images should be present: - - local-maven-build/tb-node - local-maven-build/tb-web-ui - local-maven-build/tb-web-ui - -- Run the integration tests in the [msa/integration-tests](../integration-tests) directory: - - mvn clean install -DintegrationTests.skip=false \ No newline at end of file diff --git a/msa/pom.xml b/msa/pom.xml index 41926608be..5a9d9abd80 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -16,7 +16,7 @@ --> + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.thingsboard @@ -41,7 +41,7 @@ web-ui tb-node transport - integration-tests + black-box-tests From df27d7acc8b9371a60fd6b2741d2a2f7941118df Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 31 Oct 2018 13:17:29 +0200 Subject: [PATCH 09/35] Validation of TsKvQuery --- .../src/main/resources/thingsboard.yml | 1 + .../dao/timeseries/BaseTimeseriesService.java | 21 +++++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 51ddaa9181..51a59c9721 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -83,6 +83,7 @@ dashboard: max_datapoints_limit: "${DASHBOARD_MAX_DATAPOINTS_LIMIT:50000}" database: + ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # mas number of DB queries generated by single API call to fetch telemetry records entities: type: "${DATABASE_ENTITIES_TYPE:sql}" # cassandra OR sql ts: diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java index 4658cbe490..10064999e1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java @@ -20,11 +20,13 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.DeleteTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; @@ -47,8 +49,11 @@ import static org.apache.commons.lang3.StringUtils.isBlank; @Slf4j public class BaseTimeseriesService implements TimeseriesService { - public static final int INSERTS_PER_ENTRY = 3; - public static final int DELETES_PER_ENTRY = INSERTS_PER_ENTRY; + private static final int INSERTS_PER_ENTRY = 3; + private static final int DELETES_PER_ENTRY = INSERTS_PER_ENTRY; + + @Value("${database.ts_max_intervals}") + private long maxTsIntervals; @Autowired private TimeseriesDao timeseriesDao; @@ -59,7 +64,7 @@ public class BaseTimeseriesService implements TimeseriesService { @Override public ListenableFuture> findAll(EntityId entityId, List queries) { validate(entityId); - queries.forEach(BaseTimeseriesService::validate); + queries.forEach(this::validate); if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) { EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId); List filteredQueries = @@ -189,7 +194,7 @@ public class BaseTimeseriesService implements TimeseriesService { Validator.validateEntityId(entityId, "Incorrect entityId " + entityId); } - private static void validate(ReadTsKvQuery query) { + private void validate(ReadTsKvQuery query) { if (query == null) { throw new IncorrectParameterException("ReadTsKvQuery can't be null"); } else if (isBlank(query.getKey())) { @@ -197,6 +202,14 @@ public class BaseTimeseriesService implements TimeseriesService { } else if (query.getAggregation() == null) { throw new IncorrectParameterException("Incorrect ReadTsKvQuery. Aggregation can't be empty"); } + if(!Aggregation.NONE.equals(query.getAggregation())) { + long step = Math.max(query.getInterval(), 1000); + long intervalCounts = (query.getEndTs() - query.getStartTs()) / step; + if (intervalCounts > maxTsIntervals || intervalCounts < 0) { + throw new IncorrectParameterException("Incorrect TsKvQuery. Number of intervals is to high - " + intervalCounts + ". " + + "Please increase 'interval' parameter for your query or reduce the time range of the query."); + } + } } private static void validate(DeleteTsKvQuery query) { From 3ba15599c55e1a4109518f30fb1e2248e975f0a5 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 31 Oct 2018 15:06:14 +0200 Subject: [PATCH 10/35] Logging improvements and termination behaviour --- .../server/actors/app/AppActor.java | 65 +++++++++------- .../server/actors/device/DeviceActor.java | 36 ++------- .../actors/device/DeviceActorCreator.java | 39 ++++++++++ .../device/DeviceActorMessageProcessor.java | 66 ++++++++-------- .../server/actors/rpc/RpcManagerActor.java | 10 +-- .../actors/ruleChain/RuleChainActor.java | 4 +- .../RuleChainActorMessageProcessor.java | 8 +- .../actors/ruleChain/RuleNodeActor.java | 8 +- .../RuleNodeActorMessageProcessor.java | 4 +- .../server/actors/service/ComponentActor.java | 14 ++-- .../actors/service/ContextAwareActor.java | 17 +++-- .../AbstractContextAwareMsgProcessor.java | 10 +-- .../actors/shared/ComponentMsgProcessor.java | 8 +- .../actors/shared/EntityActorsManager.java | 9 ++- .../server/actors/stats/StatsActor.java | 13 ++-- .../server/actors/tenant/TenantActor.java | 76 ++++++++++++------- .../controller/DashboardController.java | 1 - .../model/token/RawAccessJwtToken.java | 8 +- .../src/main/resources/actor-system.conf | 2 +- 19 files changed, 232 insertions(+), 166 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/actors/device/DeviceActorCreator.java diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index f4373db9be..15e9e9d175 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -25,6 +25,9 @@ import akka.actor.Terminated; import akka.event.Logging; import akka.event.LoggingAdapter; import akka.japi.Function; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor; import org.thingsboard.server.actors.service.ContextBasedCreator; @@ -48,18 +51,17 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; +@Slf4j public class AppActor extends RuleChainManagerActor { - private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); - - public static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); + private static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); private final TenantService tenantService; - private final Map tenantActors; + private final BiMap tenantActors; private AppActor(ActorSystemContext systemContext) { super(systemContext, new SystemRuleChainManager(systemContext)); this.tenantService = systemContext.getTenantService(); - this.tenantActors = new HashMap<>(); + this.tenantActors = HashBiMap.create(); } @Override @@ -69,22 +71,20 @@ public class AppActor extends RuleChainManagerActor { @Override public void preStart() { - logger.info("Starting main system actor."); + log.info("Starting main system actor."); try { initRuleChains(); - if (systemContext.isTenantComponentsInitEnabled()) { PageDataIterable tenantIterator = new PageDataIterable<>(tenantService::findTenants, ENTITY_PACK_LIMIT); for (Tenant tenant : tenantIterator) { - logger.debug("[{}] Creating tenant actor", tenant.getId()); + log.debug("[{}] Creating tenant actor", tenant.getId()); getOrCreateTenantActor(tenant.getId()); - logger.debug("Tenant actor created."); + log.debug("Tenant actor created."); } } - - logger.info("Main system actor started."); + log.info("Main system actor started."); } catch (Exception e) { - logger.error(e, "Unknown failure"); + log.warn("Unknown failure", e); } } @@ -130,7 +130,7 @@ public class AppActor extends RuleChainManagerActor { private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) { if (SYSTEM_TENANT.equals(msg.getTenantId())) { - //TODO: ashvayka handle this. + log.warn("[{}] Invalid service to rule engine msg called. System messages are not supported yet", SYSTEM_TENANT); } else { getOrCreateTenantActor(msg.getTenantId()).tell(msg, self()); } @@ -152,7 +152,7 @@ public class AppActor extends RuleChainManagerActor { if (target != null) { target.tell(msg, ActorRef.noSender()); } else { - logger.debug("Invalid component lifecycle msg: {}", msg); + log.debug("[{}] Invalid component lifecycle msg: {}", msg.getTenantId(), msg); } } @@ -161,14 +161,26 @@ public class AppActor extends RuleChainManagerActor { } private ActorRef getOrCreateTenantActor(TenantId tenantId) { - return tenantActors.computeIfAbsent(tenantId, k -> context().actorOf(Props.create(new TenantActor.ActorCreator(systemContext, tenantId)) - .withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), tenantId.toString())); + return tenantActors.computeIfAbsent(tenantId, k -> { + log.debug("[{}] Creating tenant actor.", tenantId); + ActorRef tenantActor = context().actorOf(Props.create(new TenantActor.ActorCreator(systemContext, tenantId)) + .withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), tenantId.toString()); + context().watch(tenantActor); + log.debug("[{}] Created tenant actor: {}.", tenantId, tenantActor); + return tenantActor; + }); } - private void processTermination(Terminated message) { + @Override + protected void processTermination(Terminated message) { ActorRef terminated = message.actor(); if (terminated instanceof LocalActorRef) { - logger.debug("Removed actor: {}", terminated); + boolean removed = tenantActors.inverse().remove(terminated) != null; + if (removed) { + log.debug("[{}] Removed actor:", terminated); + } else { + log.warn("[{}] Removed actor was not found in the tenant map!"); + } } else { throw new IllegalStateException("Remote actors are not supported!"); } @@ -182,20 +194,17 @@ public class AppActor extends RuleChainManagerActor { } @Override - public AppActor create() throws Exception { + public AppActor create() { return new AppActor(context); } } - private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function() { - @Override - public Directive apply(Throwable t) { - logger.error(t, "Unknown failure"); - if (t instanceof RuntimeException) { - return SupervisorStrategy.restart(); - } else { - return SupervisorStrategy.stop(); - } + private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), t -> { + log.warn("Unknown failure", t); + if (t instanceof RuntimeException) { + return SupervisorStrategy.restart(); + } else { + return SupervisorStrategy.stop(); } }); } 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 7b412b1328..f450027fb6 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 @@ -15,42 +15,38 @@ */ package org.thingsboard.server.actors.device; -import akka.event.Logging; -import akka.event.LoggingAdapter; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.service.ContextAwareActor; -import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbActorMsg; -import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; import org.thingsboard.server.common.msg.timeout.DeviceActorClientSideRpcTimeoutMsg; import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg; import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.service.rpc.ToServerRpcResponseActorMsg; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; +@Slf4j public class DeviceActor extends ContextAwareActor { - private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); - private final DeviceActorMessageProcessor processor; - private DeviceActor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) { + DeviceActor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) { super(systemContext); - this.processor = new DeviceActorMessageProcessor(systemContext, logger, tenantId, deviceId); + this.processor = new DeviceActorMessageProcessor(systemContext, tenantId, deviceId); } @Override public void preStart() { - logger.debug("[{}][{}] Starting device actor.", processor.tenantId, processor.deviceId); + log.debug("[{}][{}] Starting device actor.", processor.tenantId, processor.deviceId); try { processor.initSessionTimeout(context()); - logger.debug("[{}][{}] Device actor started.", processor.tenantId, processor.deviceId); + log.debug("[{}][{}] Device actor started.", processor.tenantId, processor.deviceId); } catch (Exception e) { - logger.error(e, "[{}][{}] Unknown failure", processor.tenantId, processor.deviceId); + log.warn("[{}][{}] Unknown failure", processor.tenantId, processor.deviceId, e); } } @@ -90,22 +86,4 @@ public class DeviceActor extends ContextAwareActor { return true; } - public static class ActorCreator extends ContextBasedCreator { - private static final long serialVersionUID = 1L; - - private final TenantId tenantId; - private final DeviceId deviceId; - - public ActorCreator(ActorSystemContext context, TenantId tenantId, DeviceId deviceId) { - super(context); - this.tenantId = tenantId; - this.deviceId = deviceId; - } - - @Override - public DeviceActor create() throws Exception { - return new DeviceActor(context, tenantId, deviceId); - } - } - } diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorCreator.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorCreator.java new file mode 100644 index 0000000000..18aa9268bd --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorCreator.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.actors.ActorSystemContext; +import org.thingsboard.server.actors.service.ContextBasedCreator; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; + +public class DeviceActorCreator extends ContextBasedCreator { + private static final long serialVersionUID = 1L; + + private final TenantId tenantId; + private final DeviceId deviceId; + + public DeviceActorCreator(ActorSystemContext context, TenantId tenantId, DeviceId deviceId) { + super(context); + this.tenantId = tenantId; + this.deviceId = deviceId; + } + + @Override + public DeviceActor create() { + return new DeviceActor(context, tenantId, deviceId); + } +} 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 a16bd78a25..47417d1fd7 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 @@ -24,6 +24,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; @@ -88,6 +89,7 @@ import java.util.stream.Collectors; /** * @author Andrew Shvayka */ +@Slf4j class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { final TenantId tenantId; @@ -106,8 +108,8 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { private String deviceType; private TbMsgMetaData defaultMetaData; - DeviceActorMessageProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, DeviceId deviceId) { - super(systemContext, logger); + DeviceActorMessageProcessor(ActorSystemContext systemContext, TenantId tenantId, DeviceId deviceId) { + super(systemContext); this.tenantId = tenantId; this.deviceId = deviceId; this.sessions = new LinkedHashMap<>(); @@ -136,30 +138,30 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { long timeout = request.getExpirationTime() - System.currentTimeMillis(); if (timeout <= 0) { - logger.debug("[{}][{}] Ignoring message due to exp time reached", deviceId, request.getId(), request.getExpirationTime()); + log.debug("[{}][{}] Ignoring message due to exp time reached, {}", deviceId, request.getId(), request.getExpirationTime()); return; } boolean sent = rpcSubscriptions.size() > 0; Set syncSessionSet = new HashSet<>(); - rpcSubscriptions.entrySet().forEach(sub -> { - sendToTransport(rpcRequest, sub.getKey(), sub.getValue().getNodeId()); - if (TransportProtos.SessionType.SYNC == sub.getValue().getType()) { - syncSessionSet.add(sub.getKey()); + rpcSubscriptions.forEach((key, value) -> { + sendToTransport(rpcRequest, key, value.getNodeId()); + if (TransportProtos.SessionType.SYNC == value.getType()) { + syncSessionSet.add(key); } }); syncSessionSet.forEach(rpcSubscriptions::remove); if (request.isOneway() && sent) { - logger.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId()); + log.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId()); systemContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromDeviceActor(new FromDeviceRpcResponse(msg.getMsg().getId(), null, null)); } else { registerPendingRpcRequest(context, msg, sent, rpcRequest, timeout); } if (sent) { - logger.debug("[{}] RPC request {} is sent!", deviceId, request.getId()); + log.debug("[{}] RPC request {} is sent!", deviceId, request.getId()); } else { - logger.debug("[{}] RPC request {} is NOT sent!", deviceId, request.getId()); + log.debug("[{}] RPC request {} is NOT sent!", deviceId, request.getId()); } } @@ -172,7 +174,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { void processServerSideRpcTimeout(ActorContext context, DeviceActorServerSideRpcTimeoutMsg msg) { ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(msg.getId()); if (requestMd != null) { - logger.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId()); + log.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId()); systemContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), null, requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION)); } @@ -181,13 +183,13 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { private void sendPendingRequests(ActorContext context, UUID sessionId, SessionInfoProto sessionInfo) { TransportProtos.SessionType sessionType = getSessionType(sessionId); if (!toDeviceRpcPendingMap.isEmpty()) { - logger.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, toDeviceRpcPendingMap.size(), sessionId); + log.debug("[{}] Pushing {} pending RPC messages to new async session [{}]", deviceId, toDeviceRpcPendingMap.size(), sessionId); if (sessionType == TransportProtos.SessionType.SYNC) { - logger.debug("[{}] Cleanup sync rpc session [{}]", deviceId, sessionId); + log.debug("[{}] Cleanup sync rpc session [{}]", deviceId, sessionId); rpcSubscriptions.remove(sessionId); } } else { - logger.debug("[{}] No pending RPC messages for new async session [{}]", deviceId, sessionId); + log.debug("[{}] No pending RPC messages for new async session [{}]", deviceId, sessionId); } Set sentOneWayIds = new HashSet<>(); if (sessionType == TransportProtos.SessionType.ASYNC) { @@ -335,7 +337,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { void processClientSideRpcTimeout(ActorContext context, DeviceActorClientSideRpcTimeoutMsg msg) { ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(msg.getId()); if (data != null) { - logger.debug("[{}] Client side RPC request [{}] timeout detected!", deviceId, msg.getId()); + log.debug("[{}] Client side RPC request [{}] timeout detected!", deviceId, msg.getId()); sendToTransport(TransportProtos.ToServerRpcResponseMsg.newBuilder() .setRequestId(msg.getId()).setError("timeout").build() , data.getSessionId(), data.getNodeId()); @@ -380,7 +382,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { hasNotificationData = true; } } else { - logger.debug("[{}] No public server side attributes changed!", deviceId); + log.debug("[{}] No public server side attributes changed!", deviceId); } } } @@ -391,27 +393,27 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { }); } } else { - logger.debug("[{}] No registered attributes subscriptions to process!", deviceId); + log.debug("[{}] No registered attributes subscriptions to process!", deviceId); } } private void processRpcResponses(ActorContext context, SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg responseMsg) { UUID sessionId = getSessionId(sessionInfo); - logger.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId); + log.debug("[{}] Processing rpc command response [{}]", deviceId, sessionId); ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); boolean success = requestMd != null; if (success) { systemContext.getDeviceRpcService().processResponseToServerSideRPCRequestFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), responseMsg.getPayload(), null)); } else { - logger.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId()); + log.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId()); } } 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); + log.debug("[{}] Canceling attributes subscription for session [{}]", deviceId, sessionId); attributeSubscriptions.remove(sessionId); } else { SessionInfoMetaData sessionMD = sessions.get(sessionId); @@ -419,7 +421,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { sessionMD = new SessionInfoMetaData(new SessionInfo(TransportProtos.SessionType.SYNC, sessionInfo.getNodeId())); } sessionMD.setSubscribedToAttributes(true); - logger.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId); + log.debug("[{}] Registering attributes subscription for session [{}]", deviceId, sessionId); attributeSubscriptions.put(sessionId, sessionMD.getSessionInfo()); dumpSessions(); } @@ -432,7 +434,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { private void processSubscriptionCommands(ActorContext context, SessionInfoProto sessionInfo, SubscribeToRPCMsg subscribeCmd) { UUID sessionId = getSessionId(sessionInfo); if (subscribeCmd.getUnsubscribe()) { - logger.debug("[{}] Canceling rpc subscription for session [{}]", deviceId, sessionId); + log.debug("[{}] Canceling rpc subscription for session [{}]", deviceId, sessionId); rpcSubscriptions.remove(sessionId); } else { SessionInfoMetaData sessionMD = sessions.get(sessionId); @@ -440,7 +442,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { sessionMD = new SessionInfoMetaData(new SessionInfo(TransportProtos.SessionType.SYNC, sessionInfo.getNodeId())); } sessionMD.setSubscribedToRPC(true); - logger.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId); + log.debug("[{}] Registering rpc subscription for session [{}]", deviceId, sessionId); rpcSubscriptions.put(sessionId, sessionMD.getSessionInfo()); sendPendingRequests(context, sessionId, sessionInfo); dumpSessions(); @@ -451,10 +453,10 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { UUID sessionId = getSessionId(sessionInfo); if (msg.getEvent() == SessionEvent.OPEN) { if (sessions.containsKey(sessionId)) { - logger.debug("[{}] Received duplicate session open event [{}]", deviceId, sessionId); + log.debug("[{}] Received duplicate session open event [{}]", deviceId, sessionId); return; } - logger.debug("[{}] Processing new session [{}]", deviceId, sessionId); + log.debug("[{}] Processing new session [{}]", deviceId, sessionId); if (sessions.size() >= systemContext.getMaxConcurrentSessionsPerDevice()) { UUID sessionIdToRemove = sessions.keySet().stream().findFirst().orElse(null); if (sessionIdToRemove != null) { @@ -467,7 +469,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } dumpSessions(); } else if (msg.getEvent() == SessionEvent.CLOSED) { - logger.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId); + log.debug("[{}] Canceling subscriptions for closed session [{}]", deviceId, sessionId); sessions.remove(sessionId); attributeSubscriptions.remove(sessionId); rpcSubscriptions.remove(sessionId); @@ -623,10 +625,10 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } private void restoreSessions() { - logger.debug("[{}] Restoring sessions from cache", deviceId); + log.debug("[{}] Restoring sessions from cache", deviceId); TransportProtos.DeviceSessionsCacheEntry sessionsDump = systemContext.getDeviceSessionCacheService().get(deviceId); if (sessionsDump.getSerializedSize() == 0) { - logger.debug("[{}] No session information found", deviceId); + log.debug("[{}] No session information found", deviceId); return; } for (TransportProtos.SessionSubscriptionInfoProto sessionSubscriptionInfoProto : sessionsDump.getSessionsList()) { @@ -644,13 +646,13 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { rpcSubscriptions.put(sessionId, sessionInfo); sessionMD.setSubscribedToRPC(true); } - logger.debug("[{}] Restored session: {}", deviceId, sessionMD); + log.debug("[{}] Restored session: {}", deviceId, sessionMD); } - logger.debug("[{}] Restored sessions: {}, rpc subscriptions: {}, attribute subscriptions: {}", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size()); + log.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()); + log.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) { @@ -668,7 +670,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { sessionsList.add(TransportProtos.SessionSubscriptionInfoProto.newBuilder() .setSessionInfo(sessionInfoProto) .setSubscriptionInfo(subscriptionInfoProto).build()); - logger.debug("[{}] Dumping session: {}", deviceId, sessionMD); + log.debug("[{}] Dumping session: {}", deviceId, sessionMD); }); systemContext.getDeviceSessionCacheService() .put(deviceId, TransportProtos.DeviceSessionsCacheEntry.newBuilder() 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 31320ce5d2..922d2ba988 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 @@ -19,6 +19,7 @@ import akka.actor.ActorRef; import akka.actor.Props; import akka.event.Logging; import akka.event.LoggingAdapter; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.service.ContextAwareActor; import org.thingsboard.server.actors.service.ContextBasedCreator; @@ -35,17 +36,16 @@ import java.util.*; /** * @author Andrew Shvayka */ +@Slf4j public class RpcManagerActor extends ContextAwareActor { - private final LoggingAdapter log = Logging.getLogger(getContext().system(), this); - private final Map sessionActors; private final Map> pendingMsgs; private final ServerAddress instance; - RpcManagerActor(ActorSystemContext systemContext) { + private RpcManagerActor(ActorSystemContext systemContext) { super(systemContext); this.sessionActors = new HashMap<>(); this.pendingMsgs = new HashMap<>(); @@ -116,7 +116,7 @@ public class RpcManagerActor extends ContextAwareActor { queue.add(msg); } } else { - logger.warning("Cluster msg doesn't have server address [{}]", msg); + log.warn("Cluster msg doesn't have server address [{}]", msg); } } @@ -207,7 +207,7 @@ public class RpcManagerActor extends ContextAwareActor { } @Override - public RpcManagerActor create() throws Exception { + public RpcManagerActor create() { return new RpcManagerActor(context); } } diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java index dbad7c0b66..ed765e04f8 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java @@ -33,7 +33,7 @@ public class RuleChainActor extends ComponentActor { private static final long DEFAULT_CLUSTER_PARTITION = 0L; @@ -69,8 +71,8 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor(); @@ -216,7 +218,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor { private final RuleChainId ruleChainId; @@ -32,7 +34,7 @@ public class RuleNodeActor extends ComponentActor> extends ContextAwareActor { - protected final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); - private long lastPersistedErrorTs = 0L; protected final TenantId tenantId; protected final T id; @@ -60,7 +60,7 @@ public abstract class ComponentActor getErrorPersistFrequency()) { diff --git a/application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java b/application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java index 3624127194..c9f1dcde3e 100644 --- a/application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java @@ -15,14 +15,16 @@ */ package org.thingsboard.server.actors.service; +import akka.actor.Terminated; import akka.actor.UntypedActor; import akka.event.Logging; import akka.event.LoggingAdapter; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.common.msg.TbActorMsg; +@Slf4j public abstract class ContextAwareActor extends UntypedActor { - protected final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); public static final int ENTITY_PACK_LIMIT = 1024; @@ -35,21 +37,26 @@ public abstract class ContextAwareActor extends UntypedActor { @Override public void onReceive(Object msg) throws Exception { - if (logger.isDebugEnabled()) { - logger.debug("Processing msg: {}", msg); + if (log.isDebugEnabled()) { + log.debug("Processing msg: {}", msg); } if (msg instanceof TbActorMsg) { try { if (!process((TbActorMsg) msg)) { - logger.warning("Unknown message: {}!", msg); + log.warn("Unknown message: {}!", msg); } } catch (Exception e) { throw e; } + } else if (msg instanceof Terminated) { + processTermination((Terminated) msg); } else { - logger.warning("Unknown message: {}!", msg); + log.warn("Unknown message: {}!", msg); } } + protected void processTermination(Terminated msg) { + } + protected abstract boolean process(TbActorMsg msg); } 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 c8097821d9..b707baf14e 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 @@ -22,22 +22,22 @@ import akka.event.LoggingAdapter; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.AllArgsConstructor; import lombok.Data; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.actors.ActorSystemContext; import scala.concurrent.ExecutionContextExecutor; import scala.concurrent.duration.Duration; import java.util.concurrent.TimeUnit; +@Slf4j public abstract class AbstractContextAwareMsgProcessor { protected final ActorSystemContext systemContext; - protected final LoggingAdapter logger; protected final ObjectMapper mapper = new ObjectMapper(); - protected AbstractContextAwareMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger) { + protected AbstractContextAwareMsgProcessor(ActorSystemContext systemContext) { super(); this.systemContext = systemContext; - this.logger = logger; } private Scheduler getScheduler() { @@ -53,7 +53,7 @@ public abstract class AbstractContextAwareMsgProcessor { } private void schedulePeriodicMsgWithDelay(Object msg, long delayInMs, long periodInMs, ActorRef target) { - logger.debug("Scheduling periodic msg {} every {} ms with delay {} ms", msg, periodInMs, delayInMs); + log.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); } @@ -62,7 +62,7 @@ public abstract class AbstractContextAwareMsgProcessor { } private void scheduleMsgWithDelay(Object msg, long delayInMs, ActorRef target) { - logger.debug("Scheduling msg {} with delay {} ms", msg, delayInMs); + log.debug("Scheduling msg {} with delay {} ms", msg, delayInMs); getScheduler().scheduleOnce(Duration.create(delayInMs, TimeUnit.MILLISECONDS), target, msg, getSystemDispatcher(), null); } diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java index c9dc307451..9e5d063223 100644 --- a/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java @@ -19,6 +19,7 @@ import akka.actor.ActorContext; import akka.event.LoggingAdapter; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.stats.StatsPersistTick; import org.thingsboard.server.common.data.id.EntityId; @@ -30,14 +31,15 @@ import org.thingsboard.server.common.msg.cluster.ClusterEventMsg; import javax.annotation.Nullable; import java.util.function.Consumer; +@Slf4j public abstract class ComponentMsgProcessor extends AbstractContextAwareMsgProcessor { protected final TenantId tenantId; protected final T entityId; protected ComponentLifecycleState state; - protected ComponentMsgProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, T id) { - super(systemContext, logger); + protected ComponentMsgProcessor(ActorSystemContext systemContext, TenantId tenantId, T id) { + super(systemContext); this.tenantId = tenantId; this.entityId = id; } @@ -79,7 +81,7 @@ public abstract class ComponentMsgProcessor extends Abstract protected void checkActive() { if (state != ComponentLifecycleState.ACTIVE) { - logger.warning("Rule chain is not active. Current state [{}] for processor [{}] tenant [{}]", state, tenantId, entityId); + log.warn("Rule chain is not active. Current state [{}] for processor [{}] tenant [{}]", state, tenantId, entityId); throw new IllegalStateException("Rule chain is not active! " + entityId + " - " + tenantId); } } diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java b/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java index 1b9e6a8074..dd03d69a62 100644 --- a/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java +++ b/application/src/main/java/org/thingsboard/server/actors/shared/EntityActorsManager.java @@ -20,6 +20,8 @@ import akka.actor.ActorRef; import akka.actor.Props; import akka.actor.UntypedActor; import akka.japi.Creator; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.service.ContextAwareActor; @@ -39,11 +41,11 @@ import java.util.Map; public abstract class EntityActorsManager> { protected final ActorSystemContext systemContext; - protected final Map actors; + protected final BiMap actors; public EntityActorsManager(ActorSystemContext systemContext) { this.systemContext = systemContext; - this.actors = new HashMap<>(); + this.actors = HashBiMap.create(); } protected abstract TenantId getTenantId(); @@ -65,7 +67,8 @@ public abstract class EntityActorsManager diff --git a/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java b/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java index 8623370896..79aa6da964 100644 --- a/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java @@ -15,10 +15,9 @@ */ package org.thingsboard.server.actors.stats; -import akka.event.Logging; -import akka.event.LoggingAdapter; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.service.ContextAwareActor; import org.thingsboard.server.actors.service.ContextBasedCreator; @@ -27,9 +26,9 @@ import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.cluster.ServerAddress; +@Slf4j public class StatsActor extends ContextAwareActor { - private final LoggingAdapter logger = Logging.getLogger(getContext().system(), this); private final ObjectMapper mapper = new ObjectMapper(); public StatsActor(ActorSystemContext context) { @@ -43,13 +42,13 @@ public class StatsActor extends ContextAwareActor { } @Override - public void onReceive(Object msg) throws Exception { - logger.debug("Received message: {}", msg); + public void onReceive(Object msg) { + log.debug("Received message: {}", msg); if (msg instanceof StatsPersistMsg) { try { onStatsPersistMsg((StatsPersistMsg) msg); } catch (Exception e) { - logger.warning("Failed to persist statistics: {}", msg, e); + log.warn("Failed to persist statistics: {}", msg, e); } } } @@ -75,7 +74,7 @@ public class StatsActor extends ContextAwareActor { } @Override - public StatsActor create() throws Exception { + public StatsActor create() { return new StatsActor(context); } } 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 721d828fd6..b76e2984ae 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 @@ -17,15 +17,19 @@ package org.thingsboard.server.actors.tenant; import akka.actor.ActorInitializationException; import akka.actor.ActorRef; +import akka.actor.LocalActorRef; import akka.actor.OneForOneStrategy; import akka.actor.Props; import akka.actor.SupervisorStrategy; +import akka.actor.Terminated; import akka.japi.Function; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.actors.device.DeviceActor; +import org.thingsboard.server.actors.device.DeviceActorCreator; import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg; import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor; -import org.thingsboard.server.actors.ruleChain.RuleChainToRuleChainMsg; import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.actors.shared.rulechain.TenantRuleChainManager; @@ -44,18 +48,18 @@ import scala.concurrent.duration.Duration; import java.util.HashMap; import java.util.Map; +@Slf4j public class TenantActor extends RuleChainManagerActor { private final TenantId tenantId; - private final Map deviceActors; + private final BiMap deviceActors; private TenantActor(ActorSystemContext systemContext, TenantId tenantId) { super(systemContext, new TenantRuleChainManager(systemContext, tenantId)); this.tenantId = tenantId; - this.deviceActors = new HashMap<>(); + this.deviceActors = HashBiMap.create(); } - @Override public SupervisorStrategy supervisorStrategy() { return strategy; @@ -63,12 +67,12 @@ public class TenantActor extends RuleChainManagerActor { @Override public void preStart() { - logger.info("[{}] Starting tenant actor.", tenantId); + log.info("[{}] Starting tenant actor.", tenantId); try { initRuleChains(); - logger.info("[{}] Tenant actor started.", tenantId); + log.info("[{}] Tenant actor started.", tenantId); } catch (Exception e) { - logger.error(e, "[{}] Unknown failure", tenantId); + log.warn("[{}] Unknown failure", tenantId, e); } } @@ -105,22 +109,20 @@ public class TenantActor extends RuleChainManagerActor { return true; } - @Override - protected void broadcast(Object msg) { - super.broadcast(msg); -// deviceActors.values().forEach(actorRef -> actorRef.tell(msg, ActorRef.noSender())); - } - private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) { - if (ruleChainManager.getRootChainActor()!=null) - ruleChainManager.getRootChainActor().tell(msg, self()); - else logger.info("[{}] No Root Chain", msg); + if (ruleChainManager.getRootChainActor() != null) { + ruleChainManager.getRootChainActor().tell(msg, self()); + } else { + log.info("[{}] No Root Chain: {}", tenantId, msg); + } } private void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg msg) { - if (ruleChainManager.getRootChainActor()!=null) - ruleChainManager.getRootChainActor().tell(msg, self()); - else logger.info("[{}] No Root Chain", msg); + if (ruleChainManager.getRootChainActor() != null) { + ruleChainManager.getRootChainActor().tell(msg, self()); + } else { + log.info("[{}] No Root Chain: {}", tenantId, msg); + } } private void onRuleChainMsg(RuleChainAwareMsg msg) { @@ -141,13 +143,35 @@ public class TenantActor extends RuleChainManagerActor { } target.tell(msg, ActorRef.noSender()); } else { - logger.debug("Invalid component lifecycle msg: {}", msg); + log.debug("[{}] Invalid component lifecycle msg: {}", tenantId, msg); } } private ActorRef getOrCreateDeviceActor(DeviceId deviceId) { - return deviceActors.computeIfAbsent(deviceId, k -> context().actorOf(Props.create(new DeviceActor.ActorCreator(systemContext, tenantId, deviceId)) - .withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME), deviceId.toString())); + return deviceActors.computeIfAbsent(deviceId, k -> { + log.debug("[{}][{}] Creating device actor.", tenantId, deviceId); + ActorRef deviceActor = context().actorOf(Props.create(new DeviceActorCreator(systemContext, tenantId, deviceId)) + .withDispatcher(DefaultActorService.CORE_DISPATCHER_NAME) + , deviceId.toString()); + context().watch(deviceActor); + log.debug("[{}][{}] Created device actor: {}.", tenantId, deviceId, deviceActor); + return deviceActor; + }); + } + + @Override + protected void processTermination(Terminated message) { + ActorRef terminated = message.actor(); + if (terminated instanceof LocalActorRef) { + boolean removed = deviceActors.inverse().remove(terminated) != null; + if (removed) { + log.debug("[{}] Removed actor:", terminated); + } else { + log.warn("[{}] Removed actor was not found in the device map!"); + } + } else { + throw new IllegalStateException("Remote actors are not supported!"); + } } public static class ActorCreator extends ContextBasedCreator { @@ -161,7 +185,7 @@ public class TenantActor extends RuleChainManagerActor { } @Override - public TenantActor create() throws Exception { + public TenantActor create() { return new TenantActor(context, tenantId); } } @@ -169,8 +193,8 @@ public class TenantActor extends RuleChainManagerActor { private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function() { @Override public SupervisorStrategy.Directive apply(Throwable t) { - logger.error(t, "Unknown failure"); - if(t instanceof ActorInitializationException){ + log.warn("[{}] Unknown failure", tenantId, t); + if (t instanceof ActorInitializationException) { return SupervisorStrategy.stop(); } else { return SupervisorStrategy.resume(); diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java index 450b9761e9..0bb6a6683d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java @@ -52,7 +52,6 @@ public class DashboardController extends BaseController { public static final String DASHBOARD_ID = "dashboardId"; @Value("${dashboard.max_datapoints_limit}") - @Getter private long maxDatapointsLimit; diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java b/application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java index 544b6fdea4..68d9ada212 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/token/RawAccessJwtToken.java @@ -22,6 +22,7 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.SignatureException; import io.jsonwebtoken.UnsupportedJwtException; +import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.authentication.BadCredentialsException; @@ -29,12 +30,11 @@ import org.thingsboard.server.service.security.exception.JwtExpiredTokenExceptio import java.io.Serializable; +@Slf4j public class RawAccessJwtToken implements JwtToken, Serializable { private static final long serialVersionUID = -797397445703066079L; - private static Logger logger = LoggerFactory.getLogger(RawAccessJwtToken.class); - private String token; public RawAccessJwtToken(String token) { @@ -52,10 +52,10 @@ public class RawAccessJwtToken implements JwtToken, Serializable { try { return Jwts.parser().setSigningKey(signingKey).parseClaimsJws(this.token); } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) { - logger.error("Invalid JWT Token", ex); + log.error("Invalid JWT Token", ex); throw new BadCredentialsException("Invalid JWT token: ", ex); } catch (ExpiredJwtException expiredEx) { - logger.info("JWT Token is expired", expiredEx); + log.info("JWT Token is expired", expiredEx); throw new JwtExpiredTokenException(this, "JWT Token expired", expiredEx); } } diff --git a/application/src/main/resources/actor-system.conf b/application/src/main/resources/actor-system.conf index 763319e52c..28673f6377 100644 --- a/application/src/main/resources/actor-system.conf +++ b/application/src/main/resources/actor-system.conf @@ -19,7 +19,7 @@ akka { # JVM shutdown, System.exit(-1), in case of a fatal error, # such as OutOfMemoryError jvm-exit-on-fatal-error = off - loglevel = "DEBUG" + loglevel = "INFO" loggers = ["akka.event.slf4j.Slf4jLogger"] } From 19c16aa107f72f9545cf0a61eb2701089145a5d1 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 31 Oct 2018 15:57:15 +0200 Subject: [PATCH 11/35] Fix tests --- dao/src/test/resources/application-test.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/test/resources/application-test.properties b/dao/src/test/resources/application-test.properties index a61c285136..3bd0f7d41a 100644 --- a/dao/src/test/resources/application-test.properties +++ b/dao/src/test/resources/application-test.properties @@ -35,4 +35,4 @@ redis.connection.port=6379 redis.connection.db=0 redis.connection.password= -rule.queue.type=memory +database.ts_max_intervals=700 \ No newline at end of file From 4e81f9cc14dd6daa138dce046ccee5ba04b02cc1 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 31 Oct 2018 19:23:00 +0200 Subject: [PATCH 12/35] Update black box tests infrastructure. --- docker/.env | 2 + docker/docker-compose.postgres.volumes.yml | 36 ++++++ docker/docker-compose.yml | 2 +- docker/tb-node/conf/logback.xml | 2 +- msa/black-box-tests/pom.xml | 6 + .../server/msa/ContainerTestSuite.java | 34 +++-- .../server/msa/DockerComposeExecutor.java | 119 ++++++++++++++++++ .../server/msa/ThingsBoardDbInstaller.java | 106 ++++++++++++++++ 8 files changed, 298 insertions(+), 9 deletions(-) create mode 100644 docker/docker-compose.postgres.volumes.yml create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/DockerComposeExecutor.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java diff --git a/docker/.env b/docker/.env index c03845dfe0..0138501ebc 100644 --- a/docker/.env +++ b/docker/.env @@ -15,4 +15,6 @@ TB_VERSION=latest DATABASE=postgres +LOAD_BALANCER_NAME=haproxy-certbot + 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/docker/docker-compose.postgres.volumes.yml b/docker/docker-compose.postgres.volumes.yml new file mode 100644 index 0000000000..d94b066cb0 --- /dev/null +++ b/docker/docker-compose.postgres.volumes.yml @@ -0,0 +1,36 @@ +# +# 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: + postgres: + volumes: + - postgres-db-volume:/var/lib/postgresql/data + tb1: + volumes: + - tb-log-volume:/var/log/thingsboard + tb2: + volumes: + - tb-log-volume:/var/log/thingsboard + +volumes: + postgres-db-volume: + external: true + name: ${POSTGRES_DATA_VOLUME} + tb-log-volume: + external: true + name: ${TB_LOG_VOLUME} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 1741db3694..0a09d8a024 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -145,7 +145,7 @@ services: - tb-web-ui.env haproxy: restart: always - container_name: haproxy-certbot + container_name: "${LOAD_BALANCER_NAME}" image: xalauc/haproxy-certbot:1.7.9 volumes: - ./haproxy/config:/config diff --git a/docker/tb-node/conf/logback.xml b/docker/tb-node/conf/logback.xml index 1c69f537bc..6ec2d0b2d0 100644 --- a/docker/tb-node/conf/logback.xml +++ b/docker/tb-node/conf/logback.xml @@ -24,7 +24,7 @@ /var/log/thingsboard/${TB_HOST}/thingsboard.log - /var/log/thingsboard/thingsboard.%d{yyyy-MM-dd}.%i.log + /var/log/thingsboard/${TB_HOST}/thingsboard.%d{yyyy-MM-dd}.%i.log 100MB 30 3GB diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index 2af4d2c14e..9f5ac0912c 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -36,6 +36,7 @@ ${basedir}/../.. true 1.9.1 + 1.10 1.3.9 4.5.6
@@ -46,6 +47,11 @@ testcontainers ${testcontainers.version}
+ + org.zeroturnaround + zt-exec + ${zeroturnaround.version} + org.java-websocket Java-WebSocket diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java index 495fd94d2e..3233617823 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java @@ -17,23 +17,43 @@ package org.thingsboard.server.msa; import org.junit.ClassRule; import org.junit.extensions.cpsuite.ClasspathSuite; +import org.junit.rules.ExternalResource; import org.junit.runner.RunWith; import org.testcontainers.containers.DockerComposeContainer; import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.Base58; import java.io.File; import java.time.Duration; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*Test"}) public class ContainerTestSuite { + private static DockerComposeContainer testContainer; + + @ClassRule + public static ThingsBoardDbInstaller installTb = new ThingsBoardDbInstaller(); + @ClassRule - public static DockerComposeContainer composeContainer = new DockerComposeContainer( - new File("./../../docker/docker-compose.yml"), - new File("./../../docker/docker-compose.postgres.yml")) - .withPull(false) - .withLocalCompose(true) - .withTailChildContainers(true) - .withExposedService("tb-web-ui1", 8080, Wait.forHttp("/login").withStartupTimeout(Duration.ofSeconds(120))); + public static DockerComposeContainer getTestContainer() { + if (testContainer == null) { + testContainer = new DockerComposeContainer( + new File("./../../docker/docker-compose.yml"), + new File("./../../docker/docker-compose.postgres.yml"), + new File("./../../docker/docker-compose.postgres.volumes.yml")) + .withPull(false) + .withLocalCompose(true) + .withTailChildContainers(true) + .withEnv("POSTGRES_DATA_VOLUME", installTb.getPostgresDataVolume()) + .withEnv("TB_LOG_VOLUME", installTb.getTbLogVolume()) + .withEnv("LOAD_BALANCER_NAME", "") + .withExposedService("haproxy", 80, Wait.forHttp("/swagger-ui.html").withStartupTimeout(Duration.ofSeconds(120))); + } + return testContainer; + } } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/DockerComposeExecutor.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/DockerComposeExecutor.java new file mode 100644 index 0000000000..25d2e6ee4c --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/DockerComposeExecutor.java @@ -0,0 +1,119 @@ +/** + * 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.msa; + + +import com.google.common.base.Splitter; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.SystemUtils; +import org.testcontainers.containers.ContainerLaunchException; +import org.testcontainers.utility.CommandLine; +import org.zeroturnaround.exec.InvalidExitValueException; +import org.zeroturnaround.exec.ProcessExecutor; +import org.zeroturnaround.exec.stream.slf4j.Slf4jStream; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.stream.Collectors.joining; + +@Slf4j +public class DockerComposeExecutor { + + String ENV_PROJECT_NAME = "COMPOSE_PROJECT_NAME"; + String ENV_COMPOSE_FILE = "COMPOSE_FILE"; + + private static final String COMPOSE_EXECUTABLE = SystemUtils.IS_OS_WINDOWS ? "docker-compose.exe" : "docker-compose"; + private static final String DOCKER_EXECUTABLE = SystemUtils.IS_OS_WINDOWS ? "docker.exe" : "docker"; + + private final List composeFiles; + private final String identifier; + private String cmd = ""; + private Map env = new HashMap<>(); + + public DockerComposeExecutor(List composeFiles, String identifier) { + validateFileList(composeFiles); + this.composeFiles = composeFiles; + this.identifier = identifier; + } + + public DockerComposeExecutor withCommand(String cmd) { + this.cmd = cmd; + return this; + } + + public DockerComposeExecutor withEnv(Map env) { + this.env = env; + return this; + } + + public void invokeCompose() { + // bail out early + if (!CommandLine.executableExists(COMPOSE_EXECUTABLE)) { + throw new ContainerLaunchException("Local Docker Compose not found. Is " + COMPOSE_EXECUTABLE + " on the PATH?"); + } + final Map environment = Maps.newHashMap(env); + environment.put(ENV_PROJECT_NAME, identifier); + final Stream absoluteDockerComposeFilePaths = composeFiles.stream().map(File::getAbsolutePath).map(Objects::toString); + final String composeFileEnvVariableValue = absoluteDockerComposeFilePaths.collect(joining(File.pathSeparator + "")); + log.debug("Set env COMPOSE_FILE={}", composeFileEnvVariableValue); + final File pwd = composeFiles.get(0).getAbsoluteFile().getParentFile().getAbsoluteFile(); + environment.put(ENV_COMPOSE_FILE, composeFileEnvVariableValue); + log.info("Local Docker Compose is running command: {}", cmd); + final List command = Splitter.onPattern(" ").omitEmptyStrings().splitToList(COMPOSE_EXECUTABLE + " " + cmd); + try { + new ProcessExecutor().command(command).redirectOutput(Slf4jStream.of(log).asInfo()).redirectError(Slf4jStream.of(log).asError()).environment(environment).directory(pwd).exitValueNormal().executeNoTimeout(); + log.info("Docker Compose has finished running"); + } catch (InvalidExitValueException e) { + throw new ContainerLaunchException("Local Docker Compose exited abnormally with code " + e.getExitValue() + " whilst running command: " + cmd); + } catch (Exception e) { + throw new ContainerLaunchException("Error running local Docker Compose command: " + cmd, e); + } + } + + public void invokeDocker() { + // bail out early + if (!CommandLine.executableExists(DOCKER_EXECUTABLE)) { + throw new ContainerLaunchException("Local Docker not found. Is " + DOCKER_EXECUTABLE + " on the PATH?"); + } + final File pwd = composeFiles.get(0).getAbsoluteFile().getParentFile().getAbsoluteFile(); + log.info("Local Docker is running command: {}", cmd); + final List command = Splitter.onPattern(" ").omitEmptyStrings().splitToList(DOCKER_EXECUTABLE + " " + cmd); + try { + new ProcessExecutor().command(command).redirectOutput(Slf4jStream.of(log).asInfo()).redirectError(Slf4jStream.of(log).asError()).directory(pwd).exitValueNormal().executeNoTimeout(); + log.info("Docker has finished running"); + } catch (InvalidExitValueException e) { + throw new ContainerLaunchException("Local Docker exited abnormally with code " + e.getExitValue() + " whilst running command: " + cmd); + } catch (Exception e) { + throw new ContainerLaunchException("Error running local Docker command: " + cmd, e); + } + } + + void validateFileList(List composeFiles) { + checkNotNull(composeFiles); + checkArgument(!composeFiles.isEmpty(), "No docker compose file have been provided"); + } + + +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java new file mode 100644 index 0000000000..0a902b9c5e --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java @@ -0,0 +1,106 @@ +/** + * 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.msa; + +import org.junit.rules.ExternalResource; +import org.testcontainers.utility.Base58; + +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ThingsBoardDbInstaller extends ExternalResource { + + private final static String POSTGRES_DATA_VOLUME = "tb-postgres-test-data-volume"; + private final static String TB_LOG_VOLUME = "tb-log-test-volume"; + + private final DockerComposeExecutor dockerCompose; + + private final String postgresDataVolume; + private final String tbLogVolume; + + public ThingsBoardDbInstaller() { + List composeFiles = Arrays.asList(new File("./../../docker/docker-compose.yml"), + new File("./../../docker/docker-compose.postgres.yml"), + new File("./../../docker/docker-compose.postgres.volumes.yml")); + + String identifier = Base58.randomString(6).toLowerCase(); + String project = identifier + Base58.randomString(6).toLowerCase(); + + postgresDataVolume = project + "_" + POSTGRES_DATA_VOLUME; + tbLogVolume = project + "_" + TB_LOG_VOLUME; + + dockerCompose = new DockerComposeExecutor(composeFiles, project); + + Map env = new HashMap<>(); + env.put("POSTGRES_DATA_VOLUME", postgresDataVolume); + env.put("TB_LOG_VOLUME", tbLogVolume); + dockerCompose.withEnv(env); + } + + public String getPostgresDataVolume() { + return postgresDataVolume; + } + + public String getTbLogVolume() { + return tbLogVolume; + } + + @Override + protected void before() throws Throwable { + try { + + dockerCompose.withCommand("volume create " + postgresDataVolume); + dockerCompose.invokeDocker(); + + dockerCompose.withCommand("volume create " + tbLogVolume); + dockerCompose.invokeDocker(); + + dockerCompose.withCommand("up -d redis postgres"); + dockerCompose.invokeCompose(); + + dockerCompose.withCommand("run --no-deps --rm -e INSTALL_TB=true -e LOAD_DEMO=true tb1"); + dockerCompose.invokeCompose(); + + } finally { + try { + dockerCompose.withCommand("down -v"); + dockerCompose.invokeCompose(); + } catch (Exception e) {} + } + } + + @Override + protected void after() { + File tbLogsDir = new File("./target/tb-logs/"); + tbLogsDir.mkdirs(); + + dockerCompose.withCommand("run -d --rm --name tb-logs-container -v " + tbLogVolume + ":/root alpine tail -f /dev/null"); + dockerCompose.invokeDocker(); + + dockerCompose.withCommand("cp tb-logs-container:/root/. "+tbLogsDir.getAbsolutePath()); + dockerCompose.invokeDocker(); + + dockerCompose.withCommand("rm -f tb-logs-container"); + dockerCompose.invokeDocker(); + + dockerCompose.withCommand("volume rm -f " + postgresDataVolume + " " + tbLogVolume); + dockerCompose.invokeDocker(); + } + +} From 62db6001fd12fbf32fd1e2298ecca041a2741e0f Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Thu, 1 Nov 2018 11:00:44 +0200 Subject: [PATCH 13/35] cast incoming attributes/telemetry numeric data type if possible --- .../transport/adaptor/JsonConverter.java | 55 ++++++++++++++----- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index 7498e8ee2a..b497fe08ea 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -22,6 +22,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; import com.google.gson.JsonSyntaxException; +import org.apache.commons.lang3.math.NumberUtils; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.kv.AttributeKey; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -128,24 +129,22 @@ public class JsonConverter { if (element.isJsonPrimitive()) { JsonPrimitive value = element.getAsJsonPrimitive(); if (value.isString()) { - result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.STRING_V) - .setStringV(value.getAsString()).build()); + if(NumberUtils.isParsable(value.getAsString())) { + try { + result.add(buildNumericKeyValueProto(value, valueEntry.getKey())); + } catch (Throwable th) { + result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.STRING_V) + .setStringV(value.getAsString()).build()); + } + } else { + result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.STRING_V) + .setStringV(value.getAsString()).build()); + } } else if (value.isBoolean()) { result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.BOOLEAN_V) .setBoolV(value.getAsBoolean()).build()); } else if (value.isNumber()) { - if (value.getAsString().contains(".")) { - result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.DOUBLE_V) - .setDoubleV(value.getAsDouble()).build()); - } else { - try { - long longValue = Long.parseLong(value.getAsString()); - result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.LONG_V) - .setLongV(longValue).build()); - } catch (NumberFormatException e) { - throw new JsonSyntaxException("Big integer values are not supported!"); - } - } + result.add(buildNumericKeyValueProto(value, valueEntry.getKey())); } else { throw new JsonSyntaxException(CAN_T_PARSE_VALUE + value); } @@ -156,6 +155,24 @@ public class JsonConverter { return result; } + private static KeyValueProto buildNumericKeyValueProto(JsonPrimitive value, String key) { + if (value.getAsString().contains(".")) { + return KeyValueProto.newBuilder() + .setKey(key) + .setType(KeyValueType.DOUBLE_V) + .setDoubleV(value.getAsDouble()) + .build(); + } else { + try { + long longValue = Long.parseLong(value.getAsString()); + return KeyValueProto.newBuilder().setKey(key).setType(KeyValueType.LONG_V) + .setLongV(longValue).build(); + } catch (NumberFormatException e) { + throw new JsonSyntaxException("Big integer values are not supported!"); + } + } + } + public static TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(JsonElement json, int requestId) throws JsonSyntaxException { JsonObject object = json.getAsJsonObject(); return TransportProtos.ToServerRpcRequestMsg.newBuilder().setRequestId(requestId).setMethodName(object.get("method").getAsString()).setParams(GSON.toJson(object.get("params"))).build(); @@ -370,7 +387,15 @@ public class JsonConverter { if (element.isJsonPrimitive()) { JsonPrimitive value = element.getAsJsonPrimitive(); if (value.isString()) { - result.add(new StringDataEntry(valueEntry.getKey(), value.getAsString())); + if(NumberUtils.isParsable(value.getAsString())) { + try { + parseNumericValue(result, valueEntry, value); + } catch (Throwable th) { + result.add(new StringDataEntry(valueEntry.getKey(), value.getAsString())); + } + } else { + result.add(new StringDataEntry(valueEntry.getKey(), value.getAsString())); + } } else if (value.isBoolean()) { result.add(new BooleanDataEntry(valueEntry.getKey(), value.getAsBoolean())); } else if (value.isNumber()) { From 5d5100ef1bfdee3e484419289dcf6381fb284710 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Thu, 1 Nov 2018 18:02:09 +0200 Subject: [PATCH 14/35] Improved logging --- .../server/actors/app/AppActor.java | 1 - .../server/actors/device/DeviceActor.java | 2 -- .../device/DeviceActorMessageProcessor.java | 3 ++ .../actors/rpc/BasicRpcSessionListener.java | 10 +++--- .../server/actors/rpc/RpcManagerActor.java | 1 - .../RuleChainActorMessageProcessor.java | 30 +++++++++++++--- .../actors/ruleChain/RuleNodeActor.java | 10 +++--- .../RuleNodeActorMessageProcessor.java | 9 +++-- .../server/actors/service/ComponentActor.java | 8 +++-- .../actors/service/ContextAwareActor.java | 9 ++--- .../actors/shared/ComponentMsgProcessor.java | 2 ++ .../server/actors/tenant/TenantActor.java | 1 - .../service/script/RemoteJsInvokeService.java | 4 +-- .../RemoteRuleEngineTransportService.java | 12 ++++--- .../server/kafka/TBKafkaProducerTemplate.java | 5 +-- .../server/kafka/TbKafkaRequestTemplate.java | 20 +++++++++-- msa/black-box-tests/README.md | 4 ++- .../server/msa/AbstractContainerTest.java | 31 +++++++++++++++++ .../org/thingsboard/server/msa/WsClient.java | 34 ++++++++++++++----- .../msa/connectivity/MqttClientTest.java | 12 +++++-- 20 files changed, 157 insertions(+), 51 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index 15e9e9d175..6df37d6731 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -51,7 +51,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; -@Slf4j public class AppActor extends RuleChainManagerActor { private static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); 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 f450027fb6..f53a410d5f 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 @@ -15,7 +15,6 @@ */ package org.thingsboard.server.actors.device; -import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; import org.thingsboard.server.actors.ActorSystemContext; @@ -29,7 +28,6 @@ import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg; import org.thingsboard.server.service.rpc.ToServerRpcResponseActorMsg; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; -@Slf4j public class DeviceActor extends ContextAwareActor { private final DeviceActorMessageProcessor processor; 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 47417d1fd7..41d1ea08c6 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 @@ -348,9 +348,12 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { int requestId = msg.getMsg().getRequestId(); ToServerRpcRequestMetadata data = toServerRpcPendingMap.remove(requestId); if (data != null) { + log.debug("[{}] Pushing reply to [{}][{}]!", deviceId, data.getNodeId(), data.getSessionId()); sendToTransport(TransportProtos.ToServerRpcResponseMsg.newBuilder() .setRequestId(requestId).setPayload(msg.getMsg().getData()).build() , data.getSessionId(), data.getNodeId()); + } else { + log.debug("[{}][{}] Pending RPC request to server not found!", deviceId, requestId); } } 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 1eba066960..760d4a6d96 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 @@ -40,7 +40,7 @@ public class BasicRpcSessionListener implements GrpcSessionListener { @Override public void onConnected(GrpcSession session) { - log.info("{} session started -> {}", getType(session), session.getRemoteServer()); + log.info("[{}][{}] session started", session.getRemoteServer(), getType(session)); if (!session.isClient()) { manager.tell(new RpcSessionConnectedMsg(session.getRemoteServer(), session.getSessionId()), self); } @@ -48,21 +48,19 @@ public class BasicRpcSessionListener implements GrpcSessionListener { @Override public void onDisconnected(GrpcSession session) { - log.info("{} session closed -> {}", getType(session), session.getRemoteServer()); + log.info("[{}][{}] session closed", session.getRemoteServer(), getType(session)); manager.tell(new RpcSessionDisconnectedMsg(session.isClient(), session.getRemoteServer()), self); } @Override public void onReceiveClusterGrpcMsg(GrpcSession session, ClusterAPIProtos.ClusterMessage clusterMessage) { - log.trace("{} Service [{}] received session actor msg {}", getType(session), - session.getRemoteServer(), - clusterMessage); + log.trace("Received session actor msg from [{}][{}]: {}", session.getRemoteServer(), getType(session), clusterMessage); service.onReceivedMsg(session.getRemoteServer(), clusterMessage); } @Override public void onError(GrpcSession session, Throwable t) { - log.warn("{} session got error -> {}", getType(session), session.getRemoteServer(), t); + log.warn("[{}][{}] session got error -> {}", session.getRemoteServer(), getType(session), t); manager.tell(new RpcSessionClosedMsg(session.isClient(), session.getRemoteServer()), self); session.close(); } 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 922d2ba988..9fa087e630 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 @@ -36,7 +36,6 @@ import java.util.*; /** * @author Andrew Shvayka */ -@Slf4j public class RpcManagerActor extends ContextAwareActor { private final Map sessionActors; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java index e0f7e4ad26..e4e82c55e5 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java @@ -69,6 +69,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor(); this.nodeRoutes = new HashMap<>(); this.service = systemContext.getRuleChainService(); + this.ruleChainName = ruleChainId.toString(); } @Override - public void start(ActorContext context) throws Exception { + public String getComponentName() { + return null; + } + + @Override + public void start(ActorContext context) { if (!started) { RuleChain ruleChain = service.findRuleChainById(entityId); + ruleChainName = ruleChain.getName(); List ruleNodeList = service.getRuleChainNodes(entityId); + log.trace("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size()); // Creating and starting the actors; for (RuleNode ruleNode : ruleNodeList) { + log.trace("[{}][{}] Creating rule node [{}]: {}", tenantId, entityId, ruleNode.getName(), ruleNode); ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); } @@ -98,16 +108,19 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor ruleNodeList = service.getRuleChainNodes(entityId); - + log.trace("[{}][{}] Updating rule chain with {} nodes", tenantId, entityId, ruleNodeList.size()); for (RuleNode ruleNode : ruleNodeList) { RuleNodeCtx existing = nodeActors.get(ruleNode.getId()); if (existing == null) { + log.trace("[{}][{}] Creating rule node [{}]: {}", tenantId, entityId, ruleNode.getName(), ruleNode); ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode); nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode)); } else { + log.trace("[{}][{}] Updating rule node [{}]: {}", tenantId, entityId, ruleNode.getName(), ruleNode); existing.setSelf(ruleNode); existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self); } @@ -116,6 +129,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet()); List removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList()); removedRules.forEach(ruleNodeId -> { + log.trace("[{}][{}] Removing rule node [{}]", tenantId, entityId, ruleNodeId); RuleNodeCtx removed = nodeActors.remove(ruleNodeId); removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self); }); @@ -124,7 +138,8 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor relations = service.getRuleNodeRelations(ruleNode.getId()); + log.trace("[{}][{}][{}] Processing rule node relations [{}]", tenantId, entityId, ruleNode.getId(), relations.size()); if (relations.size() == 0) { nodeRoutes.put(ruleNode.getId(), Collections.emptyList()); } else { for (EntityRelation relation : relations) { + log.trace("[{}][{}][{}] Processing rule node relation [{}]", tenantId, entityId, ruleNode.getId(), relation.getTo()); if (relation.getTo().getEntityType() == EntityType.RULE_NODE) { RuleNodeCtx ruleNodeCtx = nodeActors.get(new RuleNodeId(relation.getTo().getId())); if (ruleNodeCtx == null) { @@ -232,17 +249,20 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor { private final RuleChainId ruleChainId; @@ -62,7 +60,9 @@ public class RuleNodeActor extends ComponentActor componentClazz = Class.forName(ruleNode.getType()); TbNode tbNode = (TbNode) (componentClazz.newInstance()); diff --git a/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java b/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java index ff54fc8fa1..1f084e1bb7 100644 --- a/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java @@ -31,7 +31,6 @@ import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; /** * @author Andrew Shvayka */ -@Slf4j public abstract class ComponentActor> extends ContextAwareActor { private long lastPersistedErrorTs = 0L; @@ -54,6 +53,7 @@ public abstract class ComponentActor getErrorPersistFrequency()) { diff --git a/application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java b/application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java index c9f1dcde3e..1c7e2d2664 100644 --- a/application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java @@ -17,15 +17,16 @@ package org.thingsboard.server.actors.service; import akka.actor.Terminated; import akka.actor.UntypedActor; -import akka.event.Logging; -import akka.event.LoggingAdapter; -import lombok.extern.slf4j.Slf4j; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.common.msg.TbActorMsg; -@Slf4j + public abstract class ContextAwareActor extends UntypedActor { + protected final Logger log = LoggerFactory.getLogger(getClass()); + public static final int ENTITY_PACK_LIMIT = 1024; protected final ActorSystemContext systemContext; diff --git a/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java b/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java index 9e5d063223..46a76e6d43 100644 --- a/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java @@ -44,6 +44,8 @@ public abstract class ComponentMsgProcessor extends Abstract this.entityId = id; } + public abstract String getComponentName(); + public abstract void start(ActorContext context) throws Exception; public abstract void stop(ActorContext context) throws Exception; 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 b76e2984ae..6c65dbe531 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 @@ -48,7 +48,6 @@ import scala.concurrent.duration.Duration; import java.util.HashMap; import java.util.Map; -@Slf4j public class TenantActor extends RuleChainManagerActor { private final TenantId tenantId; diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index db31bda942..957624c4da 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -71,7 +71,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { private int maxErrors; private TbKafkaRequestTemplate kafkaTemplate; - protected Map scriptIdToBodysMap = new ConcurrentHashMap<>(); + private Map scriptIdToBodysMap = new ConcurrentHashMap<>(); @PostConstruct public void init() { @@ -100,7 +100,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { responseBuilder.settings(kafkaSettings); responseBuilder.topic(responseTopicPrefix + "." + nodeIdProvider.getNodeId()); responseBuilder.clientId("js-" + nodeIdProvider.getNodeId()); - responseBuilder.groupId("rule-engine-node"); + responseBuilder.groupId("rule-engine-node-" + nodeIdProvider.getNodeId()); responseBuilder.autoCommit(true); responseBuilder.autoCommitIntervalMs(autoCommitInterval); responseBuilder.decoder(new RemoteJsResponseDecoder()); 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 6c903cc955..a8ab2cd625 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 @@ -149,6 +149,7 @@ public class RemoteRuleEngineTransportService implements RuleEngineTransportServ records.forEach(record -> { try { ToRuleEngineMsg toRuleEngineMsg = ruleEngineConsumer.decode(record); + log.trace("Forwarding message to rule engine {}", toRuleEngineMsg); if (toRuleEngineMsg.hasToDeviceActorMsg()) { forwardToDeviceActor(toRuleEngineMsg.getToDeviceActorMsg()); } @@ -175,18 +176,21 @@ public class RemoteRuleEngineTransportService implements RuleEngineTransportServ @Override public void process(String nodeId, DeviceActorToTransportMsg msg, Runnable onSuccess, Consumer onFailure) { - notificationsProducer.send(notificationsTopic + "." + nodeId, - new UUID(msg.getSessionIdMSB(), msg.getSessionIdLSB()).toString(), - ToTransportMsg.newBuilder().setToDeviceSessionMsg(msg).build() - , new QueueCallbackAdaptor(onSuccess, onFailure)); + String topic = notificationsTopic + "." + nodeId; + UUID sessionId = new UUID(msg.getSessionIdMSB(), msg.getSessionIdLSB()); + ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setToDeviceSessionMsg(msg).build(); + log.trace("[{}][{}] Pushing session data to topic: {}", topic, sessionId, transportMsg); + notificationsProducer.send(topic, sessionId.toString(), transportMsg, new QueueCallbackAdaptor(onSuccess, onFailure)); } private void forwardToDeviceActor(TransportToDeviceActorMsg toDeviceActorMsg) { TransportToDeviceActorMsgWrapper wrapper = new TransportToDeviceActorMsgWrapper(toDeviceActorMsg); Optional address = routingService.resolveById(wrapper.getDeviceId()); if (address.isPresent()) { + log.trace("[{}] Pushing message to remote server: {}", address.get(), toDeviceActorMsg); rpcService.tell(encodingService.convertToProtoDataMessage(address.get(), wrapper)); } else { + log.trace("Pushing message to local server: {}", toDeviceActorMsg); actorContext.getAppActor().tell(wrapper, ActorRef.noSender()); } } 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 bd42f310a6..ee652f455c 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 @@ -77,9 +77,10 @@ public class TBKafkaProducerTemplate { result.all().get(); } catch (Exception e) { if ((e instanceof TopicExistsException) || (e.getCause() != null && e.getCause() instanceof TopicExistsException)) { - log.trace("[{}] Topic already exists: ", defaultTopic); + log.trace("[{}] Topic already exists.", defaultTopic); } else { - log.trace("[{}] Failed to create topic: {}", defaultTopic, e.getMessage(), e); + log.info("[{}] Failed to create topic: {}", defaultTopic, e.getMessage(), e); + throw new RuntimeException(e); } } //Maybe this should not be cached, but we don't plan to change size of partitions diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java index 77ad0331ab..86fda76624 100644 --- a/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java @@ -23,6 +23,9 @@ import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.admin.CreateTopicsResult; import org.apache.kafka.clients.admin.NewTopic; import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.clients.producer.Callback; +import org.apache.kafka.clients.producer.RecordMetadata; +import org.apache.kafka.common.errors.TopicExistsException; import org.apache.kafka.common.header.Header; import org.apache.kafka.common.header.internals.RecordHeader; @@ -83,7 +86,13 @@ public class TbKafkaRequestTemplate extends AbstractTbKafkaTe CreateTopicsResult result = admin.createTopic(new NewTopic(responseTemplate.getTopic(), 1, (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. ", responseTemplate.getTopic()); + } else { + log.info("[{}] Failed to create topic: {}", responseTemplate.getTopic(), e.getMessage(), e); + throw new RuntimeException(e); + } + } this.requestTemplate.init(); tickTs = System.currentTimeMillis(); @@ -93,6 +102,7 @@ public class TbKafkaRequestTemplate extends AbstractTbKafkaTe while (!stopped) { ConsumerRecords responses = responseTemplate.poll(Duration.ofMillis(pollInterval)); responses.forEach(response -> { + log.trace("Received response to Kafka Template request: {}", response); Header requestIdHeader = response.headers().lastHeader(TbKafkaSettings.REQUEST_ID_HEADER); Response decodedResponse = null; UUID requestId = null; @@ -160,7 +170,13 @@ public class TbKafkaRequestTemplate extends AbstractTbKafkaTe SettableFuture future = SettableFuture.create(); pendingRequests.putIfAbsent(requestId, new ResponseMetaData<>(tickTs + maxRequestTimeout, future)); request = requestTemplate.enrich(request, responseTemplate.getTopic(), requestId); - requestTemplate.send(key, request, headers, null); + requestTemplate.send(key, request, headers, (metadata, exception) -> { + if (exception != null) { + log.trace("[{}] Failed to post the request", requestId, exception); + } else { + log.trace("[{}] Posted the request", requestId, metadata); + } + }); return future; } diff --git a/msa/black-box-tests/README.md b/msa/black-box-tests/README.md index c26d9c5fc6..044fe00255 100644 --- a/msa/black-box-tests/README.md +++ b/msa/black-box-tests/README.md @@ -18,4 +18,6 @@ As result, in REPOSITORY column, next images should be present: - Run the black box tests in the [msa/black-box-tests](../black-box-tests) directory: - mvn clean install -DblackBoxTests.skip=false \ No newline at end of file + mvn clean install -DblackBoxTests.skip=false + + diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java index 5ebab780be..4c3ac8362c 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java @@ -33,6 +33,9 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.ssl.SSLContexts; import org.junit.*; +import org.junit.rules.TestRule; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; import org.thingsboard.client.tools.RestClient; import org.thingsboard.server.common.data.Device; @@ -60,6 +63,33 @@ public abstract class AbstractContainerTest { restClient.getRestTemplate().setRequestFactory(getRequestFactoryForSelfSignedCert()); } + @Rule + public TestRule watcher = new TestWatcher() { + protected void starting(Description description) { + log.info("================================================="); + log.info("STARTING TEST: {}" , description.getMethodName()); + log.info("================================================="); + } + + /** + * Invoked when a test succeeds + */ + protected void succeeded(Description description) { + log.info("================================================="); + log.info("SUCCEEDED TEST: {}" , description.getMethodName()); + log.info("================================================="); + } + + /** + * Invoked when a test fails + */ + protected void failed(Throwable e, Description description) { + log.info("================================================="); + log.info("FAILED TEST: {}" , description.getMethodName(), e); + log.info("================================================="); + } + }; + protected Device createDevice(String name) { return restClient.createDevice(name + RandomStringUtils.randomAlphanumeric(7), "DEFAULT"); } @@ -82,6 +112,7 @@ public abstract class AbstractContainerTest { JsonObject wsRequest = new JsonObject(); wsRequest.add(property.toString(), cmd); wsClient.send(wsRequest.toString()); + wsClient.waitForFirstReply(); return wsClient; } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/WsClient.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/WsClient.java index a9835ed618..fa3a63a1e6 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/WsClient.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/WsClient.java @@ -31,9 +31,11 @@ public class WsClient extends WebSocketClient { private static final ObjectMapper mapper = new ObjectMapper(); private WsTelemetryResponse message; - private CountDownLatch latch = new CountDownLatch(1);; + private volatile boolean firstReplyReceived; + private CountDownLatch firstReply = new CountDownLatch(1); + private CountDownLatch latch = new CountDownLatch(1); - public WsClient(URI serverUri) { + WsClient(URI serverUri) { super(serverUri); } @@ -43,14 +45,19 @@ public class WsClient extends WebSocketClient { @Override public void onMessage(String message) { - try { - WsTelemetryResponse response = mapper.readValue(message, WsTelemetryResponse.class); - if (!response.getData().isEmpty()) { - this.message = response; - latch.countDown(); + if (!firstReplyReceived) { + firstReplyReceived = true; + firstReply.countDown(); + } else { + try { + WsTelemetryResponse response = mapper.readValue(message, WsTelemetryResponse.class); + if (!response.getData().isEmpty()) { + this.message = response; + latch.countDown(); + } + } catch (IOException e) { + log.error("ws message can't be read"); } - } catch (IOException e) { - log.error("ws message can't be read"); } } @@ -73,4 +80,13 @@ public class WsClient extends WebSocketClient { } return null; } + + void waitForFirstReply() { + try { + firstReply.await(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.error("Timeout, ws message wasn't received"); + throw new RuntimeException(e); + } + } } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java index d889d2c52e..9918963a1c 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java @@ -28,6 +28,9 @@ import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.junit.*; +import org.junit.rules.TestRule; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; import org.springframework.core.ParameterizedTypeReference; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; @@ -65,6 +68,7 @@ public class MqttClientTest extends AbstractContainerTest { MqttClient mqttClient = getMqttClient(deviceCredentials, null); mqttClient.publish("v1/devices/me/telemetry", Unpooled.wrappedBuffer(createPayload().toString().getBytes())); WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); + log.info("Received telemetry: {}", actualLatestTelemetry); wsClient.closeBlocking(); Assert.assertEquals(4, actualLatestTelemetry.getData().size()); @@ -91,6 +95,7 @@ public class MqttClientTest extends AbstractContainerTest { MqttClient mqttClient = getMqttClient(deviceCredentials, null); mqttClient.publish("v1/devices/me/telemetry", Unpooled.wrappedBuffer(createPayload(ts).toString().getBytes())); WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); + log.info("Received telemetry: {}", actualLatestTelemetry); wsClient.closeBlocking(); Assert.assertEquals(4, actualLatestTelemetry.getData().size()); @@ -120,6 +125,7 @@ public class MqttClientTest extends AbstractContainerTest { clientAttributes.addProperty("attr4", 73); mqttClient.publish("v1/devices/me/attributes", Unpooled.wrappedBuffer(clientAttributes.toString().getBytes())); WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); + log.info("Received telemetry: {}", actualLatestTelemetry); wsClient.closeBlocking(); Assert.assertEquals(4, actualLatestTelemetry.getData().size()); @@ -168,6 +174,7 @@ public class MqttClientTest extends AbstractContainerTest { mqttClient.publish("v1/devices/me/attributes/request/" + new Random().nextInt(100), Unpooled.wrappedBuffer(request.toString().getBytes())); MqttEvent event = listener.getEvents().poll(10, TimeUnit.SECONDS); AttributesResponse attributes = mapper.readValue(Objects.requireNonNull(event).getMessage(), AttributesResponse.class); + log.info("Received telemetry: {}", attributes); Assert.assertEquals(1, attributes.getClient().size()); Assert.assertEquals(clientAttributeValue, attributes.getClient().get("clientAttr")); @@ -281,6 +288,7 @@ public class MqttClientTest extends AbstractContainerTest { // Create a new root rule chain RuleChainId ruleChainId = createRootRuleChainForRpcResponse(); + TimeUnit.SECONDS.sleep(3); // Send the request to the server JsonObject clientRequest = new JsonObject(); clientRequest.addProperty("method", "getResponse"); @@ -360,12 +368,12 @@ public class MqttClientTest extends AbstractContainerTest { return defaultRuleChain.get().getId(); } - private MqttClient getMqttClient(DeviceCredentials deviceCredentials, MqttMessageListener listener) throws InterruptedException { + private MqttClient getMqttClient(DeviceCredentials deviceCredentials, MqttMessageListener listener) throws InterruptedException, ExecutionException { MqttClientConfig clientConfig = new MqttClientConfig(); clientConfig.setClientId("MQTT client from test"); clientConfig.setUsername(deviceCredentials.getCredentialsId()); MqttClient mqttClient = MqttClient.create(clientConfig, listener); - mqttClient.connect("localhost", 1883).sync(); + mqttClient.connect("localhost", 1883).get(); return mqttClient; } From 600da893858415fb0cb80111629126d845e0e0f7 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Thu, 1 Nov 2018 18:39:07 +0200 Subject: [PATCH 15/35] Refactoring of Attribute keys --- .../model/sql/AttributeKvCompositeKey.java | 15 ++++++++++ .../dao/model/sql/AttributeKvEntity.java | 28 +++++-------------- .../sql/attributes/AttributeKvRepository.java | 11 ++++++-- .../dao/sql/attributes/JpaAttributeDao.java | 10 ++----- 4 files changed, 32 insertions(+), 32 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvCompositeKey.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvCompositeKey.java index 3001e1279f..1c9f17a46d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvCompositeKey.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvCompositeKey.java @@ -20,14 +20,29 @@ import lombok.Data; import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.EntityType; +import javax.persistence.Column; +import javax.persistence.Embeddable; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import java.io.Serializable; +import static org.thingsboard.server.dao.model.ModelConstants.ATTRIBUTE_KEY_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.ATTRIBUTE_TYPE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_ID_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.ENTITY_TYPE_COLUMN; + @Data @AllArgsConstructor @NoArgsConstructor +@Embeddable public class AttributeKvCompositeKey implements Serializable { + @Enumerated(EnumType.STRING) + @Column(name = ENTITY_TYPE_COLUMN) private EntityType entityType; + @Column(name = ENTITY_ID_COLUMN) private String entityId; + @Column(name = ATTRIBUTE_TYPE_COLUMN) private String attributeType; + @Column(name = ATTRIBUTE_KEY_COLUMN) private String attributeKey; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java index 587a31485e..515c86c059 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AttributeKvEntity.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.dao.model.ToData; import javax.persistence.Column; +import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.EnumType; import javax.persistence.Enumerated; @@ -48,25 +49,10 @@ import static org.thingsboard.server.dao.model.ModelConstants.STRING_VALUE_COLUM @Data @Entity @Table(name = "attribute_kv") -@IdClass(AttributeKvCompositeKey.class) public class AttributeKvEntity implements ToData, Serializable { - @Id - @Enumerated(EnumType.STRING) - @Column(name = ENTITY_TYPE_COLUMN) - private EntityType entityType; - - @Id - @Column(name = ENTITY_ID_COLUMN) - private String entityId; - - @Id - @Column(name = ATTRIBUTE_TYPE_COLUMN) - private String attributeType; - - @Id - @Column(name = ATTRIBUTE_KEY_COLUMN) - private String attributeKey; + @EmbeddedId + private AttributeKvCompositeKey id; @Column(name = BOOLEAN_VALUE_COLUMN) private Boolean booleanValue; @@ -87,13 +73,13 @@ public class AttributeKvEntity implements ToData, Serializable public AttributeKvEntry toData() { KvEntry kvEntry = null; if (strValue != null) { - kvEntry = new StringDataEntry(attributeKey, strValue); + kvEntry = new StringDataEntry(id.getAttributeKey(), strValue); } else if (booleanValue != null) { - kvEntry = new BooleanDataEntry(attributeKey, booleanValue); + kvEntry = new BooleanDataEntry(id.getAttributeKey(), booleanValue); } else if (doubleValue != null) { - kvEntry = new DoubleDataEntry(attributeKey, doubleValue); + kvEntry = new DoubleDataEntry(id.getAttributeKey(), doubleValue); } else if (longValue != null) { - kvEntry = new LongDataEntry(attributeKey, longValue); + kvEntry = new LongDataEntry(id.getAttributeKey(), longValue); } return new BaseAttributeKvEntry(kvEntry, lastUpdateTs); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java index c76cefef19..d7168864a0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java @@ -15,7 +15,9 @@ */ package org.thingsboard.server.dao.sql.attributes; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; import org.thingsboard.server.dao.model.sql.AttributeKvEntity; @@ -26,8 +28,11 @@ import java.util.List; @SqlDao public interface AttributeKvRepository extends CrudRepository { - List findAllByEntityTypeAndEntityIdAndAttributeType(EntityType entityType, - String entityId, - String attributeType); + @Query("SELECT a FROM AttributeKvEntity a WHERE a.id.entityType = :entityType " + + "AND a.id.entityId = :entityId " + + "AND a.id.attributeType = :attributeType") + List findAllByEntityTypeAndEntityIdAndAttributeType(@Param("entityType") EntityType entityType, + @Param("entityId") String entityId, + @Param("attributeType") String attributeType); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java index 0dabf4cfb5..4ac0c0ca38 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java @@ -79,10 +79,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl @Override public ListenableFuture save(EntityId entityId, String attributeType, AttributeKvEntry attribute) { AttributeKvEntity entity = new AttributeKvEntity(); - entity.setEntityType(entityId.getEntityType()); - entity.setEntityId(fromTimeUUID(entityId.getId())); - entity.setAttributeType(attributeType); - entity.setAttributeKey(attribute.getKey()); + entity.setId(new AttributeKvCompositeKey(entityId.getEntityType(), fromTimeUUID(entityId.getId()), attributeType, attribute.getKey())); entity.setLastUpdateTs(attribute.getLastUpdateTs()); entity.setStrValue(attribute.getStrValue().orElse(null)); entity.setDoubleValue(attribute.getDoubleValue().orElse(null)); @@ -100,10 +97,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl .stream() .map(key -> { AttributeKvEntity entityToDelete = new AttributeKvEntity(); - entityToDelete.setEntityType(entityId.getEntityType()); - entityToDelete.setEntityId(fromTimeUUID(entityId.getId())); - entityToDelete.setAttributeType(attributeType); - entityToDelete.setAttributeKey(key); + entityToDelete.setId(new AttributeKvCompositeKey(entityId.getEntityType(), fromTimeUUID(entityId.getId()), attributeType, key)); return entityToDelete; }).collect(Collectors.toList()); From 5f40dcacdbd8c5524db2031a65d775bd3bf67de0 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 1 Nov 2018 18:46:18 +0200 Subject: [PATCH 16/35] Fix docker env files. Improve logging. --- .../service/script/RemoteJsInvokeService.java | 1 + .../server/kafka/TbKafkaRequestTemplate.java | 9 ++++++++- docker/.env | 2 -- docker/kafka.env | 2 +- .../server/msa/ThingsBoardDbInstaller.java | 14 +++++++++----- msa/js-executor/server.js | 18 ++++++++++++++++++ 6 files changed, 37 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index db31bda942..05ed2853b5 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -136,6 +136,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { .setCompileRequest(jsRequest) .build(); + log.trace("Post compile request for scriptId [{}]", scriptId); ListenableFuture future = kafkaTemplate.post(scriptId.toString(), jsRequestWrapper); return Futures.transform(future, response -> { JsInvokeProtos.JsCompileResponse compilationResult = response.getCompileResponse(); diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java index 77ad0331ab..b0aca0f54d 100644 --- a/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaRequestTemplate.java @@ -92,6 +92,9 @@ public class TbKafkaRequestTemplate extends AbstractTbKafkaTe long nextCleanupMs = 0L; while (!stopped) { ConsumerRecords responses = responseTemplate.poll(Duration.ofMillis(pollInterval)); + if (responses.count() > 0) { + log.trace("Polling responses completed, consumer records count [{}]", responses.count()); + } responses.forEach(response -> { Header requestIdHeader = response.headers().lastHeader(TbKafkaSettings.REQUEST_ID_HEADER); Response decodedResponse = null; @@ -109,6 +112,7 @@ public class TbKafkaRequestTemplate extends AbstractTbKafkaTe if (requestId == null) { log.error("[{}] Missing requestId in header and body", response); } else { + log.trace("[{}] Response received", requestId); ResponseMetaData expectedResponse = pendingRequests.remove(requestId); if (expectedResponse == null) { log.trace("[{}] Invalid or stale request", requestId); @@ -132,6 +136,7 @@ public class TbKafkaRequestTemplate extends AbstractTbKafkaTe if (kv.getValue().expTime < tickTs) { ResponseMetaData staleRequest = pendingRequests.remove(kv.getKey()); if (staleRequest != null) { + log.trace("[{}] Request timeout detected, expTime [{}], tickTs [{}]", kv.getKey(), staleRequest.expTime, tickTs); staleRequest.future.setException(new TimeoutException()); } } @@ -158,8 +163,10 @@ public class TbKafkaRequestTemplate extends AbstractTbKafkaTe headers.add(new RecordHeader(TbKafkaSettings.REQUEST_ID_HEADER, uuidToBytes(requestId))); headers.add(new RecordHeader(TbKafkaSettings.RESPONSE_TOPIC_HEADER, stringToBytes(responseTemplate.getTopic()))); SettableFuture future = SettableFuture.create(); - pendingRequests.putIfAbsent(requestId, new ResponseMetaData<>(tickTs + maxRequestTimeout, future)); + ResponseMetaData responseMetaData = new ResponseMetaData<>(tickTs + maxRequestTimeout, future); + pendingRequests.putIfAbsent(requestId, responseMetaData); request = requestTemplate.enrich(request, responseTemplate.getTopic(), requestId); + log.trace("[{}] Sending request, key [{}], expTime [{}]", requestId, key, responseMetaData.expTime); requestTemplate.send(key, request, headers, null); return future; } diff --git a/docker/.env b/docker/.env index 0138501ebc..ef0bf40316 100644 --- a/docker/.env +++ b/docker/.env @@ -16,5 +16,3 @@ TB_VERSION=latest DATABASE=postgres LOAD_BALANCER_NAME=haproxy-certbot - -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/docker/kafka.env b/docker/kafka.env index 69fbdf6116..87dad076d2 100644 --- a/docker/kafka.env +++ b/docker/kafka.env @@ -4,7 +4,7 @@ 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_CREATE_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 KAFKA_AUTO_CREATE_TOPICS_ENABLE=false KAFKA_LOG_RETENTION_BYTES=1073741824 KAFKA_LOG_SEGMENT_BYTES=268435456 diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java index 0a902b9c5e..fef69b5163 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java @@ -87,10 +87,17 @@ public class ThingsBoardDbInstaller extends ExternalResource { @Override protected void after() { - File tbLogsDir = new File("./target/tb-logs/"); + copyLogs(tbLogVolume, "./target/tb-logs/"); + + dockerCompose.withCommand("volume rm -f " + postgresDataVolume + " " + tbLogVolume); + dockerCompose.invokeDocker(); + } + + private void copyLogs(String volumeName, String targetDir) { + File tbLogsDir = new File(targetDir); tbLogsDir.mkdirs(); - dockerCompose.withCommand("run -d --rm --name tb-logs-container -v " + tbLogVolume + ":/root alpine tail -f /dev/null"); + dockerCompose.withCommand("run -d --rm --name tb-logs-container -v " + volumeName + ":/root alpine tail -f /dev/null"); dockerCompose.invokeDocker(); dockerCompose.withCommand("cp tb-logs-container:/root/. "+tbLogsDir.getAbsolutePath()); @@ -98,9 +105,6 @@ public class ThingsBoardDbInstaller extends ExternalResource { dockerCompose.withCommand("rm -f tb-logs-container"); dockerCompose.invokeDocker(); - - dockerCompose.withCommand("volume rm -f " + postgresDataVolume + " " + tbLogVolume); - dockerCompose.invokeDocker(); } } diff --git a/msa/js-executor/server.js b/msa/js-executor/server.js index 03fac2ebe0..17f70cbf9f 100644 --- a/msa/js-executor/server.js +++ b/msa/js-executor/server.js @@ -45,6 +45,24 @@ var kafkaClient; kafkaRequestTopic ); + consumer.on('error', (err) => { + logger.error('Unexpected kafka consumer error: %s', err.message); + logger.error(err.stack); + }); + + consumer.on('offsetOutOfRange', (err) => { + logger.error('Offset out of range error: %s', err.message); + logger.error(err.stack); + }); + + consumer.on('rebalancing', () => { + logger.info('Rebalancing event received.'); + }) + + consumer.on('rebalanced', () => { + logger.info('Rebalanced event received.'); + }); + var producer = new Producer(kafkaClient); producer.on('error', (err) => { logger.error('Unexpected kafka producer error: %s', err.message); From f6b00b35db3b14d40c99f55adcb0eebc8d4289d6 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 1 Nov 2018 20:52:48 +0200 Subject: [PATCH 17/35] Improve docker compose. Improve TB shutdown logic. --- .../cluster/discovery/ZkDiscoveryService.java | 22 +++++++++++++++++++ .../server/kafka/TbKafkaResponseTemplate.java | 5 +++++ docker/docker-compose.yml | 2 ++ 3 files changed, 29 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java index 997058ce41..f5321e5806 100644 --- a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java @@ -21,6 +21,7 @@ import org.apache.commons.lang3.SerializationException; import org.apache.commons.lang3.SerializationUtils; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; +import org.apache.curator.framework.imps.CuratorFrameworkState; import org.apache.curator.framework.recipes.cache.ChildData; import org.apache.curator.framework.recipes.cache.PathChildrenCache; import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; @@ -98,6 +99,8 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi private PathChildrenCache cache; private String nodePath; + private volatile boolean stopped = false; + @PostConstruct public void init() { log.info("Initializing..."); @@ -118,6 +121,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi cache.start(); } catch (Exception e) { log.error("Failed to connect to ZK: {}", e.getMessage(), e); + CloseableUtils.closeQuietly(cache); CloseableUtils.closeQuietly(client); throw new RuntimeException(e); } @@ -125,7 +129,9 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi @PreDestroy public void destroy() { + stopped = true; unpublishCurrentServer(); + CloseableUtils.closeQuietly(cache); CloseableUtils.closeQuietly(client); log.info("Stopped discovery service"); } @@ -228,6 +234,14 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi @Override public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { + if (stopped) { + log.debug("Ignoring application ready event. Service is stopped."); + return; + } + if (client.getState() != CuratorFrameworkState.STARTED) { + log.debug("Ignoring application ready event, ZK client is not started, ZK client state [{}]", client.getState()); + return; + } publishCurrentServer(); getOtherServers().forEach( server -> log.info("Found active server: [{}:{}]", server.getHost(), server.getPort()) @@ -236,6 +250,14 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi @Override public void childEvent(CuratorFramework curatorFramework, PathChildrenCacheEvent pathChildrenCacheEvent) throws Exception { + if (stopped) { + log.debug("Ignoring {}. Service is stopped.", pathChildrenCacheEvent); + return; + } + if (client.getState() != CuratorFrameworkState.STARTED) { + log.debug("Ignoring {}, ZK client is not started, ZK client state [{}]", pathChildrenCacheEvent, client.getState()); + return; + } ChildData data = pathChildrenCacheEvent.getData(); if (data == null) { log.debug("Ignoring {} due to empty child data", pathChildrenCacheEvent); diff --git a/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaResponseTemplate.java b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaResponseTemplate.java index 4c23ac2211..e10cd3c917 100644 --- a/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaResponseTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/kafka/TbKafkaResponseTemplate.java @@ -18,6 +18,7 @@ package org.thingsboard.server.kafka; import lombok.Builder; import lombok.extern.slf4j.Slf4j; import org.apache.kafka.clients.consumer.ConsumerRecords; +import org.apache.kafka.common.errors.InterruptException; import org.apache.kafka.common.header.Header; import org.apache.kafka.common.header.internals.RecordHeader; @@ -127,6 +128,10 @@ public class TbKafkaResponseTemplate extends AbstractTbKafkaT log.warn("[{}] Failed to process the request: {}", requestId, request, e); } }); + } catch (InterruptException ie) { + if (!stopped) { + log.warn("Fetching data from kafka was interrupted.", ie); + } } catch (Throwable e) { log.warn("Failed to obtain messages from queue.", e); try { diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 0a09d8a024..b9d8fd1a54 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -64,6 +64,7 @@ services: depends_on: - kafka - redis + - tb-js-executor tb2: restart: always image: "${DOCKER_REPO}/${TB_NODE_DOCKER_NAME}:${TB_VERSION}" @@ -84,6 +85,7 @@ services: depends_on: - kafka - redis + - tb-js-executor tb-mqtt-transport1: restart: always image: "${DOCKER_REPO}/${MQTT_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" From d51b76fd476ec90d697d5b232e51750884e09689 Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Fri, 2 Nov 2018 15:05:11 +0200 Subject: [PATCH 18/35] enable/disable type cast for telemetry/attributes json --- application/src/main/resources/thingsboard.yml | 4 ++++ .../common/transport/adaptor/JsonConverter.java | 14 +++++++++----- .../transport/adaptor/JsonConverterConfig.java | 16 ++++++++++++++++ .../src/main/resources/tb-coap-transport.yml | 4 ++++ .../src/main/resources/tb-http-transport.yml | 4 ++++ .../src/main/resources/tb-mqtt-transport.yml | 4 ++++ 6 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 51a59c9721..cb7beb6350 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -443,3 +443,7 @@ transport: bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" bind_port: "${COAP_BIND_PORT:5683}" timeout: "${COAP_TIMEOUT:10000}" + +json: + # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON + type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:false}" \ No newline at end of file diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index b497fe08ea..d52a896f10 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -59,6 +59,8 @@ public class JsonConverter { private static final String CAN_T_PARSE_VALUE = "Can't parse value: "; private static final String DEVICE_PROPERTY = "device"; + private static boolean isTypeCastEnabled = true; + public static PostTelemetryMsg convertToTelemetryProto(JsonElement jsonObject) throws JsonSyntaxException { long systemTs = System.currentTimeMillis(); PostTelemetryMsg.Builder builder = PostTelemetryMsg.newBuilder(); @@ -129,10 +131,10 @@ public class JsonConverter { if (element.isJsonPrimitive()) { JsonPrimitive value = element.getAsJsonPrimitive(); if (value.isString()) { - if(NumberUtils.isParsable(value.getAsString())) { + if(isTypeCastEnabled && NumberUtils.isParsable(value.getAsString())) { try { result.add(buildNumericKeyValueProto(value, valueEntry.getKey())); - } catch (Throwable th) { + } catch (RuntimeException th) { result.add(KeyValueProto.newBuilder().setKey(valueEntry.getKey()).setType(KeyValueType.STRING_V) .setStringV(value.getAsString()).build()); } @@ -387,10 +389,10 @@ public class JsonConverter { if (element.isJsonPrimitive()) { JsonPrimitive value = element.getAsJsonPrimitive(); if (value.isString()) { - if(NumberUtils.isParsable(value.getAsString())) { + if(isTypeCastEnabled && NumberUtils.isParsable(value.getAsString())) { try { parseNumericValue(result, valueEntry, value); - } catch (Throwable th) { + } catch (RuntimeException th) { result.add(new StringDataEntry(valueEntry.getKey(), value.getAsString())); } } else { @@ -451,5 +453,7 @@ public class JsonConverter { } } - + public static void setTypeCastEnabled(boolean enabled) { + isTypeCastEnabled = enabled; + } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java new file mode 100644 index 0000000000..8b3bda3f98 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java @@ -0,0 +1,16 @@ +package org.thingsboard.server.common.transport.adaptor; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +@Slf4j +public class JsonConverterConfig { + + @Value("${json.type_cast_enabled}") + public void setIsJsonTypeCastEnabled(boolean jsonTypeCastEnabled) { + JsonConverter.setTypeCastEnabled(jsonTypeCastEnabled); + log.info("JSON type cast enabled = {}", jsonTypeCastEnabled); + } +} diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index f96bcbe6d6..d20bc79ea4 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -52,3 +52,7 @@ kafka: topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}" poll_interval: "${TB_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}" auto_commit_interval: "${TB_TRANSPORT_NOTIFICATIONS_AUTO_COMMIT_INTERVAL_MS:100}" + +json: + # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON + type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" \ No newline at end of file diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index 6d593ed1b4..f5f7855db7 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -53,3 +53,7 @@ kafka: topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}" poll_interval: "${TB_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}" auto_commit_interval: "${TB_TRANSPORT_NOTIFICATIONS_AUTO_COMMIT_INTERVAL_MS:100}" + +json: + # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON + type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" \ No newline at end of file diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index e7f8942e77..af1b6f01ac 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -72,3 +72,7 @@ kafka: topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}" poll_interval: "${TB_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}" auto_commit_interval: "${TB_TRANSPORT_NOTIFICATIONS_AUTO_COMMIT_INTERVAL_MS:100}" + +json: + # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON + type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" \ No newline at end of file From e724dcd0885380fa016a1a9373f4fe05af171d70 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 2 Nov 2018 15:11:42 +0200 Subject: [PATCH 19/35] Added shutdown of all rule chains on tenant deletion --- .../server/actors/app/AppActor.java | 18 +++++++++++++++--- .../actors/ruleChain/RuleChainActor.java | 1 + .../RuleChainActorMessageProcessor.java | 10 ++++++---- .../server/actors/tenant/TenantActor.java | 6 ++++++ .../server/controller/TenantController.java | 3 +++ 5 files changed, 31 insertions(+), 7 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index 6df37d6731..915704eedd 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -34,9 +34,11 @@ import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.actors.shared.rulechain.SystemRuleChainManager; import org.thingsboard.server.actors.tenant.TenantActor; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageDataIterable; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.aware.TenantAwareMsg; import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; @@ -129,7 +131,7 @@ public class AppActor extends RuleChainManagerActor { private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) { if (SYSTEM_TENANT.equals(msg.getTenantId())) { - log.warn("[{}] Invalid service to rule engine msg called. System messages are not supported yet", SYSTEM_TENANT); + log.warn("[{}] Invalid service to rule engine msg called. System messages are not supported yet: {}", SYSTEM_TENANT, msg); } else { getOrCreateTenantActor(msg.getTenantId()).tell(msg, self()); } @@ -142,11 +144,21 @@ public class AppActor extends RuleChainManagerActor { } private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { - ActorRef target; + ActorRef target = null; if (SYSTEM_TENANT.equals(msg.getTenantId())) { target = getEntityActorRef(msg.getEntityId()); } else { - target = getOrCreateTenantActor(msg.getTenantId()); + if (msg.getEntityId().getEntityType() == EntityType.TENANT + && msg.getEvent() == ComponentLifecycleEvent.DELETED) { + log.debug("[{}] Handling tenant deleted notification: {}", msg.getTenantId(), msg); + ActorRef tenantActor = tenantActors.remove(new TenantId(msg.getEntityId().getId())); + if (tenantActor != null) { + log.debug("[{}] Deleting tenant actor: {}", msg.getTenantId(), tenantActor); + context().stop(tenantActor); + } + } else { + target = getOrCreateTenantActor(msg.getTenantId()); + } } if (target != null) { target.tell(msg, ActorRef.noSender()); diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java index ed765e04f8..be393204ed 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.actors.ruleChain; +import akka.actor.ActorInitializationException; import akka.actor.OneForOneStrategy; import akka.actor.SupervisorStrategy; import org.thingsboard.server.actors.ActorSystemContext; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java index e4e82c55e5..5c6c676177 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java @@ -96,7 +96,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor Date: Fri, 2 Nov 2018 15:12:30 +0200 Subject: [PATCH 20/35] Added session registration #1212 --- .../server/transport/mqtt/session/GatewaySessionHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ac33ba677e..37fb84b3e6 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 @@ -119,10 +119,10 @@ public class GatewaySessionHandler { GatewayDeviceSessionCtx deviceSessionCtx = new GatewayDeviceSessionCtx(GatewaySessionHandler.this, msg.getDeviceInfo(), mqttQoSMap); if (devices.putIfAbsent(deviceName, deviceSessionCtx) == null) { SessionInfoProto deviceSessionInfo = deviceSessionCtx.getSessionInfo(); + transportService.registerAsyncSession(deviceSessionInfo, deviceSessionCtx); 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); } future.set(devices.get(deviceName)); } From 48e2b1a2173174bf00b8c40c2d5a9fd9fe1141c2 Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Fri, 2 Nov 2018 15:21:21 +0200 Subject: [PATCH 21/35] fix license header --- .../transport/adaptor/JsonConverterConfig.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java index 8b3bda3f98..cc41fe0eca 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java @@ -1,3 +1,18 @@ +/** + * 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.common.transport.adaptor; import lombok.extern.slf4j.Slf4j; From fc93d93b62f54364dd264b85ffe5ccfccbd26e1d Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Fri, 2 Nov 2018 15:45:28 +0200 Subject: [PATCH 22/35] JSON type cast enhancements --- application/src/main/resources/thingsboard.yml | 7 +++---- .../common/transport/adaptor/JsonConverterConfig.java | 2 +- transport/coap/src/main/resources/tb-coap-transport.yml | 7 +++---- transport/http/src/main/resources/tb-http-transport.yml | 7 +++---- transport/mqtt/src/main/resources/tb-mqtt-transport.yml | 7 +++---- 5 files changed, 13 insertions(+), 17 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index cb7beb6350..da2b7b10db 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -406,6 +406,9 @@ transport: enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" + json: + # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON + type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" # Local HTTP transport parameters http: enabled: "${HTTP_ENABLED:true}" @@ -443,7 +446,3 @@ transport: bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" bind_port: "${COAP_BIND_PORT:5683}" timeout: "${COAP_TIMEOUT:10000}" - -json: - # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON - type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:false}" \ No newline at end of file diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java index cc41fe0eca..ac3f4dc580 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverterConfig.java @@ -23,7 +23,7 @@ import org.springframework.context.annotation.Configuration; @Slf4j public class JsonConverterConfig { - @Value("${json.type_cast_enabled}") + @Value("${transport.json.type_cast_enabled:true}") public void setIsJsonTypeCastEnabled(boolean jsonTypeCastEnabled) { JsonConverter.setTypeCastEnabled(jsonTypeCastEnabled); log.info("JSON type cast enabled = {}", jsonTypeCastEnabled); diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index d20bc79ea4..95e7b7571b 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -30,6 +30,9 @@ transport: enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" + json: + # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON + type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" kafka: enabled: true @@ -52,7 +55,3 @@ kafka: topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}" poll_interval: "${TB_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}" auto_commit_interval: "${TB_TRANSPORT_NOTIFICATIONS_AUTO_COMMIT_INTERVAL_MS:100}" - -json: - # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON - type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" \ No newline at end of file diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index f5f7855db7..bb1d3c4888 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -31,6 +31,9 @@ transport: enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" + json: + # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON + type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" kafka: enabled: true @@ -53,7 +56,3 @@ kafka: topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}" poll_interval: "${TB_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}" auto_commit_interval: "${TB_TRANSPORT_NOTIFICATIONS_AUTO_COMMIT_INTERVAL_MS:100}" - -json: - # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON - type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" \ No newline at end of file diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index af1b6f01ac..719530c73f 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -50,6 +50,9 @@ transport: enabled: "${TB_TRANSPORT_RATE_LIMITS_ENABLED:false}" tenant: "${TB_TRANSPORT_RATE_LIMITS_TENANT:1000:1,20000:60}" device: "${TB_TRANSPORT_RATE_LIMITS_DEVICE:10:1,300:60}" + json: + # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON + type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" kafka: enabled: true @@ -72,7 +75,3 @@ kafka: topic: "${TB_TRANSPORT_NOTIFICATIONS_TOPIC:tb.transport.notifications}" poll_interval: "${TB_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}" auto_commit_interval: "${TB_TRANSPORT_NOTIFICATIONS_AUTO_COMMIT_INTERVAL_MS:100}" - -json: - # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON - type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" \ No newline at end of file From 80d7e3d6091b3743d7529e18d300666172caeffb Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Sun, 4 Nov 2018 10:45:46 +0200 Subject: [PATCH 23/35] Added Serializable --- .../server/common/data/objects/AttributesEntityView.java | 3 ++- .../server/common/data/objects/TelemetryEntityView.java | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/objects/AttributesEntityView.java b/common/data/src/main/java/org/thingsboard/server/common/data/objects/AttributesEntityView.java index 1c32579b72..d125f26146 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/objects/AttributesEntityView.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/objects/AttributesEntityView.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.objects; import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; @@ -26,7 +27,7 @@ import java.util.List; */ @Data @NoArgsConstructor -public class AttributesEntityView { +public class AttributesEntityView implements Serializable { private List cs = new ArrayList<>(); private List ss = new ArrayList<>(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/objects/TelemetryEntityView.java b/common/data/src/main/java/org/thingsboard/server/common/data/objects/TelemetryEntityView.java index c899c65590..6b9f5dd8ef 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/objects/TelemetryEntityView.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/objects/TelemetryEntityView.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.objects; import lombok.Data; import lombok.NoArgsConstructor; +import java.io.Serializable; import java.util.ArrayList; import java.util.List; @@ -26,7 +27,7 @@ import java.util.List; */ @Data @NoArgsConstructor -public class TelemetryEntityView { +public class TelemetryEntityView implements Serializable { private List timeseries; private AttributesEntityView attributes; From 766a56fa9e2f23edada830635926e8107b0eadfb Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Sun, 4 Nov 2018 11:03:37 +0200 Subject: [PATCH 24/35] Reduced logging using configuration parameter --- .travis.yml | 1 + .../java/org/thingsboard/server/actors/app/AppActor.java | 5 ++--- .../thingsboard/server/controller/BaseController.java | 9 ++++++++- application/src/main/resources/thingsboard.yml | 1 + application/src/test/resources/logback.xml | 2 +- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index d3591cf1a4..e8d88478d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,7 @@ before_install: - sudo rm -f /etc/mavenrc - export M2_HOME=/usr/local/maven - export MAVEN_OPTS="-Dmaven.repo.local=$HOME/.m2/repository -Xms1024m -Xmx3072m" + - export HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE=false jdk: - oraclejdk8 language: java diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index 915704eedd..714ab577af 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -131,7 +131,8 @@ public class AppActor extends RuleChainManagerActor { private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) { if (SYSTEM_TENANT.equals(msg.getTenantId())) { - log.warn("[{}] Invalid service to rule engine msg called. System messages are not supported yet: {}", SYSTEM_TENANT, msg); +// this may be a notification about system entities created. +// log.warn("[{}] Invalid service to rule engine msg called. System messages are not supported yet: {}", SYSTEM_TENANT, msg); } else { getOrCreateTenantActor(msg.getTenantId()).tell(msg, self()); } @@ -189,8 +190,6 @@ public class AppActor extends RuleChainManagerActor { boolean removed = tenantActors.inverse().remove(terminated) != null; if (removed) { log.debug("[{}] Removed actor:", terminated); - } else { - log.warn("[{}] Removed actor was not found in the tenant map!"); } } else { throw new IllegalStateException("Remote actors are not supported!"); 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 be1b6db1d9..825929f863 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -19,9 +19,11 @@ import com.datastax.driver.core.utils.UUIDs; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -152,6 +154,11 @@ public abstract class BaseController { @Autowired protected AttributesService attributesService; + @Value("${server.log_controller_error_stack_trace}") + @Getter + private boolean logControllerErrorStackTrace; + + @ExceptionHandler(ThingsboardException.class) public void handleThingsboardException(ThingsboardException ex, HttpServletResponse response) { errorResponseHandler.handle(ex, response); @@ -162,7 +169,7 @@ public abstract class BaseController { } private ThingsboardException handleException(Exception exception, boolean logException) { - if (logException) { + if (logException && logControllerErrorStackTrace) { log.error("Error [{}]", exception.getMessage(), exception); } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index da2b7b10db..60ce107a27 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -31,6 +31,7 @@ server: key-store-type: "${SSL_KEY_STORE_TYPE:PKCS12}" # Alias that identifies the key in the key store key-alias: "${SSL_KEY_ALIAS:tomcat}" + log_controller_error_stack_trace: "${HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE:true}" # Zookeeper connection parameters. Used for service discovery. zk: diff --git a/application/src/test/resources/logback.xml b/application/src/test/resources/logback.xml index b77027a761..47dacce343 100644 --- a/application/src/test/resources/logback.xml +++ b/application/src/test/resources/logback.xml @@ -9,7 +9,7 @@ - + From aa24461d30ee4e9e7c117e812d7c201056a302ab Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 5 Nov 2018 18:38:41 +0200 Subject: [PATCH 25/35] modify TbRestApiCallNode --- .../org/thingsboard/rule/engine/rest/TbRestApiCallNode.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java index f7f2d2d023..c0b122c980 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java @@ -118,7 +118,7 @@ public class TbRestApiCallNode implements TbNode { } private TbMsg processResponse(TbContext ctx, TbMsg origMsg, ResponseEntity response) { - TbMsgMetaData metaData = new TbMsgMetaData(); + TbMsgMetaData metaData = origMsg.getMetaData(); metaData.putValue(STATUS, response.getStatusCode().name()); metaData.putValue(STATUS_CODE, response.getStatusCode().value()+""); metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); @@ -127,7 +127,7 @@ public class TbRestApiCallNode implements TbNode { } private TbMsg processFailureResponse(TbContext ctx, TbMsg origMsg, ResponseEntity response) { - TbMsgMetaData metaData = origMsg.getMetaData().copy(); + TbMsgMetaData metaData = origMsg.getMetaData(); metaData.putValue(STATUS, response.getStatusCode().name()); metaData.putValue(STATUS_CODE, response.getStatusCode().value()+""); metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); @@ -136,7 +136,7 @@ public class TbRestApiCallNode implements TbNode { } private TbMsg processException(TbContext ctx, TbMsg origMsg, Throwable e) { - TbMsgMetaData metaData = origMsg.getMetaData().copy(); + TbMsgMetaData metaData = origMsg.getMetaData(); metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); if (e instanceof HttpClientErrorException) { HttpClientErrorException httpClientErrorException = (HttpClientErrorException)e; From 4a61616b49d78f0b0baa7bb8732696e8d8c711f1 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 5 Nov 2018 18:50:03 +0200 Subject: [PATCH 26/35] modify TbRestApiCallNode 2 --- .../org/thingsboard/rule/engine/rest/TbRestApiCallNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java index c0b122c980..c7c33fd741 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java @@ -122,7 +122,7 @@ public class TbRestApiCallNode implements TbNode { metaData.putValue(STATUS, response.getStatusCode().name()); metaData.putValue(STATUS_CODE, response.getStatusCode().value()+""); metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); - response.getHeaders().toSingleValueMap().forEach((k,v) -> metaData.putValue(k,v) ); + response.getHeaders().toSingleValueMap().forEach(metaData::putValue); return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, response.getBody()); } From 4d6ee559fdc55bfd8ea3bf3bbd51b984b8b30f95 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 5 Nov 2018 19:46:33 +0200 Subject: [PATCH 27/35] Fix Telemetry subscription service. --- .../DefaultTelemetrySubscriptionService.java | 40 ++++++++----------- 1 file changed, 17 insertions(+), 23 deletions(-) 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 7c772420ba..6e0657d347 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 @@ -35,18 +35,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; 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.kv.BaseAttributeKvEntry; -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.DataType; -import org.thingsboard.server.common.data.kv.DoubleDataEntry; -import org.thingsboard.server.common.data.kv.KvEntry; -import org.thingsboard.server.common.data.kv.LongDataEntry; -import org.thingsboard.server.common.data.kv.ReadTsKvQuery; -import org.thingsboard.server.common.data.kv.StringDataEntry; -import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.kv.*; import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.dao.attributes.AttributesService; @@ -381,7 +370,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio private void addRemoteWsSubscription(ServerAddress address, String sessionId, Subscription subscription) { EntityId entityId = subscription.getEntityId(); - log.trace("[{}] Registering remote subscription [{}] for device [{}] to [{}]", sessionId, subscription.getSubscriptionId(), entityId, address); + log.trace("[{}] Registering remote subscription [{}] for entity [{}] to [{}]", sessionId, subscription.getSubscriptionId(), entityId, address); registerSubscription(sessionId, entityId, subscription); if (subscription.getType() == TelemetryFeature.ATTRIBUTES) { final Map keyStates = subscription.getKeyStates(); @@ -401,17 +390,22 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio long curTs = System.currentTimeMillis(); List queries = new ArrayList<>(); subscription.getKeyStates().entrySet().forEach(e -> { - queries.add(new BaseReadTsKvQuery(e.getKey(), e.getValue() + 1L, curTs)); + if (curTs > e.getValue()) { + queries.add(new BaseReadTsKvQuery(e.getKey(), e.getValue() + 1L, curTs, 0, 1000, Aggregation.NONE)); + } else { + log.debug("[{}] Invalid subscription [{}], entityId [{}] curTs [{}]", sessionId, subscription, entityId, curTs); + } }); - - DonAsynchron.withCallback(tsService.findAll(entityId, queries), - missedUpdates -> { - if (missedUpdates != null && !missedUpdates.isEmpty()) { - tellRemoteSubUpdate(address, sessionId, new SubscriptionUpdate(subscription.getSubscriptionId(), missedUpdates)); - } - }, - e -> log.error("Failed to fetch missed updates.", e), - tsCallBackExecutor); + if (!queries.isEmpty()) { + DonAsynchron.withCallback(tsService.findAll(entityId, queries), + missedUpdates -> { + if (missedUpdates != null && !missedUpdates.isEmpty()) { + tellRemoteSubUpdate(address, sessionId, new SubscriptionUpdate(subscription.getSubscriptionId(), missedUpdates)); + } + }, + e -> log.error("Failed to fetch missed updates.", e), + tsCallBackExecutor); + } } } From f0186c9084c21c1a9081d1b6c6b643bad437d755 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 6 Nov 2018 10:13:29 +0200 Subject: [PATCH 28/35] Telemetry subscription service methods typo fix. --- .../telemetry/DefaultTelemetrySubscriptionService.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) 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 6e0657d347..30299ee59e 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 @@ -40,7 +40,6 @@ import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entityview.EntityViewService; -import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.gen.cluster.ClusterAPIProtos; import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; @@ -57,7 +56,6 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -328,9 +326,9 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio Set subscriptions = e.getValue(); Optional newAddressOptional = routingService.resolveById(e.getKey()); if (newAddressOptional.isPresent()) { - newAddressOptional.ifPresent(serverAddress -> checkSubsciptionsNewAddress(serverAddress, subscriptions)); + newAddressOptional.ifPresent(serverAddress -> checkSubscriptionsNewAddress(serverAddress, subscriptions)); } else { - checkSubsciptionsPrevAddress(subscriptions); + checkSubscriptionsPrevAddress(subscriptions); } if (subscriptions.size() == 0) { log.trace("[{}] No more subscriptions for this device on current server.", e.getKey()); @@ -339,7 +337,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio } } - private void checkSubsciptionsNewAddress(ServerAddress newAddress, Set subscriptions) { + private void checkSubscriptionsNewAddress(ServerAddress newAddress, Set subscriptions) { Iterator subscriptionIterator = subscriptions.iterator(); while (subscriptionIterator.hasNext()) { Subscription s = subscriptionIterator.next(); @@ -357,7 +355,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio } } - private void checkSubsciptionsPrevAddress(Set subscriptions) { + private void checkSubscriptionsPrevAddress(Set subscriptions) { for (Subscription s : subscriptions) { if (s.isLocal() && s.getServer() != null) { log.trace("[{}] Local subscription is no longer handled on remote server address [{}]", s.getWsSessionId(), s.getServer()); From cdb2903f95e542821a52b6318cca377f5eefe5c9 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Tue, 6 Nov 2018 15:42:01 +0200 Subject: [PATCH 29/35] Fixed initialization order --- .../server/actors/app/AppActor.java | 44 ++++++++++++------- .../server/actors/app/AppInitMsg.java | 27 ++++++++++++ .../device/DeviceActorMessageProcessor.java | 39 +++++++++------- .../actors/service/DefaultActorService.java | 17 +++++++ .../cluster/discovery/ZkDiscoveryService.java | 6 ++- .../DefaultDeviceSessionCacheService.java | 14 +++--- .../session/DeviceSessionCacheService.java | 4 +- .../RemoteRuleEngineTransportService.java | 7 +++ .../transport/RemoteTransportApiService.java | 7 +++ .../server/common/msg/MsgType.java | 2 + .../transport/mqtt/MqttTransportHandler.java | 15 ++++--- 11 files changed, 133 insertions(+), 49 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/actors/app/AppInitMsg.java diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index 714ab577af..9f27641592 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.aware.TenantAwareMsg; import org.thingsboard.server.common.msg.cluster.SendToClusterMsg; @@ -58,6 +59,7 @@ public class AppActor extends RuleChainManagerActor { private static final TenantId SYSTEM_TENANT = new TenantId(ModelConstants.NULL_UUID); private final TenantService tenantService; private final BiMap tenantActors; + private boolean ruleChainsInitialized; private AppActor(ActorSystemContext systemContext) { super(systemContext, new SystemRuleChainManager(systemContext)); @@ -72,26 +74,20 @@ public class AppActor extends RuleChainManagerActor { @Override public void preStart() { - log.info("Starting main system actor."); - try { - initRuleChains(); - if (systemContext.isTenantComponentsInitEnabled()) { - PageDataIterable tenantIterator = new PageDataIterable<>(tenantService::findTenants, ENTITY_PACK_LIMIT); - for (Tenant tenant : tenantIterator) { - log.debug("[{}] Creating tenant actor", tenant.getId()); - getOrCreateTenantActor(tenant.getId()); - log.debug("Tenant actor created."); - } - } - log.info("Main system actor started."); - } catch (Exception e) { - log.warn("Unknown failure", e); - } } @Override protected boolean process(TbActorMsg msg) { + if (!ruleChainsInitialized) { + initRuleChainsAndTenantActors(); + ruleChainsInitialized = true; + if (msg.getMsgType() != MsgType.APP_INIT_MSG) { + log.warn("Rule Chains initialized by unexpected message: {}", msg); + } + } switch (msg.getMsgType()) { + case APP_INIT_MSG: + break; case SEND_TO_CLUSTER_MSG: onPossibleClusterMsg((SendToClusterMsg) msg); break; @@ -119,6 +115,24 @@ public class AppActor extends RuleChainManagerActor { return true; } + private void initRuleChainsAndTenantActors() { + log.info("Starting main system actor."); + try { + initRuleChains(); + if (systemContext.isTenantComponentsInitEnabled()) { + PageDataIterable tenantIterator = new PageDataIterable<>(tenantService::findTenants, ENTITY_PACK_LIMIT); + for (Tenant tenant : tenantIterator) { + log.debug("[{}] Creating tenant actor", tenant.getId()); + getOrCreateTenantActor(tenant.getId()); + log.debug("Tenant actor created."); + } + } + log.info("Main system actor started."); + } catch (Exception e) { + log.warn("Unknown failure", e); + } + } + private void onPossibleClusterMsg(SendToClusterMsg msg) { Optional address = systemContext.getRoutingService().resolveById(msg.getEntityId()); if (address.isPresent()) { diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppInitMsg.java b/application/src/main/java/org/thingsboard/server/actors/app/AppInitMsg.java new file mode 100644 index 0000000000..fd8c1789e8 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppInitMsg.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.actors.app; + +import org.thingsboard.server.common.msg.MsgType; +import org.thingsboard.server.common.msg.TbActorMsg; + +public class AppInitMsg implements TbActorMsg { + + @Override + public MsgType getMsgType() { + return MsgType.APP_INIT_MSG; + } +} 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 41d1ea08c6..833e5ea005 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 @@ -24,6 +24,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.gson.Gson; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; @@ -483,19 +484,19 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } } - 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()); - } + private void handleSessionActivity(ActorContext context, SessionInfoProto sessionInfoProto, TransportProtos.SubscriptionInfoProto subscriptionInfo) { + UUID sessionId = getSessionId(sessionInfoProto); + SessionInfoMetaData sessionMD = sessions.computeIfAbsent(sessionId, + id -> new SessionInfoMetaData(new SessionInfo(TransportProtos.SessionType.ASYNC, sessionInfoProto.getNodeId()), 0L)); + + 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(); } @@ -629,8 +630,14 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { private void restoreSessions() { log.debug("[{}] Restoring sessions from cache", deviceId); - TransportProtos.DeviceSessionsCacheEntry sessionsDump = systemContext.getDeviceSessionCacheService().get(deviceId); - if (sessionsDump.getSerializedSize() == 0) { + TransportProtos.DeviceSessionsCacheEntry sessionsDump = null; + try { + sessionsDump = TransportProtos.DeviceSessionsCacheEntry.parseFrom(systemContext.getDeviceSessionCacheService().get(deviceId)); + } catch (InvalidProtocolBufferException e) { + log.warn("[{}] Failed to decode device sessions from cache", deviceId); + return; + } + if (sessionsDump.getSessionsCount() == 0) { log.debug("[{}] No session information found", deviceId); return; } @@ -677,7 +684,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { }); systemContext.getDeviceSessionCacheService() .put(deviceId, TransportProtos.DeviceSessionsCacheEntry.newBuilder() - .addAllSessions(sessionsList).build()); + .addAllSessions(sessionsList).build().toByteArray()); } void initSessionTimeout(ActorContext context) { 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 85b8943569..69061ba410 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 @@ -22,11 +22,14 @@ import akka.actor.Terminated; import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.app.AppActor; +import org.thingsboard.server.actors.app.AppInitMsg; import org.thingsboard.server.actors.rpc.RpcBroadcastMsg; import org.thingsboard.server.actors.rpc.RpcManagerActor; import org.thingsboard.server.actors.rpc.RpcSessionCreateRequestMsg; @@ -54,6 +57,12 @@ import scala.concurrent.duration.Duration; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; +import java.util.Arrays; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CLUSTER_ACTOR_MESSAGE; @Service @@ -86,6 +95,8 @@ public class DefaultActorService implements ActorService { private ActorRef rpcManagerActor; + private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + @PostConstruct public void initActorSystem() { log.info("Initializing Actor system. {}", actorContext.getRuleChainService()); @@ -106,6 +117,12 @@ public class DefaultActorService implements ActorService { log.info("Actor system initialized."); } + @EventListener(ApplicationReadyEvent.class) + public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { + log.info("Received application ready event. Sending application init message to actor system"); + appActor.tell(new AppInitMsg(), ActorRef.noSender()); + } + @PreDestroy public void stopActorSystem() { Future status = system.terminate(); diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java index f5321e5806..e0bdd12e69 100644 --- a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java +++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java @@ -38,6 +38,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationListener; import org.springframework.context.annotation.Lazy; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import org.thingsboard.server.actors.service.ActorService; @@ -61,7 +62,7 @@ import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent. @Service @ConditionalOnProperty(prefix = "zk", value = "enabled", havingValue = "true", matchIfMissing = false) @Slf4j -public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheListener, ApplicationListener { +public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheListener { @Value("${zk.url}") private String zkUrl; @@ -232,8 +233,9 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi .collect(Collectors.toList()); } - @Override + @EventListener(ApplicationReadyEvent.class) public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { + log.info("Received application ready event. Starting current ZK node."); if (stopped) { log.debug("Ignoring application ready event. Service is stopped."); return; 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 index 6201dabdda..cc8ecee1d6 100644 --- a/application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java +++ b/application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java @@ -22,7 +22,6 @@ 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; @@ -35,16 +34,17 @@ import static org.thingsboard.server.common.data.CacheConstants.SESSIONS_CACHE; public class DefaultDeviceSessionCacheService implements DeviceSessionCacheService { @Override - @Cacheable(cacheNames = SESSIONS_CACHE, key = "#deviceId") - public DeviceSessionsCacheEntry get(DeviceId deviceId) { + @Cacheable(cacheNames = SESSIONS_CACHE, key = "#deviceId.toString()") + public byte[] get(DeviceId deviceId) { log.debug("[{}] Fetching session data from cache", deviceId); - return DeviceSessionsCacheEntry.newBuilder().addAllSessions(Collections.emptyList()).build(); + return DeviceSessionsCacheEntry.newBuilder().addAllSessions(Collections.emptyList()).build().toByteArray(); } @Override - @CachePut(cacheNames = SESSIONS_CACHE, key = "#deviceId") - public DeviceSessionsCacheEntry put(DeviceId deviceId, DeviceSessionsCacheEntry sessions) { - log.debug("[{}] Pushing session data from cache: {}", deviceId, sessions); + @CachePut(cacheNames = SESSIONS_CACHE, key = "#deviceId.toString()") + public byte[] put(DeviceId deviceId, byte[] sessions) { + log.debug("[{}] Pushing session data to 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 index 0a2e6a52e5..a9a17021f0 100644 --- a/application/src/main/java/org/thingsboard/server/service/session/DeviceSessionCacheService.java +++ b/application/src/main/java/org/thingsboard/server/service/session/DeviceSessionCacheService.java @@ -23,8 +23,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheE */ public interface DeviceSessionCacheService { - DeviceSessionsCacheEntry get(DeviceId deviceId); + byte[] get(DeviceId deviceId); - DeviceSessionsCacheEntry put(DeviceId deviceId, DeviceSessionsCacheEntry sessions); + byte[] put(DeviceId deviceId, byte[] sessions); } 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 a8ab2cd625..45f84337f8 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 @@ -29,6 +29,9 @@ import org.apache.kafka.clients.producer.RecordMetadata; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.service.ActorService; @@ -127,7 +130,11 @@ public class RemoteRuleEngineTransportService implements RuleEngineTransportServ ruleEngineConsumer = ruleEngineConsumerBuilder.build(); ruleEngineConsumer.subscribe(); + } + @EventListener(ApplicationReadyEvent.class) + public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { + log.info("Received application ready event. Starting polling for events."); LocalBucketBuilder builder = Bucket4j.builder(); builder.addLimit(Bandwidth.simple(pollRecordsPerSecond, Duration.ofSeconds(1))); builder.addLimit(Bandwidth.simple(pollRecordsPerMinute, Duration.ofMinutes(1))); diff --git a/application/src/main/java/org/thingsboard/server/service/transport/RemoteTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/RemoteTransportApiService.java index 6d422f4dd6..88033aa86b 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/RemoteTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/RemoteTransportApiService.java @@ -19,6 +19,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; @@ -93,6 +95,11 @@ public class RemoteTransportApiService { builder.executor(transportCallbackExecutor); builder.handler(transportApiService); transportApiTemplate = builder.build(); + } + + @EventListener(ApplicationReadyEvent.class) + public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { + log.info("Received application ready event. Starting polling for events."); transportApiTemplate.init(); } 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 44a98d6ee4..a63ef6b7aa 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 @@ -28,6 +28,8 @@ public enum MsgType { */ CLUSTER_EVENT_MSG, + APP_INIT_MSG, + /** * All messages, could be send to cluster */ 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 a178bdf891..fa6e55741b 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 @@ -141,7 +141,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement processUnsubscribe(ctx, (MqttUnsubscribeMessage) msg); break; case PINGREQ: - if (checkConnected(ctx)) { + if (checkConnected(ctx, msg)) { ctx.writeAndFlush(new MqttMessage(new MqttFixedHeader(PINGRESP, false, AT_MOST_ONCE, false, 0))); transportService.reportActivity(sessionInfo); if (gatewaySessionHandler != null) { @@ -150,7 +150,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } break; case DISCONNECT: - if (checkConnected(ctx)) { + if (checkConnected(ctx, msg)) { processDisconnect(ctx); } break; @@ -161,7 +161,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } private void processPublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg) { - if (!checkConnected(ctx)) { + if (!checkConnected(ctx, mqttMsg)) { return; } String topicName = mqttMsg.variableHeader().topicName(); @@ -248,7 +248,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) { - if (!checkConnected(ctx)) { + if (!checkConnected(ctx, mqttMsg)) { return; } log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId()); @@ -293,7 +293,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } private void processUnsubscribe(ChannelHandlerContext ctx, MqttUnsubscribeMessage mqttMsg) { - if (!checkConnected(ctx)) { + if (!checkConnected(ctx, mqttMsg)) { return; } log.trace("[{}] Processing subscription [{}]!", sessionId, mqttMsg.variableHeader().messageId()); @@ -444,11 +444,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement return new MqttPubAckMessage(mqttFixedHeader, mqttMsgIdVariableHeader); } - private boolean checkConnected(ChannelHandlerContext ctx) { + private boolean checkConnected(ChannelHandlerContext ctx, MqttMessage msg) { if (deviceSessionCtx.isConnected()) { return true; } else { - log.info("[{}] Closing current session due to invalid msg order [{}][{}]", sessionId); + log.info("[{}] Closing current session due to invalid msg order: {}", sessionId, msg); ctx.close(); return false; } @@ -496,6 +496,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement transportService.registerAsyncSession(sessionInfo, this); checkGatewaySession(); ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_ACCEPTED)); + log.info("[{}] Client connected!", sessionId); } } From 7563e030743d81b9c54f311147d34fde6deae225 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Tue, 6 Nov 2018 16:57:50 +0200 Subject: [PATCH 30/35] Adding extra logs on transport layer --- .../transport/mqtt/MqttTransportHandler.java | 3 +- .../common/transport/TransportService.java | 2 +- .../service/AbstractTransportService.java | 32 ++++++++++++------- .../service/RemoteTransportService.java | 6 ++++ .../session/DeviceAwareSessionContext.java | 1 - 5 files changed, 30 insertions(+), 14 deletions(-) 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 fa6e55741b..3986837f6f 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 @@ -166,7 +166,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } String topicName = mqttMsg.variableHeader().topicName(); int msgId = mqttMsg.variableHeader().packetId(); - log.trace("[{}] Processing publish msg [{}][{}]!", sessionId, topicName, msgId); + log.trace("[{}][{}] Processing publish msg [{}][{}]!", sessionId, deviceSessionCtx.getDeviceId(), topicName, msgId); if (topicName.startsWith(MqttTopics.BASE_GATEWAY_API_TOPIC)) { if (gatewaySessionHandler != null) { @@ -336,6 +336,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private void processAuthTokenConnect(ChannelHandlerContext ctx, MqttConnectMessage msg) { String userName = msg.payload().userName(); + log.info("[{}] Processing connect msg for client with user name: {}!", sessionId, userName); if (StringUtils.isEmpty(userName)) { ctx.writeAndFlush(createMqttConnAckMsg(CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD)); ctx.close(); 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 8944e94a86..bb9cb4ef30 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 @@ -43,7 +43,7 @@ public interface TransportService { void process(TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg msg, TransportServiceCallback callback); - boolean checkLimits(SessionInfoProto sessionInfo, TransportServiceCallback callback); + boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback); void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback callback); 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 fad19541b4..4af594c421 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 @@ -68,7 +68,7 @@ public abstract class AbstractTransportService implements TransportService { @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SessionEventMsg msg, TransportServiceCallback callback) { - if (checkLimits(sessionInfo, callback)) { + if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); doProcess(sessionInfo, msg, callback); } @@ -76,7 +76,7 @@ public abstract class AbstractTransportService implements TransportService { @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback callback) { - if (checkLimits(sessionInfo, callback)) { + if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); doProcess(sessionInfo, msg, callback); } @@ -84,7 +84,7 @@ public abstract class AbstractTransportService implements TransportService { @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback callback) { - if (checkLimits(sessionInfo, callback)) { + if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); doProcess(sessionInfo, msg, callback); } @@ -92,7 +92,7 @@ public abstract class AbstractTransportService implements TransportService { @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.GetAttributeRequestMsg msg, TransportServiceCallback callback) { - if (checkLimits(sessionInfo, callback)) { + if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); doProcess(sessionInfo, msg, callback); } @@ -100,7 +100,7 @@ public abstract class AbstractTransportService implements TransportService { @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback callback) { - if (checkLimits(sessionInfo, callback)) { + if (checkLimits(sessionInfo, msg, callback)) { SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); sessionMetaData.setSubscribedToAttributes(!msg.getUnsubscribe()); doProcess(sessionInfo, msg, callback); @@ -109,7 +109,7 @@ public abstract class AbstractTransportService implements TransportService { @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToRPCMsg msg, TransportServiceCallback callback) { - if (checkLimits(sessionInfo, callback)) { + if (checkLimits(sessionInfo, msg, callback)) { SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); sessionMetaData.setSubscribedToRPC(!msg.getUnsubscribe()); doProcess(sessionInfo, msg, callback); @@ -118,7 +118,7 @@ public abstract class AbstractTransportService implements TransportService { @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDeviceRpcResponseMsg msg, TransportServiceCallback callback) { - if (checkLimits(sessionInfo, callback)) { + if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); doProcess(sessionInfo, msg, callback); } @@ -126,7 +126,7 @@ public abstract class AbstractTransportService implements TransportService { @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToServerRpcRequestMsg msg, TransportServiceCallback callback) { - if (checkLimits(sessionInfo, callback)) { + if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); doProcess(sessionInfo, msg, callback); } @@ -196,7 +196,10 @@ public abstract class AbstractTransportService implements TransportService { } @Override - public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, TransportServiceCallback callback) { + public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback) { + if (log.isTraceEnabled()) { + log.trace("[{}] Processing msg: {}", toId(sessionInfo), msg); + } if (!rateLimitEnabled) { return true; } @@ -206,6 +209,9 @@ public abstract class AbstractTransportService implements TransportService { if (callback != null) { callback.onError(new TbRateLimitsException(EntityType.TENANT)); } + if (log.isTraceEnabled()) { + log.trace("[{}][{}] Tenant level rate limit detected: {}", toId(sessionInfo), tenantId, msg); + } return false; } DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); @@ -214,8 +220,12 @@ public abstract class AbstractTransportService implements TransportService { if (callback != null) { callback.onError(new TbRateLimitsException(EntityType.DEVICE)); } + if (log.isTraceEnabled()) { + log.trace("[{}][{}] Device level rate limit detected: {}", toId(sessionInfo), deviceId, msg); + } return false; } + return true; } @@ -250,11 +260,11 @@ public abstract class AbstractTransportService implements TransportService { } } - private UUID toId(TransportProtos.SessionInfoProto sessionInfo) { + protected UUID toId(TransportProtos.SessionInfoProto sessionInfo) { return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); } - String getRoutingKey(TransportProtos.SessionInfoProto sessionInfo) { + protected String getRoutingKey(TransportProtos.SessionInfoProto sessionInfo) { return new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()).toString(); } 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 4b11bf58c9..5c123a69a0 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 @@ -197,6 +197,7 @@ public class RemoteTransportService extends AbstractTransportService { @Override public void process(ValidateDeviceTokenRequestMsg msg, TransportServiceCallback callback) { + log.trace("Processing msg: {}", msg); AsyncCallbackTemplate.withCallback(transportApiTemplate.post(msg.getToken(), TransportApiRequestMsg.newBuilder().setValidateTokenRequestMsg(msg).build()), response -> callback.onSuccess(response.getValidateTokenResponseMsg()), callback::onError, transportCallbackExecutor); @@ -204,6 +205,7 @@ public class RemoteTransportService extends AbstractTransportService { @Override public void process(ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { + log.trace("Processing msg: {}", msg); AsyncCallbackTemplate.withCallback(transportApiTemplate.post(msg.getHash(), TransportApiRequestMsg.newBuilder().setValidateX509CertRequestMsg(msg).build()), response -> callback.onSuccess(response.getValidateTokenResponseMsg()), callback::onError, transportCallbackExecutor); @@ -211,6 +213,7 @@ public class RemoteTransportService extends AbstractTransportService { @Override public void process(GetOrCreateDeviceFromGatewayRequestMsg msg, TransportServiceCallback callback) { + log.trace("Processing msg: {}", msg); AsyncCallbackTemplate.withCallback(transportApiTemplate.post(msg.getDeviceName(), TransportApiRequestMsg.newBuilder().setGetOrCreateDeviceRequestMsg(msg).build()), response -> callback.onSuccess(response.getGetOrCreateDeviceResponseMsg()), callback::onError, transportCallbackExecutor); @@ -218,6 +221,9 @@ public class RemoteTransportService extends AbstractTransportService { @Override public void process(SessionInfoProto sessionInfo, SubscriptionInfoProto msg, TransportServiceCallback callback) { + if (log.isTraceEnabled()) { + log.trace("[{}] Processing msg: {}", toId(sessionInfo), msg); + } ToRuleEngineMsg toRuleEngineMsg = ToRuleEngineMsg.newBuilder().setToDeviceActorMsg( TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) .setSubscriptionInfo(msg).build() diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java index 93c0f41ddd..5763c3560b 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/session/DeviceAwareSessionContext.java @@ -45,7 +45,6 @@ public abstract class DeviceAwareSessionContext implements SessionContext { this.deviceId = new DeviceId(new UUID(deviceInfo.getDeviceIdMSB(), deviceInfo.getDeviceIdLSB())); } - public boolean isConnected() { return deviceInfo != null; } From e4eb3358815e1c45d314c328726181d819766fcb Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 6 Nov 2018 18:00:38 +0200 Subject: [PATCH 31/35] Add external config and log volumes for transport microservices. --- docker/.gitignore | 1 + docker/docker-compose.postgres.volumes.yml | 24 ++++++++++++ docker/docker-compose.yml | 25 ++++++++++++ docker/tb-node/conf/thingsboard.conf | 2 +- .../tb-transports/coap/conf}/logback.xml | 4 +- .../coap/conf}/tb-coap-transport.conf | 6 +-- .../tb-transports/http/conf}/logback.xml | 4 +- .../http/conf}/tb-http-transport.conf | 6 +-- .../tb-transports/mqtt/conf}/logback.xml | 4 +- .../mqtt/conf}/tb-mqtt-transport.conf | 6 +-- .../server/msa/ContainerTestSuite.java | 3 +- .../server/msa/ThingsBoardDbInstaller.java | 38 +++++++++++++++---- msa/transport/coap/docker/Dockerfile | 5 +-- .../coap/docker/start-tb-coap-transport.sh | 6 ++- msa/transport/http/docker/Dockerfile | 5 +-- .../http/docker/start-tb-http-transport.sh | 6 ++- msa/transport/mqtt/docker/Dockerfile | 5 +-- .../mqtt/docker/start-tb-mqtt-transport.sh | 6 ++- 18 files changed, 112 insertions(+), 44 deletions(-) rename {msa/transport/coap/docker => docker/tb-transports/coap/conf}/logback.xml (88%) rename {msa/transport/coap/docker => docker/tb-transports/coap/conf}/tb-coap-transport.conf (77%) rename {msa/transport/mqtt/docker => docker/tb-transports/http/conf}/logback.xml (88%) rename {msa/transport/http/docker => docker/tb-transports/http/conf}/tb-http-transport.conf (77%) rename {msa/transport/http/docker => docker/tb-transports/mqtt/conf}/logback.xml (88%) rename {msa/transport/mqtt/docker => docker/tb-transports/mqtt/conf}/tb-mqtt-transport.conf (77%) diff --git a/docker/.gitignore b/docker/.gitignore index bc09e846b0..eee422d573 100644 --- a/docker/.gitignore +++ b/docker/.gitignore @@ -4,4 +4,5 @@ tb-node/log/** tb-node/db/** tb-node/postgres/** tb-node/cassandra/** +tb-transports/*/log !.env diff --git a/docker/docker-compose.postgres.volumes.yml b/docker/docker-compose.postgres.volumes.yml index d94b066cb0..a1dbaa5f1a 100644 --- a/docker/docker-compose.postgres.volumes.yml +++ b/docker/docker-compose.postgres.volumes.yml @@ -26,6 +26,21 @@ services: tb2: volumes: - tb-log-volume:/var/log/thingsboard + tb-coap-transport: + volumes: + - tb-coap-transport-log-volume:/var/log/tb-coap-transport + tb-http-transport1: + volumes: + - tb-http-transport-log-volume:/var/log/tb-http-transport + tb-http-transport2: + volumes: + - tb-http-transport-log-volume:/var/log/tb-http-transport + tb-mqtt-transport1: + volumes: + - tb-mqtt-transport-log-volume:/var/log/tb-mqtt-transport + tb-mqtt-transport2: + volumes: + - tb-mqtt-transport-log-volume:/var/log/tb-mqtt-transport volumes: postgres-db-volume: @@ -34,3 +49,12 @@ volumes: tb-log-volume: external: true name: ${TB_LOG_VOLUME} + tb-coap-transport-log-volume: + external: true + name: ${TB_COAP_TRANSPORT_LOG_VOLUME} + tb-http-transport-log-volume: + external: true + name: ${TB_HTTP_TRANSPORT_LOG_VOLUME} + tb-mqtt-transport-log-volume: + external: true + name: ${TB_MQTT_TRANSPORT_LOG_VOLUME} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index b9d8fd1a54..1ac861d88e 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -91,8 +91,13 @@ services: image: "${DOCKER_REPO}/${MQTT_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" ports: - "1883" + environment: + TB_HOST: tb-mqtt-transport1 env_file: - tb-mqtt-transport.env + volumes: + - ./tb-transports/mqtt/conf:/config + - ./tb-transports/mqtt/log:/var/log/tb-mqtt-transport depends_on: - kafka tb-mqtt-transport2: @@ -100,8 +105,13 @@ services: image: "${DOCKER_REPO}/${MQTT_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" ports: - "1883" + environment: + TB_HOST: tb-mqtt-transport2 env_file: - tb-mqtt-transport.env + volumes: + - ./tb-transports/mqtt/conf:/config + - ./tb-transports/mqtt/log:/var/log/tb-mqtt-transport depends_on: - kafka tb-http-transport1: @@ -109,8 +119,13 @@ services: image: "${DOCKER_REPO}/${HTTP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" ports: - "8081" + environment: + TB_HOST: tb-http-transport1 env_file: - tb-http-transport.env + volumes: + - ./tb-transports/http/conf:/config + - ./tb-transports/http/log:/var/log/tb-http-transport depends_on: - kafka tb-http-transport2: @@ -118,8 +133,13 @@ services: image: "${DOCKER_REPO}/${HTTP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" ports: - "8081" + environment: + TB_HOST: tb-http-transport2 env_file: - tb-http-transport.env + volumes: + - ./tb-transports/http/conf:/config + - ./tb-transports/http/log:/var/log/tb-http-transport depends_on: - kafka tb-coap-transport: @@ -127,8 +147,13 @@ services: image: "${DOCKER_REPO}/${COAP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" ports: - "5683:5683/udp" + environment: + TB_HOST: tb-coap-transport env_file: - tb-coap-transport.env + volumes: + - ./tb-transports/coap/conf:/config + - ./tb-transports/coap/log:/var/log/tb-coap-transport depends_on: - kafka tb-web-ui1: diff --git a/docker/tb-node/conf/thingsboard.conf b/docker/tb-node/conf/thingsboard.conf index aa430b4a79..392b3302fd 100644 --- a/docker/tb-node/conf/thingsboard.conf +++ b/docker/tb-node/conf/thingsboard.conf @@ -15,7 +15,7 @@ # 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 -Xloggc:/var/log/thingsboard/${TB_HOST}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/thingsboard/${TB_HOST}/heapdump.bin -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" diff --git a/msa/transport/coap/docker/logback.xml b/docker/tb-transports/coap/conf/logback.xml similarity index 88% rename from msa/transport/coap/docker/logback.xml rename to docker/tb-transports/coap/conf/logback.xml index e9d86928bd..f8bbb2f1bb 100644 --- a/msa/transport/coap/docker/logback.xml +++ b/docker/tb-transports/coap/conf/logback.xml @@ -21,10 +21,10 @@ - /var/log/${pkg.name}/${pkg.name}.log + /var/log/tb-coap-transport/${TB_HOST}/tb-coap-transport.log - /var/log/${pkg.name}/${pkg.name}.%d{yyyy-MM-dd}.%i.log + /var/log/tb-coap-transport/${TB_HOST}/tb-coap-transport.%d{yyyy-MM-dd}.%i.log 100MB 30 3GB diff --git a/msa/transport/coap/docker/tb-coap-transport.conf b/docker/tb-transports/coap/conf/tb-coap-transport.conf similarity index 77% rename from msa/transport/coap/docker/tb-coap-transport.conf rename to docker/tb-transports/coap/conf/tb-coap-transport.conf index 6d4c54aad1..31a9baa744 100644 --- a/msa/transport/coap/docker/tb-coap-transport.conf +++ b/docker/tb-transports/coap/conf/tb-coap-transport.conf @@ -14,10 +14,10 @@ # limitations under the License. # -export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" +export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-coap-transport/${TB_HOST}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-coap-transport/${TB_HOST}/heapdump.bin -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=${pkg.name}.out -export LOADER_PATH=${pkg.installFolder}/conf +export LOG_FILENAME=tb-coap-transport.out +export LOADER_PATH=/usr/share/tb-coap-transport/conf diff --git a/msa/transport/mqtt/docker/logback.xml b/docker/tb-transports/http/conf/logback.xml similarity index 88% rename from msa/transport/mqtt/docker/logback.xml rename to docker/tb-transports/http/conf/logback.xml index e9d86928bd..ef8a7b4e97 100644 --- a/msa/transport/mqtt/docker/logback.xml +++ b/docker/tb-transports/http/conf/logback.xml @@ -21,10 +21,10 @@ - /var/log/${pkg.name}/${pkg.name}.log + /var/log/tb-http-transport/${TB_HOST}/tb-http-transport.log - /var/log/${pkg.name}/${pkg.name}.%d{yyyy-MM-dd}.%i.log + /var/log/tb-http-transport/${TB_HOST}/tb-http-transport.%d{yyyy-MM-dd}.%i.log 100MB 30 3GB diff --git a/msa/transport/http/docker/tb-http-transport.conf b/docker/tb-transports/http/conf/tb-http-transport.conf similarity index 77% rename from msa/transport/http/docker/tb-http-transport.conf rename to docker/tb-transports/http/conf/tb-http-transport.conf index 6d4c54aad1..168704574c 100644 --- a/msa/transport/http/docker/tb-http-transport.conf +++ b/docker/tb-transports/http/conf/tb-http-transport.conf @@ -14,10 +14,10 @@ # limitations under the License. # -export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" +export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-http-transport/${TB_HOST}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-http-transport/${TB_HOST}/heapdump.bin -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=${pkg.name}.out -export LOADER_PATH=${pkg.installFolder}/conf +export LOG_FILENAME=tb-http-transport.out +export LOADER_PATH=/usr/share/tb-http-transport/conf diff --git a/msa/transport/http/docker/logback.xml b/docker/tb-transports/mqtt/conf/logback.xml similarity index 88% rename from msa/transport/http/docker/logback.xml rename to docker/tb-transports/mqtt/conf/logback.xml index e9d86928bd..cd397bd4c3 100644 --- a/msa/transport/http/docker/logback.xml +++ b/docker/tb-transports/mqtt/conf/logback.xml @@ -21,10 +21,10 @@ - /var/log/${pkg.name}/${pkg.name}.log + /var/log/tb-mqtt-transport/${TB_HOST}/tb-mqtt-transport.log - /var/log/${pkg.name}/${pkg.name}.%d{yyyy-MM-dd}.%i.log + /var/log/tb-mqtt-transport/${TB_HOST}/tb-mqtt-transport.%d{yyyy-MM-dd}.%i.log 100MB 30 3GB diff --git a/msa/transport/mqtt/docker/tb-mqtt-transport.conf b/docker/tb-transports/mqtt/conf/tb-mqtt-transport.conf similarity index 77% rename from msa/transport/mqtt/docker/tb-mqtt-transport.conf rename to docker/tb-transports/mqtt/conf/tb-mqtt-transport.conf index 6d4c54aad1..388f61024b 100644 --- a/msa/transport/mqtt/docker/tb-mqtt-transport.conf +++ b/docker/tb-transports/mqtt/conf/tb-mqtt-transport.conf @@ -14,10 +14,10 @@ # limitations under the License. # -export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps" +export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-mqtt-transport/${TB_HOST}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-mqtt-transport/${TB_HOST}/heapdump.bin -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=${pkg.name}.out -export LOADER_PATH=${pkg.installFolder}/conf +export LOG_FILENAME=tb-mqtt-transport.out +export LOADER_PATH=/usr/share/tb-mqtt-transport/conf diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java index 3233617823..21d095fe00 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java @@ -49,8 +49,7 @@ public class ContainerTestSuite { .withPull(false) .withLocalCompose(true) .withTailChildContainers(true) - .withEnv("POSTGRES_DATA_VOLUME", installTb.getPostgresDataVolume()) - .withEnv("TB_LOG_VOLUME", installTb.getTbLogVolume()) + .withEnv(installTb.getEnv()) .withEnv("LOAD_BALANCER_NAME", "") .withExposedService("haproxy", 80, Wait.forHttp("/swagger-ui.html").withStartupTimeout(Duration.ofSeconds(120))); } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java index fef69b5163..d0422b0c13 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java @@ -28,11 +28,18 @@ public class ThingsBoardDbInstaller extends ExternalResource { private final static String POSTGRES_DATA_VOLUME = "tb-postgres-test-data-volume"; private final static String TB_LOG_VOLUME = "tb-log-test-volume"; + private final static String TB_COAP_TRANSPORT_LOG_VOLUME = "tb-coap-transport-log-test-volume"; + private final static String TB_HTTP_TRANSPORT_LOG_VOLUME = "tb-http-transport-log-test-volume"; + private final static String TB_MQTT_TRANSPORT_LOG_VOLUME = "tb-mqtt-transport-log-test-volume"; private final DockerComposeExecutor dockerCompose; private final String postgresDataVolume; private final String tbLogVolume; + private final String tbCoapTransportLogVolume; + private final String tbHttpTransportLogVolume; + private final String tbMqttTransportLogVolume; + private final Map env; public ThingsBoardDbInstaller() { List composeFiles = Arrays.asList(new File("./../../docker/docker-compose.yml"), @@ -44,21 +51,23 @@ public class ThingsBoardDbInstaller extends ExternalResource { postgresDataVolume = project + "_" + POSTGRES_DATA_VOLUME; tbLogVolume = project + "_" + TB_LOG_VOLUME; + tbCoapTransportLogVolume = project + "_" + TB_COAP_TRANSPORT_LOG_VOLUME; + tbHttpTransportLogVolume = project + "_" + TB_HTTP_TRANSPORT_LOG_VOLUME; + tbMqttTransportLogVolume = project + "_" + TB_MQTT_TRANSPORT_LOG_VOLUME; dockerCompose = new DockerComposeExecutor(composeFiles, project); - Map env = new HashMap<>(); + env = new HashMap<>(); env.put("POSTGRES_DATA_VOLUME", postgresDataVolume); env.put("TB_LOG_VOLUME", tbLogVolume); + env.put("TB_COAP_TRANSPORT_LOG_VOLUME", tbCoapTransportLogVolume); + env.put("TB_HTTP_TRANSPORT_LOG_VOLUME", tbHttpTransportLogVolume); + env.put("TB_MQTT_TRANSPORT_LOG_VOLUME", tbMqttTransportLogVolume); dockerCompose.withEnv(env); } - public String getPostgresDataVolume() { - return postgresDataVolume; - } - - public String getTbLogVolume() { - return tbLogVolume; + public Map getEnv() { + return env; } @Override @@ -71,6 +80,15 @@ public class ThingsBoardDbInstaller extends ExternalResource { dockerCompose.withCommand("volume create " + tbLogVolume); dockerCompose.invokeDocker(); + dockerCompose.withCommand("volume create " + tbCoapTransportLogVolume); + dockerCompose.invokeDocker(); + + dockerCompose.withCommand("volume create " + tbHttpTransportLogVolume); + dockerCompose.invokeDocker(); + + dockerCompose.withCommand("volume create " + tbMqttTransportLogVolume); + dockerCompose.invokeDocker(); + dockerCompose.withCommand("up -d redis postgres"); dockerCompose.invokeCompose(); @@ -88,8 +106,12 @@ public class ThingsBoardDbInstaller extends ExternalResource { @Override protected void after() { copyLogs(tbLogVolume, "./target/tb-logs/"); + copyLogs(tbCoapTransportLogVolume, "./target/tb-coap-transport-logs/"); + copyLogs(tbHttpTransportLogVolume, "./target/tb-http-transport-logs/"); + copyLogs(tbMqttTransportLogVolume, "./target/tb-mqtt-transport-logs/"); - dockerCompose.withCommand("volume rm -f " + postgresDataVolume + " " + tbLogVolume); + dockerCompose.withCommand("volume rm -f " + postgresDataVolume + " " + tbLogVolume + + " " + tbCoapTransportLogVolume + " " + tbHttpTransportLogVolume + " " + tbMqttTransportLogVolume); dockerCompose.invokeDocker(); } diff --git a/msa/transport/coap/docker/Dockerfile b/msa/transport/coap/docker/Dockerfile index 9240b2a4f0..69180df791 100644 --- a/msa/transport/coap/docker/Dockerfile +++ b/msa/transport/coap/docker/Dockerfile @@ -16,7 +16,7 @@ FROM openjdk:8-jdk -COPY logback.xml ${pkg.name}.conf start-tb-coap-transport.sh ${pkg.name}.deb /tmp/ +COPY start-tb-coap-transport.sh ${pkg.name}.deb /tmp/ RUN chmod a+x /tmp/*.sh \ && mv /tmp/start-tb-coap-transport.sh /usr/bin @@ -25,7 +25,4 @@ 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 - CMD ["start-tb-coap-transport.sh"] diff --git a/msa/transport/coap/docker/start-tb-coap-transport.sh b/msa/transport/coap/docker/start-tb-coap-transport.sh index 43d4602230..d233dbcd76 100755 --- a/msa/transport/coap/docker/start-tb-coap-transport.sh +++ b/msa/transport/coap/docker/start-tb-coap-transport.sh @@ -15,15 +15,17 @@ # limitations under the License. # -CONF_FOLDER="${pkg.installFolder}/conf" +CONF_FOLDER="/config" jarfile=${pkg.installFolder}/bin/${pkg.name}.jar configfile=${pkg.name}.conf source "${CONF_FOLDER}/${configfile}" +export LOADER_PATH=/config,${LOADER_PATH} + echo "Starting '${project.name}' ..." exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.coap.ThingsboardCoapTransportApplication \ -Dspring.jpa.hibernate.ddl-auto=none \ - -Dlogging.config=${CONF_FOLDER}/logback.xml \ + -Dlogging.config=/config/logback.xml \ org.springframework.boot.loader.PropertiesLauncher diff --git a/msa/transport/http/docker/Dockerfile b/msa/transport/http/docker/Dockerfile index 6c83b9c76b..2ed6318e96 100644 --- a/msa/transport/http/docker/Dockerfile +++ b/msa/transport/http/docker/Dockerfile @@ -16,7 +16,7 @@ FROM openjdk:8-jdk -COPY logback.xml ${pkg.name}.conf start-tb-http-transport.sh ${pkg.name}.deb /tmp/ +COPY start-tb-http-transport.sh ${pkg.name}.deb /tmp/ RUN chmod a+x /tmp/*.sh \ && mv /tmp/start-tb-http-transport.sh /usr/bin @@ -25,7 +25,4 @@ 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 - CMD ["start-tb-http-transport.sh"] diff --git a/msa/transport/http/docker/start-tb-http-transport.sh b/msa/transport/http/docker/start-tb-http-transport.sh index 667988fa42..e00a66cfae 100755 --- a/msa/transport/http/docker/start-tb-http-transport.sh +++ b/msa/transport/http/docker/start-tb-http-transport.sh @@ -15,15 +15,17 @@ # limitations under the License. # -CONF_FOLDER="${pkg.installFolder}/conf" +CONF_FOLDER="/config" jarfile=${pkg.installFolder}/bin/${pkg.name}.jar configfile=${pkg.name}.conf source "${CONF_FOLDER}/${configfile}" +export LOADER_PATH=/config,${LOADER_PATH} + echo "Starting '${project.name}' ..." exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.http.ThingsboardHttpTransportApplication \ -Dspring.jpa.hibernate.ddl-auto=none \ - -Dlogging.config=${CONF_FOLDER}/logback.xml \ + -Dlogging.config=/config/logback.xml \ org.springframework.boot.loader.PropertiesLauncher diff --git a/msa/transport/mqtt/docker/Dockerfile b/msa/transport/mqtt/docker/Dockerfile index f636e2f3a7..4fb707a59e 100644 --- a/msa/transport/mqtt/docker/Dockerfile +++ b/msa/transport/mqtt/docker/Dockerfile @@ -16,7 +16,7 @@ FROM openjdk:8-jdk -COPY logback.xml ${pkg.name}.conf start-tb-mqtt-transport.sh ${pkg.name}.deb /tmp/ +COPY start-tb-mqtt-transport.sh ${pkg.name}.deb /tmp/ RUN chmod a+x /tmp/*.sh \ && mv /tmp/start-tb-mqtt-transport.sh /usr/bin @@ -25,7 +25,4 @@ 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 - CMD ["start-tb-mqtt-transport.sh"] diff --git a/msa/transport/mqtt/docker/start-tb-mqtt-transport.sh b/msa/transport/mqtt/docker/start-tb-mqtt-transport.sh index 8fe06d272e..dd184fe094 100755 --- a/msa/transport/mqtt/docker/start-tb-mqtt-transport.sh +++ b/msa/transport/mqtt/docker/start-tb-mqtt-transport.sh @@ -15,15 +15,17 @@ # limitations under the License. # -CONF_FOLDER="${pkg.installFolder}/conf" +CONF_FOLDER="/config" jarfile=${pkg.installFolder}/bin/${pkg.name}.jar configfile=${pkg.name}.conf source "${CONF_FOLDER}/${configfile}" +export LOADER_PATH=/config,${LOADER_PATH} + echo "Starting '${project.name}' ..." exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.mqtt.ThingsboardMqttTransportApplication \ -Dspring.jpa.hibernate.ddl-auto=none \ - -Dlogging.config=${CONF_FOLDER}/logback.xml \ + -Dlogging.config=/config/logback.xml \ org.springframework.boot.loader.PropertiesLauncher From 8be682d15aaafb274da77ff24828b9a270102074 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 6 Nov 2018 20:34:53 +0200 Subject: [PATCH 32/35] Change default TB API port --- docker/docker-compose.yml | 3 ++- docker/tb-web-ui.env | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 1ac861d88e..8f9666851b 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -181,6 +181,7 @@ services: ports: - "80:80" - "8080" + - "8090" - "443:443" - "1883:1883" - "9999:9999" @@ -190,7 +191,7 @@ services: HTTP_PORT: 80 HTTPS_PORT: 443 MQTT_PORT: 1883 - TB_API_PORT: 8080 + TB_API_PORT: 8090 FORCE_HTTPS_REDIRECT: "false" links: - tb1 diff --git a/docker/tb-web-ui.env b/docker/tb-web-ui.env index dbf8120cbf..fc50db0744 100644 --- a/docker/tb-web-ui.env +++ b/docker/tb-web-ui.env @@ -2,7 +2,7 @@ HTTP_BIND_ADDRESS=0.0.0.0 HTTP_BIND_PORT=8080 TB_HOST=haproxy -TB_PORT=8080 +TB_PORT=8090 LOGGER_LEVEL=info LOG_FOLDER=logs LOGGER_FILENAME=tb-web-ui-%DATE%.log From 2d063a9c8b90aef76eb529dd9bc0300e02a01205 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 7 Nov 2018 15:55:41 +0200 Subject: [PATCH 33/35] Improved GRPC callbacks --- .../server/actors/ActorSystemContext.java | 5 +++ .../actors/rpc/BasicRpcSessionListener.java | 16 ++++++++-- .../server/actors/rpc/RpcManagerActor.java | 29 +++++++++++++---- .../server/actors/rpc/RpcSessionActor.java | 30 ++++++++++------- .../actors/service/ContextAwareActor.java | 2 +- .../ClusterRpcCallbackExecutorService.java | 32 +++++++++++++++++++ .../DefaultDeviceSessionCacheService.java | 7 ++++ .../src/main/resources/thingsboard.yml | 2 ++ 8 files changed, 102 insertions(+), 21 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/executors/ClusterRpcCallbackExecutorService.java 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 091b504ea7..a59e85cf59 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -61,6 +61,7 @@ import org.thingsboard.server.service.cluster.routing.ClusterRoutingService; import org.thingsboard.server.service.cluster.rpc.ClusterRpcService; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.encoding.DataDecodingEncodingService; +import org.thingsboard.server.service.executors.ClusterRpcCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.ExternalCallExecutorService; import org.thingsboard.server.service.mail.MailExecutorService; @@ -186,6 +187,10 @@ public class ActorSystemContext { @Getter private MailExecutorService mailExecutor; + @Autowired + @Getter + private ClusterRpcCallbackExecutorService clusterRpcCallbackExecutor; + @Autowired @Getter private DbCallbackExecutorService dbCallbackExecutor; 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 760d4a6d96..669ed74b86 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 @@ -17,10 +17,12 @@ package org.thingsboard.server.actors.rpc; import akka.actor.ActorRef; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.gen.cluster.ClusterAPIProtos; import org.thingsboard.server.service.cluster.rpc.GrpcSession; import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener; +import org.thingsboard.server.service.executors.ClusterRpcCallbackExecutorService; /** * @author Andrew Shvayka @@ -28,12 +30,14 @@ import org.thingsboard.server.service.cluster.rpc.GrpcSessionListener; @Slf4j public class BasicRpcSessionListener implements GrpcSessionListener { + private final ClusterRpcCallbackExecutorService callbackExecutorService; private final ActorService service; private final ActorRef manager; private final ActorRef self; - BasicRpcSessionListener(ActorService service, ActorRef manager, ActorRef self) { - this.service = service; + BasicRpcSessionListener(ActorSystemContext context, ActorRef manager, ActorRef self) { + this.service = context.getActorService(); + this.callbackExecutorService = context.getClusterRpcCallbackExecutor(); this.manager = manager; this.self = self; } @@ -55,7 +59,13 @@ public class BasicRpcSessionListener implements GrpcSessionListener { @Override public void onReceiveClusterGrpcMsg(GrpcSession session, ClusterAPIProtos.ClusterMessage clusterMessage) { log.trace("Received session actor msg from [{}][{}]: {}", session.getRemoteServer(), getType(session), clusterMessage); - service.onReceivedMsg(session.getRemoteServer(), clusterMessage); + callbackExecutorService.execute(() -> { + try { + service.onReceivedMsg(session.getRemoteServer(), clusterMessage); + } catch (Exception e) { + log.debug("[{}][{}] Failed to process cluster message: {}", session.getRemoteServer(), getType(session), clusterMessage, e); + } + }); } @Override 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 9fa087e630..b2931f85e1 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 @@ -16,7 +16,9 @@ package org.thingsboard.server.actors.rpc; import akka.actor.ActorRef; +import akka.actor.OneForOneStrategy; import akka.actor.Props; +import akka.actor.SupervisorStrategy; import akka.event.Logging; import akka.event.LoggingAdapter; import lombok.extern.slf4j.Slf4j; @@ -30,6 +32,7 @@ import org.thingsboard.server.common.msg.cluster.ServerAddress; import org.thingsboard.server.common.msg.cluster.ServerType; import org.thingsboard.server.gen.cluster.ClusterAPIProtos; import org.thingsboard.server.service.cluster.discovery.ServerInstance; +import scala.concurrent.duration.Duration; import java.util.*; @@ -39,9 +42,7 @@ import java.util.*; public class RpcManagerActor extends ContextAwareActor { private final Map sessionActors; - private final Map> pendingMsgs; - private final ServerAddress instance; private RpcManagerActor(ActorSystemContext systemContext) { @@ -63,7 +64,7 @@ public class RpcManagerActor extends ContextAwareActor { } @Override - public void onReceive(Object msg) throws Exception { + public void onReceive(Object msg) { if (msg instanceof ClusterAPIProtos.ClusterMessage) { onMsg((ClusterAPIProtos.ClusterMessage) msg); } else if (msg instanceof RpcBroadcastMsg) { @@ -163,6 +164,7 @@ public class RpcManagerActor extends ContextAwareActor { log.info("[{}] session closed. Should reconnect: {}", remoteAddress, reconnect); SessionActorInfo sessionRef = sessionActors.get(remoteAddress); if (sessionRef != null && context().sender() != null && context().sender().equals(sessionRef.actor)) { + context().stop(sessionRef.actor); sessionActors.remove(remoteAddress); pendingMsgs.remove(remoteAddress); if (reconnect) { @@ -172,9 +174,13 @@ public class RpcManagerActor extends ContextAwareActor { } private void onCreateSessionRequest(RpcSessionCreateRequestMsg msg) { - ActorRef actorRef = createSessionActor(msg); if (msg.getRemoteAddress() != null) { - register(msg.getRemoteAddress(), msg.getMsgUid(), actorRef); + if (!sessionActors.containsKey(msg.getRemoteAddress())) { + ActorRef actorRef = createSessionActor(msg); + register(msg.getRemoteAddress(), msg.getMsgUid(), actorRef); + } + } else { + createSessionActor(msg); } } @@ -193,7 +199,8 @@ public class RpcManagerActor extends ContextAwareActor { private ActorRef createSessionActor(RpcSessionCreateRequestMsg msg) { log.info("[{}] Creating session actor.", msg.getMsgUid()); ActorRef actor = context().actorOf( - Props.create(new RpcSessionActor.ActorCreator(systemContext, msg.getMsgUid())).withDispatcher(DefaultActorService.RPC_DISPATCHER_NAME)); + Props.create(new RpcSessionActor.ActorCreator(systemContext, msg.getMsgUid())) + .withDispatcher(DefaultActorService.RPC_DISPATCHER_NAME)); actor.tell(msg, context().self()); return actor; } @@ -210,4 +217,14 @@ public class RpcManagerActor extends ContextAwareActor { return new RpcManagerActor(context); } } + + @Override + public SupervisorStrategy supervisorStrategy() { + return strategy; + } + + private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), t -> { + log.warn("Unknown failure", t); + return SupervisorStrategy.resume(); + }); } 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 86509ca08e..2ca5c3e872 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 @@ -15,12 +15,10 @@ */ 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 lombok.extern.slf4j.Slf4j; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.service.ContextAwareActor; import org.thingsboard.server.actors.service.ContextBasedCreator; @@ -38,15 +36,15 @@ import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CO /** * @author Andrew Shvayka */ +@Slf4j public class RpcSessionActor extends ContextAwareActor { - private final LoggingAdapter log = Logging.getLogger(getContext().system(), this); private final UUID sessionId; private GrpcSession session; private GrpcSessionListener listener; - public RpcSessionActor(ActorSystemContext systemContext, UUID sessionId) { + private RpcSessionActor(ActorSystemContext systemContext, UUID sessionId) { super(systemContext); this.sessionId = sessionId; } @@ -58,7 +56,7 @@ public class RpcSessionActor extends ContextAwareActor { } @Override - public void onReceive(Object msg) throws Exception { + public void onReceive(Object msg) { if (msg instanceof ClusterAPIProtos.ClusterMessage) { tell((ClusterAPIProtos.ClusterMessage) msg); } else if (msg instanceof RpcSessionCreateRequestMsg) { @@ -67,19 +65,29 @@ public class RpcSessionActor extends ContextAwareActor { } private void tell(ClusterAPIProtos.ClusterMessage msg) { - session.sendMsg(msg); + if (session != null) { + session.sendMsg(msg); + } else { + log.trace("Failed to send message due to missing session!"); + } } @Override public void postStop() { - log.info("Closing session -> {}", session.getRemoteServer()); - session.close(); + if (session != null) { + log.info("Closing session -> {}", session.getRemoteServer()); + try { + session.close(); + } catch (RuntimeException e) { + log.trace("Failed to close session!", e); + } + } } private void initSession(RpcSessionCreateRequestMsg msg) { log.info("[{}] Initializing session", context().self()); ServerAddress remoteServer = msg.getRemoteAddress(); - listener = new BasicRpcSessionListener(systemContext.getActorService(), context().parent(), context().self()); + listener = new BasicRpcSessionListener(systemContext, context().parent(), context().self()); if (msg.getRemoteAddress() == null) { // Server session session = new GrpcSession(listener); @@ -113,7 +121,7 @@ public class RpcSessionActor extends ContextAwareActor { } @Override - public RpcSessionActor create() throws Exception { + public RpcSessionActor create() { return new RpcSessionActor(context, sessionId); } } diff --git a/application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java b/application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java index 1c7e2d2664..a53b88e20f 100644 --- a/application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/service/ContextAwareActor.java @@ -37,7 +37,7 @@ public abstract class ContextAwareActor extends UntypedActor { } @Override - public void onReceive(Object msg) throws Exception { + public void onReceive(Object msg) { if (log.isDebugEnabled()) { log.debug("Processing msg: {}", msg); } diff --git a/application/src/main/java/org/thingsboard/server/service/executors/ClusterRpcCallbackExecutorService.java b/application/src/main/java/org/thingsboard/server/service/executors/ClusterRpcCallbackExecutorService.java new file mode 100644 index 0000000000..2def8b0e1e --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/executors/ClusterRpcCallbackExecutorService.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.service.executors; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class ClusterRpcCallbackExecutorService extends AbstractListeningExecutor { + + @Value("${actors.cluster.grpc_callback_thread_pool_size}") + private int grpcCallbackExecutorThreadPoolSize; + + @Override + protected int getThreadPollSize() { + return grpcCallbackExecutorThreadPoolSize; + } + +} 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 index cc8ecee1d6..c78d7df578 100644 --- a/application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java +++ b/application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry; import java.util.Collections; +import java.util.UUID; import static org.thingsboard.server.common.data.CacheConstants.SESSIONS_CACHE; @@ -47,4 +48,10 @@ public class DefaultDeviceSessionCacheService implements DeviceSessionCacheServi return sessions; } + public static void main (String[] args){ + UUID uuid = UUID.fromString("d5db434e-9cd2-4903-8b3b-421b2d93664d"); + System.out.println(uuid.getMostSignificantBits()); + System.out.println(uuid.getLeastSignificantBits()); + } + } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 60ce107a27..a2294d80d0 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -153,6 +153,8 @@ sql: # Actor system parameters actors: + cluster: + grpc_callback_thread_pool_size: "${ACTORS_CLUSTER_GRPC_CALLBACK_THREAD_POOL_SIZE:10}" tenant: create_components_on_init: "${ACTORS_TENANT_CREATE_COMPONENTS_ON_INIT:true}" session: From 7bb482bb65704089c8a404b12acd6cd00c9154ba Mon Sep 17 00:00:00 2001 From: vparomskiy Date: Wed, 7 Nov 2018 16:16:30 +0200 Subject: [PATCH 34/35] fix property and env variables names --- application/src/main/resources/thingsboard.yml | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index a2294d80d0..41e5fad5ac 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -64,7 +64,7 @@ cluster: # Plugins configuration parameters plugins: - # Comma seperated package list used during classpath scanning for plugins + # Comma separated package list used during classpath scanning for plugins scan_packages: "${PLUGINS_SCAN_PACKAGES:org.thingsboard.server.extensions,org.thingsboard.rule.engine}" # Security parameters @@ -84,7 +84,7 @@ dashboard: max_datapoints_limit: "${DASHBOARD_MAX_DATAPOINTS_LIMIT:50000}" database: - ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # mas number of DB queries generated by single API call to fetch telemetry records + ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records entities: type: "${DATABASE_ENTITIES_TYPE:sql}" # cassandra OR sql ts: @@ -107,7 +107,7 @@ cassandra: metrics: "${CASSANDRA_DISABLE_METRICS:true}" # NONE SNAPPY LZ4 compression: "${CASSANDRA_COMPRESSION:none}" - # Specify cassandra claster initialization timeout (if no hosts available during startup) + # Specify cassandra cluster initialization timeout in milliseconds (if no hosts available during startup) init_timeout_ms: "${CASSANDRA_CLUSTER_INIT_TIMEOUT_MS:300000}" # Specify cassandra claster initialization retry interval (if no hosts available during startup) init_retry_interval_ms: "${CASSANDRA_CLUSTER_INIT_RETRY_INTERVAL_MS:3000}" @@ -153,8 +153,6 @@ sql: # Actor system parameters actors: - cluster: - grpc_callback_thread_pool_size: "${ACTORS_CLUSTER_GRPC_CALLBACK_THREAD_POOL_SIZE:10}" tenant: create_components_on_init: "${ACTORS_TENANT_CREATE_COMPONENTS_ON_INIT:true}" session: @@ -310,7 +308,7 @@ audit_log: "user": "${AUDIT_LOG_MASK_USER:W}" "rule_chain": "${AUDIT_LOG_MASK_RULE_CHAIN:W}" "alarm": "${AUDIT_LOG_MASK_ALARM:W}" - "entity_view": "${AUDIT_LOG_MASK_RULE_CHAIN:W}" + "entity_view": "${AUDIT_LOG_MASK_ENTITY_VIEW:W}" sink: # Type of external sink. possible options: none, elasticsearch type: "${AUDIT_LOG_SINK_TYPE:none}" @@ -324,7 +322,7 @@ audit_log: date_format: "${AUDIT_LOG_SINK_DATE_FORMAT:YYYY.MM.DD}" scheme_name: "${AUDIT_LOG_SINK_SCHEME_NAME:http}" # http or https host: "${AUDIT_LOG_SINK_HOST:localhost}" - port: "${AUDIT_LOG_SINK_POST:9200}" + port: "${AUDIT_LOG_SINK_PORT:9200}" user_name: "${AUDIT_LOG_SINK_USER_NAME:}" password: "${AUDIT_LOG_SINK_PASSWORD:}" @@ -344,7 +342,7 @@ kafka: requests_topic: "${TB_TRANSPORT_API_REQUEST_TOPIC:tb.transport.api.requests}" responses_topic: "${TB_TRANSPORT_API_RESPONSE_TOPIC:tb.transport.api.responses}" max_pending_requests: "${TB_TRANSPORT_MAX_PENDING_REQUESTS:10000}" - request_timeout: "${TB_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}" + max_requests_timeout: "${TB_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}" request_poll_interval: "${TB_TRANSPORT_REQUEST_POLL_INTERVAL_MS:25}" request_auto_commit_interval: "${TB_TRANSPORT_REQUEST_AUTO_COMMIT_INTERVAL_MS:100}" rule_engine: @@ -371,7 +369,7 @@ js: # JS Eval request topic request_topic: "${REMOTE_JS_EVAL_REQUEST_TOPIC:js.eval.requests}" # JS Eval responses topic prefix that is combined with node id - response_topic_prefix: "${REMOTE_JS_EVAL_REQUEST_TOPIC:js.eval.responses}" + response_topic_prefix: "${REMOTE_JS_EVAL_RESPONSE_TOPIC:js.eval.responses}" # JS Eval max pending requests max_pending_requests: "${REMOTE_JS_MAX_PENDING_REQUESTS:10000}" # JS Eval max request timeout From 69263e3a953d4d75c728ab02e5727be909a89281 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 7 Nov 2018 19:09:55 +0200 Subject: [PATCH 35/35] Improve static resources and API requests loadbalancing. --- .../src/main/resources/thingsboard.yml | 2 + docker/check-dirs.sh | 26 ------- docker/docker-compose.yml | 3 - docker/docker-install-tb.sh | 2 - docker/docker-start-services.sh | 2 - docker/docker-upgrade-tb.sh | 2 - docker/haproxy/config/haproxy.cfg | 20 +++-- docker/tb-web-ui.env | 3 +- msa/web-ui/build.gradle | 2 +- .../config/custom-environment-variables.yml | 1 + msa/web-ui/config/default.yml | 1 + msa/web-ui/server.js | 77 +++++++++++-------- 12 files changed, 63 insertions(+), 78 deletions(-) delete mode 100755 docker/check-dirs.sh diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 41e5fad5ac..f8f71e0813 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -153,6 +153,8 @@ sql: # Actor system parameters actors: + cluster: + grpc_callback_thread_pool_size: "${ACTORS_CLUSTER_GRPC_CALLBACK_THREAD_POOL_SIZE:10}" tenant: create_components_on_init: "${ACTORS_TENANT_CREATE_COMPONENTS_ON_INIT:true}" session: diff --git a/docker/check-dirs.sh b/docker/check-dirs.sh deleted file mode 100755 index 2042a44aea..0000000000 --- a/docker/check-dirs.sh +++ /dev/null @@ -1,26 +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. -# - -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 - if [ ! -d "$dir" ]; then - echo creating dir $dir - mkdir -p $dir - fi -done diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 8f9666851b..267d56002d 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -180,8 +180,6 @@ services: - ./haproxy/certs.d:/usr/local/etc/haproxy/certs.d ports: - "80:80" - - "8080" - - "8090" - "443:443" - "1883:1883" - "9999:9999" @@ -191,7 +189,6 @@ services: HTTP_PORT: 80 HTTPS_PORT: 443 MQTT_PORT: 1883 - TB_API_PORT: 8090 FORCE_HTTPS_REDIRECT: "false" links: - tb1 diff --git a/docker/docker-install-tb.sh b/docker/docker-install-tb.sh index 9032e2fd15..3816229f3a 100755 --- a/docker/docker-install-tb.sh +++ b/docker/docker-install-tb.sh @@ -15,8 +15,6 @@ # limitations under the License. # -./check-dirs.sh - while [[ $# -gt 0 ]] do key="$1" diff --git a/docker/docker-start-services.sh b/docker/docker-start-services.sh index 8437695a1a..452a823d42 100755 --- a/docker/docker-start-services.sh +++ b/docker/docker-start-services.sh @@ -15,8 +15,6 @@ # limitations under the License. # -./check-dirs.sh - set -e source compose-utils.sh diff --git a/docker/docker-upgrade-tb.sh b/docker/docker-upgrade-tb.sh index d83b1b7579..6f460f3baf 100755 --- a/docker/docker-upgrade-tb.sh +++ b/docker/docker-upgrade-tb.sh @@ -15,8 +15,6 @@ # limitations under the License. # -./check-dirs.sh - for i in "$@" do case $i in diff --git a/docker/haproxy/config/haproxy.cfg b/docker/haproxy/config/haproxy.cfg index 8ba0ce50c7..50566c93b9 100644 --- a/docker/haproxy/config/haproxy.cfg +++ b/docker/haproxy/config/haproxy.cfg @@ -56,13 +56,20 @@ frontend http-in reqadd X-Forwarded-Proto:\ http + acl acl_static path_beg /static/ /index.html + acl acl_static path / + acl acl_static_rulenode path_beg /static/rulenode/ + 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 { env(FORCE_HTTPS_REDIRECT) -m str true } + use_backend letsencrypt_http if letsencrypt_http_acl use_backend tb-http-backend if transport_http_acl + use_backend tb-web-backend if acl_static !acl_static_rulenode - default_backend tb-web-backend + default_backend tb-api-backend frontend https_in bind *:${HTTPS_PORT} ssl crt /usr/local/etc/haproxy/default.pem crt /usr/local/etc/haproxy/certs.d ciphers ECDHE-RSA-AES256-SHA:RC4-SHA:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM @@ -72,14 +79,15 @@ frontend https_in reqadd X-Forwarded-Proto:\ https acl transport_http_acl path_beg /api/v1/ - use_backend tb-http-backend if transport_http_acl - default_backend tb-web-backend + acl acl_static path_beg /static/ /index.html + acl acl_static path / + acl acl_static_rulenode path_beg /static/rulenode/ -frontend http-api-in - bind *:${TB_API_PORT} + use_backend tb-http-backend if transport_http_acl + use_backend tb-web-backend if acl_static !acl_static_rulenode - default_backend tb-api-backend + default_backend tb-api-backend backend letsencrypt_http server letsencrypt_http_srv 127.0.0.1:8080 diff --git a/docker/tb-web-ui.env b/docker/tb-web-ui.env index fc50db0744..3916337eeb 100644 --- a/docker/tb-web-ui.env +++ b/docker/tb-web-ui.env @@ -1,8 +1,7 @@ HTTP_BIND_ADDRESS=0.0.0.0 HTTP_BIND_PORT=8080 -TB_HOST=haproxy -TB_PORT=8090 +TB_ENABLE_PROXY=false LOGGER_LEVEL=info LOG_FOLDER=logs LOGGER_FILENAME=tb-web-ui-%DATE%.log diff --git a/msa/web-ui/build.gradle b/msa/web-ui/build.gradle index 7372c0a819..e298541b29 100644 --- a/msa/web-ui/build.gradle +++ b/msa/web-ui/build.gradle @@ -66,7 +66,7 @@ ospackage { } // Copy web files - from("target/package/linux/web") { + from("target/web") { into "web" } diff --git a/msa/web-ui/config/custom-environment-variables.yml b/msa/web-ui/config/custom-environment-variables.yml index 9472a50571..de357c9234 100644 --- a/msa/web-ui/config/custom-environment-variables.yml +++ b/msa/web-ui/config/custom-environment-variables.yml @@ -20,6 +20,7 @@ server: # Server bind port port: "HTTP_BIND_PORT" thingsboard: + enableProxy: "TB_ENABLE_PROXY" # ThingsBoard node host host: "TB_HOST" # ThingsBoard node port diff --git a/msa/web-ui/config/default.yml b/msa/web-ui/config/default.yml index cf27a14015..c7f3ba96f5 100644 --- a/msa/web-ui/config/default.yml +++ b/msa/web-ui/config/default.yml @@ -20,6 +20,7 @@ server: # Server bind port port: "8090" thingsboard: + enableProxy: "false" # ThingsBoard node host host: "localhost" # ThingsBoard node port diff --git a/msa/web-ui/server.js b/msa/web-ui/server.js index 44e175a84c..31d35a29fa 100644 --- a/msa/web-ui/server.js +++ b/msa/web-ui/server.js @@ -31,14 +31,19 @@ var server; const bindAddress = config.get('server.address'); const bindPort = config.get('server.port'); + const thingsboardEnableProxy = config.get('thingsboard.enableProxy'); + const thingsboardHost = config.get('thingsboard.host'); const thingsboardPort = config.get('thingsboard.port'); logger.info('Bind address: %s', bindAddress); logger.info('Bind port: %s', bindPort); + logger.info('ThingsBoard Enable Proxy: %s', thingsboardEnableProxy); logger.info('ThingsBoard host: %s', thingsboardHost); logger.info('ThingsBoard port: %s', thingsboardPort); + const useApiProxy = thingsboardEnableProxy === "true"; + var webDir = path.join(__dirname, 'web'); if (typeof process.env.WEB_FOLDER === 'string') { @@ -49,47 +54,51 @@ var server; const app = express(); server = http.createServer(app); - const apiProxy = httpProxy.createProxyServer({ - target: { - host: thingsboardHost, - port: thingsboardPort - } - }); - - apiProxy.on('error', function (err, req, res) { - logger.warn('API proxy error: %s', err.message); - res.writeHead(500); - if (err.code && err.code === 'ECONNREFUSED') { - res.end('Unable to connect to ThingsBoard server.'); - } else { - res.end('Thingsboard server connection error: ' + err.code ? err.code : ''); - } - }); - - const root = path.join(webDir, 'public'); - - const staticDir = path.join(root, 'static'); + if (useApiProxy) { + const apiProxy = httpProxy.createProxyServer({ + target: { + host: thingsboardHost, + port: thingsboardPort + } + }); + + apiProxy.on('error', function (err, req, res) { + logger.warn('API proxy error: %s', err.message); + res.writeHead(500); + if (err.code && err.code === 'ECONNREFUSED') { + res.end('Unable to connect to ThingsBoard server.'); + } else { + res.end('Thingsboard server connection error: ' + err.code ? err.code : ''); + } + }); + } - app.all('/api/*', (req, res) => { - logger.debug(req.method + ' ' + req.originalUrl); - apiProxy.web(req, res); - }); + if (useApiProxy) { + app.all('/api/*', (req, res) => { + logger.debug(req.method + ' ' + req.originalUrl); + apiProxy.web(req, res); + }); - app.all('/static/rulenode/*', (req, res) => { - apiProxy.web(req, res); - }); + app.all('/static/rulenode/*', (req, res) => { + apiProxy.web(req, res); + }); + } app.use(historyApiFallback()); - app.use('/static', express.static(staticDir)); + const root = path.join(webDir, 'public'); - app.get('*', (req, res) => { - apiProxy.web(req, res); - }); + app.use(express.static(root)); - server.on('upgrade', (req, socket, head) => { - apiProxy.ws(req, socket, head); - }); + if (useApiProxy) { + app.get('*', (req, res) => { + apiProxy.web(req, res); + }); + + server.on('upgrade', (req, socket, head) => { + apiProxy.ws(req, socket, head); + }); + } server.listen(bindPort, bindAddress, (error) => { if (error) {