Commit 905273ea3dba010f01781f28b998c66fc95b2477

Authored by Volodymyr Babak
2 parents efaca8e7 eeec6bac

Merge remote-tracking branch 'upstream/develop/2.5.5' into develop/2.6-edge

Showing 30 changed files with 453 additions and 43 deletions
... ... @@ -107,6 +107,7 @@ class DefaultTbContext implements TbContext {
107 107 if (nodeCtx.getSelf().isDebugMode()) {
108 108 relationTypes.forEach(relationType -> mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, relationType, th));
109 109 }
  110 + msg.getCallback().onProcessingEnd(nodeCtx.getSelf().getId());
110 111 nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationTypes, msg, th != null ? th.getMessage() : null));
111 112 }
112 113
... ... @@ -216,6 +217,7 @@ class DefaultTbContext implements TbContext {
216 217 if (nodeCtx.getSelf().isDebugMode()) {
217 218 mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), tbMsg, "ACK", null);
218 219 }
  220 + tbMsg.getCallback().onProcessingEnd(nodeCtx.getSelf().getId());
219 221 tbMsg.getCallback().onSuccess();
220 222 }
221 223
... ... @@ -252,26 +254,26 @@ class DefaultTbContext implements TbContext {
252 254 }
253 255
254 256 public TbMsg customerCreatedMsg(Customer customer, RuleNodeId ruleNodeId) {
255   - return entityCreatedMsg(customer, customer.getId(), ruleNodeId);
  257 + return entityActionMsg(customer, customer.getId(), ruleNodeId, DataConstants.ENTITY_CREATED);
256 258 }
257 259
258 260 public TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId) {
259   - return entityCreatedMsg(device, device.getId(), ruleNodeId);
  261 + return entityActionMsg(device, device.getId(), ruleNodeId, DataConstants.ENTITY_CREATED);
260 262 }
261 263
262 264 public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) {
263   - return entityCreatedMsg(asset, asset.getId(), ruleNodeId);
  265 + return entityActionMsg(asset, asset.getId(), ruleNodeId, DataConstants.ENTITY_CREATED);
264 266 }
265 267
266   - public TbMsg alarmCreatedMsg(Alarm alarm, RuleNodeId ruleNodeId) {
267   - return entityCreatedMsg(alarm, alarm.getId(), ruleNodeId);
  268 + public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action) {
  269 + return entityActionMsg(alarm, alarm.getId(), ruleNodeId, action);
268 270 }
269 271
270   - public <E, I extends EntityId> TbMsg entityCreatedMsg(E entity, I id, RuleNodeId ruleNodeId) {
  272 + public <E, I extends EntityId> TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action) {
271 273 try {
272   - return TbMsg.newMsg(DataConstants.ENTITY_CREATED, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity)));
  274 + return TbMsg.newMsg(action, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity)));
273 275 } catch (JsonProcessingException | IllegalArgumentException e) {
274   - throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " created msg: " + e);
  276 + throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + action + " msg: " + e);
275 277 }
276 278 }
277 279
... ...
... ... @@ -103,7 +103,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
103 103 }
104 104
105 105 void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception {
106   - msg.getMsg().getCallback().visit(info);
  106 + msg.getMsg().getCallback().onProcessingStart(info);
107 107 checkActive(msg.getMsg());
108 108 if (ruleNode.isDebugMode()) {
109 109 systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), msg.getFromRelationType());
... ...
... ... @@ -132,6 +132,7 @@ public class AlarmController extends BaseController {
132 132 long ackTs = System.currentTimeMillis();
133 133 alarmService.ackAlarm(getCurrentUser().getTenantId(), alarmId, ackTs).get();
134 134 alarm.setAckTs(ackTs);
  135 + alarm.setStatus(alarm.getStatus().isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK);
135 136 logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null);
136 137
137 138 sendNotificationMsgToEdgeService(getTenantId(), alarmId, EdgeEventActionType.ALARM_ACK);
... ... @@ -151,6 +152,7 @@ public class AlarmController extends BaseController {
151 152 long clearTs = System.currentTimeMillis();
152 153 alarmService.clearAlarm(getCurrentUser().getTenantId(), alarmId, null, clearTs).get();
153 154 alarm.setClearTs(clearTs);
  155 + alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK);
154 156 logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null);
155 157
156 158 sendNotificationMsgToEdgeService(getTenantId(), alarmId, EdgeEventActionType.ALARM_CLEAR);
... ...
... ... @@ -176,7 +176,8 @@ public class AuthController extends BaseController {
176 176 try {
177 177 String email = resetPasswordByEmailRequest.get("email").asText();
178 178 UserCredentials userCredentials = userService.requestPasswordReset(TenantId.SYS_TENANT_ID, email);
179   - String baseUrl = MiscUtils.constructBaseUrl(request);
  179 + User user = userService.findUserById(TenantId.SYS_TENANT_ID, userCredentials.getUserId());
  180 + String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request);
180 181 String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl,
181 182 userCredentials.getResetToken());
182 183
... ... @@ -224,7 +225,7 @@ public class AuthController extends BaseController {
224 225 User user = userService.findUserById(TenantId.SYS_TENANT_ID, credentials.getUserId());
225 226 UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
226 227 SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal);
227   - String baseUrl = MiscUtils.constructBaseUrl(request);
  228 + String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request);
228 229 String loginUrl = String.format("%s/login", baseUrl);
229 230 String email = user.getEmail();
230 231
... ... @@ -273,7 +274,7 @@ public class AuthController extends BaseController {
273 274 User user = userService.findUserById(TenantId.SYS_TENANT_ID, userCredentials.getUserId());
274 275 UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
275 276 SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), principal);
276   - String baseUrl = MiscUtils.constructBaseUrl(request);
  277 + String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request);
277 278 String loginUrl = String.format("%s/login", baseUrl);
278 279 String email = user.getEmail();
279 280 mailService.sendPasswordWasResetEmail(loginUrl, email);
... ...
... ... @@ -53,6 +53,7 @@ import org.thingsboard.server.service.security.model.token.JwtToken;
53 53 import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
54 54 import org.thingsboard.server.service.security.permission.Operation;
55 55 import org.thingsboard.server.service.security.permission.Resource;
  56 +import org.thingsboard.server.service.security.system.SystemSecurityService;
56 57 import org.thingsboard.server.utils.MiscUtils;
57 58
58 59 import javax.servlet.http.HttpServletRequest;
... ... @@ -79,6 +80,9 @@ public class UserController extends BaseController {
79 80 @Autowired
80 81 private RefreshTokenRepository refreshTokenRepository;
81 82
  83 + @Autowired
  84 + private SystemSecurityService systemSecurityService;
  85 +
82 86
83 87 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
84 88 @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET)
... ... @@ -146,7 +150,7 @@ public class UserController extends BaseController {
146 150 if (sendEmail) {
147 151 SecurityUser authUser = getCurrentUser();
148 152 UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), savedUser.getId());
149   - String baseUrl = MiscUtils.constructBaseUrl(request);
  153 + String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request);
150 154 String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl,
151 155 userCredentials.getActivateToken());
152 156 String email = savedUser.getEmail();
... ... @@ -189,7 +193,7 @@ public class UserController extends BaseController {
189 193
190 194 UserCredentials userCredentials = userService.findUserCredentialsByUserId(getCurrentUser().getTenantId(), user.getId());
191 195 if (!userCredentials.isEnabled()) {
192   - String baseUrl = MiscUtils.constructBaseUrl(request);
  196 + String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request);
193 197 String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl,
194 198 userCredentials.getActivateToken());
195 199 mailService.sendActivationEmail(activateUrl, email);
... ... @@ -214,7 +218,7 @@ public class UserController extends BaseController {
214 218 SecurityUser authUser = getCurrentUser();
215 219 UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), user.getId());
216 220 if (!userCredentials.isEnabled()) {
217   - String baseUrl = MiscUtils.constructBaseUrl(request);
  221 + String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request);
218 222 String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl,
219 223 userCredentials.getActivateToken());
220 224 return activateUrl;
... ...
... ... @@ -116,6 +116,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
116 116 generalSettings.setKey("general");
117 117 ObjectNode node = objectMapper.createObjectNode();
118 118 node.put("baseUrl", "http://localhost:8080");
  119 + node.put("prohibitDifferentUrl", true);
119 120 generalSettings.setJsonValue(node);
120 121 adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, generalSettings);
121 122
... ...
... ... @@ -165,7 +165,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
165 165 submitStrategy.init(msgs);
166 166
167 167 while (!stopped) {
168   - TbMsgPackProcessingContext ctx = new TbMsgPackProcessingContext(submitStrategy);
  168 + TbMsgPackProcessingContext ctx = new TbMsgPackProcessingContext(configuration.getName(), submitStrategy);
169 169 submitStrategy.submitAttempt((id, msg) -> submitExecutor.submit(() -> {
170 170 log.trace("[{}] Creating callback for message: {}", id, msg.getValue());
171 171 ToRuleEngineMsg toRuleEngineMsg = msg.getValue();
... ... @@ -194,6 +194,8 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
194 194 if (!ctx.getFailedMap().isEmpty()) {
195 195 printFirstOrAll(configuration, ctx, ctx.getFailedMap(), "Failed");
196 196 }
  197 + ctx.printProfilerStats();
  198 +
197 199 TbRuleEngineProcessingDecision decision = ackStrategy.analyze(result);
198 200 if (statsEnabled) {
199 201 stats.log(result, decision.isCommit());
... ...
... ... @@ -49,8 +49,14 @@ public class TbMsgPackCallback implements TbMsgCallback {
49 49 }
50 50
51 51 @Override
52   - public void visit(RuleNodeInfo ruleNodeInfo) {
53   - log.trace("[{}] ON PROCESS: {}", id, ruleNodeInfo);
54   - ctx.visit(id, ruleNodeInfo);
  52 + public void onProcessingStart(RuleNodeInfo ruleNodeInfo) {
  53 + log.trace("[{}] ON PROCESSING START: {}", id, ruleNodeInfo);
  54 + ctx.onProcessingStart(id, ruleNodeInfo);
  55 + }
  56 +
  57 + @Override
  58 + public void onProcessingEnd(RuleNodeId ruleNodeId) {
  59 + log.trace("[{}] ON PROCESSING END: {}", id, ruleNodeId);
  60 + ctx.onProcessingEnd(id, ruleNodeId);
55 61 }
56 62 }
... ...
... ... @@ -16,6 +16,7 @@
16 16 package org.thingsboard.server.service.queue;
17 17
18 18 import lombok.Getter;
  19 +import lombok.extern.slf4j.Slf4j;
19 20 import org.thingsboard.server.common.data.id.RuleNodeId;
20 21 import org.thingsboard.server.common.data.id.TenantId;
21 22 import org.thingsboard.server.common.msg.queue.RuleEngineException;
... ... @@ -24,6 +25,8 @@ import org.thingsboard.server.gen.transport.TransportProtos;
24 25 import org.thingsboard.server.queue.common.TbProtoQueueMsg;
25 26 import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy;
26 27
  28 +import java.util.Comparator;
  29 +import java.util.Map;
27 30 import java.util.UUID;
28 31 import java.util.concurrent.ConcurrentHashMap;
29 32 import java.util.concurrent.ConcurrentMap;
... ... @@ -31,9 +34,13 @@ import java.util.concurrent.CountDownLatch;
31 34 import java.util.concurrent.TimeUnit;
32 35 import java.util.concurrent.atomic.AtomicInteger;
33 36
  37 +@Slf4j
34 38 public class TbMsgPackProcessingContext {
35 39
  40 + private final String queueName;
36 41 private final TbRuleEngineSubmitStrategy submitStrategy;
  42 + @Getter
  43 + private final boolean profilerEnabled;
37 44 private final AtomicInteger pendingCount;
38 45 private final CountDownLatch processingTimeoutLatch = new CountDownLatch(1);
39 46 @Getter
... ... @@ -47,14 +54,20 @@ public class TbMsgPackProcessingContext {
47 54
48 55 private final ConcurrentMap<UUID, RuleNodeInfo> lastRuleNodeMap = new ConcurrentHashMap<>();
49 56
50   - public TbMsgPackProcessingContext(TbRuleEngineSubmitStrategy submitStrategy) {
  57 + public TbMsgPackProcessingContext(String queueName, TbRuleEngineSubmitStrategy submitStrategy) {
  58 + this.queueName = queueName;
51 59 this.submitStrategy = submitStrategy;
  60 + this.profilerEnabled = log.isDebugEnabled();
52 61 this.pendingMap = submitStrategy.getPendingMap();
53 62 this.pendingCount = new AtomicInteger(pendingMap.size());
54 63 }
55 64
56 65 public boolean await(long packProcessingTimeout, TimeUnit milliseconds) throws InterruptedException {
57   - return processingTimeoutLatch.await(packProcessingTimeout, milliseconds);
  66 + boolean success = processingTimeoutLatch.await(packProcessingTimeout, milliseconds);
  67 + if (!success && profilerEnabled) {
  68 + msgProfilerMap.values().forEach(this::onTimeout);
  69 + }
  70 + return success;
58 71 }
59 72
60 73 public void onSuccess(UUID id) {
... ... @@ -85,12 +98,53 @@ public class TbMsgPackProcessingContext {
85 98 }
86 99 }
87 100
88   - public void visit(UUID id, RuleNodeInfo ruleNodeInfo) {
  101 + private final ConcurrentHashMap<UUID, TbMsgProfilerInfo> msgProfilerMap = new ConcurrentHashMap<>();
  102 + private final ConcurrentHashMap<UUID, TbRuleNodeProfilerInfo> ruleNodeProfilerMap = new ConcurrentHashMap<>();
  103 +
  104 + public void onProcessingStart(UUID id, RuleNodeInfo ruleNodeInfo) {
89 105 lastRuleNodeMap.put(id, ruleNodeInfo);
  106 + if (profilerEnabled) {
  107 + msgProfilerMap.computeIfAbsent(id, TbMsgProfilerInfo::new).onStart(ruleNodeInfo.getRuleNodeId());
  108 + ruleNodeProfilerMap.putIfAbsent(ruleNodeInfo.getRuleNodeId().getId(), new TbRuleNodeProfilerInfo(ruleNodeInfo));
  109 + }
  110 + }
  111 +
  112 + public void onProcessingEnd(UUID id, RuleNodeId ruleNodeId) {
  113 + if (profilerEnabled) {
  114 + long processingTime = msgProfilerMap.computeIfAbsent(id, TbMsgProfilerInfo::new).onEnd(ruleNodeId);
  115 + if (processingTime > 0) {
  116 + ruleNodeProfilerMap.computeIfAbsent(ruleNodeId.getId(), TbRuleNodeProfilerInfo::new).record(processingTime);
  117 + }
  118 + }
  119 + }
  120 +
  121 + public void onTimeout(TbMsgProfilerInfo profilerInfo) {
  122 + Map.Entry<UUID, Long> ruleNodeInfo = profilerInfo.onTimeout();
  123 + if (ruleNodeInfo != null) {
  124 + ruleNodeProfilerMap.computeIfAbsent(ruleNodeInfo.getKey(), TbRuleNodeProfilerInfo::new).record(ruleNodeInfo.getValue());
  125 + }
90 126 }
91 127
92 128 public RuleNodeInfo getLastVisitedRuleNode(UUID id) {
93 129 return lastRuleNodeMap.get(id);
94 130 }
95 131
  132 + public void printProfilerStats() {
  133 + if (profilerEnabled) {
  134 + log.debug("Top Rule Nodes by max execution time:");
  135 + ruleNodeProfilerMap.values().stream()
  136 + .sorted(Comparator.comparingLong(TbRuleNodeProfilerInfo::getMaxExecutionTime).reversed()).limit(5)
  137 + .forEach(info -> log.debug("[{}][{}] max execution time: {}. {}", queueName, info.getRuleNodeId(), info.getMaxExecutionTime(), info.getLabel()));
  138 +
  139 + log.info("Top Rule Nodes by avg execution time:");
  140 + ruleNodeProfilerMap.values().stream()
  141 + .sorted(Comparator.comparingDouble(TbRuleNodeProfilerInfo::getAvgExecutionTime).reversed()).limit(5)
  142 + .forEach(info -> log.info("[{}][{}] avg execution time: {}. {}", queueName, info.getRuleNodeId(), info.getAvgExecutionTime(), info.getLabel()));
  143 +
  144 + log.info("Top Rule Nodes by execution count:");
  145 + ruleNodeProfilerMap.values().stream()
  146 + .sorted(Comparator.comparingInt(TbRuleNodeProfilerInfo::getExecutionCount).reversed()).limit(5)
  147 + .forEach(info -> log.info("[{}][{}] execution count: {}. {}", queueName, info.getRuleNodeId(), info.getExecutionCount(), info.getLabel()));
  148 + }
  149 + }
96 150 }
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.queue;
  17 +
  18 +import lombok.extern.slf4j.Slf4j;
  19 +import org.thingsboard.server.common.data.id.RuleNodeId;
  20 +import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
  21 +
  22 +import java.util.AbstractMap;
  23 +import java.util.Map;
  24 +import java.util.UUID;
  25 +import java.util.concurrent.atomic.AtomicLong;
  26 +import java.util.concurrent.locks.Lock;
  27 +import java.util.concurrent.locks.ReentrantLock;
  28 +
  29 +@Slf4j
  30 +public class TbMsgProfilerInfo {
  31 + private final UUID msgId;
  32 + private AtomicLong totalProcessingTime = new AtomicLong();
  33 + private Lock stateLock = new ReentrantLock();
  34 + private RuleNodeId currentRuleNodeId;
  35 + private long stateChangeTime;
  36 +
  37 + public TbMsgProfilerInfo(UUID msgId) {
  38 + this.msgId = msgId;
  39 + }
  40 +
  41 + public void onStart(RuleNodeId ruleNodeId) {
  42 + long currentTime = System.currentTimeMillis();
  43 + stateLock.lock();
  44 + try {
  45 + currentRuleNodeId = ruleNodeId;
  46 + stateChangeTime = currentTime;
  47 + } finally {
  48 + stateLock.unlock();
  49 + }
  50 + }
  51 +
  52 + public long onEnd(RuleNodeId ruleNodeId) {
  53 + long currentTime = System.currentTimeMillis();
  54 + stateLock.lock();
  55 + try {
  56 + if (ruleNodeId.equals(currentRuleNodeId)) {
  57 + long processingTime = currentTime - stateChangeTime;
  58 + stateChangeTime = currentTime;
  59 + totalProcessingTime.addAndGet(processingTime);
  60 + currentRuleNodeId = null;
  61 + return processingTime;
  62 + } else {
  63 + log.trace("[{}] Invalid sequence of rule node processing detected. Expected [{}] but was [{}]", msgId, currentRuleNodeId, ruleNodeId);
  64 + return 0;
  65 + }
  66 + } finally {
  67 + stateLock.unlock();
  68 + }
  69 + }
  70 +
  71 + public Map.Entry<UUID, Long> onTimeout() {
  72 + long currentTime = System.currentTimeMillis();
  73 + stateLock.lock();
  74 + try {
  75 + if (currentRuleNodeId != null && stateChangeTime > 0) {
  76 + long timeoutTime = currentTime - stateChangeTime;
  77 + totalProcessingTime.addAndGet(timeoutTime);
  78 + return new AbstractMap.SimpleEntry<>(currentRuleNodeId.getId(), timeoutTime);
  79 + }
  80 + } finally {
  81 + stateLock.unlock();
  82 + }
  83 + return null;
  84 + }
  85 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.queue;
  17 +
  18 +import lombok.Getter;
  19 +import org.thingsboard.server.common.msg.queue.RuleNodeInfo;
  20 +
  21 +import java.util.UUID;
  22 +import java.util.concurrent.atomic.AtomicInteger;
  23 +import java.util.concurrent.atomic.AtomicLong;
  24 +
  25 +public class TbRuleNodeProfilerInfo {
  26 + @Getter
  27 + private final UUID ruleNodeId;
  28 + @Getter
  29 + private final String label;
  30 + private AtomicInteger executionCount = new AtomicInteger(0);
  31 + private AtomicLong executionTime = new AtomicLong(0);
  32 + private AtomicLong maxExecutionTime = new AtomicLong(0);
  33 +
  34 + public TbRuleNodeProfilerInfo(RuleNodeInfo ruleNodeInfo) {
  35 + this.ruleNodeId = ruleNodeInfo.getRuleNodeId().getId();
  36 + this.label = ruleNodeInfo.toString();
  37 + }
  38 +
  39 + public TbRuleNodeProfilerInfo(UUID ruleNodeId) {
  40 + this.ruleNodeId = ruleNodeId;
  41 + this.label = "";
  42 + }
  43 +
  44 + public void record(long processingTime) {
  45 + executionCount.incrementAndGet();
  46 + executionTime.addAndGet(processingTime);
  47 + while (true) {
  48 + long value = maxExecutionTime.get();
  49 + if (value >= processingTime) {
  50 + break;
  51 + }
  52 + if (maxExecutionTime.compareAndSet(value, processingTime)) {
  53 + break;
  54 + }
  55 + }
  56 + }
  57 +
  58 + int getExecutionCount() {
  59 + return executionCount.get();
  60 + }
  61 +
  62 + long getMaxExecutionTime() {
  63 + return maxExecutionTime.get();
  64 + }
  65 +
  66 + double getAvgExecutionTime() {
  67 + double executionCnt = (double) executionCount.get();
  68 + if (executionCnt > 0) {
  69 + return executionTime.get() / executionCnt;
  70 + } else {
  71 + return 0.0;
  72 + }
  73 + }
  74 +
  75 +}
\ No newline at end of file
... ...
... ... @@ -68,18 +68,20 @@ public class BatchTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSubmitS
68 68 int listSize = orderedMsgList.size();
69 69 int startIdx = Math.min(packIdx.get() * batchSize, listSize);
70 70 int endIdx = Math.min(startIdx + batchSize, listSize);
  71 + Map<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> tmpPack;
71 72 synchronized (pendingPack) {
72 73 pendingPack.clear();
73 74 for (int i = startIdx; i < endIdx; i++) {
74 75 IdMsgPair pair = orderedMsgList.get(i);
75 76 pendingPack.put(pair.uuid, pair.msg);
76 77 }
  78 + tmpPack = new LinkedHashMap<>(pendingPack);
77 79 }
78 80 int submitSize = pendingPack.size();
79 81 if (log.isDebugEnabled() && submitSize > 0) {
80 82 log.debug("[{}] submitting [{}] messages to rule engine", queueName, submitSize);
81 83 }
82   - pendingPack.forEach(msgConsumer);
  84 + tmpPack.forEach(msgConsumer);
83 85 }
84 86
85 87 }
... ...
... ... @@ -30,7 +30,7 @@ import java.nio.charset.StandardCharsets;
30 30
31 31 @Component(value = "oauth2AuthenticationFailureHandler")
32 32 @ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
33   -public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
  33 +public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
34 34
35 35 @Override
36 36 public void onAuthenticationFailure(HttpServletRequest request,
... ...
... ... @@ -59,7 +59,6 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
59 59 public void onAuthenticationSuccess(HttpServletRequest request,
60 60 HttpServletResponse response,
61 61 Authentication authentication) throws IOException {
62   -
63 62 String baseUrl = MiscUtils.constructBaseUrl(request);
64 63 try {
65 64 OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
... ...
... ... @@ -40,17 +40,20 @@ import org.thingsboard.rule.engine.api.MailService;
40 40 import org.thingsboard.server.common.data.AdminSettings;
41 41 import org.thingsboard.server.common.data.User;
42 42 import org.thingsboard.server.common.data.exception.ThingsboardException;
  43 +import org.thingsboard.server.common.data.id.CustomerId;
43 44 import org.thingsboard.server.common.data.id.TenantId;
44 45 import org.thingsboard.server.common.data.security.UserCredentials;
  46 +import org.thingsboard.server.common.data.security.model.SecuritySettings;
  47 +import org.thingsboard.server.common.data.security.model.UserPasswordPolicy;
45 48 import org.thingsboard.server.dao.exception.DataValidationException;
46 49 import org.thingsboard.server.dao.settings.AdminSettingsService;
47 50 import org.thingsboard.server.dao.user.UserService;
48 51 import org.thingsboard.server.dao.user.UserServiceImpl;
49 52 import org.thingsboard.server.service.security.exception.UserPasswordExpiredException;
50   -import org.thingsboard.server.common.data.security.model.SecuritySettings;
51   -import org.thingsboard.server.common.data.security.model.UserPasswordPolicy;
  53 +import org.thingsboard.server.utils.MiscUtils;
52 54
53 55 import javax.annotation.Resource;
  56 +import javax.servlet.http.HttpServletRequest;
54 57 import java.util.ArrayList;
55 58 import java.util.List;
56 59 import java.util.Map;
... ... @@ -146,7 +149,7 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
146 149 if (isPositiveInteger(securitySettings.getPasswordPolicy().getPasswordExpirationPeriodDays())) {
147 150 if ((userCredentials.getCreatedTime()
148 151 + TimeUnit.DAYS.toMillis(securitySettings.getPasswordPolicy().getPasswordExpirationPeriodDays()))
149   - < System.currentTimeMillis()) {
  152 + < System.currentTimeMillis()) {
150 153 userCredentials = userService.requestExpiredPasswordReset(tenantId, userCredentials.getId());
151 154 throw new UserPasswordExpiredException("User password expired!", userCredentials.getResetToken());
152 155 }
... ... @@ -197,6 +200,21 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
197 200 }
198 201 }
199 202
  203 + @Override
  204 + public String getBaseUrl(TenantId tenantId, CustomerId customerId, HttpServletRequest httpServletRequest) {
  205 + String baseUrl;
  206 + AdminSettings generalSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "general");
  207 +
  208 + JsonNode prohibitDifferentUrl = generalSettings.getJsonValue().get("prohibitDifferentUrl");
  209 +
  210 + if (prohibitDifferentUrl != null && prohibitDifferentUrl.asBoolean()) {
  211 + baseUrl = generalSettings.getJsonValue().get("baseUrl").asText();
  212 + } else {
  213 + baseUrl = MiscUtils.constructBaseUrl(httpServletRequest);
  214 + }
  215 + return baseUrl;
  216 + }
  217 +
200 218 private static boolean isPositiveInteger(Integer val) {
201 219 return val != null && val.intValue() > 0;
202 220 }
... ...
... ... @@ -16,11 +16,14 @@
16 16 package org.thingsboard.server.service.security.system;
17 17
18 18 import org.springframework.security.core.AuthenticationException;
  19 +import org.thingsboard.server.common.data.id.CustomerId;
19 20 import org.thingsboard.server.common.data.id.TenantId;
20 21 import org.thingsboard.server.common.data.security.UserCredentials;
21 22 import org.thingsboard.server.dao.exception.DataValidationException;
22 23 import org.thingsboard.server.common.data.security.model.SecuritySettings;
23 24
  25 +import javax.servlet.http.HttpServletRequest;
  26 +
24 27 public interface SystemSecurityService {
25 28
26 29 SecuritySettings getSecuritySettings(TenantId tenantId);
... ... @@ -31,4 +34,6 @@ public interface SystemSecurityService {
31 34
32 35 void validatePassword(TenantId tenantId, String password, UserCredentials userCredentials) throws DataValidationException;
33 36
  37 + String getBaseUrl(TenantId tenantId, CustomerId customerId, HttpServletRequest httpServletRequest);
  38 +
34 39 }
... ...
... ... @@ -175,7 +175,6 @@ public abstract class AbstractControllerTest {
175 175 .apply(springSecurity()).build();
176 176 }
177 177 loginSysAdmin();
178   -
179 178 Tenant tenant = new Tenant();
180 179 tenant.setTitle(TEST_TENANT_NAME);
181 180 Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
... ...
... ... @@ -51,7 +51,7 @@ public class TbMsgPackProcessingContextTest {
51 51 messages.put(UUID.randomUUID(), new TbProtoQueueMsg<>(UUID.randomUUID(), null));
52 52 }
53 53 when(strategyMock.getPendingMap()).thenReturn(messages);
54   - TbMsgPackProcessingContext context = new TbMsgPackProcessingContext(strategyMock);
  54 + TbMsgPackProcessingContext context = new TbMsgPackProcessingContext("Main", strategyMock);
55 55 for (UUID uuid : messages.keySet()) {
56 56 for (int i = 0; i < parallelCount; i++) {
57 57 executorService.submit(() -> context.onSuccess(uuid));
... ...
... ... @@ -15,12 +15,16 @@
15 15 */
16 16 package org.thingsboard.server.common.msg.queue;
17 17
  18 +import lombok.Getter;
18 19 import org.thingsboard.server.common.data.id.RuleNodeId;
19 20
20 21 public class RuleNodeInfo {
21 22 private final String label;
  23 + @Getter
  24 + private final RuleNodeId ruleNodeId;
22 25
23 26 public RuleNodeInfo(RuleNodeId id, String ruleChainName, String ruleNodeName) {
  27 + this.ruleNodeId = id;
24 28 this.label = "[RuleChain: " + ruleChainName + "|RuleNode: " + ruleNodeName + "(" + id + ")]";
25 29 }
26 30
... ...
... ... @@ -15,6 +15,8 @@
15 15 */
16 16 package org.thingsboard.server.common.msg.queue;
17 17
  18 +import org.thingsboard.server.common.data.id.RuleNodeId;
  19 +
18 20 public interface TbMsgCallback {
19 21
20 22 TbMsgCallback EMPTY = new TbMsgCallback() {
... ... @@ -34,7 +36,11 @@ public interface TbMsgCallback {
34 36
35 37 void onFailure(RuleEngineException e);
36 38
37   - default void visit(RuleNodeInfo ruleNodeInfo) {
  39 + default void onProcessingStart(RuleNodeInfo ruleNodeInfo) {
  40 + }
  41 +
  42 + default void onProcessingEnd(RuleNodeId ruleNodeId) {
38 43 }
39 44
  45 +
40 46 }
... ...
... ... @@ -87,8 +87,9 @@ public class TbAwsSqsProducerTemplate<T extends TbQueueMsg> implements TbQueuePr
87 87 sendMsgRequest.withQueueUrl(getQueueUrl(tpi.getFullTopicName()));
88 88 sendMsgRequest.withMessageBody(gson.toJson(new DefaultTbQueueMsg(msg)));
89 89
90   - sendMsgRequest.withMessageGroupId(tpi.getTopic());
91   - sendMsgRequest.withMessageDeduplicationId(UUID.randomUUID().toString());
  90 + String sqsMsgId = UUID.randomUUID().toString();
  91 + sendMsgRequest.withMessageGroupId(sqsMsgId);
  92 + sendMsgRequest.withMessageDeduplicationId(sqsMsgId);
92 93
93 94 ListenableFuture<SendMessageResult> future = producerExecutor.submit(() -> sqsClient.sendMessage(sendMsgRequest));
94 95
... ...
... ... @@ -20,6 +20,7 @@ import com.google.common.util.concurrent.Futures;
20 20 import com.google.common.util.concurrent.ListenableFuture;
21 21 import com.google.common.util.concurrent.MoreExecutors;
22 22 import lombok.extern.slf4j.Slf4j;
  23 +import org.apache.commons.collections.CollectionUtils;
23 24 import org.apache.commons.lang3.StringUtils;
24 25 import org.springframework.beans.factory.annotation.Autowired;
25 26 import org.springframework.stereotype.Service;
... ... @@ -54,8 +55,10 @@ import org.thingsboard.server.dao.tenant.TenantDao;
54 55 import javax.annotation.Nullable;
55 56 import java.util.ArrayList;
56 57 import java.util.HashMap;
  58 +import java.util.HashSet;
57 59 import java.util.List;
58 60 import java.util.Map;
  61 +import java.util.Set;
59 62 import java.util.concurrent.ExecutionException;
60 63
61 64 import static org.thingsboard.server.dao.service.Validator.validateId;
... ... @@ -135,6 +138,10 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
135 138 return null;
136 139 }
137 140
  141 + if (CollectionUtils.isNotEmpty(ruleChainMetaData.getConnections())) {
  142 + validateCircles(ruleChainMetaData.getConnections());
  143 + }
  144 +
138 145 List<RuleNode> nodes = ruleChainMetaData.getNodes();
139 146 List<RuleNode> toAddOrUpdate = new ArrayList<>();
140 147 List<RuleNode> toDelete = new ArrayList<>();
... ... @@ -217,6 +224,31 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
217 224 return loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId());
218 225 }
219 226
  227 + private void validateCircles(List<NodeConnectionInfo> connectionInfos) {
  228 + Map<Integer, Set<Integer>> connectionsMap = new HashMap<>();
  229 + for (NodeConnectionInfo nodeConnection : connectionInfos) {
  230 + if (nodeConnection.getFromIndex() == nodeConnection.getToIndex()) {
  231 + throw new DataValidationException("Can't create the relation to yourself.");
  232 + }
  233 + connectionsMap
  234 + .computeIfAbsent(nodeConnection.getFromIndex(), from -> new HashSet<>())
  235 + .add(nodeConnection.getToIndex());
  236 + }
  237 + connectionsMap.keySet().forEach(key -> validateCircles(key, connectionsMap.get(key), connectionsMap));
  238 + }
  239 +
  240 + private void validateCircles(int from, Set<Integer> toList, Map<Integer, Set<Integer>> connectionsMap) {
  241 + if (toList == null) {
  242 + return;
  243 + }
  244 + for (Integer to : toList) {
  245 + if (from == to) {
  246 + throw new DataValidationException("Can't create circling relations in rule chain.");
  247 + }
  248 + validateCircles(from, connectionsMap.get(to), connectionsMap);
  249 + }
  250 + }
  251 +
220 252 @Override
221 253 public RuleChainMetaData loadRuleChainMetaData(TenantId tenantId, RuleChainId ruleChainId) {
222 254 Validator.validateId(ruleChainId, "Incorrect rule chain id.");
... ... @@ -299,7 +331,6 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
299 331 }
300 332 }
301 333
302   -
303 334 @Override
304 335 public List<RuleNode> getRuleChainNodes(TenantId tenantId, RuleChainId ruleChainId) {
305 336 Validator.validateId(ruleChainId, "Incorrect rule chain id for search request.");
... ...
... ... @@ -319,6 +319,16 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest {
319 319 ruleChainService.deleteRuleChainById(tenantId, savedRuleChainMetaData.getRuleChainId());
320 320 }
321 321
  322 + @Test(expected = DataValidationException.class)
  323 + public void testUpdateRuleChainMetaDataWithCirclingRelation() throws Exception {
  324 + ruleChainService.saveRuleChainMetaData(tenantId, createRuleChainMetadataWithCirclingRelation());
  325 + }
  326 +
  327 + @Test(expected = DataValidationException.class)
  328 + public void testUpdateRuleChainMetaDataWithCirclingRelation2() throws Exception {
  329 + ruleChainService.saveRuleChainMetaData(tenantId, createRuleChainMetadataWithCirclingRelation2());
  330 + }
  331 +
322 332 @Test
323 333 public void testGetDefaultEdgeRuleChains() throws Exception {
324 334 RuleChainId ruleChainId = saveRuleChainAndSetDefaultEdge("Default Edge Rule Chain 1");
... ... @@ -397,5 +407,85 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest {
397 407 return ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData);
398 408 }
399 409
  410 + private RuleChainMetaData createRuleChainMetadataWithCirclingRelation() throws Exception {
  411 + RuleChain ruleChain = new RuleChain();
  412 + ruleChain.setName("My RuleChain");
  413 + ruleChain.setTenantId(tenantId);
  414 + RuleChain savedRuleChain = ruleChainService.saveRuleChain(ruleChain);
  415 +
  416 + RuleChainMetaData ruleChainMetaData = new RuleChainMetaData();
  417 + ruleChainMetaData.setRuleChainId(savedRuleChain.getId());
  418 +
  419 + ObjectMapper mapper = new ObjectMapper();
  420 +
  421 + RuleNode ruleNode1 = new RuleNode();
  422 + ruleNode1.setName("name1");
  423 + ruleNode1.setType("type1");
  424 + ruleNode1.setConfiguration(mapper.readTree("\"key1\": \"val1\""));
  425 +
  426 + RuleNode ruleNode2 = new RuleNode();
  427 + ruleNode2.setName("name2");
  428 + ruleNode2.setType("type2");
  429 + ruleNode2.setConfiguration(mapper.readTree("\"key2\": \"val2\""));
  430 +
  431 + RuleNode ruleNode3 = new RuleNode();
  432 + ruleNode3.setName("name3");
  433 + ruleNode3.setType("type3");
  434 + ruleNode3.setConfiguration(mapper.readTree("\"key3\": \"val3\""));
  435 +
  436 + List<RuleNode> ruleNodes = new ArrayList<>();
  437 + ruleNodes.add(ruleNode1);
  438 + ruleNodes.add(ruleNode2);
  439 + ruleNodes.add(ruleNode3);
  440 + ruleChainMetaData.setFirstNodeIndex(0);
  441 + ruleChainMetaData.setNodes(ruleNodes);
  442 +
  443 + ruleChainMetaData.addConnectionInfo(0,1,"success");
  444 + ruleChainMetaData.addConnectionInfo(0,2,"fail");
  445 + ruleChainMetaData.addConnectionInfo(1,2,"success");
  446 + ruleChainMetaData.addConnectionInfo(2,2,"success");
  447 +
  448 + return ruleChainMetaData;
  449 + }
  450 +
  451 + private RuleChainMetaData createRuleChainMetadataWithCirclingRelation2() throws Exception {
  452 + RuleChain ruleChain = new RuleChain();
  453 + ruleChain.setName("My RuleChain");
  454 + ruleChain.setTenantId(tenantId);
  455 + RuleChain savedRuleChain = ruleChainService.saveRuleChain(ruleChain);
  456 +
  457 + RuleChainMetaData ruleChainMetaData = new RuleChainMetaData();
  458 + ruleChainMetaData.setRuleChainId(savedRuleChain.getId());
  459 +
  460 + ObjectMapper mapper = new ObjectMapper();
  461 +
  462 + RuleNode ruleNode1 = new RuleNode();
  463 + ruleNode1.setName("name1");
  464 + ruleNode1.setType("type1");
  465 + ruleNode1.setConfiguration(mapper.readTree("\"key1\": \"val1\""));
  466 +
  467 + RuleNode ruleNode2 = new RuleNode();
  468 + ruleNode2.setName("name2");
  469 + ruleNode2.setType("type2");
  470 + ruleNode2.setConfiguration(mapper.readTree("\"key2\": \"val2\""));
  471 +
  472 + RuleNode ruleNode3 = new RuleNode();
  473 + ruleNode3.setName("name3");
  474 + ruleNode3.setType("type3");
  475 + ruleNode3.setConfiguration(mapper.readTree("\"key3\": \"val3\""));
  476 +
  477 + List<RuleNode> ruleNodes = new ArrayList<>();
  478 + ruleNodes.add(ruleNode1);
  479 + ruleNodes.add(ruleNode2);
  480 + ruleNodes.add(ruleNode3);
  481 + ruleChainMetaData.setFirstNodeIndex(0);
  482 + ruleChainMetaData.setNodes(ruleNodes);
  483 +
  484 + ruleChainMetaData.addConnectionInfo(0,1,"success");
  485 + ruleChainMetaData.addConnectionInfo(0,2,"fail");
  486 + ruleChainMetaData.addConnectionInfo(1,2,"success");
  487 + ruleChainMetaData.addConnectionInfo(2,0,"success");
400 488
  489 + return ruleChainMetaData;
  490 + }
401 491 }
... ...
1 1 TB_QUEUE_TYPE=kafka
2 2 TB_KAFKA_SERVERS=kafka:9092
3   -TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES=retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100
... ...
... ... @@ -52,11 +52,13 @@ function AwsSqsProducer() {
52 52 queueUrls.set(responseTopic, responseQueueUrl);
53 53 }
54 54
  55 + let msgId = uuid();
  56 +
55 57 let params = {
56 58 MessageBody: msgBody,
57 59 QueueUrl: responseQueueUrl,
58   - MessageGroupId: 'js_eval',
59   - MessageDeduplicationId: uuid()
  60 + MessageGroupId: msgId,
  61 + MessageDeduplicationId: msgId
60 62 };
61 63
62 64 return new Promise((resolve, reject) => {
... ...
... ... @@ -143,7 +143,7 @@ public interface TbContext {
143 143 TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId);
144 144
145 145 // TODO: Does this changes the message?
146   - TbMsg alarmCreatedMsg(Alarm alarm, RuleNodeId ruleNodeId);
  146 + TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action);
147 147
148 148 /*
149 149 *
... ...
... ... @@ -25,6 +25,7 @@ import org.thingsboard.rule.engine.api.TbContext;
25 25 import org.thingsboard.rule.engine.api.TbNode;
26 26 import org.thingsboard.rule.engine.api.TbNodeConfiguration;
27 27 import org.thingsboard.rule.engine.api.TbNodeException;
  28 +import org.thingsboard.server.common.data.DataConstants;
28 29 import org.thingsboard.server.common.data.alarm.Alarm;
29 30 import org.thingsboard.server.common.msg.TbMsg;
30 31 import org.thingsboard.server.common.msg.TbMsgMetaData;
... ... @@ -61,13 +62,11 @@ public abstract class TbAbstractAlarmNode<C extends TbAbstractAlarmNodeConfigura
61 62 if (alarmResult.alarm == null) {
62 63 ctx.tellNext(msg, "False");
63 64 } else if (alarmResult.isCreated) {
64   - ctx.enqueue(ctx.alarmCreatedMsg(alarmResult.alarm, ctx.getSelfId()),
65   - () -> ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Created"),
66   - throwable -> ctx.tellFailure(toAlarmMsg(ctx, alarmResult, msg), throwable));
  65 + tellNext(ctx, msg, alarmResult, DataConstants.ENTITY_CREATED, "Created");
67 66 } else if (alarmResult.isUpdated) {
68   - ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Updated");
  67 + tellNext(ctx, msg, alarmResult, DataConstants.ENTITY_UPDATED, "Updated");
69 68 } else if (alarmResult.isCleared) {
70   - ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Cleared");
  69 + tellNext(ctx, msg, alarmResult, DataConstants.ALARM_CLEAR, "Cleared");
71 70 } else {
72 71 ctx.tellSuccess(msg);
73 72 }
... ... @@ -126,4 +125,10 @@ public abstract class TbAbstractAlarmNode<C extends TbAbstractAlarmNodeConfigura
126 125 this.alarm = alarm;
127 126 }
128 127 }
  128 +
  129 + private void tellNext(TbContext ctx, TbMsg msg, AlarmResult alarmResult, String alarmAction, String alarmResultMsgType) {
  130 + ctx.enqueue(ctx.alarmActionMsg(alarmResult.alarm, ctx.getSelfId(), alarmAction),
  131 + () -> ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), alarmResultMsgType),
  132 + throwable -> ctx.tellFailure(toAlarmMsg(ctx, alarmResult, msg), throwable));
  133 + }
129 134 }
... ...
... ... @@ -34,6 +34,7 @@ import org.thingsboard.rule.engine.api.ScriptEngine;
34 34 import org.thingsboard.rule.engine.api.TbContext;
35 35 import org.thingsboard.rule.engine.api.TbNodeConfiguration;
36 36 import org.thingsboard.rule.engine.api.TbNodeException;
  37 +import org.thingsboard.server.common.data.DataConstants;
37 38 import org.thingsboard.server.common.data.alarm.Alarm;
38 39 import org.thingsboard.server.common.data.id.AlarmId;
39 40 import org.thingsboard.server.common.data.id.DeviceId;
... ... @@ -249,6 +250,8 @@ public class TbAlarmNodeTest {
249 250
250 251 node.onMsg(ctx, msg);
251 252
  253 + verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture());
  254 + successCaptor.getValue().run();
252 255 verify(ctx).tellNext(any(), eq("Updated"));
253 256
254 257 ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
... ... @@ -297,6 +300,8 @@ public class TbAlarmNodeTest {
297 300
298 301 node.onMsg(ctx, msg);
299 302
  303 + verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture());
  304 + successCaptor.getValue().run();
300 305 verify(ctx).tellNext(any(), eq("Cleared"));
301 306
302 307 ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
... ... @@ -345,6 +350,8 @@ public class TbAlarmNodeTest {
345 350
346 351 node.onMsg(ctx, msg);
347 352
  353 + verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture());
  354 + successCaptor.getValue().run();
348 355 verify(ctx).tellNext(any(), eq("Cleared"));
349 356
350 357 ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
... ...
... ... @@ -34,6 +34,14 @@
34 34 <div translate ng-message="required">admin.base-url-required</div>
35 35 </div>
36 36 </md-input-container>
  37 + <md-checkbox class="md-block"
  38 + aria-label="{{ 'admin.prohibit-different-url' | translate }}"
  39 + ng-model="vm.settings.jsonValue.prohibitDifferentUrl">
  40 + {{ 'admin.prohibit-different-url' | translate }}
  41 + </md-checkbox>
  42 + <div translate class="tb-hint">
  43 + admin.prohibit-different-url-hint
  44 + </div>
37 45 <div layout="row" layout-align="end center" width="100%" layout-wrap>
38 46 <md-button ng-disabled="$root.loading || vm.settingsForm.$invalid || !vm.settingsForm.$dirty" type="submit" class="md-raised md-primary">{{'action.save' | translate}}</md-button>
39 47 </div>
... ...
... ... @@ -74,6 +74,8 @@
74 74 "test-mail-sent": "Test mail was successfully sent!",
75 75 "base-url": "Base URL",
76 76 "base-url-required": "Base URL is required.",
  77 + "prohibit-different-url": "Prohibit to use hostname from the client request headers",
  78 + "prohibit-different-url-hint": "This setting should be enabled for production environments. May cause security issues when disabled",
77 79 "mail-from": "Mail From",
78 80 "mail-from-required": "Mail From is required.",
79 81 "smtp-protocol": "SMTP protocol",
... ...