Commit c03356e94456c526bc17c56acfc1d9563fb20483

Authored by Viacheslav Kukhtyn
2 parents 6ccb9d60 627c0577

Merge branch 'master' into feature/log-telemetry-updated

Showing 31 changed files with 206 additions and 63 deletions
... ... @@ -392,6 +392,11 @@ public class TelemetryController extends BaseController {
392 392 if (attributes.isEmpty()) {
393 393 return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST);
394 394 }
  395 + for (AttributeKvEntry attributeKvEntry: attributes) {
  396 + if (attributeKvEntry.getKey().isEmpty() || attributeKvEntry.getKey().trim().length() == 0) {
  397 + return getImmediateDeferredResult("Key cannot be empty or contains only spaces", HttpStatus.BAD_REQUEST);
  398 + }
  399 + }
395 400 SecurityUser user = getCurrentUser();
396 401 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> {
397 402 tsSubService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback<Void>() {
... ...
... ... @@ -221,6 +221,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
221 221
222 222 @Override
223 223 public void onTenantProfileUpdate(TenantProfileId tenantProfileId) {
  224 + log.info("[{}] On Tenant Profile Update", tenantProfileId);
224 225 TenantProfile tenantProfile = tenantProfileCache.get(tenantProfileId);
225 226 updateLock.lock();
226 227 try {
... ... @@ -236,6 +237,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
236 237
237 238 @Override
238 239 public void onTenantUpdate(TenantId tenantId) {
  240 + log.info("[{}] On Tenant Update.", tenantId);
239 241 TenantProfile tenantProfile = tenantProfileCache.get(tenantId);
240 242 updateLock.lock();
241 243 try {
... ... @@ -248,16 +250,16 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService {
248 250 }
249 251 }
250 252
251   - private void updateTenantState(TenantApiUsageState state, TenantProfile tenantProfile) {
  253 + private void updateTenantState(TenantApiUsageState state, TenantProfile profile) {
252 254 TenantProfileData oldProfileData = state.getTenantProfileData();
253   - state.setTenantProfileId(tenantProfile.getId());
254   - state.setTenantProfileData(tenantProfile.getProfileData());
  255 + state.setTenantProfileId(profile.getId());
  256 + state.setTenantProfileData(profile.getProfileData());
255 257 Map<ApiFeature, ApiUsageStateValue> result = state.checkStateUpdatedDueToThresholds();
256 258 if (!result.isEmpty()) {
257 259 persistAndNotify(state, result);
258 260 }
259 261 updateProfileThresholds(state.getTenantId(), state.getApiUsageState().getId(),
260   - oldProfileData.getConfiguration(), tenantProfile.getProfileData().getConfiguration());
  262 + oldProfileData.getConfiguration(), profile.getProfileData().getConfiguration());
261 263 }
262 264
263 265 private void updateProfileThresholds(TenantId tenantId, ApiUsageStateId id,
... ...
... ... @@ -134,7 +134,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
134 134 TenantProfile isolatedTbCoreProfile = new TenantProfile();
135 135 isolatedTbCoreProfile.setDefault(false);
136 136 isolatedTbCoreProfile.setName("Isolated TB Core");
137   - isolatedTbCoreProfile.setProfileData(new TenantProfileData());
138 137 isolatedTbCoreProfile.setDescription("Isolated TB Core tenant profile");
139 138 isolatedTbCoreProfile.setIsolatedTbCore(true);
140 139 isolatedTbCoreProfile.setIsolatedTbRuleEngine(false);
... ... @@ -148,7 +147,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
148 147 TenantProfile isolatedTbRuleEngineProfile = new TenantProfile();
149 148 isolatedTbRuleEngineProfile.setDefault(false);
150 149 isolatedTbRuleEngineProfile.setName("Isolated TB Rule Engine");
151   - isolatedTbRuleEngineProfile.setProfileData(new TenantProfileData());
152 150 isolatedTbRuleEngineProfile.setDescription("Isolated TB Rule Engine tenant profile");
153 151 isolatedTbRuleEngineProfile.setIsolatedTbCore(false);
154 152 isolatedTbRuleEngineProfile.setIsolatedTbRuleEngine(true);
... ... @@ -163,7 +161,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
163 161 TenantProfile isolatedTbCoreAndTbRuleEngineProfile = new TenantProfile();
164 162 isolatedTbCoreAndTbRuleEngineProfile.setDefault(false);
165 163 isolatedTbCoreAndTbRuleEngineProfile.setName("Isolated TB Core and TB Rule Engine");
166   - isolatedTbCoreAndTbRuleEngineProfile.setProfileData(new TenantProfileData());
167 164 isolatedTbCoreAndTbRuleEngineProfile.setDescription("Isolated TB Core and TB Rule Engine tenant profile");
168 165 isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbCore(true);
169 166 isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbRuleEngine(true);
... ...
... ... @@ -274,6 +274,7 @@ public class DefaultTbClusterService implements TbClusterService {
274 274 TbQueueProducer<TbProtoQueueMsg<ToRuleEngineNotificationMsg>> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer();
275 275 Set<String> tbRuleEngineServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE));
276 276 if (msg.getEntityId().getEntityType().equals(EntityType.TENANT)
  277 + || msg.getEntityId().getEntityType().equals(EntityType.TENANT_PROFILE)
277 278 || msg.getEntityId().getEntityType().equals(EntityType.DEVICE_PROFILE)
278 279 || msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE)) {
279 280 TbQueueProducer<TbProtoQueueMsg<ToCoreNotificationMsg>> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer();
... ...
... ... @@ -153,6 +153,8 @@ public abstract class AbstractConsumerService<N extends com.google.protobuf.Gene
153 153 TbActorMsg actorMsg = actorMsgOpt.get();
154 154 if (actorMsg instanceof ComponentLifecycleMsg) {
155 155 ComponentLifecycleMsg componentLifecycleMsg = (ComponentLifecycleMsg) actorMsg;
  156 + log.info("[{}][{}][{}] Received Lifecycle event: {}", componentLifecycleMsg.getTenantId(), componentLifecycleMsg.getEntityId().getEntityType(),
  157 + componentLifecycleMsg.getEntityId(), componentLifecycleMsg.getEvent());
156 158 if (EntityType.TENANT_PROFILE.equals(componentLifecycleMsg.getEntityId().getEntityType())) {
157 159 TenantProfileId tenantProfileId = new TenantProfileId(componentLifecycleMsg.getEntityId().getId());
158 160 tenantProfileCache.evict(tenantProfileId);
... ...
... ... @@ -35,14 +35,17 @@ import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
35 35 import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig;
36 36 import org.thingsboard.server.common.data.page.PageData;
37 37 import org.thingsboard.server.common.data.page.PageLink;
  38 +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
38 39 import org.thingsboard.server.common.data.security.Authority;
39 40 import org.thingsboard.server.common.data.security.UserCredentials;
40 41 import org.thingsboard.server.dao.customer.CustomerService;
41 42 import org.thingsboard.server.dao.dashboard.DashboardService;
42 43 import org.thingsboard.server.dao.oauth2.OAuth2User;
  44 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
43 45 import org.thingsboard.server.dao.tenant.TenantService;
44 46 import org.thingsboard.server.dao.user.UserService;
45 47 import org.thingsboard.server.service.install.InstallScripts;
  48 +import org.thingsboard.server.service.queue.TbClusterService;
46 49 import org.thingsboard.server.service.security.model.SecurityUser;
47 50 import org.thingsboard.server.service.security.model.UserPrincipal;
48 51
... ... @@ -76,6 +79,12 @@ public abstract class AbstractOAuth2ClientMapper {
76 79 @Autowired
77 80 private InstallScripts installScripts;
78 81
  82 + @Autowired
  83 + protected TbTenantProfileCache tenantProfileCache;
  84 +
  85 + @Autowired
  86 + protected TbClusterService tbClusterService;
  87 +
79 88 private final Lock userCreationLock = new ReentrantLock();
80 89
81 90 protected SecurityUser getOrCreateSecurityUserFromOAuth2User(OAuth2User oauth2User, OAuth2ClientRegistrationInfo clientRegistration) {
... ... @@ -162,6 +171,10 @@ public abstract class AbstractOAuth2ClientMapper {
162 171 tenant.setTitle(tenantName);
163 172 tenant = tenantService.saveTenant(tenant);
164 173 installScripts.createDefaultRuleChains(tenant.getId());
  174 + tenantProfileCache.evict(tenant.getId());
  175 + tbClusterService.onTenantChange(tenant, null);
  176 + tbClusterService.onEntityStateChange(tenant.getId(), tenant.getId(),
  177 + ComponentLifecycleEvent.CREATED);
165 178 } else {
166 179 tenant = tenants.get(0);
167 180 }
... ...
... ... @@ -15,10 +15,15 @@
15 15 */
16 16 package org.thingsboard.server.service.security.auth.oauth2;
17 17
  18 +import org.springframework.beans.factory.annotation.Autowired;
18 19 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
19 20 import org.springframework.security.core.AuthenticationException;
20 21 import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
21 22 import org.springframework.stereotype.Component;
  23 +import org.thingsboard.server.common.data.id.CustomerId;
  24 +import org.thingsboard.server.common.data.id.EntityId;
  25 +import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.service.security.system.SystemSecurityService;
22 27 import org.thingsboard.server.utils.MiscUtils;
23 28
24 29 import javax.servlet.ServletException;
... ... @@ -32,11 +37,18 @@ import java.nio.charset.StandardCharsets;
32 37 @ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
33 38 public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
34 39
  40 + private final SystemSecurityService systemSecurityService;
  41 +
  42 + @Autowired
  43 + public Oauth2AuthenticationFailureHandler(final SystemSecurityService systemSecurityService) {
  44 + this.systemSecurityService = systemSecurityService;
  45 + }
  46 +
35 47 @Override
36 48 public void onAuthenticationFailure(HttpServletRequest request,
37 49 HttpServletResponse response, AuthenticationException exception)
38 50 throws IOException, ServletException {
39   - String baseUrl = MiscUtils.constructBaseUrl(request);
  51 + String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request);
40 52 getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" +
41 53 URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString()));
42 54 }
... ...
... ... @@ -22,12 +22,16 @@ import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
22 22 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
23 23 import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
24 24 import org.springframework.stereotype.Component;
  25 +import org.thingsboard.server.common.data.id.CustomerId;
  26 +import org.thingsboard.server.common.data.id.EntityId;
  27 +import org.thingsboard.server.common.data.id.TenantId;
25 28 import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationInfo;
26 29 import org.thingsboard.server.dao.oauth2.OAuth2Service;
27 30 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
28 31 import org.thingsboard.server.service.security.model.SecurityUser;
29 32 import org.thingsboard.server.service.security.model.token.JwtToken;
30 33 import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
  34 +import org.thingsboard.server.service.security.system.SystemSecurityService;
31 35 import org.thingsboard.server.utils.MiscUtils;
32 36
33 37 import javax.servlet.http.HttpServletRequest;
... ... @@ -45,25 +49,27 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
45 49 private final OAuth2ClientMapperProvider oauth2ClientMapperProvider;
46 50 private final OAuth2Service oAuth2Service;
47 51 private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
  52 + private final SystemSecurityService systemSecurityService;
48 53
49 54 @Autowired
50 55 public Oauth2AuthenticationSuccessHandler(final JwtTokenFactory tokenFactory,
51 56 final RefreshTokenRepository refreshTokenRepository,
52 57 final OAuth2ClientMapperProvider oauth2ClientMapperProvider,
53 58 final OAuth2Service oAuth2Service,
54   - final OAuth2AuthorizedClientService oAuth2AuthorizedClientService) {
  59 + final OAuth2AuthorizedClientService oAuth2AuthorizedClientService, final SystemSecurityService systemSecurityService) {
55 60 this.tokenFactory = tokenFactory;
56 61 this.refreshTokenRepository = refreshTokenRepository;
57 62 this.oauth2ClientMapperProvider = oauth2ClientMapperProvider;
58 63 this.oAuth2Service = oAuth2Service;
59 64 this.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService;
  65 + this.systemSecurityService = systemSecurityService;
60 66 }
61 67
62 68 @Override
63 69 public void onAuthenticationSuccess(HttpServletRequest request,
64 70 HttpServletResponse response,
65 71 Authentication authentication) throws IOException {
66   - String baseUrl = MiscUtils.constructBaseUrl(request);
  72 + String baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request);
67 73 try {
68 74 OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
69 75
... ...
... ... @@ -202,16 +202,19 @@ public class DefaultSystemSecurityService implements SystemSecurityService {
202 202
203 203 @Override
204 204 public String getBaseUrl(TenantId tenantId, CustomerId customerId, HttpServletRequest httpServletRequest) {
205   - String baseUrl;
  205 + String baseUrl = null;
206 206 AdminSettings generalSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "general");
207 207
208 208 JsonNode prohibitDifferentUrl = generalSettings.getJsonValue().get("prohibitDifferentUrl");
209 209
210 210 if (prohibitDifferentUrl != null && prohibitDifferentUrl.asBoolean()) {
211 211 baseUrl = generalSettings.getJsonValue().get("baseUrl").asText();
212   - } else {
  212 + }
  213 +
  214 + if (StringUtils.isEmpty(baseUrl)) {
213 215 baseUrl = MiscUtils.constructBaseUrl(httpServletRequest);
214 216 }
  217 +
215 218 return baseUrl;
216 219 }
217 220
... ...
... ... @@ -108,7 +108,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer
108 108 * Since number of subscriptions is usually much less then number of devices that are pushing data.
109 109 */
110 110 subscriptionsBySessionId.values().forEach(map -> map.values()
111   - .forEach(sub -> pushSubscriptionToManagerService(sub, false)));
  111 + .forEach(sub -> pushSubscriptionToManagerService(sub, true)));
112 112 }
113 113 }
114 114
... ...
... ... @@ -164,22 +164,25 @@ public class DefaultTransportApiService implements TransportApiService {
164 164 }
165 165
166 166 private ListenableFuture<TransportApiResponseMsg> validateCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) {
167   - DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName());
168   - if (credentials != null) {
169   - if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) {
170   - return getDeviceInfo(credentials.getDeviceId(), credentials);
171   - } else if (credentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) {
172   - if (!checkMqttCredentials(mqtt, credentials)) {
173   - credentials = null;
  167 + DeviceCredentials credentials = null;
  168 + if (!StringUtils.isEmpty(mqtt.getUserName())) {
  169 + credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName());
  170 + if (credentials != null) {
  171 + if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) {
  172 + return getDeviceInfo(credentials.getDeviceId(), credentials);
  173 + } else if (credentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) {
  174 + if (!checkMqttCredentials(mqtt, credentials)) {
  175 + credentials = null;
  176 + }
174 177 }
175 178 }
176   - }
177   - if (credentials == null) {
178   - credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash("|", mqtt.getClientId(), mqtt.getUserName()));
179 179 if (credentials == null) {
180   - credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash(mqtt.getClientId()));
  180 + credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash("|", mqtt.getClientId(), mqtt.getUserName()));
181 181 }
182 182 }
  183 + if (credentials == null) {
  184 + credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash(mqtt.getClientId()));
  185 + }
183 186 if (credentials != null) {
184 187 return getDeviceInfo(credentials.getDeviceId(), credentials);
185 188 } else {
... ...
... ... @@ -766,7 +766,6 @@ metrics:
766 766 # Metrics percentiles returned by actuator for timer metrics. List of double values (divided by ,).
767 767 percentiles: "${METRICS_TIMER_PERCENTILES:0.5}"
768 768
769   -
770 769 management:
771 770 endpoints:
772 771 web:
... ...
... ... @@ -21,5 +21,5 @@ import java.io.Serializable;
21 21 * @author Andrew Shvayka
22 22 */
23 23 public enum ComponentLifecycleEvent implements Serializable {
24   - CREATED, STARTED, ACTIVATED, SUSPENDED, UPDATED, STOPPED, DELETED, ADDED_TO_ALLOW_LIST, ADDED_TO_DENY_LIST
  24 + CREATED, STARTED, ACTIVATED, SUSPENDED, UPDATED, STOPPED, DELETED
25 25 }
\ No newline at end of file
... ...
... ... @@ -94,7 +94,9 @@ public class DefaultTbApiUsageClient implements TbApiUsageClient {
94 94 TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).newByTopic(msgProducer.getDefaultTopic());
95 95 msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), builder.build()), null);
96 96 }));
97   - log.info("Report statistics for: {} tenants", report.size());
  97 + if (!report.isEmpty()) {
  98 + log.info("Report statistics for: {} tenants", report.size());
  99 + }
98 100 } catch (Exception e) {
99 101 log.warn("Failed to report statistics: ", e);
100 102 }
... ...
... ... @@ -122,7 +122,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
122 122 log.trace("[{}] Processing msg: {}", sessionId, msg);
123 123 try {
124 124 if (msg instanceof MqttMessage) {
125   - processMqttMsg(ctx, (MqttMessage) msg);
  125 + MqttMessage message = (MqttMessage) msg;
  126 + if (message.decoderResult().isSuccess()) {
  127 + processMqttMsg(ctx, message);
  128 + } else {
  129 + log.error("[{}] Message processing failed: {}", sessionId, message.decoderResult().cause().getMessage());
  130 + ctx.close();
  131 + }
126 132 } else {
127 133 ctx.close();
128 134 }
... ... @@ -464,8 +470,10 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
464 470 String userName = msg.payload().userName();
465 471 log.info("[{}] Processing connect msg for client with user name: {}!", sessionId, userName);
466 472 TransportProtos.ValidateBasicMqttCredRequestMsg.Builder request = TransportProtos.ValidateBasicMqttCredRequestMsg.newBuilder()
467   - .setClientId(msg.payload().clientIdentifier())
468   - .setUserName(userName);
  473 + .setClientId(msg.payload().clientIdentifier());
  474 + if (userName != null) {
  475 + request.setUserName(userName);
  476 + }
469 477 String password = msg.payload().password();
470 478 if (password != null) {
471 479 request.setPassword(password);
... ...
... ... @@ -21,6 +21,7 @@ import org.springframework.util.StringUtils;
21 21 import org.thingsboard.server.common.data.EntityType;
22 22 import org.thingsboard.server.common.data.TenantProfile;
23 23 import org.thingsboard.server.common.data.id.DeviceId;
  24 +import org.thingsboard.server.common.data.id.EntityId;
24 25 import org.thingsboard.server.common.data.id.TenantId;
25 26 import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
26 27 import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
... ... @@ -77,6 +78,7 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi
77 78
78 79 @Override
79 80 public void update(TenantProfileUpdateResult update) {
  81 + log.info("Received tenant profile update: {}", update.getProfile());
80 82 EntityTransportRateLimits tenantRateLimitPrototype = createRateLimits(update.getProfile(), true);
81 83 EntityTransportRateLimits deviceRateLimitPrototype = createRateLimits(update.getProfile(), false);
82 84 for (TenantId tenantId : update.getAffectedTenants()) {
... ... @@ -114,16 +116,26 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi
114 116 tenantAllowed.put(tenantId, allowed);
115 117 }
116 118
117   - private <T> void mergeLimits(T deviceId, EntityTransportRateLimits newRateLimits,
118   - Function<T, EntityTransportRateLimits> getFunction,
119   - BiConsumer<T, EntityTransportRateLimits> putFunction) {
120   - EntityTransportRateLimits oldRateLimits = getFunction.apply(deviceId);
  119 + private <T extends EntityId> void mergeLimits(T entityId, EntityTransportRateLimits newRateLimits,
  120 + Function<T, EntityTransportRateLimits> getFunction,
  121 + BiConsumer<T, EntityTransportRateLimits> putFunction) {
  122 + EntityTransportRateLimits oldRateLimits = getFunction.apply(entityId);
121 123 if (oldRateLimits == null) {
122   - putFunction.accept(deviceId, newRateLimits);
  124 + if (EntityType.TENANT.equals(entityId.getEntityType())) {
  125 + log.info("[{}] New rate limits: {}", entityId, newRateLimits);
  126 + } else {
  127 + log.debug("[{}] New rate limits: {}", entityId, newRateLimits);
  128 + }
  129 + putFunction.accept(entityId, newRateLimits);
123 130 } else {
124 131 EntityTransportRateLimits updated = merge(oldRateLimits, newRateLimits);
125 132 if (updated != null) {
126   - putFunction.accept(deviceId, updated);
  133 + if (EntityType.TENANT.equals(entityId.getEntityType())) {
  134 + log.info("[{}] Updated rate limits: {}", entityId, updated);
  135 + } else {
  136 + log.debug("[{}] Updated rate limits: {}", entityId, updated);
  137 + }
  138 + putFunction.accept(entityId, updated);
127 139 }
128 140 }
129 141 }
... ...
... ... @@ -548,7 +548,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe
548 548
549 549 @Override
550 550 protected void validateDataImpl(TenantId tenantId, Device device) {
551   - if (StringUtils.isEmpty(device.getName())) {
  551 + if (StringUtils.isEmpty(device.getName()) || device.getName().trim().length() == 0) {
552 552 throw new DataValidationException("Device name should be specified!");
553 553 }
554 554 if (device.getTenantId() == null) {
... ...
... ... @@ -202,6 +202,9 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
202 202 " THEN (select additional_info from entity_view where id = entity_id)" +
203 203 " END as additional_info";
204 204
  205 + private static final String SELECT_API_USAGE_STATE = "(select aus.id, aus.created_time, aus.tenant_id, '13814000-1dd2-11b2-8080-808080808080'::uuid as customer_id, " +
  206 + "(select title from tenant where id = aus.tenant_id) as name from api_usage_state as aus)";
  207 +
205 208 static {
206 209 entityTableMap.put(EntityType.ASSET, "asset");
207 210 entityTableMap.put(EntityType.DEVICE, "device");
... ... @@ -210,7 +213,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
210 213 entityTableMap.put(EntityType.CUSTOMER, "customer");
211 214 entityTableMap.put(EntityType.USER, "tb_user");
212 215 entityTableMap.put(EntityType.TENANT, "tenant");
213   - entityTableMap.put(EntityType.API_USAGE_STATE, "(select aus.id, aus.created_time, aus.tenant_id, '' as name, '' as additional_info from api_usage_state as aus)");
  216 + entityTableMap.put(EntityType.API_USAGE_STATE, SELECT_API_USAGE_STATE);
214 217 }
215 218
216 219 public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{
... ...
... ... @@ -80,6 +80,7 @@ public class EntityKeyMapping {
80 80 public static final List<String> labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, ADDITIONAL_INFO);
81 81 public static final List<String> contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO);
82 82
  83 + public static final Set<String> apiUsageStateEntityFields = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME));
83 84 public static final Set<String> commonEntityFieldsSet = new HashSet<>(commonEntityFields);
84 85 public static final Set<String> relationQueryEntityFieldsSet = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, FIRST_NAME, LAST_NAME, EMAIL, REGION, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO));
85 86
... ... @@ -99,6 +100,7 @@ public class EntityKeyMapping {
99 100 allowedEntityFieldMap.put(EntityType.RULE_NODE, new HashSet<>(commonEntityFields));
100 101 allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(widgetEntityFields));
101 102 allowedEntityFieldMap.put(EntityType.WIDGETS_BUNDLE, new HashSet<>(widgetEntityFields));
  103 + allowedEntityFieldMap.put(EntityType.API_USAGE_STATE, apiUsageStateEntityFields);
102 104
103 105 entityFieldColumnMap.put(CREATED_TIME, ModelConstants.CREATED_TIME_PROPERTY);
104 106 entityFieldColumnMap.put(ENTITY_TYPE, ModelConstants.ENTITY_TYPE_PROPERTY);
... ...
... ... @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.usagerecord;
17 17
18 18 import lombok.extern.slf4j.Slf4j;
19 19 import org.springframework.stereotype.Service;
  20 +import org.thingsboard.server.common.data.ApiFeature;
20 21 import org.thingsboard.server.common.data.ApiUsageRecordKey;
21 22 import org.thingsboard.server.common.data.ApiUsageState;
22 23 import org.thingsboard.server.common.data.ApiUsageStateValue;
... ... @@ -27,6 +28,7 @@ import org.thingsboard.server.common.data.id.ApiUsageStateId;
27 28 import org.thingsboard.server.common.data.id.TenantId;
28 29 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
29 30 import org.thingsboard.server.common.data.kv.LongDataEntry;
  31 +import org.thingsboard.server.common.data.kv.StringDataEntry;
30 32 import org.thingsboard.server.common.data.kv.TsKvEntry;
31 33 import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration;
32 34 import org.thingsboard.server.dao.entity.AbstractEntityService;
... ... @@ -83,7 +85,19 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A
83 85 Tenant tenant = tenantDao.findById(tenantId, tenantId.getId());
84 86 TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId());
85 87 TenantProfileConfiguration configuration = tenantProfile.getProfileData().getConfiguration();
  88 + List<TsKvEntry> apiUsageStates = new ArrayList<>();
  89 + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(),
  90 + new StringDataEntry(ApiFeature.TRANSPORT.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
  91 + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(),
  92 + new StringDataEntry(ApiFeature.DB.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
  93 + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(),
  94 + new StringDataEntry(ApiFeature.RE.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
  95 + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(),
  96 + new StringDataEntry(ApiFeature.JS.getApiStateKey(), ApiUsageStateValue.ENABLED.name())));
  97 + tsService.save(tenantId, saved.getId(), apiUsageStates, 0L);
  98 +
86 99 List<TsKvEntry> profileThresholds = new ArrayList<>();
  100 +
87 101 for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) {
88 102 profileThresholds.add(new BasicTsKvEntry(saved.getCreatedTime(), new LongDataEntry(key.getApiLimitKey(), configuration.getProfileThreshold(key))));
89 103 }
... ...
... ... @@ -58,7 +58,7 @@ In case of any issues you can examine service logs for errors.
58 58 For example to see ThingsBoard node logs execute the following command:
59 59
60 60 `
61   -$ docker-compose logs -f tb-core1 tb-rule-engine1
  61 +$ docker-compose logs -f tb-core1 tb-core2 tb-rule-engine1 tb-rule-engine2 tb-mqtt-transport1 tb-mqtt-transport2
62 62 `
63 63
64 64 Or use `docker-compose ps` to see the state of all the containers.
... ...
... ... @@ -67,6 +67,7 @@ class AlarmState {
67 67 initCurrentAlarm(ctx);
68 68 lastMsgMetaData = msg.getMetaData();
69 69 lastMsgQueueName = msg.getQueueName();
  70 + this.dataSnapshot = data;
70 71 return createOrClearAlarms(ctx, data, update, AlarmRuleState::eval);
71 72 }
72 73
... ... @@ -90,8 +91,7 @@ class AlarmState {
90 91 resultState = state;
91 92 break;
92 93 } else if (AlarmEvalResult.FALSE.equals(evalResult)) {
93   - state.clear();
94   - stateUpdate |= state.checkUpdate();
  94 + stateUpdate = clearAlarmState(stateUpdate, state);
95 95 }
96 96 }
97 97 if (resultState != null) {
... ... @@ -99,6 +99,7 @@ class AlarmState {
99 99 if (result != null) {
100 100 pushMsg(ctx, result);
101 101 }
  102 + stateUpdate = clearAlarmState(stateUpdate, clearState);
102 103 } else if (currentAlarm != null && clearState != null) {
103 104 if (!validateUpdate(update, clearState)) {
104 105 log.debug("[{}] Update is not valid for current clear state", alarmDefinition.getId());
... ... @@ -106,23 +107,26 @@ class AlarmState {
106 107 }
107 108 AlarmEvalResult evalResult = evalFunction.apply(clearState, data);
108 109 if (AlarmEvalResult.TRUE.equals(evalResult)) {
109   - clearState.clear();
110   - stateUpdate |= clearState.checkUpdate();
  110 + stateUpdate = clearAlarmState(stateUpdate, clearState);
111 111 for (AlarmRuleState state : createRulesSortedBySeverityDesc) {
112   - state.clear();
113   - stateUpdate |= state.checkUpdate();
  112 + stateUpdate = clearAlarmState(stateUpdate, state);
114 113 }
115 114 ctx.getAlarmService().clearAlarm(ctx.getTenantId(), currentAlarm.getId(), JacksonUtil.OBJECT_MAPPER.createObjectNode(), System.currentTimeMillis());
116 115 pushMsg(ctx, new TbAlarmResult(false, false, true, currentAlarm));
117 116 currentAlarm = null;
118 117 } else if (AlarmEvalResult.FALSE.equals(evalResult)) {
119   - clearState.clear();
120   - stateUpdate |= clearState.checkUpdate();
  118 + stateUpdate = clearAlarmState(stateUpdate, clearState);
121 119 }
122 120 }
123 121 return stateUpdate;
124 122 }
125 123
  124 + public boolean clearAlarmState(boolean stateUpdate, AlarmRuleState state) {
  125 + state.clear();
  126 + stateUpdate |= state.checkUpdate();
  127 + return stateUpdate;
  128 + }
  129 +
126 130 public boolean validateUpdate(SnapshotUpdate update, AlarmRuleState state) {
127 131 if (update != null) {
128 132 //Check that the update type and that keys match.
... ... @@ -190,7 +194,7 @@ class AlarmState {
190 194 }
191 195 }
192 196
193   - private <T> TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState) {
  197 + private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState) {
194 198 AlarmSeverity severity = ruleState.getSeverity();
195 199 if (currentAlarm != null) {
196 200 // TODO: In some extremely rare cases, we might miss the event of alarm clear (If one use in-mem queue and restarted the server) or (if one manipulated the rule chain).
... ... @@ -230,7 +234,7 @@ class AlarmState {
230 234 }
231 235 }
232 236
233   - private <T> JsonNode createDetails(AlarmRuleState ruleState) {
  237 + private JsonNode createDetails(AlarmRuleState ruleState) {
234 238 ObjectNode details = JacksonUtil.OBJECT_MAPPER.createObjectNode();
235 239 String alarmDetails = ruleState.getAlarmRule().getAlarmDetails();
236 240
... ... @@ -273,8 +277,7 @@ class AlarmState {
273 277 if (currentAlarm != null && currentAlarm.getId().equals(alarmNf.getId())) {
274 278 currentAlarm = null;
275 279 for (AlarmRuleState state : createRulesSortedBySeverityDesc) {
276   - state.clear();
277   - updated |= state.checkUpdate();
  280 + updated = clearAlarmState(updated, state);
278 281 }
279 282 }
280 283 return updated;
... ...
... ... @@ -630,6 +630,9 @@ export class EntityService {
630 630 case EntityType.DASHBOARD:
631 631 entityFieldKeys.push(entityFields.title.keyName);
632 632 break;
  633 + case EntityType.API_USAGE_STATE:
  634 + entityFieldKeys.push(entityFields.name.keyName);
  635 + break;
633 636 }
634 637 return query ? entityFieldKeys.filter((entityField) => entityField.toLowerCase().indexOf(query) === 0) : entityFieldKeys;
635 638 }
... ...
... ... @@ -422,6 +422,12 @@ export class DashboardUtilsService {
422 422 widgetLayout.row = row;
423 423 widgetLayout.col = 0;
424 424 }
  425 +
  426 + widgetLayout.sizeX = Math.floor(widgetLayout.sizeX);
  427 + widgetLayout.sizeY = Math.floor(widgetLayout.sizeY);
  428 + widgetLayout.row = Math.floor(widgetLayout.row);
  429 + widgetLayout.col = Math.floor(widgetLayout.col);
  430 +
425 431 layout.widgets[widget.id] = widgetLayout;
426 432 }
427 433
... ...
... ... @@ -63,6 +63,9 @@
63 63 </mat-error>
64 64 </mat-form-field>
65 65 </div>
  66 + <mat-error *ngIf="mqttDeviceProfileTransportConfigurationFormGroup.get('configuration').hasError('unique')">
  67 + {{ 'device-profile.mqtt-device-topic-filters-unique' | translate }}
  68 + </mat-error>
66 69 <div class="tb-hint" innerHTML="{{ 'device-profile.support-level-wildcards' | translate }}"></div>
67 70 <div class="tb-hint" innerHTML="{{ 'device-profile.single-level-wildcards-hint' | translate }}"></div>
68 71 <div class="tb-hint" innerHTML="{{ 'device-profile.multi-level-wildcards-hint' | translate }}"></div>
... ...
... ... @@ -51,7 +51,6 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
51 51
52 52 mqttTransportPayloadTypeTranslations = mqttTransportPayloadTypeTranslationMap;
53 53
54   -
55 54 mqttDeviceProfileTransportConfigurationFormGroup: FormGroup;
56 55
57 56 private requiredValue: boolean;
... ... @@ -87,7 +86,7 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
87 86 deviceAttributesTopic: [null, [Validators.required, this.validationMQTTTopic()]],
88 87 deviceTelemetryTopic: [null, [Validators.required, this.validationMQTTTopic()]],
89 88 transportPayloadType: [MqttTransportPayloadType.JSON, Validators.required]
90   - })
  89 + }, {validator: this.uniqueDeviceTopicValidator})
91 90 });
92 91 this.mqttDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => {
93 92 this.updateModel();
... ... @@ -147,4 +146,14 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control
147 146 return null;
148 147 };
149 148 }
  149 +
  150 + private uniqueDeviceTopicValidator(control: FormGroup): { [key: string]: boolean } | null {
  151 + if (control.value) {
  152 + const formValue = control.value as MqttDeviceProfileTransportConfiguration;
  153 + if (formValue.deviceAttributesTopic === formValue.deviceTelemetryTopic) {
  154 + return {unique: true};
  155 + }
  156 + }
  157 + return null;
  158 + }
150 159 }
... ...
... ... @@ -131,10 +131,13 @@ export default abstract class LeafletMap {
131 131 tooltipAnchor: [16, -28],
132 132 shadowSize: [41, 41]
133 133 });
  134 + const customLatLng = this.convertToCustomFormat(mousePositionOnMap);
  135 + mousePositionOnMap.lat = customLatLng[this.options.latKeyName];
  136 + mousePositionOnMap.lng = customLatLng[this.options.lngKeyName];
  137 +
134 138 const newMarker = L.marker(mousePositionOnMap, { icon }).addTo(this.map);
135 139 this.addMarkers.push(newMarker);
136 140 const datasourcesList = document.createElement('div');
137   - const customLatLng = this.convertToCustomFormat(mousePositionOnMap);
138 141 const header = document.createElement('p');
139 142 header.appendChild(document.createTextNode('Select entity:'));
140 143 header.setAttribute('style', 'font-size: 14px; margin: 8px 0');
... ... @@ -410,10 +413,15 @@ export default abstract class LeafletMap {
410 413 }
411 414
412 415 convertToCustomFormat(position: L.LatLng): object {
413   - return {
414   - [this.options.latKeyName]: position.lat % 90,
415   - [this.options.lngKeyName]: position.lng % 180
416   - };
  416 + if (position.lng > 180) {
  417 + position.lng = 180;
  418 + } else if (position.lng < -180) {
  419 + position.lng = -180;
  420 + }
  421 + return {
  422 + [this.options.latKeyName]: position.lat,
  423 + [this.options.lngKeyName]: position.lng
  424 + };
417 425 }
418 426
419 427 convertToPolygonFormat(points: Array<any>): Array<any> {
... ...
... ... @@ -94,7 +94,7 @@ export class Marker {
94 94 }
95 95
96 96 updateMarkerPosition(position: L.LatLng) {
97   - if (!this.location.equals(position)) {
  97 + if (!this.leafletMarker.getLatLng().equals(position)) {
98 98 this.location = position;
99 99 this.leafletMarker.setLatLng(position);
100 100 }
... ...
... ... @@ -259,9 +259,27 @@ export class ImageMap extends LeafletMap {
259 259
260 260 convertToCustomFormat(position: L.LatLng, width = this.width, height = this.height): object {
261 261 const point = this.latLngToPoint(position);
  262 + const customX = calculateNewPointCoordinate(point.x, width);
  263 + const customY = calculateNewPointCoordinate(point.y, height);
  264 +
  265 + if (customX === 0) {
  266 + point.x = 0;
  267 + } else if (customX === 1) {
  268 + point.x = width;
  269 + }
  270 +
  271 + if (customY === 0) {
  272 + point.y = 0;
  273 + } else if (customY === 1) {
  274 + point.y = height;
  275 + }
  276 + const customLatLng = this.pointToLatLng(point.x, point.y);
  277 +
262 278 return {
263   - [this.options.xPosKeyName]: calculateNewPointCoordinate(point.x, width),
264   - [this.options.yPosKeyName]: calculateNewPointCoordinate(point.y, height)
  279 + [this.options.xPosKeyName]: customX,
  280 + [this.options.yPosKeyName]: customY,
  281 + [this.options.latKeyName]: customLatLng.lat,
  282 + [this.options.lngKeyName]: customLatLng.lng
265 283 };
266 284 }
267 285
... ...
... ... @@ -47,7 +47,8 @@ export enum EntityType {
47 47 RULE_NODE = 'RULE_NODE',
48 48 ENTITY_VIEW = 'ENTITY_VIEW',
49 49 WIDGETS_BUNDLE = 'WIDGETS_BUNDLE',
50   - WIDGET_TYPE = 'WIDGET_TYPE'
  50 + WIDGET_TYPE = 'WIDGET_TYPE',
  51 + API_USAGE_STATE = 'API_USAGE_STATE'
51 52 }
52 53
53 54 export enum AliasEntityType {
... ... @@ -239,6 +240,12 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti
239 240 }
240 241 ],
241 242 [
  243 + EntityType.API_USAGE_STATE,
  244 + {
  245 + type: 'entity.type-api-usage-state'
  246 + }
  247 + ],
  248 + [
242 249 EntityType.WIDGETS_BUNDLE,
243 250 {
244 251 details: 'widgets-bundle.widgets-bundle-details',
... ...
... ... @@ -885,6 +885,7 @@
885 885 "no-device-profiles-found": "No device profiles found.",
886 886 "create-new-device-profile": "Create a new one!",
887 887 "mqtt-device-topic-filters": "MQTT device topic filters",
  888 + "mqtt-device-topic-filters-unique": "MQTT device topic filters need to be unique.",
888 889 "mqtt-device-payload-type": "MQTT device payload",
889 890 "mqtt-device-payload-type-json": "JSON",
890 891 "mqtt-device-payload-type-proto": "Protobuf",
... ... @@ -1099,7 +1100,8 @@
1099 1100 "details": "Entity details",
1100 1101 "no-entities-prompt": "No entities found",
1101 1102 "no-data": "No data to display",
1102   - "columns-to-display": "Columns to Display"
  1103 + "columns-to-display": "Columns to Display",
  1104 + "type-api-usage-state": "Api Usage State"
1103 1105 },
1104 1106 "entity-field": {
1105 1107 "created-time": "Created time",
... ...