Commit c2bd4117e440da4c1a2ff982e31ae7ae45e23ef2

Authored by zbeacon
2 parents 9cdf3dd8 f73b05a8
Showing 18 changed files with 210 additions and 107 deletions
@@ -50,4 +50,8 @@ public class TbRateLimits { @@ -50,4 +50,8 @@ public class TbRateLimits {
50 return bucket.tryConsume(1); 50 return bucket.tryConsume(1);
51 } 51 }
52 52
  53 + public boolean tryConsume(long number) {
  54 + return bucket.tryConsume(number);
  55 + }
  56 +
53 } 57 }
  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.queue.util;
  17 +
  18 +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
  19 +
  20 +import java.lang.annotation.Retention;
  21 +import java.lang.annotation.RetentionPolicy;
  22 +
  23 +@Retention(RetentionPolicy.RUNTIME)
  24 +@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'")
  25 +public @interface TbTransportComponent {
  26 +}
@@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceTransportType;
20 import org.thingsboard.server.common.data.id.DeviceProfileId; 20 import org.thingsboard.server.common.data.id.DeviceProfileId;
21 import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; 21 import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
22 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; 22 import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
  23 +import org.thingsboard.server.common.transport.limits.TransportRateLimitType;
23 import org.thingsboard.server.gen.transport.TransportProtos; 24 import org.thingsboard.server.gen.transport.TransportProtos;
24 import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; 25 import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg;
25 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; 26 import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
@@ -69,6 +70,8 @@ public interface TransportService { @@ -69,6 +70,8 @@ public interface TransportService {
69 70
70 boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback); 71 boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback);
71 72
  73 + boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback, int dataPoints, TransportRateLimitType... limits);
  74 +
72 void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback<Void> callback); 75 void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback<Void> callback);
73 76
74 void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback<Void> callback); 77 void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback<Void> callback);
@@ -23,11 +23,13 @@ import org.thingsboard.server.common.data.id.DeviceId; @@ -23,11 +23,13 @@ import org.thingsboard.server.common.data.id.DeviceId;
23 import org.thingsboard.server.common.data.id.TenantId; 23 import org.thingsboard.server.common.data.id.TenantId;
24 import org.thingsboard.server.common.transport.TransportTenantProfileCache; 24 import org.thingsboard.server.common.transport.TransportTenantProfileCache;
25 import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; 25 import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult;
  26 +import org.thingsboard.server.queue.util.TbTransportComponent;
26 27
27 import java.util.concurrent.ConcurrentHashMap; 28 import java.util.concurrent.ConcurrentHashMap;
28 import java.util.concurrent.ConcurrentMap; 29 import java.util.concurrent.ConcurrentMap;
29 30
30 @Service 31 @Service
  32 +@TbTransportComponent
31 @Slf4j 33 @Slf4j
32 public class DefaultTransportRateLimitService implements TransportRateLimitService { 34 public class DefaultTransportRateLimitService implements TransportRateLimitService {
33 35
@@ -43,23 +45,21 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi @@ -43,23 +45,21 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi
43 } 45 }
44 46
45 @Override 47 @Override
46 - public TransportRateLimit getRateLimit(TenantId tenantId, TransportRateLimitType limitType) {  
47 - TransportRateLimit[] limits = perTenantLimits.get(tenantId);  
48 - if (limits == null) {  
49 - limits = fetchProfileAndInit(tenantId);  
50 - perTenantLimits.put(tenantId, limits);  
51 - }  
52 - return limits[limitType.ordinal()];  
53 - }  
54 -  
55 - @Override  
56 - public TransportRateLimit getRateLimit(TenantId tenantId, DeviceId deviceId, TransportRateLimitType limitType) {  
57 - TransportRateLimit[] limits = perDeviceLimits.get(deviceId);  
58 - if (limits == null) {  
59 - limits = fetchProfileAndInit(tenantId);  
60 - perDeviceLimits.put(deviceId, limits); 48 + public TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits) {
  49 + TransportRateLimit[] tenantLimits = getTenantRateLimits(tenantId);
  50 + TransportRateLimit[] deviceLimits = getDeviceRateLimits(tenantId, deviceId);
  51 + for (TransportRateLimitType limitType : limits) {
  52 + TransportRateLimit rateLimit;
  53 + if (limitType.isTenantLevel()) {
  54 + rateLimit = tenantLimits[limitType.ordinal()];
  55 + } else {
  56 + rateLimit = deviceLimits[limitType.ordinal()];
  57 + }
  58 + if (!rateLimit.tryConsume(limitType.isMessageLevel() ? 1L : dataPoints)) {
  59 + return limitType;
  60 + }
61 } 61 }
62 - return limits[limitType.ordinal()]; 62 + return null;
63 } 63 }
64 64
65 @Override 65 @Override
@@ -75,7 +75,17 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi @@ -75,7 +75,17 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi
75 mergeLimits(tenantId, fetchProfileAndInit(tenantId)); 75 mergeLimits(tenantId, fetchProfileAndInit(tenantId));
76 } 76 }
77 77
78 - public void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) { 78 + @Override
  79 + public void remove(TenantId tenantId) {
  80 + perTenantLimits.remove(tenantId);
  81 + }
  82 +
  83 + @Override
  84 + public void remove(DeviceId deviceId) {
  85 + perDeviceLimits.remove(deviceId);
  86 + }
  87 +
  88 + private void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) {
79 TransportRateLimit[] oldRateLimits = perTenantLimits.get(tenantId); 89 TransportRateLimit[] oldRateLimits = perTenantLimits.get(tenantId);
80 if (oldRateLimits == null) { 90 if (oldRateLimits == null) {
81 perTenantLimits.put(tenantId, newRateLimits); 91 perTenantLimits.put(tenantId, newRateLimits);
@@ -90,16 +100,6 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi @@ -90,16 +100,6 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi
90 } 100 }
91 } 101 }
92 102
93 - @Override  
94 - public void remove(TenantId tenantId) {  
95 - perTenantLimits.remove(tenantId);  
96 - }  
97 -  
98 - @Override  
99 - public void remove(DeviceId deviceId) {  
100 - perDeviceLimits.remove(deviceId);  
101 - }  
102 -  
103 private TransportRateLimit[] fetchProfileAndInit(TenantId tenantId) { 103 private TransportRateLimit[] fetchProfileAndInit(TenantId tenantId) {
104 return perTenantLimits.computeIfAbsent(tenantId, tmp -> createTransportRateLimits(tenantProfileCache.get(tenantId))); 104 return perTenantLimits.computeIfAbsent(tenantId, tmp -> createTransportRateLimits(tenantProfileCache.get(tenantId)));
105 } 105 }
@@ -112,4 +112,22 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi @@ -112,4 +112,22 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi
112 } 112 }
113 return rateLimits; 113 return rateLimits;
114 } 114 }
  115 +
  116 + private TransportRateLimit[] getTenantRateLimits(TenantId tenantId) {
  117 + TransportRateLimit[] limits = perTenantLimits.get(tenantId);
  118 + if (limits == null) {
  119 + limits = fetchProfileAndInit(tenantId);
  120 + perTenantLimits.put(tenantId, limits);
  121 + }
  122 + return limits;
  123 + }
  124 +
  125 + private TransportRateLimit[] getDeviceRateLimits(TenantId tenantId, DeviceId deviceId) {
  126 + TransportRateLimit[] limits = perDeviceLimits.get(deviceId);
  127 + if (limits == null) {
  128 + limits = fetchProfileAndInit(tenantId);
  129 + perDeviceLimits.put(deviceId, limits);
  130 + }
  131 + return limits;
  132 + }
115 } 133 }
@@ -23,6 +23,11 @@ public class DummyTransportRateLimit implements TransportRateLimit { @@ -23,6 +23,11 @@ public class DummyTransportRateLimit implements TransportRateLimit {
23 } 23 }
24 24
25 @Override 25 @Override
  26 + public boolean tryConsume(long number) {
  27 + return true;
  28 + }
  29 +
  30 + @Override
26 public boolean tryConsume() { 31 public boolean tryConsume() {
27 return true; 32 return true;
28 } 33 }
@@ -31,4 +31,8 @@ public class SimpleTransportRateLimit implements TransportRateLimit { @@ -31,4 +31,8 @@ public class SimpleTransportRateLimit implements TransportRateLimit {
31 return rateLimit.tryConsume(); 31 return rateLimit.tryConsume();
32 } 32 }
33 33
  34 + @Override
  35 + public boolean tryConsume(long number) {
  36 + return number <= 0 || rateLimit.tryConsume(number);
  37 + }
34 } 38 }
@@ -21,4 +21,6 @@ public interface TransportRateLimit { @@ -21,4 +21,6 @@ public interface TransportRateLimit {
21 21
22 boolean tryConsume(); 22 boolean tryConsume();
23 23
  24 + boolean tryConsume(long number);
  25 +
24 } 26 }
@@ -21,9 +21,7 @@ import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult @@ -21,9 +21,7 @@ import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult
21 21
22 public interface TransportRateLimitService { 22 public interface TransportRateLimitService {
23 23
24 - TransportRateLimit getRateLimit(TenantId tenantId, TransportRateLimitType limit);  
25 -  
26 - TransportRateLimit getRateLimit(TenantId tenantId, DeviceId deviceId, TransportRateLimitType limit); 24 + TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits);
27 25
28 void update(TenantProfileUpdateResult update); 26 void update(TenantProfileUpdateResult update);
29 27
@@ -19,15 +19,29 @@ import lombok.Getter; @@ -19,15 +19,29 @@ import lombok.Getter;
19 19
20 public enum TransportRateLimitType { 20 public enum TransportRateLimitType {
21 21
22 - TENANT_MAX_MSGS("transport.tenant.max.msg"),  
23 - TENANT_MAX_DATA_POINTS("transport.tenant.max.dataPoints"),  
24 - DEVICE_MAX_MSGS("transport.device.max.msg"),  
25 - DEVICE_MAX_DATA_POINTS("transport.device.max.dataPoints"); 22 + TENANT_MAX_MSGS("transport.tenant.msg", true, true),
  23 + TENANT_TELEMETRY_MSGS("transport.tenant.telemetry", true, true),
  24 + TENANT_MAX_DATA_POINTS("transport.tenant.dataPoints", true, false),
  25 + DEVICE_MAX_MSGS("transport.device.msg", false, true),
  26 + DEVICE_TELEMETRY_MSGS("transport.device.telemetry", false, true),
  27 + DEVICE_MAX_DATA_POINTS("transport.device.dataPoints", false, false);
26 28
27 @Getter 29 @Getter
28 private final String configurationKey; 30 private final String configurationKey;
  31 + @Getter
  32 + private final boolean tenantLevel;
  33 + @Getter
  34 + private final boolean deviceLevel;
  35 + @Getter
  36 + private final boolean messageLevel;
  37 + @Getter
  38 + private final boolean dataPointLevel;
29 39
30 - TransportRateLimitType(String configurationKey) { 40 + TransportRateLimitType(String configurationKey, boolean tenantLevel, boolean messageLevel) {
31 this.configurationKey = configurationKey; 41 this.configurationKey = configurationKey;
  42 + this.tenantLevel = tenantLevel;
  43 + this.deviceLevel = !tenantLevel;
  44 + this.messageLevel = messageLevel;
  45 + this.dataPointLevel = !messageLevel;
32 } 46 }
33 } 47 }
@@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.DeviceProfile; @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
23 import org.thingsboard.server.common.data.id.DeviceProfileId; 23 import org.thingsboard.server.common.data.id.DeviceProfileId;
24 import org.thingsboard.server.common.transport.TransportDeviceProfileCache; 24 import org.thingsboard.server.common.transport.TransportDeviceProfileCache;
25 import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; 25 import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
  26 +import org.thingsboard.server.queue.util.TbTransportComponent;
26 27
27 import java.util.Optional; 28 import java.util.Optional;
28 import java.util.concurrent.ConcurrentHashMap; 29 import java.util.concurrent.ConcurrentHashMap;
@@ -30,7 +31,7 @@ import java.util.concurrent.ConcurrentMap; @@ -30,7 +31,7 @@ import java.util.concurrent.ConcurrentMap;
30 31
31 @Slf4j 32 @Slf4j
32 @Component 33 @Component
33 -@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") 34 +@TbTransportComponent
34 public class DefaultTransportDeviceProfileCache implements TransportDeviceProfileCache { 35 public class DefaultTransportDeviceProfileCache implements TransportDeviceProfileCache {
35 36
36 private final ConcurrentMap<DeviceProfileId, DeviceProfile> deviceProfiles = new ConcurrentHashMap<>(); 37 private final ConcurrentMap<DeviceProfileId, DeviceProfile> deviceProfiles = new ConcurrentHashMap<>();
@@ -79,6 +79,7 @@ import org.thingsboard.server.queue.discovery.PartitionService; @@ -79,6 +79,7 @@ import org.thingsboard.server.queue.discovery.PartitionService;
79 import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; 79 import org.thingsboard.server.queue.discovery.TbServiceInfoProvider;
80 import org.thingsboard.server.queue.provider.TbQueueProducerProvider; 80 import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
81 import org.thingsboard.server.queue.provider.TbTransportQueueFactory; 81 import org.thingsboard.server.queue.provider.TbTransportQueueFactory;
  82 +import org.thingsboard.server.queue.util.TbTransportComponent;
82 83
83 import javax.annotation.PostConstruct; 84 import javax.annotation.PostConstruct;
84 import javax.annotation.PreDestroy; 85 import javax.annotation.PreDestroy;
@@ -103,7 +104,7 @@ import java.util.concurrent.atomic.AtomicInteger; @@ -103,7 +104,7 @@ import java.util.concurrent.atomic.AtomicInteger;
103 */ 104 */
104 @Slf4j 105 @Slf4j
105 @Service 106 @Service
106 -@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") 107 +@TbTransportComponent
107 public class DefaultTransportService implements TransportService { 108 public class DefaultTransportService implements TransportService {
108 109
109 @Value("${transport.sessions.inactivity_timeout}") 110 @Value("${transport.sessions.inactivity_timeout}")
@@ -363,7 +364,11 @@ public class DefaultTransportService implements TransportService { @@ -363,7 +364,11 @@ public class DefaultTransportService implements TransportService {
363 364
364 @Override 365 @Override
365 public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback<Void> callback) { 366 public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback<Void> callback) {
366 - if (checkLimits(sessionInfo, msg, callback)) { 367 + int dataPoints = 0;
  368 + for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) {
  369 + dataPoints += tsKv.getKvCount();
  370 + }
  371 + if (checkLimits(sessionInfo, msg, callback, dataPoints, TELEMETRY)) {
367 reportActivityInternal(sessionInfo); 372 reportActivityInternal(sessionInfo);
368 TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); 373 TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB()));
369 DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); 374 DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
@@ -384,7 +389,7 @@ public class DefaultTransportService implements TransportService { @@ -384,7 +389,7 @@ public class DefaultTransportService implements TransportService {
384 389
385 @Override 390 @Override
386 public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback<Void> callback) { 391 public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback<Void> callback) {
387 - if (checkLimits(sessionInfo, msg, callback)) { 392 + if (checkLimits(sessionInfo, msg, callback, msg.getKvCount(), TELEMETRY)) {
388 reportActivityInternal(sessionInfo); 393 reportActivityInternal(sessionInfo);
389 TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); 394 TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB()));
390 DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); 395 DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
@@ -574,37 +579,34 @@ public class DefaultTransportService implements TransportService { @@ -574,37 +579,34 @@ public class DefaultTransportService implements TransportService {
574 sessions.remove(toSessionId(sessionInfo)); 579 sessions.remove(toSessionId(sessionInfo));
575 } 580 }
576 581
  582 + private TransportRateLimitType[] DEFAULT = new TransportRateLimitType[]{TransportRateLimitType.TENANT_MAX_MSGS, TransportRateLimitType.DEVICE_MAX_MSGS};
  583 + private TransportRateLimitType[] TELEMETRY = TransportRateLimitType.values();
  584 +
577 @Override 585 @Override
578 public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback) { 586 public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback) {
  587 + return checkLimits(sessionInfo, msg, callback, 0, DEFAULT);
  588 + }
  589 +
  590 + @Override
  591 + public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback<Void> callback, int dataPoints, TransportRateLimitType... limits) {
579 if (log.isTraceEnabled()) { 592 if (log.isTraceEnabled()) {
580 log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); 593 log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg);
581 } 594 }
582 TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); 595 TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB()));
583 -  
584 - TransportRateLimit tenantRateLimit = rateLimitService.getRateLimit(tenantId, TransportRateLimitType.TENANT_MAX_MSGS);  
585 -  
586 - if (!tenantRateLimit.tryConsume()) {  
587 - if (callback != null) {  
588 - callback.onError(new TbRateLimitsException(EntityType.TENANT));  
589 - }  
590 - if (log.isTraceEnabled()) {  
591 - log.trace("[{}][{}] Tenant level rate limit detected: {}", toSessionId(sessionInfo), tenantId, msg);  
592 - }  
593 - return false;  
594 - }  
595 DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); 596 DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
596 - TransportRateLimit deviceRateLimit = rateLimitService.getRateLimit(tenantId, deviceId, TransportRateLimitType.DEVICE_MAX_MSGS);  
597 - if (!deviceRateLimit.tryConsume()) { 597 +
  598 + TransportRateLimitType limit = rateLimitService.checkLimits(tenantId, deviceId, 0, limits);
  599 + if (limit == null) {
  600 + return true;
  601 + } else {
598 if (callback != null) { 602 if (callback != null) {
599 - callback.onError(new TbRateLimitsException(EntityType.DEVICE)); 603 + callback.onError(new TbRateLimitsException(limit.isTenantLevel() ? EntityType.TENANT : EntityType.DEVICE));
600 } 604 }
601 if (log.isTraceEnabled()) { 605 if (log.isTraceEnabled()) {
602 - log.trace("[{}][{}] Device level rate limit detected: {}", toSessionId(sessionInfo), deviceId, msg); 606 + log.trace("[{}][{}] {} rateLimit detected: {}", toSessionId(sessionInfo), tenantId, limit, msg);
603 } 607 }
604 return false; 608 return false;
605 } 609 }
606 -  
607 - return true;  
608 } 610 }
609 611
610 protected void processToTransportMsg(TransportProtos.ToTransportMsg toSessionMsg) { 612 protected void processToTransportMsg(TransportProtos.ToTransportMsg toSessionMsg) {
@@ -33,6 +33,7 @@ import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; @@ -33,6 +33,7 @@ import org.thingsboard.server.common.transport.util.DataDecodingEncodingService;
33 import org.thingsboard.server.gen.transport.TransportProtos; 33 import org.thingsboard.server.gen.transport.TransportProtos;
34 import org.thingsboard.server.queue.discovery.TenantRoutingInfo; 34 import org.thingsboard.server.queue.discovery.TenantRoutingInfo;
35 import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; 35 import org.thingsboard.server.queue.discovery.TenantRoutingInfoService;
  36 +import org.thingsboard.server.queue.util.TbTransportComponent;
36 37
37 import java.util.Collections; 38 import java.util.Collections;
38 import java.util.Optional; 39 import java.util.Optional;
@@ -43,7 +44,7 @@ import java.util.concurrent.locks.Lock; @@ -43,7 +44,7 @@ import java.util.concurrent.locks.Lock;
43 import java.util.concurrent.locks.ReentrantLock; 44 import java.util.concurrent.locks.ReentrantLock;
44 45
45 @Component 46 @Component
46 -@ConditionalOnExpression("('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport'") 47 +@TbTransportComponent
47 @Slf4j 48 @Slf4j
48 public class DefaultTransportTenantProfileCache implements TransportTenantProfileCache { 49 public class DefaultTransportTenantProfileCache implements TransportTenantProfileCache {
49 50
@@ -91,6 +91,12 @@ @@ -91,6 +91,12 @@
91 </mat-error> 91 </mat-error>
92 </mat-form-field> 92 </mat-form-field>
93 </div> 93 </div>
  94 + <div fxLayout="column" fxLayoutAlign="flex-end start">
  95 + <tb-rule-chain-autocomplete
  96 + labelText="device-profile.default-rule-chain"
  97 + formControlName="defaultRuleChainId">
  98 + </tb-rule-chain-autocomplete>
  99 + </div>
94 </div> 100 </div>
95 <mat-checkbox formControlName="gateway" style="padding-bottom: 16px;"> 101 <mat-checkbox formControlName="gateway" style="padding-bottom: 16px;">
96 {{ 'device.is-gateway' | translate }} 102 {{ 'device.is-gateway' | translate }}
@@ -42,6 +42,7 @@ import { ErrorStateMatcher } from '@angular/material/core'; @@ -42,6 +42,7 @@ import { ErrorStateMatcher } from '@angular/material/core';
42 import { StepperSelectionEvent } from '@angular/cdk/stepper'; 42 import { StepperSelectionEvent } from '@angular/cdk/stepper';
43 import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; 43 import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout';
44 import { MediaBreakpoints } from '@shared/models/constants'; 44 import { MediaBreakpoints } from '@shared/models/constants';
  45 +import { RuleChainId } from '@shared/models/id/rule-chain-id';
45 46
46 @Component({ 47 @Component({
47 selector: 'tb-device-wizard', 48 selector: 'tb-device-wizard',
@@ -103,6 +104,7 @@ export class DeviceWizardDialogComponent extends @@ -103,6 +104,7 @@ export class DeviceWizardDialogComponent extends
103 addProfileType: [0], 104 addProfileType: [0],
104 deviceProfileId: [null, Validators.required], 105 deviceProfileId: [null, Validators.required],
105 newDeviceProfileTitle: [{value: null, disabled: true}], 106 newDeviceProfileTitle: [{value: null, disabled: true}],
  107 + defaultRuleChainId: [{value: null, disabled: true}],
106 description: [''] 108 description: ['']
107 } 109 }
108 ); 110 );
@@ -114,6 +116,7 @@ export class DeviceWizardDialogComponent extends @@ -114,6 +116,7 @@ export class DeviceWizardDialogComponent extends
114 this.deviceWizardFormGroup.get('deviceProfileId').enable(); 116 this.deviceWizardFormGroup.get('deviceProfileId').enable();
115 this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators(null); 117 this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators(null);
116 this.deviceWizardFormGroup.get('newDeviceProfileTitle').disable(); 118 this.deviceWizardFormGroup.get('newDeviceProfileTitle').disable();
  119 + this.deviceWizardFormGroup.get('defaultRuleChainId').disable();
117 this.deviceWizardFormGroup.updateValueAndValidity(); 120 this.deviceWizardFormGroup.updateValueAndValidity();
118 this.createProfile = false; 121 this.createProfile = false;
119 this.createTransportConfiguration = false; 122 this.createTransportConfiguration = false;
@@ -122,6 +125,7 @@ export class DeviceWizardDialogComponent extends @@ -122,6 +125,7 @@ export class DeviceWizardDialogComponent extends
122 this.deviceWizardFormGroup.get('deviceProfileId').disable(); 125 this.deviceWizardFormGroup.get('deviceProfileId').disable();
123 this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]); 126 this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]);
124 this.deviceWizardFormGroup.get('newDeviceProfileTitle').enable(); 127 this.deviceWizardFormGroup.get('newDeviceProfileTitle').enable();
  128 + this.deviceWizardFormGroup.get('defaultRuleChainId').enable();
125 this.deviceWizardFormGroup.updateValueAndValidity(); 129 this.deviceWizardFormGroup.updateValueAndValidity();
126 this.createProfile = true; 130 this.createProfile = true;
127 this.createTransportConfiguration = this.deviceWizardFormGroup.get('transportType').value && 131 this.createTransportConfiguration = this.deviceWizardFormGroup.get('transportType').value &&
@@ -274,6 +278,9 @@ export class DeviceWizardDialogComponent extends @@ -274,6 +278,9 @@ export class DeviceWizardDialogComponent extends
274 provisionConfiguration: deviceProvisionConfiguration 278 provisionConfiguration: deviceProvisionConfiguration
275 } 279 }
276 }; 280 };
  281 + if (this.deviceWizardFormGroup.get('defaultRuleChainId').value) {
  282 + deviceProfile.defaultRuleChainId = new RuleChainId(this.deviceWizardFormGroup.get('defaultRuleChainId').value);
  283 + }
277 return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe( 284 return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe(
278 map(profile => profile.id), 285 map(profile => profile.id),
279 tap((profileId) => { 286 tap((profileId) => {
@@ -142,6 +142,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha @@ -142,6 +142,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
142 tenantNamePattern = {value: null, disabled: true}; 142 tenantNamePattern = {value: null, disabled: true};
143 } 143 }
144 const basicGroup = this.fb.group({ 144 const basicGroup = this.fb.group({
  145 + emailAttributeKey: [mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required],
145 firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : ''], 146 firstNameAttributeKey: [mapperConfigBasic?.firstNameAttributeKey ? mapperConfigBasic.firstNameAttributeKey : ''],
146 lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : ''], 147 lastNameAttributeKey: [mapperConfigBasic?.lastNameAttributeKey ? mapperConfigBasic.lastNameAttributeKey : ''],
147 tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN], 148 tenantNameStrategy: [mapperConfigBasic?.tenantNameStrategy ? mapperConfigBasic.tenantNameStrategy : TenantNameStrategy.DOMAIN],
@@ -151,11 +152,6 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha @@ -151,11 +152,6 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
151 alwaysFullScreen: [isDefinedAndNotNull(mapperConfigBasic?.alwaysFullScreen) ? mapperConfigBasic.alwaysFullScreen : false] 152 alwaysFullScreen: [isDefinedAndNotNull(mapperConfigBasic?.alwaysFullScreen) ? mapperConfigBasic.alwaysFullScreen : false]
152 }); 153 });
153 154
154 - if (MapperConfigType.GITHUB !== type) {  
155 - basicGroup.addControl('emailAttributeKey',  
156 - this.fb.control( mapperConfigBasic?.emailAttributeKey ? mapperConfigBasic.emailAttributeKey : 'email', Validators.required));  
157 - }  
158 -  
159 this.subscriptions.push(basicGroup.get('tenantNameStrategy').valueChanges.subscribe((domain) => { 155 this.subscriptions.push(basicGroup.get('tenantNameStrategy').valueChanges.subscribe((domain) => {
160 if (domain === 'CUSTOM') { 156 if (domain === 'CUSTOM') {
161 basicGroup.get('tenantNamePattern').enable(); 157 basicGroup.get('tenantNamePattern').enable();
@@ -358,11 +354,15 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha @@ -358,11 +354,15 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha
358 mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom)); 354 mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom));
359 } else { 355 } else {
360 mapperConfig.removeControl('custom'); 356 mapperConfig.removeControl('custom');
361 - if (mapperConfig.get('basic')) {  
362 - mapperConfig.setControl('basic', this.formBasicGroup(type, predefinedValue?.basic));  
363 - } else { 357 + if (!mapperConfig.get('basic')) {
364 mapperConfig.addControl('basic', this.formBasicGroup(type, predefinedValue?.basic)); 358 mapperConfig.addControl('basic', this.formBasicGroup(type, predefinedValue?.basic));
365 } 359 }
  360 + if (type === MapperConfigType.GITHUB) {
  361 + mapperConfig.get('basic.emailAttributeKey').disable();
  362 + mapperConfig.get('basic.emailAttributeKey').patchValue(null, {emitEvent: false});
  363 + } else {
  364 + mapperConfig.get('basic.emailAttributeKey').enable();
  365 + }
366 } 366 }
367 } 367 }
368 368
@@ -97,10 +97,6 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte @@ -97,10 +97,6 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte
97 ngOnInit() { 97 ngOnInit() {
98 this.entitySelectFormGroup.get('entityType').valueChanges.subscribe( 98 this.entitySelectFormGroup.get('entityType').valueChanges.subscribe(
99 (value) => { 99 (value) => {
100 - if(value === AliasEntityType.CURRENT_TENANT || value === AliasEntityType.CURRENT_USER ||  
101 - value === AliasEntityType.CURRENT_USER_OWNER) {  
102 - this.modelValue.id = NULL_UUID;  
103 - }  
104 this.updateView(value, this.modelValue.id); 100 this.updateView(value, this.modelValue.id);
105 } 101 }
106 ); 102 );
@@ -140,20 +136,23 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte @@ -140,20 +136,23 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte
140 } 136 }
141 137
142 updateView(entityType: EntityType | AliasEntityType | null, entityId: string | null) { 138 updateView(entityType: EntityType | AliasEntityType | null, entityId: string | null) {
143 - if (this.modelValue.entityType !== entityType ||  
144 - this.modelValue.id !== entityId) {  
145 - this.modelValue = {  
146 - entityType,  
147 - id: this.modelValue.entityType !== entityType ? null : entityId  
148 - };  
149 - if (this.modelValue.entityType && (this.modelValue.id ||  
150 - this.modelValue.entityType === AliasEntityType.CURRENT_TENANT ||  
151 - this.modelValue.entityType === AliasEntityType.CURRENT_USER ||  
152 - this.modelValue.entityType === AliasEntityType.CURRENT_USER_OWNER)) {  
153 - this.propagateChange(this.modelValue);  
154 - } else {  
155 - this.propagateChange(null);  
156 - } 139 + if (this.modelValue.entityType !== entityType || this.modelValue.id !== entityId) {
  140 + this.modelValue = {
  141 + entityType,
  142 + id: this.modelValue.entityType !== entityType ? null : entityId
  143 + };
  144 +
  145 + if (this.modelValue.entityType === AliasEntityType.CURRENT_TENANT
  146 + || this.modelValue.entityType === AliasEntityType.CURRENT_USER
  147 + || this.modelValue.entityType === AliasEntityType.CURRENT_USER_OWNER) {
  148 + this.modelValue.id = NULL_UUID;
  149 + }
  150 +
  151 + if (this.modelValue.entityType && this.modelValue.id) {
  152 + this.propagateChange(this.modelValue);
  153 + } else {
  154 + this.propagateChange(null);
  155 + }
157 } 156 }
158 } 157 }
159 } 158 }
@@ -36,7 +36,6 @@ import { TranslateService } from '@ngx-translate/core'; @@ -36,7 +36,6 @@ import { TranslateService } from '@ngx-translate/core';
36 import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; 36 import { CancelAnimationFrame, RafService } from '@core/services/raf.service';
37 import { ResizeObserver } from '@juggle/resize-observer'; 37 import { ResizeObserver } from '@juggle/resize-observer';
38 import { TbEditorCompleter } from '@shared/models/ace/completion.models'; 38 import { TbEditorCompleter } from '@shared/models/ace/completion.models';
39 -import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models';  
40 39
41 @Component({ 40 @Component({
42 selector: 'tb-js-func', 41 selector: 'tb-js-func',
@@ -64,6 +63,7 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, @@ -64,6 +63,7 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
64 private jsEditor: ace.Ace.Editor; 63 private jsEditor: ace.Ace.Editor;
65 private editorsResizeCaf: CancelAnimationFrame; 64 private editorsResizeCaf: CancelAnimationFrame;
66 private editorResize$: ResizeObserver; 65 private editorResize$: ResizeObserver;
  66 + private ignoreChange = false;
67 67
68 toastTargetId = `jsFuncEditor-${guid()}`; 68 toastTargetId = `jsFuncEditor-${guid()}`;
69 69
@@ -154,8 +154,10 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, @@ -154,8 +154,10 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
154 this.jsEditor.session.setUseWrapMode(true); 154 this.jsEditor.session.setUseWrapMode(true);
155 this.jsEditor.setValue(this.modelValue ? this.modelValue : '', -1); 155 this.jsEditor.setValue(this.modelValue ? this.modelValue : '', -1);
156 this.jsEditor.on('change', () => { 156 this.jsEditor.on('change', () => {
157 - this.cleanupJsErrors();  
158 - this.updateView(); 157 + if (!this.ignoreChange) {
  158 + this.cleanupJsErrors();
  159 + this.updateView();
  160 + }
159 }); 161 });
160 if (this.editorCompleter) { 162 if (this.editorCompleter) {
161 this.jsEditor.completers = [this.editorCompleter, ...(this.jsEditor.completers || [])]; 163 this.jsEditor.completers = [this.editorCompleter, ...(this.jsEditor.completers || [])];
@@ -332,7 +334,9 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor, @@ -332,7 +334,9 @@ export class JsFuncComponent implements OnInit, OnDestroy, ControlValueAccessor,
332 writeValue(value: string): void { 334 writeValue(value: string): void {
333 this.modelValue = value; 335 this.modelValue = value;
334 if (this.jsEditor) { 336 if (this.jsEditor) {
  337 + this.ignoreChange = true;
335 this.jsEditor.setValue(this.modelValue ? this.modelValue : '', -1); 338 this.jsEditor.setValue(this.modelValue ? this.modelValue : '', -1);
  339 + this.ignoreChange = false;
336 } 340 }
337 } 341 }
338 342
@@ -61,6 +61,7 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid @@ -61,6 +61,7 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
61 private jsonEditor: ace.Ace.Editor; 61 private jsonEditor: ace.Ace.Editor;
62 private editorsResizeCaf: CancelAnimationFrame; 62 private editorsResizeCaf: CancelAnimationFrame;
63 private editorResize$: ResizeObserver; 63 private editorResize$: ResizeObserver;
  64 + private ignoreChange = false;
64 65
65 toastTargetId = `jsonContentEditor-${guid()}`; 66 toastTargetId = `jsonContentEditor-${guid()}`;
66 67
@@ -140,8 +141,13 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid @@ -140,8 +141,13 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
140 this.jsonEditor.session.setUseWrapMode(true); 141 this.jsonEditor.session.setUseWrapMode(true);
141 this.jsonEditor.setValue(this.contentBody ? this.contentBody : '', -1); 142 this.jsonEditor.setValue(this.contentBody ? this.contentBody : '', -1);
142 this.jsonEditor.on('change', () => { 143 this.jsonEditor.on('change', () => {
143 - this.cleanupJsonErrors();  
144 - this.updateView(); 144 + if (!this.ignoreChange) {
  145 + this.cleanupJsonErrors();
  146 + this.updateView();
  147 + }
  148 + });
  149 + this.jsonEditor.on('blur', () => {
  150 + this.contentValid = !this.validateContent || this.doValidate(true);
145 }); 151 });
146 this.editorResize$ = new ResizeObserver(() => { 152 this.editorResize$ = new ResizeObserver(() => {
147 this.onAceEditorResize(); 153 this.onAceEditorResize();
@@ -210,34 +216,36 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid @@ -210,34 +216,36 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
210 this.cleanupJsonErrors(); 216 this.cleanupJsonErrors();
211 this.contentValid = true; 217 this.contentValid = true;
212 this.propagateChange(this.contentBody); 218 this.propagateChange(this.contentBody);
213 - this.contentValid = this.doValidate(); 219 + this.contentValid = this.doValidate(true);
214 this.propagateChange(this.contentBody); 220 this.propagateChange(this.contentBody);
215 } 221 }
216 } 222 }
217 223
218 - private doValidate(): boolean { 224 + private doValidate(showErrorToast = false): boolean {
219 try { 225 try {
220 if (this.validateContent && this.contentType === ContentType.JSON) { 226 if (this.validateContent && this.contentType === ContentType.JSON) {
221 JSON.parse(this.contentBody); 227 JSON.parse(this.contentBody);
222 } 228 }
223 return true; 229 return true;
224 } catch (ex) { 230 } catch (ex) {
225 - let errorInfo = 'Error:';  
226 - if (ex.name) {  
227 - errorInfo += ' ' + ex.name + ':';  
228 - }  
229 - if (ex.message) {  
230 - errorInfo += ' ' + ex.message; 231 + if (showErrorToast) {
  232 + let errorInfo = 'Error:';
  233 + if (ex.name) {
  234 + errorInfo += ' ' + ex.name + ':';
  235 + }
  236 + if (ex.message) {
  237 + errorInfo += ' ' + ex.message;
  238 + }
  239 + this.store.dispatch(new ActionNotificationShow(
  240 + {
  241 + message: errorInfo,
  242 + type: 'error',
  243 + target: this.toastTargetId,
  244 + verticalPosition: 'bottom',
  245 + horizontalPosition: 'left'
  246 + }));
  247 + this.errorShowed = true;
231 } 248 }
232 - this.store.dispatch(new ActionNotificationShow(  
233 - {  
234 - message: errorInfo,  
235 - type: 'error',  
236 - target: this.toastTargetId,  
237 - verticalPosition: 'bottom',  
238 - horizontalPosition: 'left'  
239 - }));  
240 - this.errorShowed = true;  
241 return false; 249 return false;
242 } 250 }
243 } 251 }
@@ -256,8 +264,9 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid @@ -256,8 +264,9 @@ export class JsonContentComponent implements OnInit, ControlValueAccessor, Valid
256 this.contentBody = value; 264 this.contentBody = value;
257 this.contentValid = true; 265 this.contentValid = true;
258 if (this.jsonEditor) { 266 if (this.jsonEditor) {
  267 + this.ignoreChange = true;
259 this.jsonEditor.setValue(this.contentBody ? this.contentBody : '', -1); 268 this.jsonEditor.setValue(this.contentBody ? this.contentBody : '', -1);
260 - // this.jsonEditor. 269 + this.ignoreChange = false;
261 } 270 }
262 } 271 }
263 272