From d00bcbfa83d5c4e759a68a5948413227fb583076 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 28 Oct 2022 13:00:28 +0300 Subject: [PATCH] MVEL executor tests --- .../service/script/MvelInvokeServiceTest.java | 128 ++++++++++++++++++ ...t.java => NashornJsInvokeServiceTest.java} | 36 ++++- .../api/AbstractScriptInvokeService.java | 7 +- 3 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/service/script/MvelInvokeServiceTest.java rename application/src/test/java/org/thingsboard/server/service/script/{LocalJsInvokeServiceTest.java => NashornJsInvokeServiceTest.java} (67%) diff --git a/application/src/test/java/org/thingsboard/server/service/script/MvelInvokeServiceTest.java b/application/src/test/java/org/thingsboard/server/service/script/MvelInvokeServiceTest.java new file mode 100644 index 0000000000..9b714e26ae --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/script/MvelInvokeServiceTest.java @@ -0,0 +1,128 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.script; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.Assert; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.test.context.TestPropertySource; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.script.api.ScriptType; +import org.thingsboard.script.api.mvel.MvelInvokeService; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.controller.AbstractControllerTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@DaoSqlTest +@TestPropertySource(properties = { + "mvel.max_script_body_size=100", + "mvel.max_total_args_size=50", + "mvel.max_result_size=50", + "mvel.max_errors=2", +}) +class MvelInvokeServiceTest extends AbstractControllerTest { + + @Autowired + private MvelInvokeService invokeService; + + @Value("${mvel.max_errors}") + private int maxJsErrors; + + @Test + void givenSimpleScriptTestPerformance() throws ExecutionException, InterruptedException { + int iterations = 100000; + UUID scriptId = evalScript("return msg.temperature > 20"); + // warmup + ObjectNode msg = JacksonUtil.newObjectNode(); + for (int i = 0; i < 100; i++) { + msg.put("temperature", i); + boolean expected = i > 20; + boolean result = Boolean.valueOf(invokeScript(scriptId, JacksonUtil.toString(msg))); + Assert.assertEquals(expected, result); + } + long startTs = System.currentTimeMillis(); + for (int i = 0; i < iterations; i++) { + msg.put("temperature", i); + boolean expected = i > 20; + boolean result = Boolean.valueOf(invokeScript(scriptId, JacksonUtil.toString(msg))); + Assert.assertEquals(expected, result); + } + long duration = System.currentTimeMillis() - startTs; + System.out.println(iterations + " invocations took: " + duration + "ms"); + Assert.assertTrue(duration < TimeUnit.MINUTES.toMillis(1)); + } + + @Test + void givenTooBigScriptForEval_thenReturnError() { + String hugeScript = "var a = 'qwertyqwertywertyqwabababerqwertyqwertywertyqwabababerqwertyqwertywertyqwabababerqwertyqwertywertyqwabababerqwertyqwertywertyqwabababer'; return {a: a};"; + + assertThatThrownBy(() -> { + evalScript(hugeScript); + }).hasMessageContaining("body exceeds maximum allowed size"); + } + + @Test + void givenTooBigScriptInputArgs_thenReturnErrorAndReportScriptExecutionError() throws Exception { + String script = "return { msg: msg };"; + String hugeMsg = "{\"input\":\"123456781234349\"}"; + UUID scriptId = evalScript(script); + + for (int i = 0; i < maxJsErrors; i++) { + assertThatThrownBy(() -> { + invokeScript(scriptId, hugeMsg); + }).hasMessageContaining("input arguments exceed maximum"); + } + assertThatScriptIsBlocked(scriptId); + } + + @Test + void whenScriptInvocationResultIsTooBig_thenReturnErrorAndReportScriptExecutionError() throws Exception { + String script = "s = 'a'; for(int i=0; i<50; i++){ s +='a';} return { s: s};"; + UUID scriptId = evalScript(script); + + for (int i = 0; i < maxJsErrors; i++) { + assertThatThrownBy(() -> { + invokeScript(scriptId, "{}"); + }).hasMessageContaining("result exceeds maximum allowed size"); + } + assertThatScriptIsBlocked(scriptId); + } + + private void assertThatScriptIsBlocked(UUID scriptId) { + assertThatThrownBy(() -> { + invokeScript(scriptId, "{}"); + }).hasMessageContaining("invocation is blocked due to maximum error"); + } + + private UUID evalScript(String script) throws ExecutionException, InterruptedException { + return invokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, script, "msg", "metadata", "msgType").get(); + } + + private String invokeScript(UUID scriptId, String str) throws ExecutionException, InterruptedException { + var msg = JacksonUtil.fromString(str, Map.class); + return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", "POST_TELEMETRY_REQUEST").get().toString(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/service/script/LocalJsInvokeServiceTest.java b/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java similarity index 67% rename from application/src/test/java/org/thingsboard/server/service/script/LocalJsInvokeServiceTest.java rename to application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java index 857a9b7902..414878898e 100644 --- a/application/src/test/java/org/thingsboard/server/service/script/LocalJsInvokeServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java @@ -15,10 +15,13 @@ */ package org.thingsboard.server.service.script; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.Assert; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.test.context.TestPropertySource; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.script.api.ScriptType; import org.thingsboard.script.api.js.NashornJsInvokeService; import org.thingsboard.server.common.data.id.TenantId; @@ -27,6 +30,7 @@ import org.thingsboard.server.dao.service.DaoSqlTest; import java.util.UUID; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -37,14 +41,38 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; "js.max_result_size=50", "js.local.max_errors=2" }) -class LocalJsInvokeServiceTest extends AbstractControllerTest { +class NashornJsInvokeServiceTest extends AbstractControllerTest { @Autowired - private NashornJsInvokeService jsInvokeService; + private NashornJsInvokeService invokeService; @Value("${js.local.max_errors}") private int maxJsErrors; + @Test + void givenSimpleScriptTestPerformance() throws ExecutionException, InterruptedException { + int iterations = 1000; + UUID scriptId = evalScript("return msg.temperature > 20"); + // warmup + ObjectNode msg = JacksonUtil.newObjectNode(); + for (int i = 0; i < 100; i++) { + msg.put("temperature", i); + boolean expected = i > 20; + boolean result = Boolean.valueOf(invokeScript(scriptId, JacksonUtil.toString(msg))); + Assert.assertEquals(expected, result); + } + long startTs = System.currentTimeMillis(); + for (int i = 0; i < iterations; i++) { + msg.put("temperature", i); + boolean expected = i > 20; + boolean result = Boolean.valueOf(invokeScript(scriptId, JacksonUtil.toString(msg))); + Assert.assertEquals(expected, result); + } + long duration = System.currentTimeMillis() - startTs; + System.out.println(iterations + " invocations took: " + duration + "ms"); + Assert.assertTrue(duration < TimeUnit.MINUTES.toMillis(1)); + } + @Test void givenTooBigScriptForEval_thenReturnError() { String hugeScript = "var a = 'qwertyqwertywertyqwabababer'; return {a: a};"; @@ -88,11 +116,11 @@ class LocalJsInvokeServiceTest extends AbstractControllerTest { } private UUID evalScript(String script) throws ExecutionException, InterruptedException { - return jsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, script).get(); + return invokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, script).get(); } private String invokeScript(UUID scriptId, String msg) throws ExecutionException, InterruptedException { - return jsInvokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", "POST_TELEMETRY_REQUEST").get().toString(); + return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", "POST_TELEMETRY_REQUEST").get().toString(); } } diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/AbstractScriptInvokeService.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/AbstractScriptInvokeService.java index bfaadfb4cc..9fb594a7dd 100644 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/AbstractScriptInvokeService.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/AbstractScriptInvokeService.java @@ -222,7 +222,7 @@ public abstract class AbstractScriptInvokeService implements ScriptInvokeService counter, scriptId, t.getMessage()); } } - if(timeout){ + if (timeout) { return new TimeoutException("Script timeout!"); } else { return t; @@ -267,6 +267,11 @@ public abstract class AbstractScriptInvokeService implements ScriptInvokeService for (Object arg : args) { if (arg instanceof CharSequence) { totalArgsSize += ((CharSequence) arg).length(); + } else { + var str = JacksonUtil.toString(arg); + if (str != null) { + totalArgsSize += str.length(); + } } } return totalArgsSize > getMaxTotalArgsSize();