From c676ebb2675a9a3d055b86ee51b9da40a34878fc Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Mon, 16 Sep 2024 17:38:26 +0300 Subject: [PATCH 01/25] Move lastLoginTs and failedLoginAttempts from user's additionalInfo --- .../main/data/upgrade/3.7.1/schema_update.sql | 25 +++++++++++ .../server/controller/UserController.java | 7 +++- .../edge/EdgeEventSourcingListener.java | 3 -- .../system/DefaultSystemSecurityService.java | 2 +- .../server/dao/user/UserService.java | 2 +- .../common/data/security/UserCredentials.java | 2 + .../server/dao/model/ModelConstants.java | 3 +- .../dao/model/sql/UserCredentialsEntity.java | 17 +++++--- .../dao/sql/user/JpaUserCredentialsDao.java | 15 +++++++ .../sql/user/UserCredentialsRepository.java | 18 ++++++++ .../server/dao/user/UserCredentialsDao.java | 6 +++ .../server/dao/user/UserServiceImpl.java | 42 +++++-------------- 12 files changed, 97 insertions(+), 45 deletions(-) create mode 100644 application/src/main/data/upgrade/3.7.1/schema_update.sql diff --git a/application/src/main/data/upgrade/3.7.1/schema_update.sql b/application/src/main/data/upgrade/3.7.1/schema_update.sql new file mode 100644 index 0000000000..240daa18d5 --- /dev/null +++ b/application/src/main/data/upgrade/3.7.1/schema_update.sql @@ -0,0 +1,25 @@ +-- +-- Copyright © 2016-2024 The Thingsboard Authors +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +ALTER TABLE user_credentials ADD COLUMN IF NOT EXISTS last_login_ts BIGINT; +UPDATE user_credentials c SET last_login_ts = (SELECT (additional_info::json ->> 'lastLoginTs')::bigint FROM tb_user u WHERE u.id = c.user_id) + WHERE last_login_ts IS NULL; + +ALTER TABLE user_credentials ADD COLUMN IF NOT EXISTS failed_login_attempts INT; +UPDATE user_credentials c SET failed_login_attempts = (SELECT (additional_info::json ->> 'failedLoginAttempts')::int FROM tb_user u WHERE u.id = c.user_id) + WHERE failed_login_attempts IS NULL; + +UPDATE tb_user SET additional_info = (additional_info::jsonb - 'lastLoginTs' - 'failedLoginAttempts')::text WHERE additional_info IS NOT NULL AND additional_info != 'null'; diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index a71bd4dd38..df7f47c378 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -109,6 +109,8 @@ import static org.thingsboard.server.controller.ControllerConstants.USER_ID_PARA import static org.thingsboard.server.controller.ControllerConstants.USER_TEXT_SEARCH_DESCRIPTION; import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; import static org.thingsboard.server.dao.entity.BaseEntityService.NULL_CUSTOMER_ID; +import static org.thingsboard.server.dao.user.UserServiceImpl.LAST_LOGIN_TS; +import static org.thingsboard.server.dao.user.UserServiceImpl.USER_CREDENTIALS_ENABLED; @RequiredArgsConstructor @RestController @@ -151,9 +153,10 @@ public class UserController extends BaseController { processDashboardIdFromAdditionalInfo(additionalInfo, DEFAULT_DASHBOARD); processDashboardIdFromAdditionalInfo(additionalInfo, HOME_DASHBOARD); UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); - if (userCredentials.isEnabled() && !additionalInfo.has("userCredentialsEnabled")) { - additionalInfo.put("userCredentialsEnabled", true); + if (userCredentials.isEnabled() && !additionalInfo.has(USER_CREDENTIALS_ENABLED)) { + additionalInfo.put(USER_CREDENTIALS_ENABLED, true); } + additionalInfo.put(LAST_LOGIN_TS, userCredentials.getLastLoginTs()); } return user; } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java index 17ceb17ed1..907872d503 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java @@ -50,7 +50,6 @@ import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.RelationActionEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.tenant.TenantService; -import org.thingsboard.server.dao.user.UserServiceImpl; /** * This event listener does not support async event processing because relay on ThreadLocal @@ -231,8 +230,6 @@ public class EdgeEventSourcingListener { user.setAdditionalInfo(null); } if (user.getAdditionalInfo() instanceof ObjectNode additionalInfo) { - additionalInfo.remove(UserServiceImpl.FAILED_LOGIN_ATTEMPTS); - additionalInfo.remove(UserServiceImpl.LAST_LOGIN_TS); if (additionalInfo.isEmpty()) { user.setAdditionalInfo(null); } else { diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java index 6516bd7ce6..c969fa71ed 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java @@ -265,7 +265,7 @@ public class DefaultSystemSecurityService implements SystemSecurityService { } } if (actionType == ActionType.LOGIN && e == null) { - userService.setLastLoginTs(user.getTenantId(), user.getId()); + userService.updateLastLoginTs(user.getTenantId(), user.getId()); } auditLogService.logEntityAction( user.getTenantId(), user.getCustomerId(), user.getId(), diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java index 8f22812cfc..586ae50f5d 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java @@ -97,7 +97,7 @@ public interface UserService extends EntityDaoService { int increaseFailedLoginAttempts(TenantId tenantId, UserId userId); - void setLastLoginTs(TenantId tenantId, UserId userId); + void updateLastLoginTs(TenantId tenantId, UserId userId); void saveMobileSession(TenantId tenantId, UserId userId, String mobileToken, MobileSessionInfo sessionInfo); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java index 1104ae2949..4a882795db 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/UserCredentials.java @@ -40,6 +40,8 @@ public class UserCredentials extends BaseDataWithAdditionalInfo private Long resetTokenExpTime; @Convert(converter = JsonConverter.class) - @Column(name = ModelConstants.USER_CREDENTIALS_ADDITIONAL_PROPERTY) + @Column(name = ModelConstants.ADDITIONAL_INFO_PROPERTY) private JsonNode additionalInfo; + @Column(name = ModelConstants.USER_CREDENTIALS_LAST_LOGIN_TS_PROPERTY) + private Long lastLoginTs; + + @Column(name = ModelConstants.USER_CREDENTIALS_FAILED_LOGIN_ATTEMPTS_PROPERTY) + private Integer failedLoginAttempts; + public UserCredentialsEntity() { super(); } public UserCredentialsEntity(UserCredentials userCredentials) { - if (userCredentials.getId() != null) { - this.setUuid(userCredentials.getId().getId()); - } - this.setCreatedTime(userCredentials.getCreatedTime()); + super(userCredentials); if (userCredentials.getUserId() != null) { this.userId = userCredentials.getUserId().getId(); } @@ -82,6 +85,8 @@ public final class UserCredentialsEntity extends BaseSqlEntity this.resetToken = userCredentials.getResetToken(); this.resetTokenExpTime = userCredentials.getResetTokenExpTime(); this.additionalInfo = userCredentials.getAdditionalInfo(); + this.lastLoginTs = userCredentials.getLastLoginTs(); + this.failedLoginAttempts = userCredentials.getFailedLoginAttempts(); } @Override @@ -98,6 +103,8 @@ public final class UserCredentialsEntity extends BaseSqlEntity userCredentials.setResetToken(resetToken); userCredentials.setResetTokenExpTime(resetTokenExpTime); userCredentials.setAdditionalInfo(additionalInfo); + userCredentials.setLastLoginTs(lastLoginTs); + userCredentials.setFailedLoginAttempts(failedLoginAttempts); return userCredentials; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserCredentialsDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserCredentialsDao.java index 1f502db792..f277475896 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserCredentialsDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserCredentialsDao.java @@ -69,4 +69,19 @@ public class JpaUserCredentialsDao extends JpaAbstractDao { void removeByUserId(TenantId tenantId, UserId userId); + void setLastLoginTs(TenantId tenantId, UserId userId, long lastLoginTs); + + int incrementFailedLoginAttempts(TenantId tenantId, UserId userId); + + void setFailedLoginAttempts(TenantId tenantId, UserId userId, int failedLoginAttempts); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index ba0d3d69f3..c927c3e465 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -18,8 +18,6 @@ package org.thingsboard.server.dao.user; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.BooleanNode; -import com.fasterxml.jackson.databind.node.IntNode; -import com.fasterxml.jackson.databind.node.LongNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; @@ -86,16 +84,13 @@ import static org.thingsboard.server.dao.service.Validator.validateString; public class UserServiceImpl extends AbstractCachedEntityService implements UserService { public static final String USER_PASSWORD_HISTORY = "userPasswordHistory"; - + public static final String USER_CREDENTIALS_ENABLED = "userCredentialsEnabled"; public static final String LAST_LOGIN_TS = "lastLoginTs"; - public static final String FAILED_LOGIN_ATTEMPTS = "failedLoginAttempts"; private static final int DEFAULT_TOKEN_LENGTH = 30; public static final String INCORRECT_USER_ID = "Incorrect userId "; public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; - private static final String USER_CREDENTIALS_ENABLED = "userCredentialsEnabled"; - @Value("${security.user_login_case_sensitive:true}") private boolean userLoginCaseSensitive; @@ -428,6 +423,7 @@ public class UserServiceImpl extends AbstractCachedEntityService Date: Tue, 17 Sep 2024 13:20:07 +0300 Subject: [PATCH 02/25] Update user_credentials in schema-entities.sql --- dao/src/main/resources/sql/schema-entities.sql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 9c95f385f8..c553562007 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -497,7 +497,9 @@ CREATE TABLE IF NOT EXISTS user_credentials ( reset_token varchar(255) UNIQUE, reset_token_exp_time BIGINT, user_id uuid UNIQUE, - additional_info varchar DEFAULT '{}' + additional_info varchar DEFAULT '{}', + last_login_ts BIGINT, + failed_login_attempts INT ); CREATE TABLE IF NOT EXISTS widget_type ( From 345c1e5a31779b1f0b8a03381f5319f5464f564a Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Tue, 17 Sep 2024 19:05:03 +0300 Subject: [PATCH 03/25] Fix incrementFailedLoginAttemptsByUserId --- .../org/thingsboard/server/controller/TwoFactorAuthTest.java | 4 +++- .../server/dao/sql/user/UserCredentialsRepository.java | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java b/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java index 63703ae27b..7fa848f95d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java @@ -319,7 +319,9 @@ public class TwoFactorAuthTest extends AbstractControllerTest { assertThat(successfulLogInAuditLog.getActionStatus()).isEqualTo(ActionStatus.SUCCESS); assertThat(successfulLogInAuditLog.getUserName()).isEqualTo(username); }); - assertThat(userService.findUserById(tenantId, user.getId()).getAdditionalInfo() + + loginTenantAdmin(); + assertThat(doGet("/api/user/" + user.getId(), User.class).getAdditionalInfo() .get("lastLoginTs").asLong()) .isGreaterThan(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(3)); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserCredentialsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserCredentialsRepository.java index 60cdffb410..4aca40647c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserCredentialsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserCredentialsRepository.java @@ -43,7 +43,6 @@ public interface UserCredentialsRepository extends JpaRepository Date: Tue, 17 Sep 2024 18:34:48 +0200 Subject: [PATCH 04/25] fixed concurrent modification in TbSubscriptionsInfo --- .../server/service/subscription/TbSubscriptionsInfo.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionsInfo.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionsInfo.java index 48464d5297..a65fc0bedb 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionsInfo.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionsInfo.java @@ -20,6 +20,7 @@ import lombok.EqualsAndHashCode; import lombok.RequiredArgsConstructor; import lombok.ToString; +import java.util.HashSet; import java.util.Set; /** @@ -48,7 +49,7 @@ public class TbSubscriptionsInfo { } protected TbSubscriptionsInfo copy(int seqNumber) { - return new TbSubscriptionsInfo(notifications, alarms, tsAllKeys, tsKeys, attrAllKeys, attrKeys, seqNumber); + return new TbSubscriptionsInfo(notifications, alarms, tsAllKeys, tsKeys != null ? new HashSet<>(tsKeys) : null, attrAllKeys, attrKeys != null ? new HashSet<>(attrKeys) : null, seqNumber); } } From 8d04c5040e5e5f83750ba204c3e6f87b3cdcfcd4 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 18 Sep 2024 22:06:28 +0200 Subject: [PATCH 05/25] implemented removing all subscriptions by batch --- .../DefaultTbLocalSubscriptionService.java | 39 +++++++++-- .../subscription/TbEntityLocalSubsInfo.java | 64 +++++++++++++++++-- 2 files changed, 91 insertions(+), 12 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java index 4043b519da..0a47f5f375 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbLocalSubscriptionService.java @@ -282,7 +282,6 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer if (sessionSubscriptions != null) { TbSubscription subscription = sessionSubscriptions.remove(subscriptionId); if (subscription != null) { - if (sessionSubscriptions.isEmpty()) { subscriptionsBySessionId.remove(sessionId); } @@ -304,22 +303,26 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer @Override public void cancelAllSessionSubscriptions(TenantId tenantId, String sessionId) { log.debug("[{}][{}] Going to remove session subscriptions.", tenantId, sessionId); - List results = new ArrayList<>(); Lock subsLock = getSubsLock(tenantId); subsLock.lock(); try { Map> sessionSubscriptions = subscriptionsBySessionId.remove(sessionId); if (sessionSubscriptions != null) { - for (TbSubscription subscription : sessionSubscriptions.values()) { - results.add(modifySubscription(tenantId, subscription.getEntityId(), subscription, false)); - } + Map>> entitySubscriptions = + sessionSubscriptions.values().stream().collect(Collectors.groupingBy(TbSubscription::getEntityId)); + + entitySubscriptions.forEach((entityId, subscriptions) -> { + TbEntitySubEvent event = removeAllSubscriptions(tenantId, entityId, subscriptions); + if (event != null) { + pushSubscriptionsEvent(tenantId, entityId, event); + } + }); } else { log.debug("[{}][{}] No session subscriptions found!", tenantId, sessionId); } } finally { subsLock.unlock(); } - results.stream().filter(SubscriptionModificationResult::hasEvent).forEach(this::pushSubscriptionEvent); } @Override @@ -500,6 +503,30 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer return new SubscriptionModificationResult(tenantId, entityId, subscription, missedUpdatesCandidate, event); } + private TbEntitySubEvent removeAllSubscriptions(TenantId tenantId, EntityId entityId, List> subscriptions) { + TbEntitySubEvent event = null; + try { + TbEntityLocalSubsInfo entitySubs = subscriptionsByEntityId.get(entityId.getId()); + event = entitySubs.removeAll(subscriptions); + if (entitySubs.isEmpty()) { + subscriptionsByEntityId.remove(entityId.getId()); + entityUpdates.remove(entityId.getId()); + } + } catch (Exception e) { + log.warn("[{}][{}] Failed to remove all subscriptions {} due to ", tenantId, entityId, subscriptions, e); + } + return event; + } + + private void pushSubscriptionsEvent(TenantId tenantId, EntityId entityId, TbEntitySubEvent event) { + try { + log.trace("[{}][{}] Event: {}", tenantId, entityId, event); + pushSubEventToManagerService(tenantId, entityId, event); + } catch (Exception e) { + log.warn("[{}][{}] Failed to push subscription event {} due to ", tenantId, entityId, event, e); + } + } + private void pushSubscriptionEvent(SubscriptionModificationResult modificationResult) { try { TbEntitySubEvent event = modificationResult.getEvent(); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfo.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfo.java index f5a5639ec0..a865faeb1a 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfo.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfo.java @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -129,13 +130,64 @@ public class TbEntityLocalSubsInfo { if (!subs.remove(sub)) { return null; } - if (subs.isEmpty()) { + if (isEmpty()) { return toEvent(ComponentLifecycleEvent.DELETED); } - TbSubscriptionsInfo oldState = state.copy(); - TbSubscriptionsInfo newState = new TbSubscriptionsInfo(); + TbSubscriptionType type = sub.getType(); + TbSubscriptionsInfo newState = state.copy(); + updateNewState(newState, type); + return updateState(Set.of(type), newState); + } + + public TbEntitySubEvent removeAll(List> subsToRemove) { + Set changedTypes = new HashSet<>(); + TbSubscriptionsInfo newState = state.copy(); + for (TbSubscription sub : subsToRemove) { + log.trace("[{}][{}][{}] Removing: {}", tenantId, entityId, sub.getSubscriptionId(), sub); + if (!subs.remove(sub)) { + continue; + } + if (isEmpty()) { + return toEvent(ComponentLifecycleEvent.DELETED); + } + TbSubscriptionType type = sub.getType(); + if (changedTypes.contains(type)) { + continue; + } + + updateNewState(newState, type); + changedTypes.add(type); + } + + return updateState(changedTypes, newState); + } + + private void updateNewState(TbSubscriptionsInfo state, TbSubscriptionType type) { + switch (type) { + case NOTIFICATIONS: + case NOTIFICATIONS_COUNT: + state.notifications = false; + break; + case ALARMS: + state.alarms = false; + break; + case ATTRIBUTES: + state.attrAllKeys = false; + state.attrKeys = null; + break; + case TIMESERIES: + state.tsAllKeys = false; + state.tsKeys = null; + } + } + + private TbEntitySubEvent updateState(Set updatedTypes, TbSubscriptionsInfo newState) { for (TbSubscription subscription : subs) { - switch (subscription.getType()) { + TbSubscriptionType type = subscription.getType(); + if (!updatedTypes.contains(type)) { + continue; + } + switch (type) { case NOTIFICATIONS: case NOTIFICATIONS_COUNT: if (!newState.notifications) { @@ -173,7 +225,7 @@ public class TbEntityLocalSubsInfo { break; } } - if (newState.equals(oldState)) { + if (newState.equals(state)) { return null; } else { this.state = newState; @@ -196,7 +248,7 @@ public class TbEntityLocalSubsInfo { public boolean isEmpty() { - return state.isEmpty(); + return subs.isEmpty(); } public TbSubscription registerPendingSubscription(TbSubscription subscription, TbEntitySubEvent event) { From 20aca9466fc7a0d0514a6ac24d933d9aab618cd9 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Mon, 30 Sep 2024 12:53:19 +0300 Subject: [PATCH 06/25] Add tests for failed login and lastLoginTs --- .../server/controller/AuthControllerTest.java | 82 +++++++++++++------ 1 file changed, 58 insertions(+), 24 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java index 817ded33e7..62b8188cfd 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java @@ -27,6 +27,7 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.UserActivationLink; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.model.SecuritySettings; @@ -67,31 +68,30 @@ public class AuthControllerTest extends AbstractControllerTest { .andExpect(status().isUnauthorized()); loginSysAdmin(); - doGet("/api/auth/user") - .andExpect(status().isOk()) - .andExpect(jsonPath("$.authority", is(Authority.SYS_ADMIN.name()))) - .andExpect(jsonPath("$.email", is(SYS_ADMIN_EMAIL))); + User user = getCurrentUser(); + assertThat(user.getAuthority()).isEqualTo(Authority.SYS_ADMIN); + assertThat(user.getEmail()).isEqualTo(SYS_ADMIN_EMAIL); loginTenantAdmin(); - doGet("/api/auth/user") - .andExpect(status().isOk()) - .andExpect(jsonPath("$.authority", is(Authority.TENANT_ADMIN.name()))) - .andExpect(jsonPath("$.email", is(TENANT_ADMIN_EMAIL))); + user = getCurrentUser(); + assertThat(user.getAuthority()).isEqualTo(Authority.TENANT_ADMIN); + assertThat(user.getEmail()).isEqualTo(TENANT_ADMIN_EMAIL); loginCustomerUser(); - doGet("/api/auth/user") - .andExpect(status().isOk()) - .andExpect(jsonPath("$.authority", is(Authority.CUSTOMER_USER.name()))) - .andExpect(jsonPath("$.email", is(CUSTOMER_USER_EMAIL))); + user = getCurrentUser(); + assertThat(user.getAuthority()).isEqualTo(Authority.CUSTOMER_USER); + assertThat(user.getEmail()).isEqualTo(CUSTOMER_USER_EMAIL); + assertThat(user.getAdditionalInfo().get("userCredentialsEnabled").asBoolean()).isTrue(); + user = getUser(customerUserId); + assertThat(user.getAdditionalInfo().get("lastLoginTs").asLong()).isCloseTo(System.currentTimeMillis(), within(10000L)); } @Test public void testLoginLogout() throws Exception { loginSysAdmin(); - doGet("/api/auth/user") - .andExpect(status().isOk()) - .andExpect(jsonPath("$.authority", is(Authority.SYS_ADMIN.name()))) - .andExpect(jsonPath("$.email", is(SYS_ADMIN_EMAIL))); + User user = getCurrentUser(); + assertThat(user.getAuthority()).isEqualTo(Authority.SYS_ADMIN); + assertThat(user.getEmail()).isEqualTo(SYS_ADMIN_EMAIL); TimeUnit.SECONDS.sleep(1); //We need to make sure that event for invalidating token was successfully processed @@ -102,19 +102,45 @@ public class AuthControllerTest extends AbstractControllerTest { resetTokens(); } + @Test + public void testFailedLogin() throws Exception { + int maxFailedLoginAttempts = 3; + loginSysAdmin(); + updateSecuritySettings(securitySettings -> { + securitySettings.setMaxFailedLoginAttempts(maxFailedLoginAttempts); + }); + loginTenantAdmin(); + + for (int i = 0; i < maxFailedLoginAttempts; i++) { + String error = getErrorMessage(doPost("/api/auth/login", + new LoginRequest(CUSTOMER_USER_EMAIL, "IncorrectPassword")) + .andExpect(status().isUnauthorized())); + assertThat(error).containsIgnoringCase("invalid username or password"); + } + + User user = getUser(customerUserId); + assertThat(user.getAdditionalInfo().get("userCredentialsEnabled").asBoolean()).isTrue(); + + String error = getErrorMessage(doPost("/api/auth/login", + new LoginRequest(CUSTOMER_USER_EMAIL, "IncorrectPassword4")) + .andExpect(status().isUnauthorized())); + assertThat(error).containsIgnoringCase("account is locked"); + + user = getUser(customerUserId); + assertThat(user.getAdditionalInfo().get("userCredentialsEnabled").asBoolean()).isFalse(); + } + @Test public void testRefreshToken() throws Exception { loginSysAdmin(); - doGet("/api/auth/user") - .andExpect(status().isOk()) - .andExpect(jsonPath("$.authority", is(Authority.SYS_ADMIN.name()))) - .andExpect(jsonPath("$.email", is(SYS_ADMIN_EMAIL))); + User user = getCurrentUser(); + assertThat(user.getAuthority()).isEqualTo(Authority.SYS_ADMIN); + assertThat(user.getEmail()).isEqualTo(SYS_ADMIN_EMAIL); refreshToken(); - doGet("/api/auth/user") - .andExpect(status().isOk()) - .andExpect(jsonPath("$.authority", is(Authority.SYS_ADMIN.name()))) - .andExpect(jsonPath("$.email", is(SYS_ADMIN_EMAIL))); + user = getCurrentUser(); + assertThat(user.getAuthority()).isEqualTo(Authority.SYS_ADMIN); + assertThat(user.getEmail()).isEqualTo(SYS_ADMIN_EMAIL); } @Test @@ -277,6 +303,14 @@ public class AuthControllerTest extends AbstractControllerTest { doPost("/api/admin/securitySettings", securitySettings).andExpect(status().isOk()); } + private User getCurrentUser() throws Exception { + return doGet("/api/auth/user", User.class); + } + + private User getUser(UserId id) throws Exception { + return doGet("/api/user/" + id, User.class); + } + private String getActivationLink(User user) throws Exception { return doGet("/api/user/" + user.getId() + "/activationLink", String.class); } From 29a41e5306e86505b95c5b8c842222637016da17 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 30 Sep 2024 14:10:05 +0200 Subject: [PATCH 07/25] added corresponding tests --- .../subscription/TbEntityLocalSubsInfo.java | 2 +- .../TbEntityLocalSubsInfoTest.java | 177 ++++++++++++++++++ 2 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 application/src/test/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfoTest.java diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfo.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfo.java index a865faeb1a..12786c65ee 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfo.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfo.java @@ -139,7 +139,7 @@ public class TbEntityLocalSubsInfo { return updateState(Set.of(type), newState); } - public TbEntitySubEvent removeAll(List> subsToRemove) { + public TbEntitySubEvent removeAll(List> subsToRemove) { Set changedTypes = new HashSet<>(); TbSubscriptionsInfo newState = state.copy(); for (TbSubscription sub : subsToRemove) { diff --git a/application/src/test/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfoTest.java b/application/src/test/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfoTest.java new file mode 100644 index 0000000000..88f29308ab --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfoTest.java @@ -0,0 +1,177 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.subscription; + +import org.junit.jupiter.api.Test; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class TbEntityLocalSubsInfoTest { + + @Test + void addTest() { + Set expectedSubs = new HashSet<>(); + TbEntityLocalSubsInfo subsInfo = createSubsInfo(); + TenantId tenantId = subsInfo.getTenantId(); + EntityId entityId = subsInfo.getEntityId(); + TbAttributeSubscription attrSubscription1 = TbAttributeSubscription.builder() + .sessionId("session1") + .tenantId(tenantId) + .entityId(entityId) + .keyStates(Map.of("key1", 1L, "key2", 2L)) + .build(); + expectedSubs.add(attrSubscription1); + TbEntitySubEvent created = subsInfo.add(attrSubscription1); + assertFalse(subsInfo.isEmpty()); + assertNotNull(created); + assertEquals(expectedSubs, subsInfo.getSubs()); + checkEvent(created, expectedSubs, ComponentLifecycleEvent.CREATED); + + assertNull(subsInfo.add(attrSubscription1)); + + TbAttributeSubscription attrSubscription2 = TbAttributeSubscription.builder() + .sessionId("session2") + .tenantId(tenantId) + .entityId(entityId) + .keyStates(Map.of("key3", 3L, "key4", 4L)) + .build(); + expectedSubs.add(attrSubscription2); + TbEntitySubEvent updated = subsInfo.add(attrSubscription2); + assertNotNull(updated); + + assertEquals(expectedSubs, subsInfo.getSubs()); + checkEvent(updated, expectedSubs, ComponentLifecycleEvent.UPDATED); + } + + @Test + void removeTest() { + Set expectedSubs = new HashSet<>(); + TbEntityLocalSubsInfo subsInfo = createSubsInfo(); + TenantId tenantId = subsInfo.getTenantId(); + EntityId entityId = subsInfo.getEntityId(); + TbAttributeSubscription attrSubscription1 = TbAttributeSubscription.builder() + .sessionId("session1") + .tenantId(tenantId) + .entityId(entityId) + .keyStates(Map.of("key1", 1L, "key2", 2L)) + .build(); + + TbAttributeSubscription attrSubscription2 = TbAttributeSubscription.builder() + .sessionId("session2") + .tenantId(tenantId) + .entityId(entityId) + .keyStates(Map.of("key3", 3L, "key4", 4L)) + .build(); + + expectedSubs.add(attrSubscription1); + expectedSubs.add(attrSubscription2); + + subsInfo.add(attrSubscription1); + subsInfo.add(attrSubscription2); + + assertEquals(expectedSubs, subsInfo.getSubs()); + + TbEntitySubEvent updatedEvent = subsInfo.remove(attrSubscription1); + expectedSubs.remove(attrSubscription1); + assertNotNull(updatedEvent); + assertEquals(expectedSubs, subsInfo.getSubs()); + checkEvent(updatedEvent, expectedSubs, ComponentLifecycleEvent.UPDATED); + + TbEntitySubEvent deletedEvent = subsInfo.remove(attrSubscription2); + expectedSubs.remove(attrSubscription2); + assertNotNull(deletedEvent); + assertEquals(expectedSubs, subsInfo.getSubs()); + checkEvent(deletedEvent, expectedSubs, ComponentLifecycleEvent.DELETED); + + assertTrue(subsInfo.isEmpty()); + } + + @Test + void removeAllTest() { + List subs = new ArrayList<>(); + TbEntityLocalSubsInfo subsInfo = createSubsInfo(); + TenantId tenantId = subsInfo.getTenantId(); + EntityId entityId = subsInfo.getEntityId(); + TbAttributeSubscription attrSubscription1 = TbAttributeSubscription.builder() + .sessionId("session1") + .tenantId(tenantId) + .entityId(entityId) + .keyStates(Map.of("key1", 1L, "key2", 2L)) + .build(); + + TbAttributeSubscription attrSubscription2 = TbAttributeSubscription.builder() + .sessionId("session2") + .tenantId(tenantId) + .entityId(entityId) + .keyStates(Map.of("key3", 3L, "key4", 4L)) + .build(); + + subs.add(attrSubscription1); + subs.add(attrSubscription2); + + subsInfo.add(attrSubscription1); + subsInfo.add(attrSubscription2); + + assertFalse(subsInfo.isEmpty()); + + TbEntitySubEvent deletedEvent = subsInfo.removeAll(subs); + assertNotNull(deletedEvent); + checkEvent(deletedEvent, subs, ComponentLifecycleEvent.DELETED); + + assertTrue(subsInfo.isEmpty()); + } + + private TbEntityLocalSubsInfo createSubsInfo() { + return new TbEntityLocalSubsInfo(new TenantId(UUID.randomUUID()), new DeviceId(UUID.randomUUID())); + } + + private void checkEvent(TbEntitySubEvent event, Collection expectedSubs, ComponentLifecycleEvent expectedType) { + assertEquals(expectedType, event.getType()); + TbSubscriptionsInfo info = event.getInfo(); + if (event.getType() == ComponentLifecycleEvent.DELETED) { + assertNull(info); + return; + } + assertNotNull(info); + assertFalse(info.notifications); + assertFalse(info.alarms); + assertFalse(info.attrAllKeys); + assertFalse(info.tsAllKeys); + assertNull(info.tsKeys); + assertEquals(getAttrKeys(expectedSubs), info.attrKeys); + } + + private Set getAttrKeys(Collection attributeSubscriptions) { + return attributeSubscriptions.stream().map(s -> s.getKeyStates().keySet()).flatMap(Collection::stream).collect(Collectors.toSet()); + } +} From 8d6768009d0435a960d42ad8e6108ef726bf97e3 Mon Sep 17 00:00:00 2001 From: nick Date: Wed, 2 Oct 2024 13:12:53 +0300 Subject: [PATCH 08/25] lwm2m: update version: leshan = M15, californium = 3.12.1 --- .../lwm2m/attributes/LwM2mAttributesTest.java | 4 +- .../lwm2m/client/TbLwm2mObjectEnabler.java | 75 +++++++++---------- ...ntegrationDiscoverWriteAttributesTest.java | 44 ++++++----- ...LwM2mCredentialsSecurityInfoValidator.java | 2 +- .../device/DeviceCredentialsServiceImpl.java | 2 +- .../validator/DeviceProfileDataValidator.java | 2 +- pom.xml | 4 +- 7 files changed, 67 insertions(+), 66 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/LwM2mAttributesTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/LwM2mAttributesTest.java index 73f8346566..ad23ed9f0c 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/LwM2mAttributesTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/attributes/LwM2mAttributesTest.java @@ -49,13 +49,13 @@ public class LwM2mAttributesTest { @ParameterizedTest(name = "Tests {index} : {0}") @MethodSource("doesntSupportAttributesWithoutValue") public void check_attribute_can_not_be_created_without_value(LwM2mAttributeModel model) { - assertThrows(UnsupportedOperationException.class, () -> LwM2mAttributes.create(model)); + assertThrows(IllegalArgumentException.class, () -> LwM2mAttributes.create(model)); } @ParameterizedTest(name = "Tests {index} : {0}") @MethodSource("doesntSupportAttributesWithValueNull") public void check_attribute_can_not_be_created_with_null(LwM2mAttributeModel model) { - assertThrows(NullPointerException.class, () -> LwM2mAttributes.create(model, null)); + assertThrows(IllegalArgumentException.class, () -> LwM2mAttributes.create(model, null)); } private static Stream supportNullAttributes() throws InvalidAttributeException { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java index f548cc30e2..a7bdae3321 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java @@ -69,7 +69,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -88,6 +87,7 @@ public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyab private LinkFormatHelper tbLinkFormatHelper; protected Map lwM2mAttributes; + public TbLwm2mObjectEnabler(int id, ObjectModel objectModel, Map instances, LwM2mInstanceEnablerFactory instanceFactory, ContentFormat defaultContentFormat) { super(id, objectModel); @@ -586,10 +586,10 @@ public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyab * - Less Than lt (def = -- ) Float Resource Numerical&Readable Resource * - Step st (def = -- ) Float Resource Numerical&Readable Resource */ - public WriteAttributesResponse doWriteAttributes(LwM2mServer server, WriteAttributesRequest request) { + public WriteAttributesResponse doWriteAttributes(LwM2mServer server, WriteAttributesRequest request) { LwM2mPath lwM2mPath = request.getPath(); LwM2mAttributeSet attributeSet = lwM2mAttributes.get(lwM2mPath); - Map > attributes = new HashMap<>(); + Map> attributes = new HashMap<>(); for (LwM2mAttribute attr : request.getAttributes().getLwM2mAttributes()) { if (attr.getName().equals("pmax") || attr.getName().equals("pmin")) { @@ -606,12 +606,12 @@ public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyab } } } - if (attributes.size()>0){ + if (attributes.size() > 0) { if (attributeSet == null) { attributeSet = new LwM2mAttributeSet(attributes.values()); } else { Iterable> lwM2mAttributeIterable = attributeSet.getLwM2mAttributes(); - Map > attributesOld = new HashMap<>(); + Map> attributesOld = new HashMap<>(); for (LwM2mAttribute attr : lwM2mAttributeIterable) { attributesOld.put(attr.getName(), attr); } @@ -643,7 +643,7 @@ public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyab LwM2mPath path = request.getPath(); if (path.isObject()) { - LwM2mLink[] ObjectLinks = linkUpdateAttributes(this.tbLinkFormatHelper.getObjectDescription(this, null), server); + LwM2mLink[] ObjectLinks = linkAddUpdateAttributes(this.tbLinkFormatHelper.getObjectDescription(server, this, null), server); return DiscoverResponse.success(ObjectLinks); } else if (path.isObjectInstance()) { @@ -651,7 +651,7 @@ public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyab if (!getAvailableInstanceIds().contains(path.getObjectInstanceId())) return DiscoverResponse.notFound(); - LwM2mLink[] instanceLink = linkUpdateAttributes(this.tbLinkFormatHelper.getInstanceDescription(this, path.getObjectInstanceId(), null), server); + LwM2mLink[] instanceLink = linkAddUpdateAttributes(this.tbLinkFormatHelper.getInstanceDescription(server, this, path.getObjectInstanceId(), null), server); return DiscoverResponse.success(instanceLink); } else if (path.isResource()) { @@ -666,46 +666,43 @@ public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyab if (!getAvailableResourceIds(path.getObjectInstanceId()).contains(path.getResourceId())) return DiscoverResponse.notFound(); - LwM2mLink resourceLink = linkAddAttribute( - this.tbLinkFormatHelper.getResourceDescription(this, path.getObjectInstanceId(), path.getResourceId(), null), - server); - return DiscoverResponse.success(new LwM2mLink[] { resourceLink }); + LwM2mLink[] resourceLink = linkAddUpdateAttributes(this.tbLinkFormatHelper.getResourceDescription(server, + this, path.getObjectInstanceId(), path.getResourceId(), null), server); + return DiscoverResponse.success(resourceLink); } return DiscoverResponse.badRequest(null); } - private LwM2mLink[] linkUpdateAttributes(LwM2mLink[] links, LwM2mServer server) { - return Arrays.stream(links) - .map(link -> linkAddAttribute(link, server)) - .toArray(LwM2mLink[]::new); - } - - private LwM2mLink linkAddAttribute(LwM2mLink link, LwM2mServer server) { + private LwM2mLink[] linkAddUpdateAttributes(LwM2mLink[] links, LwM2mServer server) { + ArrayList resourceLinkList = new ArrayList<>(); + for (LwM2mLink link : links) { - LwM2mAttributeSet lwM2mAttributeSetDop = null; - if (this.lwM2mAttributes.get(link.getPath())!= null){ - lwM2mAttributeSetDop = this.lwM2mAttributes.get(link.getPath()); - } - LwM2mAttribute resourceAttributeDim = getResourceAttributes (server, link.getPath()); + LwM2mAttributeSet lwM2mAttributeSetDop = null; + if (this.lwM2mAttributes.get(link.getPath()) != null) { + lwM2mAttributeSetDop = this.lwM2mAttributes.get(link.getPath()); + } + LwM2mAttribute resourceAttributeDim = getResourceAttributes(server, link.getPath()); - Map > attributes = new HashMap<>(); - if (link.getAttributes() != null) { - for (LwM2mAttribute attr : link.getAttributes().getLwM2mAttributes()) { - attributes.put(attr.getName(), attr); + Map> attributes = new HashMap<>(); + if (link.getAttributes() != null) { + for (LwM2mAttribute attr : link.getAttributes().getLwM2mAttributes()) { + attributes.put(attr.getName(), attr); + } } - } - if (lwM2mAttributeSetDop != null) { - for (LwM2mAttribute attr : lwM2mAttributeSetDop.getLwM2mAttributes()) { - attributes.put(attr.getName(), attr); + if (lwM2mAttributeSetDop != null) { + for (LwM2mAttribute attr : lwM2mAttributeSetDop.getLwM2mAttributes()) { + attributes.put(attr.getName(), attr); + } } + if (resourceAttributeDim != null) { + attributes.put(resourceAttributeDim.getName(), resourceAttributeDim); + } + resourceLinkList.add(new LwM2mLink(link.getRootPath(), link.getPath(), attributes.values())); } - if (resourceAttributeDim != null) { - attributes.put(resourceAttributeDim.getName(), resourceAttributeDim); - } - return new LwM2mLink(link.getRootPath(), link.getPath(), attributes.values()); + return resourceLinkList.toArray(LwM2mLink[]::new); } - protected LwM2mAttribute getResourceAttributes (LwM2mServer server, LwM2mPath path) { + protected LwM2mAttribute getResourceAttributes(LwM2mServer server, LwM2mPath path) { ResourceModel resourceModel = getObjectModel().resources.get(path.getResourceId()); if (path.isResource() && resourceModel.multiple) { return getResourceAttributeDim(path, server); @@ -717,13 +714,13 @@ public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyab LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); try { ReadResponse readResponse = instance.read(server, path.getResourceId()); - if (readResponse.getCode().getCode()==205 && readResponse.getContent() instanceof LwM2mMultipleResource) { - long valueDim = ((LwM2mMultipleResource)readResponse.getContent()).getInstances().size(); + if (readResponse.getCode().getCode() == 205 && readResponse.getContent() instanceof LwM2mMultipleResource) { + long valueDim = ((LwM2mMultipleResource) readResponse.getContent()).getInstances().size(); return LwM2mAttributes.create(LwM2mAttributes.DIMENSION, valueDim); } else { return null; } - } catch (Exception e ){ + } catch (Exception e) { return null; } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverWriteAttributesTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverWriteAttributesTest.java index d7807ee24a..c44048849e 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverWriteAttributesTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverWriteAttributesTest.java @@ -49,6 +49,7 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL String actual = rpcActualResult.get("error").asText(); assertTrue(actual.equals(expected)); } + /** * WriteAttributes {"id":"/3_1.2/0/6","attributes":{"pmax":100, "pmin":10}} * if not implemented: @@ -82,7 +83,7 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL @Test public void testWriteAttributesResourceServerUriWithParametersById_Result_BAD_REQUEST() throws Exception { - String expectedPath = objectInstanceIdVer_1; + String expectedPath = objectInstanceIdVer_1; String actualResult = sendRPCReadById(expectedPath); String expectedValue = "{\"uri\":\"coaps://localhost:5690\"}"; actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); @@ -108,13 +109,13 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL * "ver" only for objectId */ @Test - public void testReadDIM_3_0_6_Only_R () throws Exception { + public void testReadDIM_3_0_6_Only_R() throws Exception { String path = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6; String actualResult = sendDiscover(path); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); String expected = ";dim=3"; - assertTrue(rpcActualResult.get("value").asText().equals(expected)); + assertTrue(rpcActualResult.get("value").asText().contains(expected)); } @@ -126,7 +127,7 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL * "ver" only for objectId */ @Test - public void testReadVer () throws Exception { + public void testReadVer() throws Exception { String path = objectIdVer_3; String actualResult = sendDiscover(path); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); @@ -149,7 +150,7 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); - // result changed + // result changed actualResult = sendDiscover(expectedPath); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); @@ -175,7 +176,7 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL * <3/0/6>;dim=8,<3/0/7>;gt=50;lt=42.2;st=0.5,<3/0/8>;... */ @Test - public void testWriteAttributesPeriodLtGt () throws Exception { + public void testWriteAttributesPeriodLtGt() throws Exception { String expectedPath = objectInstanceIdVer_3; String expectedValue = "{\"pmax\":60}"; String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); @@ -187,30 +188,33 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_7; - expectedValue ="{\"gt\":50, \"lt\":42.2, \"st\":0.5}"; + expectedValue = "{\"gt\":50.0, \"lt\":42.2, \"st\":0.5}"; actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); - // ObjectId + // ObjectId expectedPath = objectIdVer_3; actualResult = sendDiscover(expectedPath); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - // String expected = ";ver=1.2,;pmax=60,,,,,;dim=3,;st=0.5;lt=42.2;gt=50.0,,,,;dim=1,,,,,,,,,"; + String actualValue = rpcActualResult.get("value").asText(); String expected = ";ver=1.2,;pmax=65"; - assertTrue(rpcActualResult.get("value").asText().contains(expected)); - expected = ";dim=3,;st=0.5;lt=42.2;gt=50.0"; - assertTrue(rpcActualResult.get("value").asText().contains(expected)); - // ObjectInstanceId + assertTrue(actualValue.contains(expected)); + expected = ";dim=3"; + assertTrue(actualValue.contains(expected)); + expected = ";st=0.5;lt=42.2;gt=50"; + assertTrue(actualValue.contains(expected)); + // ObjectInstanceId expectedPath = objectInstanceIdVer_3; actualResult = sendDiscover(expectedPath); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + actualValue = rpcActualResult.get("value").asText(); expected = ";pmax=65"; - assertTrue(rpcActualResult.get("value").asText().contains(expected)); - expected = ";dim=3,;st=0.5;lt=42.2;gt=50.0"; - assertTrue(rpcActualResult.get("value").asText().contains(expected)); - // ResourceId + assertTrue(actualValue.contains(expected)); + expected = ";dim=3,;st=0.5;lt=42.2;gt=50"; + assertTrue(actualValue.contains(expected)); + // ResourceId expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6; actualResult = sendDiscover(expectedPath); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); @@ -221,10 +225,10 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL actualResult = sendDiscover(expectedPath); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - expected = ";st=0.5;lt=42.2;gt=50.0"; + expected = ";st=0.5;lt=42.2;gt=50"; assertTrue(rpcActualResult.get("value").asText().contains(expected)); - // ResourceInstanceId - expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6+ "/1"; + // ResourceInstanceId + expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6 + "/1"; actualResult = sendDiscover(expectedPath); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.INTERNAL_SERVER_ERROR.getName(), rpcActualResult.get("result").asText()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java index a44c6adb6e..66b072fdaa 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java @@ -18,7 +18,7 @@ package org.thingsboard.server.transport.lwm2m.secure; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.DecoderException; -import org.eclipse.leshan.core.util.SecurityUtil; +import org.eclipse.leshan.core.security.util.SecurityUtil; import org.eclipse.leshan.server.security.SecurityInfo; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java index e958313d67..cce114f361 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceCredentialsServiceImpl.java @@ -19,7 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.SecurityMode; -import org.eclipse.leshan.core.util.SecurityUtil; +import org.eclipse.leshan.core.security.util.SecurityUtil; import org.hibernate.exception.ConstraintViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.event.TransactionalEventListener; diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index 2d2c7bf21c..bfff00f6b1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -18,7 +18,7 @@ package org.thingsboard.server.dao.service.validator; import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.util.SecurityUtil; +import org.eclipse.leshan.core.security.util.SecurityUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; diff --git a/pom.xml b/pom.xml index 5e4cc50c7c..ea50c0fddb 100755 --- a/pom.xml +++ b/pom.xml @@ -74,8 +74,8 @@ 1.7.0 4.4.0 2.2.14 - 3.11.0 - 2.0.0-M14 + 3.12.1 + 2.0.0-M15 2.10.1 2.3.32 2.0.1 From 873ad00792730a29f87325b74c86dd51cc3dc5b9 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 2 Oct 2024 16:30:17 +0200 Subject: [PATCH 09/25] minor refactoring and test improvements due to comments --- .../subscription/TbEntityLocalSubsInfo.java | 6 ++-- .../TbEntityLocalSubsInfoTest.java | 33 ++++++++++++------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfo.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfo.java index 12786c65ee..ee20843538 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfo.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfo.java @@ -135,7 +135,7 @@ public class TbEntityLocalSubsInfo { } TbSubscriptionType type = sub.getType(); TbSubscriptionsInfo newState = state.copy(); - updateNewState(newState, type); + clearState(newState, type); return updateState(Set.of(type), newState); } @@ -155,14 +155,14 @@ public class TbEntityLocalSubsInfo { continue; } - updateNewState(newState, type); + clearState(newState, type); changedTypes.add(type); } return updateState(changedTypes, newState); } - private void updateNewState(TbSubscriptionsInfo state, TbSubscriptionType type) { + private void clearState(TbSubscriptionsInfo state, TbSubscriptionType type) { switch (type) { case NOTIFICATIONS: case NOTIFICATIONS_COUNT: diff --git a/application/src/test/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfoTest.java b/application/src/test/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfoTest.java index 88f29308ab..e9b95ec832 100644 --- a/application/src/test/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfoTest.java +++ b/application/src/test/java/org/thingsboard/server/service/subscription/TbEntityLocalSubsInfoTest.java @@ -21,7 +21,6 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; -import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -36,10 +35,10 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -class TbEntityLocalSubsInfoTest { +public class TbEntityLocalSubsInfoTest { @Test - void addTest() { + public void addTest() { Set expectedSubs = new HashSet<>(); TbEntityLocalSubsInfo subsInfo = createSubsInfo(); TenantId tenantId = subsInfo.getTenantId(); @@ -74,7 +73,7 @@ class TbEntityLocalSubsInfoTest { } @Test - void removeTest() { + public void removeTest() { Set expectedSubs = new HashSet<>(); TbEntityLocalSubsInfo subsInfo = createSubsInfo(); TenantId tenantId = subsInfo.getTenantId(); @@ -117,8 +116,7 @@ class TbEntityLocalSubsInfoTest { } @Test - void removeAllTest() { - List subs = new ArrayList<>(); + public void removeAllTest() { TbEntityLocalSubsInfo subsInfo = createSubsInfo(); TenantId tenantId = subsInfo.getTenantId(); EntityId entityId = subsInfo.getEntityId(); @@ -136,17 +134,28 @@ class TbEntityLocalSubsInfoTest { .keyStates(Map.of("key3", 3L, "key4", 4L)) .build(); - subs.add(attrSubscription1); - subs.add(attrSubscription2); + TbAttributeSubscription attrSubscription3 = TbAttributeSubscription.builder() + .sessionId("session3") + .tenantId(tenantId) + .entityId(entityId) + .keyStates(Map.of("key5", 5L, "key6", 6L)) + .build(); subsInfo.add(attrSubscription1); subsInfo.add(attrSubscription2); + subsInfo.add(attrSubscription3); + + assertFalse(subsInfo.isEmpty()); + + TbEntitySubEvent updatedEvent = subsInfo.removeAll(List.of(attrSubscription1, attrSubscription2)); + assertNotNull(updatedEvent); + checkEvent(updatedEvent, Set.of(attrSubscription3), ComponentLifecycleEvent.UPDATED); assertFalse(subsInfo.isEmpty()); - TbEntitySubEvent deletedEvent = subsInfo.removeAll(subs); + TbEntitySubEvent deletedEvent = subsInfo.removeAll(List.of(attrSubscription3)); assertNotNull(deletedEvent); - checkEvent(deletedEvent, subs, ComponentLifecycleEvent.DELETED); + checkEvent(deletedEvent, null, ComponentLifecycleEvent.DELETED); assertTrue(subsInfo.isEmpty()); } @@ -155,7 +164,7 @@ class TbEntityLocalSubsInfoTest { return new TbEntityLocalSubsInfo(new TenantId(UUID.randomUUID()), new DeviceId(UUID.randomUUID())); } - private void checkEvent(TbEntitySubEvent event, Collection expectedSubs, ComponentLifecycleEvent expectedType) { + private void checkEvent(TbEntitySubEvent event, Set expectedSubs, ComponentLifecycleEvent expectedType) { assertEquals(expectedType, event.getType()); TbSubscriptionsInfo info = event.getInfo(); if (event.getType() == ComponentLifecycleEvent.DELETED) { @@ -171,7 +180,7 @@ class TbEntityLocalSubsInfoTest { assertEquals(getAttrKeys(expectedSubs), info.attrKeys); } - private Set getAttrKeys(Collection attributeSubscriptions) { + private Set getAttrKeys(Set attributeSubscriptions) { return attributeSubscriptions.stream().map(s -> s.getKeyStates().keySet()).flatMap(Collection::stream).collect(Collectors.toSet()); } } From b81d2ab0c6e73413d0ff1e9691fefc78acd372ef Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 3 Oct 2024 09:27:00 +0300 Subject: [PATCH 10/25] lwm2m: fix bug test sendCollected --- .../lwm2m/client/LwM2MTestClient.java | 6 +- .../lwm2m/client/LwM2mTemperatureSensor.java | 46 ++++---- .../rpc/AbstractRpcLwM2MIntegrationTest.java | 33 ++++-- ...wm2mIntegrationReadCollectedValueTest.java | 103 ++++++++++++++++++ .../rpc/sql/RpcLwm2mIntegrationReadTest.java | 70 +----------- 5 files changed, 158 insertions(+), 100 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 655edc6db6..796f3e09c5 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -137,6 +137,8 @@ public class LwM2MTestClient { private Map clientDtlsCid; private LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest; private LwM2mClientContext clientContext; + private LwM2mTemperatureSensor lwM2mTemperatureSensor12; + public void init(Security security, Security securityBs, int port, boolean isRpc, LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler, LwM2mClientContext clientContext, boolean isWriteAttribute, Integer cIdLength, boolean queueMode, @@ -189,7 +191,7 @@ public class LwM2MTestClient { locationParams.getPos(); initializer.setInstancesForObject(LOCATION, new LwM2mLocation(locationParams.getLatitude(), locationParams.getLongitude(), locationParams.getScaleFactor(), executor, OBJECT_INSTANCE_ID_0)); LwM2mTemperatureSensor lwM2mTemperatureSensor0 = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_0); - LwM2mTemperatureSensor lwM2mTemperatureSensor12 = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_12); + lwM2mTemperatureSensor12 = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_12); initializer.setInstancesForObject(TEMPERATURE_SENSOR, lwM2mTemperatureSensor0, lwM2mTemperatureSensor12); List enablers = initializer.createAll(); @@ -315,7 +317,6 @@ public class LwM2MTestClient { clientDtlsCid = new HashMap<>(); clientStates.add(ON_INIT); leshanClient = builder.build(); - lwM2mTemperatureSensor12.setLeshanClient(leshanClient); LwM2mClientObserver observer = new LwM2mClientObserver() { @Override @@ -452,6 +453,7 @@ public class LwM2MTestClient { if (isStartLw) { this.awaitClientAfterStartConnectLw(); } + lwM2mTemperatureSensor12.setLeshanClient(leshanClient); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java index 4f594ed4c0..35d36749c9 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java @@ -50,14 +50,17 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr private double maxMeasuredValue = currentTemp; private LeshanClient leshanClient; - private int cntRead_5700; private int cntIdentitySystem; protected static final Random RANDOM = new Random(); private static final List supportedResources = Arrays.asList(5601, 5602, 5700, 5701); - public LwM2mTemperatureSensor() { + private LwM2mServer registeredServer; + private ManualDataSender sender; + + private int resourceIdForSendCollected = 5700; + public LwM2mTemperatureSensor() { } public LwM2mTemperatureSensor(ScheduledExecutorService executorService, Integer id) { @@ -72,26 +75,33 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr @Override public synchronized ReadResponse read(LwM2mServer identity, int resourceId) { - log.info("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + log.trace("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + if (this.registeredServer == null) { + try { + Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0 = Instant.now().toEpochMilli(); + this.registeredServer = this.leshanClient.getRegisteredServers().values().iterator().next(); + this.sender = (ManualDataSender) this.leshanClient.getSendService().getDataSender(ManualDataSender.DEFAULT_NAME); + this.sender.collectData(Arrays.asList(getPathForCollectedValue(resourceIdForSendCollected))); + } catch (Exception e) { + log.error("[{}] Sender for SendCollected", e.toString()); + e.printStackTrace(); + } + } switch (resourceId) { case 5601: return ReadResponse.success(resourceId, getTwoDigitValue(minMeasuredValue)); case 5602: return ReadResponse.success(resourceId, getTwoDigitValue(maxMeasuredValue)); case 5700: - if (identity == LwM2mServer.SYSTEM) { // return value for ForCollectedValue + if (identity == LwM2mServer.SYSTEM) { + double val5700 = cntIdentitySystem == 0 ? RESOURCE_ID_3303_12_5700_VALUE_0 : RESOURCE_ID_3303_12_5700_VALUE_1; cntIdentitySystem++; - return ReadResponse.success(resourceId, cntIdentitySystem == 1 ? - RESOURCE_ID_3303_12_5700_VALUE_0 : RESOURCE_ID_3303_12_5700_VALUE_1); - } - cntRead_5700++; - if (cntRead_5700 == 1) { // read value after start - return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp)); + return ReadResponse.success(resourceId, val5700); } else { - if (this.getId() == 12 && this.leshanClient != null) { + if (cntIdentitySystem == 1 && this.getId() == 12 && this.leshanClient != null) { sendCollected(); } - return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp)); + return super.read(identity, resourceId); } case 5701: return ReadResponse.success(resourceId, UNIT_CELSIUS); @@ -163,14 +173,10 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr private void sendCollected() { try { - int resourceId = 5700; - LwM2mServer registeredServer = this.leshanClient.getRegisteredServers().values().iterator().next(); - ManualDataSender sender = this.leshanClient.getSendService().getDataSender(ManualDataSender.DEFAULT_NAME, - ManualDataSender.class); - sender.collectData(Arrays.asList(getPathForCollectedValue(resourceId))); - Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0 = Instant.now().toEpochMilli(); - Thread.sleep(RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS); - sender.collectData(Arrays.asList(getPathForCollectedValue(resourceId))); + if ((Instant.now().toEpochMilli() - Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0) < RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS) { + Thread.sleep(RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS); + } + sender.collectData(Arrays.asList(getPathForCollectedValue(resourceIdForSendCollected))); Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_1 = Instant.now().toEpochMilli(); sender.sendCollectedData(registeredServer, ContentFormat.SENML_JSON, 1000, false); } catch (InterruptedException e) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java index d0b86fdab0..bd3ca0642a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -71,7 +71,7 @@ import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fr public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { protected final LinkParser linkParser = new DefaultLwM2mLinkParser(); - protected String OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC; + protected String CONFIG_PROFILE_WITH_PARAMS_RPC; public Set expectedObjects; public Set expectedObjectIdVers; public Set expectedInstances; @@ -116,10 +116,10 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationWriteCborTest")){ supportFormatOnly_SenMLJSON_SenMLCBOR = true; } - initRpc(); + initRpc(false); } - private void initRpc () throws Exception { + protected void initRpc(boolean isCollected) throws Exception { String endpoint = DEVICE_ENDPOINT_RPC_PREF + endpointSequence.incrementAndGet(); createNewClient(SECURITY_NO_SEC, null, true, endpoint); expectedObjects = ConcurrentHashMap.newKeySet(); @@ -157,15 +157,14 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg id_3_0_9 = fromVersionedIdToObjectId(idVer_3_0_9); idVer_19_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0; - OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC = + String ATTRIBUTES_TELEMETRY_WITH_PARAMS_RPC_WITH_OBSERVE = " {\n" + " \"keyName\": {\n" + " \"" + idVer_3_0_9 + "\": \"" + RESOURCE_ID_NAME_3_9 + "\",\n" + " \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\": \"" + RESOURCE_ID_NAME_3_14 + "\",\n" + " \"" + idVer_19_0_0 + "\": \"" + RESOURCE_ID_NAME_19_0_0 + "\",\n" + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\": \"" + RESOURCE_ID_NAME_19_1_0 + "\",\n" + - " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2 + "\": \"" + RESOURCE_ID_NAME_19_0_2 + "\",\n" + - " \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\": \"" + RESOURCE_ID_NAME_3303_12_5700 + "\"\n" + + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2 + "\": \"" + RESOURCE_ID_NAME_19_0_2 + "\"\n" + " },\n" + " \"observe\": [\n" + " \"" + idVer_3_0_9 + "\",\n" + @@ -180,13 +179,29 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg " \"telemetry\": [\n" + " \"" + idVer_3_0_9 + "\",\n" + " \"" + idVer_19_0_0 + "\",\n" + - " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\",\n" + - " \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\"\n" + + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\"\n" + " ],\n" + " \"attributeLwm2m\": {}\n" + " }"; - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC, getBootstrapServerCredentialsNoSec(NONE)); + String TELEMETRY_WITH_PARAMS_RPC_WITHOUT_OBSERVE = + " {\n" + + " \"keyName\": {\n" + + " \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\": \"" + RESOURCE_ID_NAME_3303_12_5700 + "\"\n" + + " },\n" + + " \"observe\": [\n" + + " ],\n" + + " \"attribute\": [\n" + + " ],\n" + + " \"telemetry\": [\n" + + " \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\"\n" + + " ],\n" + + " \"attributeLwm2m\": {}\n" + + " }" ; + + CONFIG_PROFILE_WITH_PARAMS_RPC = isCollected ? TELEMETRY_WITH_PARAMS_RPC_WITHOUT_OBSERVE : ATTRIBUTES_TELEMETRY_WITH_PARAMS_RPC_WITH_OBSERVE; + + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(CONFIG_PROFILE_WITH_PARAMS_RPC, getBootstrapServerCredentialsNoSec(NONE)); createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(endpoint)); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java new file mode 100644 index 0000000000..d76f2f5400 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java @@ -0,0 +1,103 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc.sql; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; +import java.util.concurrent.atomic.AtomicReference; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.awaitility.Awaitility.await; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_1; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_1; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3303_12_5700; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS; + +@Slf4j +public class RpcLwm2mIntegrationReadCollectedValueTest extends AbstractRpcLwM2MIntegrationTest { + + @Before + public void startInitRPC() throws Exception { + initRpc(true); + } + + /** + * Read {"id":"/3303/12/5700"} + * Trigger a Send operation from the client with multiple values for the same resource as a payload + * acked "[{"bn":"/3303/12/5700","bt":1724".. 116 bytes] + * 2 values for the resource /3303/12/5700 should be stored with: + * - timestamps1 = Instance.now() + RESOURCE_ID_VALUE_3303_12_5700_1 + * - timestamps2 = (timestamps1 + 3 sec) + RESOURCE_ID_VALUE_3303_12_5700_2 + * @throws Exception + */ + @Test + public void testReadSingleResource_sendFromClient_CollectedValue() throws Exception { + // init test + int cntValues = 2; + int resourceId = 5700; + String expectedIdVer = objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + resourceId; + sendRPCById(expectedIdVer); + + // verify time start/end send CollectedValue; + await().atMost(40, SECONDS).until(() -> RESOURCE_ID_3303_12_5700_TS_0 > 0 + && RESOURCE_ID_3303_12_5700_TS_1 > 0); + + // verify result read: verify count value: 1-2: send CollectedValue; + AtomicReference actualValues = new AtomicReference<>(); + await().atMost(40, SECONDS).until(() -> { + actualValues.set(doGetAsync( + "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" + + RESOURCE_ID_NAME_3303_12_5700 + + "&startTs=" + (RESOURCE_ID_3303_12_5700_TS_0 - RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS) + + "&endTs=" + (RESOURCE_ID_3303_12_5700_TS_1 + RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS) + + "&interval=0&limit=100&useStrictDataTypes=false", + ObjectNode.class)); + return actualValues.get() != null && actualValues.get().size() > 0 + && actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700).size() >= cntValues && verifyTs(actualValues); + }); + } + + private boolean verifyTs(AtomicReference actualValues) { + String expectedVal_0 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_0); + String expectedVal_1 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_1); + ArrayNode actual = (ArrayNode) actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700); + long actualTS0 = 0; + long actualTS1 = 0; + for (JsonNode tsNode : actual) { + if (tsNode.get("value").asText().equals(expectedVal_0)) { + actualTS0 = tsNode.get("ts").asLong(); + } else if (tsNode.get("value").asText().equals(expectedVal_1)) { + actualTS1 = tsNode.get("ts").asLong(); + } + } + return actualTS0 >= RESOURCE_ID_3303_12_5700_TS_0 + && actualTS1 <= RESOURCE_ID_3303_12_5700_TS_1 + && (actualTS1 - actualTS0) >= RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS; + } + + private String sendRPCById(String path) throws Exception { + String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java index 1ab4893ec4..b8835d427d 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java @@ -15,23 +15,15 @@ */ package org.thingsboard.server.transport.lwm2m.rpc.sql; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.map.HashedMap; import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.node.LwM2mPath; +import org.junit.Before; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; -import java.time.Instant; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; - -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.awaitility.Awaitility.await; import static org.eclipse.leshan.core.LwM2mId.SERVER; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -39,24 +31,17 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_11; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_3; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3303_12_5700; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_0; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_1; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS; @Slf4j public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest { @@ -228,59 +213,6 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest assertTrue(actualValues.contains(expected19_1_0)); } - - /** - * Read {"id":"/3303/12/5700"} - * Trigger a Send operation from the client with multiple values for the same resource as a payload - * acked "[{"bn":"/3303/12/5700","bt":1724".. 116 bytes] - * 2 values for the resource /3303/12/5700 should be stored with: - * - timestamps1 = Instance.now() + RESOURCE_ID_VALUE_3303_12_5700_1 - * - timestamps2 = (timestamps1 + 3 sec) + RESOURCE_ID_VALUE_3303_12_5700_2 - * @throws Exception - */ - @Test - public void testReadSingleResource_sendFromClient_CollectedValue() throws Exception { - // init test - long startTs = Instant.now().toEpochMilli(); - int cntValues = 4; - int resourceId = 5700; - String expectedIdVer = objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + resourceId; - sendRPCById(expectedIdVer); - // verify result read: verify count value: 1-2: send CollectedValue; 3 - response for read; - long endTs = Instant.now().toEpochMilli() + RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS * 4; - String expectedVal_1 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_0); - String expectedVal_2 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_1); - AtomicReference actualValues = new AtomicReference<>(); - await().atMost(40, SECONDS).until(() -> { - actualValues.set(doGetAsync( - "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" - + RESOURCE_ID_NAME_3303_12_5700 - + "&startTs=" + startTs - + "&endTs=" + endTs - + "&interval=0&limit=100&useStrictDataTypes=false", - ObjectNode.class)); - // verify cntValues - return actualValues.get() != null && actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700).size() == cntValues; - }); - // verify ts - ArrayNode actual = (ArrayNode) actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700); - Map keyTsMaps = new HashedMap(); - for (JsonNode tsNode: actual) { - if (tsNode.get("value").asText().equals(expectedVal_1) || tsNode.get("value").asText().equals(expectedVal_2)) { - keyTsMaps.put(tsNode.get("value").asText(), tsNode.get("ts").asLong()); - } - } - assertTrue(keyTsMaps.size() == 2); - long actualTS0 = keyTsMaps.get(expectedVal_1).longValue(); - long actualTS1 = keyTsMaps.get(expectedVal_2).longValue(); - assertTrue(actualTS0 > 0); - assertTrue(actualTS1 > 0); - assertTrue(actualTS1 > actualTS0); - assertTrue((actualTS1 - actualTS0) >= RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS); - assertTrue(actualTS0 <= RESOURCE_ID_3303_12_5700_TS_0); - assertTrue(actualTS1 <= RESOURCE_ID_3303_12_5700_TS_1); - } - /** * ReadComposite {"keys":["batteryLevel", "UtfOffset", "dataDescription"]} */ From 13c49d16899814b4285589f43c56e4dec1f5da96 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Thu, 3 Oct 2024 13:14:30 +0300 Subject: [PATCH 11/25] Get rid of "userCredentialsEnabled" in user's additional info --- .../main/data/upgrade/3.8.0/schema_update.sql | 3 ++- .../server/controller/BaseController.java | 19 +++++++++++-------- .../edge/EdgeEventSourcingListener.java | 1 - .../server/controller/AuthControllerTest.java | 2 +- .../thingsboard/server/edge/UserEdgeTest.java | 16 ++++++++-------- .../server/dao/user/UserServiceImpl.java | 12 ++---------- 6 files changed, 24 insertions(+), 29 deletions(-) diff --git a/application/src/main/data/upgrade/3.8.0/schema_update.sql b/application/src/main/data/upgrade/3.8.0/schema_update.sql index 240daa18d5..1084dd374f 100644 --- a/application/src/main/data/upgrade/3.8.0/schema_update.sql +++ b/application/src/main/data/upgrade/3.8.0/schema_update.sql @@ -22,4 +22,5 @@ ALTER TABLE user_credentials ADD COLUMN IF NOT EXISTS failed_login_attempts INT; UPDATE user_credentials c SET failed_login_attempts = (SELECT (additional_info::json ->> 'failedLoginAttempts')::int FROM tb_user u WHERE u.id = c.user_id) WHERE failed_login_attempts IS NULL; -UPDATE tb_user SET additional_info = (additional_info::jsonb - 'lastLoginTs' - 'failedLoginAttempts')::text WHERE additional_info IS NOT NULL AND additional_info != 'null'; +UPDATE tb_user SET additional_info = (additional_info::jsonb - 'lastLoginTs' - 'failedLoginAttempts' - 'userCredentialsEnabled')::text + WHERE additional_info IS NOT NULL AND additional_info != 'null'; diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 70565d975b..387b39707b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -38,6 +38,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.context.request.async.AsyncRequestTimeoutException; import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.common.util.DonAsynchron; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; @@ -193,7 +194,6 @@ import static org.thingsboard.server.controller.ControllerConstants.DEFAULT_DASH import static org.thingsboard.server.controller.ControllerConstants.HOME_DASHBOARD; import static org.thingsboard.server.controller.UserController.YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION; import static org.thingsboard.server.dao.service.Validator.validateId; -import static org.thingsboard.server.dao.user.UserServiceImpl.LAST_LOGIN_TS; @TbCoreComponent public abstract class BaseController { @@ -878,15 +878,18 @@ public abstract class BaseController { } protected void checkUserInfo(User user) throws ThingsboardException { + ObjectNode info; if (user.getAdditionalInfo() instanceof ObjectNode additionalInfo) { - checkDashboardInfo(additionalInfo); - - UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); - if (userCredentials.isEnabled() && !additionalInfo.has("userCredentialsEnabled")) { - additionalInfo.put("userCredentialsEnabled", true); - } - additionalInfo.put(LAST_LOGIN_TS, userCredentials.getLastLoginTs()); + info = additionalInfo; + checkDashboardInfo(info); + } else { + info = JacksonUtil.newObjectNode(); + user.setAdditionalInfo(info); } + + UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); + info.put("userCredentialsEnabled", userCredentials.isEnabled()); + info.put("lastLoginTs", userCredentials.getLastLoginTs()); } protected void checkDashboardInfo(JsonNode additionalInfo) throws ThingsboardException { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java index 907872d503..893bd13171 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeEventSourcingListener.java @@ -225,7 +225,6 @@ public class EdgeEventSourcingListener { } private void cleanUpUserAdditionalInfo(User user) { - // reset FAILED_LOGIN_ATTEMPTS and LAST_LOGIN_TS - edge is not interested in this information if (user.getAdditionalInfo() instanceof NullNode) { user.setAdditionalInfo(null); } diff --git a/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java index 62b8188cfd..73182587fb 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AuthControllerTest.java @@ -81,8 +81,8 @@ public class AuthControllerTest extends AbstractControllerTest { user = getCurrentUser(); assertThat(user.getAuthority()).isEqualTo(Authority.CUSTOMER_USER); assertThat(user.getEmail()).isEqualTo(CUSTOMER_USER_EMAIL); - assertThat(user.getAdditionalInfo().get("userCredentialsEnabled").asBoolean()).isTrue(); user = getUser(customerUserId); + assertThat(user.getAdditionalInfo().get("userCredentialsEnabled").asBoolean()).isTrue(); assertThat(user.getAdditionalInfo().get("lastLoginTs").asLong()).isCloseTo(System.currentTimeMillis(), within(10000L)); } diff --git a/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java index 1c3b5ccb7a..82d202533f 100644 --- a/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java @@ -47,7 +47,7 @@ public class UserEdgeTest extends AbstractEdgeTest { @Test public void testCreateUpdateDeleteTenantUser() throws Exception { // create user - edgeImitator.expectMessageAmount(6); + edgeImitator.expectMessageAmount(4); User newTenantAdmin = new User(); newTenantAdmin.setAuthority(Authority.TENANT_ADMIN); newTenantAdmin.setTenantId(tenantId); @@ -55,9 +55,9 @@ public class UserEdgeTest extends AbstractEdgeTest { newTenantAdmin.setFirstName("Boris"); newTenantAdmin.setLastName("Johnson"); User savedTenantAdmin = createUser(newTenantAdmin, "tenant"); - Assert.assertTrue(edgeImitator.waitForMessages()); // wait 6 messages - x2 user update msg and x4 user credentials update msgs (create + authenticate user) - Assert.assertEquals(2, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size()); - Assert.assertEquals(4, edgeImitator.findAllMessagesByType(UserCredentialsUpdateMsg.class).size()); + Assert.assertTrue(edgeImitator.waitForMessages()); // wait 4 messages - x1 user update msg and x3 user credentials update msgs (create + authenticate user) + Assert.assertEquals(1, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size()); + Assert.assertEquals(3, edgeImitator.findAllMessagesByType(UserCredentialsUpdateMsg.class).size()); Optional userUpdateMsgOpt = edgeImitator.findMessageByType(UserUpdateMsg.class); Assert.assertTrue(userUpdateMsgOpt.isPresent()); UserUpdateMsg userUpdateMsg = userUpdateMsgOpt.get(); @@ -133,7 +133,7 @@ public class UserEdgeTest extends AbstractEdgeTest { Assert.assertTrue(edgeImitator.waitForMessages()); // create user - edgeImitator.expectMessageAmount(6); + edgeImitator.expectMessageAmount(4); User customerUser = new User(); customerUser.setAuthority(Authority.CUSTOMER_USER); customerUser.setTenantId(tenantId); @@ -142,9 +142,9 @@ public class UserEdgeTest extends AbstractEdgeTest { customerUser.setFirstName("John"); customerUser.setLastName("Edwards"); User savedCustomerUser = createUser(customerUser, "customer"); - Assert.assertTrue(edgeImitator.waitForMessages()); // wait 6 messages - x2 user update msg and x4 user credentials update msgs (create + authenticate user) - Assert.assertEquals(2, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size()); - Assert.assertEquals(4, edgeImitator.findAllMessagesByType(UserCredentialsUpdateMsg.class).size()); + Assert.assertTrue(edgeImitator.waitForMessages()); // wait 4 messages - x1 user update msg and x3 user credentials update msgs (create + authenticate user) + Assert.assertEquals(1, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size()); + Assert.assertEquals(3, edgeImitator.findAllMessagesByType(UserCredentialsUpdateMsg.class).size()); Optional userUpdateMsgOpt = edgeImitator.findMessageByType(UserUpdateMsg.class); Assert.assertTrue(userUpdateMsgOpt.isPresent()); UserUpdateMsg userUpdateMsg = userUpdateMsgOpt.get(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index c927c3e465..dc758b6b5d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -17,7 +17,6 @@ package org.thingsboard.server.dao.user; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.BooleanNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.ListenableFuture; import lombok.RequiredArgsConstructor; @@ -84,8 +83,6 @@ import static org.thingsboard.server.dao.service.Validator.validateString; public class UserServiceImpl extends AbstractCachedEntityService implements UserService { public static final String USER_PASSWORD_HISTORY = "userPasswordHistory"; - public static final String USER_CREDENTIALS_ENABLED = "userCredentialsEnabled"; - public static final String LAST_LOGIN_TS = "lastLoginTs"; private static final int DEFAULT_TOKEN_LENGTH = 30; public static final String INCORRECT_USER_ID = "Incorrect userId "; @@ -430,17 +427,12 @@ public class UserServiceImpl extends AbstractCachedEntityService INCORRECT_USER_ID + id); UserCredentials userCredentials = userCredentialsDao.findByUserId(tenantId, userId.getId()); userCredentials.setEnabled(enabled); - saveUserCredentials(tenantId, userCredentials); - - User user = findUserById(tenantId, userId); - user.setAdditionalInfoField(USER_CREDENTIALS_ENABLED, BooleanNode.valueOf(enabled)); - saveUser(tenantId, user); if (enabled) { - resetFailedLoginAttempts(tenantId, userId); + userCredentials.setFailedLoginAttempts(0); } + saveUserCredentials(tenantId, userCredentials); } - @Override public void resetFailedLoginAttempts(TenantId tenantId, UserId userId) { log.trace("Executing resetFailedLoginAttempts [{}]", userId); From 1f8c521ba41fe80fe9a804a687bdf35f4767e076 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Thu, 3 Oct 2024 13:35:43 +0300 Subject: [PATCH 12/25] Update UserEdgeTest --- .../org/thingsboard/server/edge/UserEdgeTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java index 82d202533f..52204f8fde 100644 --- a/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java @@ -47,7 +47,7 @@ public class UserEdgeTest extends AbstractEdgeTest { @Test public void testCreateUpdateDeleteTenantUser() throws Exception { // create user - edgeImitator.expectMessageAmount(4); + edgeImitator.expectMessageAmount(3); User newTenantAdmin = new User(); newTenantAdmin.setAuthority(Authority.TENANT_ADMIN); newTenantAdmin.setTenantId(tenantId); @@ -55,9 +55,9 @@ public class UserEdgeTest extends AbstractEdgeTest { newTenantAdmin.setFirstName("Boris"); newTenantAdmin.setLastName("Johnson"); User savedTenantAdmin = createUser(newTenantAdmin, "tenant"); - Assert.assertTrue(edgeImitator.waitForMessages()); // wait 4 messages - x1 user update msg and x3 user credentials update msgs (create + authenticate user) + Assert.assertTrue(edgeImitator.waitForMessages()); // wait 3 messages - x1 user update msg and x2 user credentials update msgs (create + authenticate user) Assert.assertEquals(1, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size()); - Assert.assertEquals(3, edgeImitator.findAllMessagesByType(UserCredentialsUpdateMsg.class).size()); + Assert.assertEquals(2, edgeImitator.findAllMessagesByType(UserCredentialsUpdateMsg.class).size()); Optional userUpdateMsgOpt = edgeImitator.findMessageByType(UserUpdateMsg.class); Assert.assertTrue(userUpdateMsgOpt.isPresent()); UserUpdateMsg userUpdateMsg = userUpdateMsgOpt.get(); @@ -133,7 +133,7 @@ public class UserEdgeTest extends AbstractEdgeTest { Assert.assertTrue(edgeImitator.waitForMessages()); // create user - edgeImitator.expectMessageAmount(4); + edgeImitator.expectMessageAmount(3); User customerUser = new User(); customerUser.setAuthority(Authority.CUSTOMER_USER); customerUser.setTenantId(tenantId); @@ -142,9 +142,9 @@ public class UserEdgeTest extends AbstractEdgeTest { customerUser.setFirstName("John"); customerUser.setLastName("Edwards"); User savedCustomerUser = createUser(customerUser, "customer"); - Assert.assertTrue(edgeImitator.waitForMessages()); // wait 4 messages - x1 user update msg and x3 user credentials update msgs (create + authenticate user) + Assert.assertTrue(edgeImitator.waitForMessages()); // wait 3 messages - x1 user update msg and x2 user credentials update msgs (create + authenticate user) Assert.assertEquals(1, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size()); - Assert.assertEquals(3, edgeImitator.findAllMessagesByType(UserCredentialsUpdateMsg.class).size()); + Assert.assertEquals(2, edgeImitator.findAllMessagesByType(UserCredentialsUpdateMsg.class).size()); Optional userUpdateMsgOpt = edgeImitator.findMessageByType(UserUpdateMsg.class); Assert.assertTrue(userUpdateMsgOpt.isPresent()); UserUpdateMsg userUpdateMsg = userUpdateMsgOpt.get(); From f8ac65754f352dcac558996db8daa6ac27cc7b05 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Fri, 4 Oct 2024 12:06:54 +0300 Subject: [PATCH 13/25] Fix UserControllerTest --- .../org/thingsboard/server/controller/UserControllerTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java index 17085f3ca9..c42ecc4545 100644 --- a/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java @@ -113,6 +113,7 @@ public class UserControllerTest extends AbstractControllerTest { Assert.assertEquals(email, savedUser.getEmail()); User foundUser = doGet("/api/user/" + savedUser.getId().getId().toString(), User.class); + foundUser.setAdditionalInfo(savedUser.getAdditionalInfo()); Assert.assertEquals(foundUser, savedUser); testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(foundUser, foundUser, @@ -265,6 +266,7 @@ public class UserControllerTest extends AbstractControllerTest { User savedUser = doPost("/api/user", user, User.class); User foundUser = doGet("/api/user/" + savedUser.getId().getId().toString(), User.class); Assert.assertNotNull(foundUser); + foundUser.setAdditionalInfo(savedUser.getAdditionalInfo()); Assert.assertEquals(savedUser, foundUser); } From 4b53e81dafcfb554e883c7c635a46b255c6bfaae Mon Sep 17 00:00:00 2001 From: nick Date: Thu, 3 Oct 2024 09:27:00 +0300 Subject: [PATCH 14/25] lwm2m: fix bug test sendCollected --- .../lwm2m/client/LwM2MTestClient.java | 6 +- .../lwm2m/client/LwM2mTemperatureSensor.java | 46 ++++---- .../rpc/AbstractRpcLwM2MIntegrationTest.java | 33 ++++-- ...wm2mIntegrationReadCollectedValueTest.java | 103 ++++++++++++++++++ .../rpc/sql/RpcLwm2mIntegrationReadTest.java | 70 +----------- 5 files changed, 158 insertions(+), 100 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 655edc6db6..796f3e09c5 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -137,6 +137,8 @@ public class LwM2MTestClient { private Map clientDtlsCid; private LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest; private LwM2mClientContext clientContext; + private LwM2mTemperatureSensor lwM2mTemperatureSensor12; + public void init(Security security, Security securityBs, int port, boolean isRpc, LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler, LwM2mClientContext clientContext, boolean isWriteAttribute, Integer cIdLength, boolean queueMode, @@ -189,7 +191,7 @@ public class LwM2MTestClient { locationParams.getPos(); initializer.setInstancesForObject(LOCATION, new LwM2mLocation(locationParams.getLatitude(), locationParams.getLongitude(), locationParams.getScaleFactor(), executor, OBJECT_INSTANCE_ID_0)); LwM2mTemperatureSensor lwM2mTemperatureSensor0 = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_0); - LwM2mTemperatureSensor lwM2mTemperatureSensor12 = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_12); + lwM2mTemperatureSensor12 = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_12); initializer.setInstancesForObject(TEMPERATURE_SENSOR, lwM2mTemperatureSensor0, lwM2mTemperatureSensor12); List enablers = initializer.createAll(); @@ -315,7 +317,6 @@ public class LwM2MTestClient { clientDtlsCid = new HashMap<>(); clientStates.add(ON_INIT); leshanClient = builder.build(); - lwM2mTemperatureSensor12.setLeshanClient(leshanClient); LwM2mClientObserver observer = new LwM2mClientObserver() { @Override @@ -452,6 +453,7 @@ public class LwM2MTestClient { if (isStartLw) { this.awaitClientAfterStartConnectLw(); } + lwM2mTemperatureSensor12.setLeshanClient(leshanClient); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java index 4f594ed4c0..35d36749c9 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java @@ -50,14 +50,17 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr private double maxMeasuredValue = currentTemp; private LeshanClient leshanClient; - private int cntRead_5700; private int cntIdentitySystem; protected static final Random RANDOM = new Random(); private static final List supportedResources = Arrays.asList(5601, 5602, 5700, 5701); - public LwM2mTemperatureSensor() { + private LwM2mServer registeredServer; + private ManualDataSender sender; + + private int resourceIdForSendCollected = 5700; + public LwM2mTemperatureSensor() { } public LwM2mTemperatureSensor(ScheduledExecutorService executorService, Integer id) { @@ -72,26 +75,33 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr @Override public synchronized ReadResponse read(LwM2mServer identity, int resourceId) { - log.info("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + log.trace("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + if (this.registeredServer == null) { + try { + Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0 = Instant.now().toEpochMilli(); + this.registeredServer = this.leshanClient.getRegisteredServers().values().iterator().next(); + this.sender = (ManualDataSender) this.leshanClient.getSendService().getDataSender(ManualDataSender.DEFAULT_NAME); + this.sender.collectData(Arrays.asList(getPathForCollectedValue(resourceIdForSendCollected))); + } catch (Exception e) { + log.error("[{}] Sender for SendCollected", e.toString()); + e.printStackTrace(); + } + } switch (resourceId) { case 5601: return ReadResponse.success(resourceId, getTwoDigitValue(minMeasuredValue)); case 5602: return ReadResponse.success(resourceId, getTwoDigitValue(maxMeasuredValue)); case 5700: - if (identity == LwM2mServer.SYSTEM) { // return value for ForCollectedValue + if (identity == LwM2mServer.SYSTEM) { + double val5700 = cntIdentitySystem == 0 ? RESOURCE_ID_3303_12_5700_VALUE_0 : RESOURCE_ID_3303_12_5700_VALUE_1; cntIdentitySystem++; - return ReadResponse.success(resourceId, cntIdentitySystem == 1 ? - RESOURCE_ID_3303_12_5700_VALUE_0 : RESOURCE_ID_3303_12_5700_VALUE_1); - } - cntRead_5700++; - if (cntRead_5700 == 1) { // read value after start - return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp)); + return ReadResponse.success(resourceId, val5700); } else { - if (this.getId() == 12 && this.leshanClient != null) { + if (cntIdentitySystem == 1 && this.getId() == 12 && this.leshanClient != null) { sendCollected(); } - return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp)); + return super.read(identity, resourceId); } case 5701: return ReadResponse.success(resourceId, UNIT_CELSIUS); @@ -163,14 +173,10 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr private void sendCollected() { try { - int resourceId = 5700; - LwM2mServer registeredServer = this.leshanClient.getRegisteredServers().values().iterator().next(); - ManualDataSender sender = this.leshanClient.getSendService().getDataSender(ManualDataSender.DEFAULT_NAME, - ManualDataSender.class); - sender.collectData(Arrays.asList(getPathForCollectedValue(resourceId))); - Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0 = Instant.now().toEpochMilli(); - Thread.sleep(RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS); - sender.collectData(Arrays.asList(getPathForCollectedValue(resourceId))); + if ((Instant.now().toEpochMilli() - Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0) < RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS) { + Thread.sleep(RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS); + } + sender.collectData(Arrays.asList(getPathForCollectedValue(resourceIdForSendCollected))); Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_1 = Instant.now().toEpochMilli(); sender.sendCollectedData(registeredServer, ContentFormat.SENML_JSON, 1000, false); } catch (InterruptedException e) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java index d0b86fdab0..bd3ca0642a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -71,7 +71,7 @@ import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fr public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { protected final LinkParser linkParser = new DefaultLwM2mLinkParser(); - protected String OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC; + protected String CONFIG_PROFILE_WITH_PARAMS_RPC; public Set expectedObjects; public Set expectedObjectIdVers; public Set expectedInstances; @@ -116,10 +116,10 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationWriteCborTest")){ supportFormatOnly_SenMLJSON_SenMLCBOR = true; } - initRpc(); + initRpc(false); } - private void initRpc () throws Exception { + protected void initRpc(boolean isCollected) throws Exception { String endpoint = DEVICE_ENDPOINT_RPC_PREF + endpointSequence.incrementAndGet(); createNewClient(SECURITY_NO_SEC, null, true, endpoint); expectedObjects = ConcurrentHashMap.newKeySet(); @@ -157,15 +157,14 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg id_3_0_9 = fromVersionedIdToObjectId(idVer_3_0_9); idVer_19_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0; - OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC = + String ATTRIBUTES_TELEMETRY_WITH_PARAMS_RPC_WITH_OBSERVE = " {\n" + " \"keyName\": {\n" + " \"" + idVer_3_0_9 + "\": \"" + RESOURCE_ID_NAME_3_9 + "\",\n" + " \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\": \"" + RESOURCE_ID_NAME_3_14 + "\",\n" + " \"" + idVer_19_0_0 + "\": \"" + RESOURCE_ID_NAME_19_0_0 + "\",\n" + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\": \"" + RESOURCE_ID_NAME_19_1_0 + "\",\n" + - " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2 + "\": \"" + RESOURCE_ID_NAME_19_0_2 + "\",\n" + - " \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\": \"" + RESOURCE_ID_NAME_3303_12_5700 + "\"\n" + + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2 + "\": \"" + RESOURCE_ID_NAME_19_0_2 + "\"\n" + " },\n" + " \"observe\": [\n" + " \"" + idVer_3_0_9 + "\",\n" + @@ -180,13 +179,29 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg " \"telemetry\": [\n" + " \"" + idVer_3_0_9 + "\",\n" + " \"" + idVer_19_0_0 + "\",\n" + - " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\",\n" + - " \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\"\n" + + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\"\n" + " ],\n" + " \"attributeLwm2m\": {}\n" + " }"; - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC, getBootstrapServerCredentialsNoSec(NONE)); + String TELEMETRY_WITH_PARAMS_RPC_WITHOUT_OBSERVE = + " {\n" + + " \"keyName\": {\n" + + " \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\": \"" + RESOURCE_ID_NAME_3303_12_5700 + "\"\n" + + " },\n" + + " \"observe\": [\n" + + " ],\n" + + " \"attribute\": [\n" + + " ],\n" + + " \"telemetry\": [\n" + + " \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\"\n" + + " ],\n" + + " \"attributeLwm2m\": {}\n" + + " }" ; + + CONFIG_PROFILE_WITH_PARAMS_RPC = isCollected ? TELEMETRY_WITH_PARAMS_RPC_WITHOUT_OBSERVE : ATTRIBUTES_TELEMETRY_WITH_PARAMS_RPC_WITH_OBSERVE; + + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(CONFIG_PROFILE_WITH_PARAMS_RPC, getBootstrapServerCredentialsNoSec(NONE)); createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(endpoint)); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java new file mode 100644 index 0000000000..d76f2f5400 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java @@ -0,0 +1,103 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc.sql; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; +import java.util.concurrent.atomic.AtomicReference; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.awaitility.Awaitility.await; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_1; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_1; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3303_12_5700; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS; + +@Slf4j +public class RpcLwm2mIntegrationReadCollectedValueTest extends AbstractRpcLwM2MIntegrationTest { + + @Before + public void startInitRPC() throws Exception { + initRpc(true); + } + + /** + * Read {"id":"/3303/12/5700"} + * Trigger a Send operation from the client with multiple values for the same resource as a payload + * acked "[{"bn":"/3303/12/5700","bt":1724".. 116 bytes] + * 2 values for the resource /3303/12/5700 should be stored with: + * - timestamps1 = Instance.now() + RESOURCE_ID_VALUE_3303_12_5700_1 + * - timestamps2 = (timestamps1 + 3 sec) + RESOURCE_ID_VALUE_3303_12_5700_2 + * @throws Exception + */ + @Test + public void testReadSingleResource_sendFromClient_CollectedValue() throws Exception { + // init test + int cntValues = 2; + int resourceId = 5700; + String expectedIdVer = objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + resourceId; + sendRPCById(expectedIdVer); + + // verify time start/end send CollectedValue; + await().atMost(40, SECONDS).until(() -> RESOURCE_ID_3303_12_5700_TS_0 > 0 + && RESOURCE_ID_3303_12_5700_TS_1 > 0); + + // verify result read: verify count value: 1-2: send CollectedValue; + AtomicReference actualValues = new AtomicReference<>(); + await().atMost(40, SECONDS).until(() -> { + actualValues.set(doGetAsync( + "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" + + RESOURCE_ID_NAME_3303_12_5700 + + "&startTs=" + (RESOURCE_ID_3303_12_5700_TS_0 - RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS) + + "&endTs=" + (RESOURCE_ID_3303_12_5700_TS_1 + RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS) + + "&interval=0&limit=100&useStrictDataTypes=false", + ObjectNode.class)); + return actualValues.get() != null && actualValues.get().size() > 0 + && actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700).size() >= cntValues && verifyTs(actualValues); + }); + } + + private boolean verifyTs(AtomicReference actualValues) { + String expectedVal_0 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_0); + String expectedVal_1 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_1); + ArrayNode actual = (ArrayNode) actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700); + long actualTS0 = 0; + long actualTS1 = 0; + for (JsonNode tsNode : actual) { + if (tsNode.get("value").asText().equals(expectedVal_0)) { + actualTS0 = tsNode.get("ts").asLong(); + } else if (tsNode.get("value").asText().equals(expectedVal_1)) { + actualTS1 = tsNode.get("ts").asLong(); + } + } + return actualTS0 >= RESOURCE_ID_3303_12_5700_TS_0 + && actualTS1 <= RESOURCE_ID_3303_12_5700_TS_1 + && (actualTS1 - actualTS0) >= RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS; + } + + private String sendRPCById(String path) throws Exception { + String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java index 1ab4893ec4..b8835d427d 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java @@ -15,23 +15,15 @@ */ package org.thingsboard.server.transport.lwm2m.rpc.sql; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.map.HashedMap; import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.node.LwM2mPath; +import org.junit.Before; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; -import java.time.Instant; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; - -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.awaitility.Awaitility.await; import static org.eclipse.leshan.core.LwM2mId.SERVER; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -39,24 +31,17 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_11; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_3; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3303_12_5700; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_0; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_1; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS; @Slf4j public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest { @@ -228,59 +213,6 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest assertTrue(actualValues.contains(expected19_1_0)); } - - /** - * Read {"id":"/3303/12/5700"} - * Trigger a Send operation from the client with multiple values for the same resource as a payload - * acked "[{"bn":"/3303/12/5700","bt":1724".. 116 bytes] - * 2 values for the resource /3303/12/5700 should be stored with: - * - timestamps1 = Instance.now() + RESOURCE_ID_VALUE_3303_12_5700_1 - * - timestamps2 = (timestamps1 + 3 sec) + RESOURCE_ID_VALUE_3303_12_5700_2 - * @throws Exception - */ - @Test - public void testReadSingleResource_sendFromClient_CollectedValue() throws Exception { - // init test - long startTs = Instant.now().toEpochMilli(); - int cntValues = 4; - int resourceId = 5700; - String expectedIdVer = objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + resourceId; - sendRPCById(expectedIdVer); - // verify result read: verify count value: 1-2: send CollectedValue; 3 - response for read; - long endTs = Instant.now().toEpochMilli() + RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS * 4; - String expectedVal_1 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_0); - String expectedVal_2 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_1); - AtomicReference actualValues = new AtomicReference<>(); - await().atMost(40, SECONDS).until(() -> { - actualValues.set(doGetAsync( - "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" - + RESOURCE_ID_NAME_3303_12_5700 - + "&startTs=" + startTs - + "&endTs=" + endTs - + "&interval=0&limit=100&useStrictDataTypes=false", - ObjectNode.class)); - // verify cntValues - return actualValues.get() != null && actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700).size() == cntValues; - }); - // verify ts - ArrayNode actual = (ArrayNode) actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700); - Map keyTsMaps = new HashedMap(); - for (JsonNode tsNode: actual) { - if (tsNode.get("value").asText().equals(expectedVal_1) || tsNode.get("value").asText().equals(expectedVal_2)) { - keyTsMaps.put(tsNode.get("value").asText(), tsNode.get("ts").asLong()); - } - } - assertTrue(keyTsMaps.size() == 2); - long actualTS0 = keyTsMaps.get(expectedVal_1).longValue(); - long actualTS1 = keyTsMaps.get(expectedVal_2).longValue(); - assertTrue(actualTS0 > 0); - assertTrue(actualTS1 > 0); - assertTrue(actualTS1 > actualTS0); - assertTrue((actualTS1 - actualTS0) >= RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS); - assertTrue(actualTS0 <= RESOURCE_ID_3303_12_5700_TS_0); - assertTrue(actualTS1 <= RESOURCE_ID_3303_12_5700_TS_1); - } - /** * ReadComposite {"keys":["batteryLevel", "UtfOffset", "dataDescription"]} */ From 8f3abdf1397e3ba4780200d5798325c608398c9f Mon Sep 17 00:00:00 2001 From: nick Date: Sun, 6 Oct 2024 22:04:26 +0300 Subject: [PATCH 15/25] lwm2m: update tests lwm2mAttributes --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 3 +- .../lwm2m/client/LwM2MTestClient.java | 4 +- .../lwm2m/client/SimpleLwM2MDevice.java | 73 +- .../lwm2m/client/TbLwm2mObjectEnabler.java | 729 ------------------ .../lwm2m/client/TbObjectsInitializer.java | 71 -- .../rpc/AbstractRpcLwM2MIntegrationTest.java | 3 - .../sql/RpcLwm2mIntegrationDiscoverTest.java | 12 + ...ntegrationDiscoverWriteAttributesTest.java | 175 ++--- 8 files changed, 120 insertions(+), 950 deletions(-) delete mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java delete mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbObjectsInitializer.java diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index af5be0dc77..1acf5165fd 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -177,7 +177,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte protected LwM2MTestClient lwM2MTestClient; private String[] resources; protected String deviceId; - protected boolean isWriteAttribute = false; protected boolean supportFormatOnly_SenMLJSON_SenMLCBOR = false; @Before @@ -319,7 +318,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte try (ServerSocket socket = new ServerSocket(0)) { int clientPort = socket.getLocalPort(); lwM2MTestClient.init(security, securityBs, clientPort, isRpc, - this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest, isWriteAttribute, + this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest, clientDtlsCidLength, queueMode, supportFormatOnly_SenMLJSON_SenMLCBOR); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 796f3e09c5..fab682f854 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -141,7 +141,7 @@ public class LwM2MTestClient { public void init(Security security, Security securityBs, int port, boolean isRpc, LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler, - LwM2mClientContext clientContext, boolean isWriteAttribute, Integer cIdLength, boolean queueMode, + LwM2mClientContext clientContext, Integer cIdLength, boolean queueMode, boolean supportFormatOnly_SenMLJSON_SenMLCBOR) throws InvalidDDFFileException, IOException { Assert.assertNull("client already initialized", leshanClient); this.defaultLwM2mUplinkMsgHandlerTest = defaultLwM2mUplinkMsgHandler; @@ -151,7 +151,7 @@ public class LwM2MTestClient { models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName)); } LwM2mModel model = new StaticModel(models); - ObjectsInitializer initializer = isWriteAttribute ? new TbObjectsInitializer(model) : new ObjectsInitializer(model); + ObjectsInitializer initializer = new ObjectsInitializer(model); if (securityBs != null && security != null) { // SECURITY security.setId(serverId); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java index 370c2249df..a6a850a897 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java @@ -20,7 +20,7 @@ import org.eclipse.leshan.client.resource.BaseInstanceEnabler; import org.eclipse.leshan.client.servers.LwM2mServer; import org.eclipse.leshan.core.Destroyable; import org.eclipse.leshan.core.model.ObjectModel; -import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.request.argument.Arguments; import org.eclipse.leshan.core.response.ExecuteResponse; @@ -30,7 +30,6 @@ import org.eclipse.leshan.core.response.WriteResponse; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.PrimitiveIterator; @@ -46,9 +45,46 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl private static final Random RANDOM = new Random(); private static final int min = 5; private static final int max = 50; - private static final PrimitiveIterator.OfInt randomIterator = new Random().ints(min,max + 1).iterator(); + private static final PrimitiveIterator.OfInt randomIterator = new Random().ints(min, max + 1).iterator(); private static final List supportedResources = Arrays.asList(0, 1, 2, 3, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21); - + /** + * 0: DC power + * 1: Internal Battery + * 2: External Battery + * 3: Fuel Cell + * 4: Power over Ethernet + * 5: USB + * 6: AC (Mains) power + * 7: Solar + */ + private static final Map availablePowerSources = + Map.of(0, 0L, 1, 1L, 2, 7L); + private static Map powerSourceVoltage = + Map.of(0, 12000L, 1, 12400L, 7, 14600L); //mV + private static Map powerSourceCurrent = + Map.of(0, 72000L, 1, 2000L, 7, 25000L); // mA + + /** + * 0=No error + * 1=Low battery power + * 2=External power supply off + * 3=GPS module failure + * 4=Low received signal strength + * 5=Out of memory + * 6=SMS failure + * 7=IP connectivity failure + * 8=Peripheral malfunction + * 9..15=Reserved for future use + * 16..32=Device specific error codes + * + * When the single Device Object Instance is initiated, there is only one error code Resource Instance whose value is equal to 0 that means no error. + * When the first error happens, the LwM2M Client changes error code Resource Instance to any non-zero value to indicate the error type. + * When any other error happens, a new error code Resource Instance is created. + * When an error associated with a Resource Instance is no longer present, that Resource Instance is deleted. + * When the single existing error is no longer present, the LwM2M Client returns to the original no error state where Instance 0 has value 0. + */ + private static Map errorCode = + Map.of(0, 0L); // 0-32 public SimpleLwM2MDevice() { } @@ -81,15 +117,17 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl case 3: return ReadResponse.success(resourceId, getFirmwareVersion()); case 6: - return ReadResponse.success(resourceId, getAvailablePowerSources(), ResourceModel.Type.INTEGER); + return ReadResponse.success(resourceId, getAvailablePowerSources(), Type.INTEGER); + case 7: + return ReadResponse.success(resourceId, getPowerSourceVoltage(), Type.INTEGER); + case 8: + return ReadResponse.success(resourceId, getPowerSourceCurrent(), Type.INTEGER); case 9: return ReadResponse.success(resourceId, getBatteryLevel()); case 10: return ReadResponse.success(resourceId, getMemoryFree()); case 11: - Map errorCodes = new HashMap<>(); - errorCodes.put(0, getErrorCode()); - return ReadResponse.success(resourceId, errorCodes, ResourceModel.Type.INTEGER); + return ReadResponse.success(resourceId, getErrorCodes(), Type.INTEGER); case 14: return ReadResponse.success(resourceId, getUtcOffset()); case 15: @@ -156,16 +194,19 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl return "1.0.2"; } - private long getErrorCode() { - return 0; + private Map getAvailablePowerSources() { + return availablePowerSources; } - private Map getAvailablePowerSources() { - Map availablePowerSources = new HashMap<>(); - availablePowerSources.put(0, 1L); - availablePowerSources.put(1, 2L); - availablePowerSources.put(2, 5L); - return availablePowerSources; + private Map getPowerSourceVoltage() { + return powerSourceVoltage; + } + private Map getPowerSourceCurrent() { + return powerSourceCurrent; + } + + private Map getErrorCodes() { + return errorCode; } private int getBatteryLevel() { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java deleted file mode 100644 index a7bdae3321..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbLwm2mObjectEnabler.java +++ /dev/null @@ -1,729 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.lwm2m.client; - -import org.eclipse.leshan.client.LwM2mClient; -import org.eclipse.leshan.client.resource.BaseObjectEnabler; -import org.eclipse.leshan.client.resource.DummyInstanceEnabler; -import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; -import org.eclipse.leshan.client.resource.LwM2mInstanceEnablerFactory; -import org.eclipse.leshan.client.resource.listener.ResourceListener; -import org.eclipse.leshan.client.servers.LwM2mServer; -import org.eclipse.leshan.client.servers.ServersInfoExtractor; -import org.eclipse.leshan.client.util.LinkFormatHelper; -import org.eclipse.leshan.core.Destroyable; -import org.eclipse.leshan.core.LwM2mId; -import org.eclipse.leshan.core.Startable; -import org.eclipse.leshan.core.Stoppable; -import org.eclipse.leshan.core.link.lwm2m.LwM2mLink; -import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttribute; -import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributeSet; -import org.eclipse.leshan.core.link.lwm2m.attributes.LwM2mAttributes; -import org.eclipse.leshan.core.model.ObjectModel; -import org.eclipse.leshan.core.model.ResourceModel; -import org.eclipse.leshan.core.node.LwM2mMultipleResource; -import org.eclipse.leshan.core.node.LwM2mObject; -import org.eclipse.leshan.core.node.LwM2mObjectInstance; -import org.eclipse.leshan.core.node.LwM2mPath; -import org.eclipse.leshan.core.node.LwM2mResource; -import org.eclipse.leshan.core.node.LwM2mResourceInstance; -import org.eclipse.leshan.core.request.BootstrapDeleteRequest; -import org.eclipse.leshan.core.request.BootstrapReadRequest; -import org.eclipse.leshan.core.request.BootstrapWriteRequest; -import org.eclipse.leshan.core.request.ContentFormat; -import org.eclipse.leshan.core.request.CreateRequest; -import org.eclipse.leshan.core.request.DeleteRequest; -import org.eclipse.leshan.core.request.DiscoverRequest; -import org.eclipse.leshan.core.request.DownlinkRequest; -import org.eclipse.leshan.core.request.ExecuteRequest; -import org.eclipse.leshan.core.request.ObserveRequest; -import org.eclipse.leshan.core.request.ReadRequest; -import org.eclipse.leshan.core.request.WriteAttributesRequest; -import org.eclipse.leshan.core.request.WriteRequest; -import org.eclipse.leshan.core.request.WriteRequest.Mode; -import org.eclipse.leshan.core.response.BootstrapDeleteResponse; -import org.eclipse.leshan.core.response.BootstrapReadResponse; -import org.eclipse.leshan.core.response.BootstrapWriteResponse; -import org.eclipse.leshan.core.response.CreateResponse; -import org.eclipse.leshan.core.response.DeleteResponse; -import org.eclipse.leshan.core.response.DiscoverResponse; -import org.eclipse.leshan.core.response.ExecuteResponse; -import org.eclipse.leshan.core.response.ObserveResponse; -import org.eclipse.leshan.core.response.ReadResponse; -import org.eclipse.leshan.core.response.WriteAttributesResponse; -import org.eclipse.leshan.core.response.WriteResponse; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -public class TbLwm2mObjectEnabler extends BaseObjectEnabler implements Destroyable, Startable, Stoppable { - - private static Logger LOG = LoggerFactory.getLogger(DummyInstanceEnabler.class); - - protected Map instances; - - protected LwM2mInstanceEnablerFactory instanceFactory; - protected ContentFormat defaultContentFormat; - - private LinkFormatHelper tbLinkFormatHelper; - protected Map lwM2mAttributes; - - public TbLwm2mObjectEnabler(int id, ObjectModel objectModel, Map instances, - LwM2mInstanceEnablerFactory instanceFactory, ContentFormat defaultContentFormat) { - super(id, objectModel); - this.instances = new HashMap<>(instances); - ; - this.instanceFactory = instanceFactory; - this.defaultContentFormat = defaultContentFormat; - for (Entry entry : this.instances.entrySet()) { - instances.put(entry.getKey(), entry.getValue()); - listenInstance(entry.getValue(), entry.getKey()); - } - this.lwM2mAttributes = new HashMap<>(); - } - - public TbLwm2mObjectEnabler(int id, ObjectModel objectModel) { - super(id, objectModel); - } - - @Override - public synchronized List getAvailableInstanceIds() { - List ids = new ArrayList<>(instances.keySet()); - Collections.sort(ids); - return ids; - } - - @Override - public synchronized List getAvailableResourceIds(int instanceId) { - LwM2mInstanceEnabler instanceEnabler = instances.get(instanceId); - if (instanceEnabler != null) { - return instanceEnabler.getAvailableResourceIds(getObjectModel()); - } else { - return Collections.emptyList(); - } - } - - public synchronized void addInstance(int instanceId, LwM2mInstanceEnabler newInstance) { - instances.put(instanceId, newInstance); - listenInstance(newInstance, instanceId); - fireInstancesAdded(instanceId); - } - - public synchronized LwM2mInstanceEnabler getInstance(int instanceId) { - return instances.get(instanceId); - } - - public synchronized LwM2mInstanceEnabler removeInstance(int instanceId) { - LwM2mInstanceEnabler removedInstance = instances.remove(instanceId); - if (removedInstance != null) { - fireInstancesRemoved(removedInstance.getId()); - } - return removedInstance; - } - - @Override - protected CreateResponse doCreate(LwM2mServer server, CreateRequest request) { - if (!getObjectModel().multiple && instances.size() > 0) { - return CreateResponse.badRequest("an instance already exist for this single instance object"); - } - - if (request.unknownObjectInstanceId()) { - // create instance - LwM2mInstanceEnabler newInstance = createInstance(server, getObjectModel().multiple ? null : 0, - request.getResources()); - - // add new instance to this object - instances.put(newInstance.getId(), newInstance); - listenInstance(newInstance, newInstance.getId()); - fireInstancesAdded(newInstance.getId()); - - return CreateResponse - .success(new LwM2mPath(request.getPath().getObjectId(), newInstance.getId()).toString()); - } else { - List instanceNodes = request.getObjectInstances(); - - // checks single object instances - if (!getObjectModel().multiple) { - if (request.getObjectInstances().size() > 1) { - return CreateResponse.badRequest("can not create several instances on this single instance object"); - } - if (request.getObjectInstances().get(0).getId() != 0) { - return CreateResponse.badRequest("single instance object must use 0 as ID"); - } - } - // ensure instance does not already exists - for (LwM2mObjectInstance instance : instanceNodes) { - if (instances.containsKey(instance.getId())) { - return CreateResponse.badRequest(String.format("instance %d already exists", instance.getId())); - } - } - - // create the new instances - int[] instanceIds = new int[request.getObjectInstances().size()]; - int i = 0; - for (LwM2mObjectInstance instance : request.getObjectInstances()) { - // create instance - LwM2mInstanceEnabler newInstance = createInstance(server, instance.getId(), - instance.getResources().values()); - - // add new instance to this object - instances.put(newInstance.getId(), newInstance); - listenInstance(newInstance, newInstance.getId()); - - // store instance ids - instanceIds[i] = newInstance.getId(); - i++; - } - fireInstancesAdded(instanceIds); - return CreateResponse.success(); - } - } - - protected LwM2mInstanceEnabler createInstance(LwM2mServer server, Integer instanceId, - Collection resources) { - // create the new instance - LwM2mInstanceEnabler newInstance = instanceFactory.create(getObjectModel(), instanceId, instances.keySet()); - newInstance.setLwM2mClient(getLwm2mClient()); - - // add/write resource - for (LwM2mResource resource : resources) { - newInstance.write(server, true, resource.getId(), resource); - } - - return newInstance; - } - - @Override - protected ReadResponse doRead(LwM2mServer server, ReadRequest request) { - LwM2mPath path = request.getPath(); - - // Manage Object case - if (path.isObject()) { - List lwM2mObjectInstances = new ArrayList<>(); - for (LwM2mInstanceEnabler instance : instances.values()) { - ReadResponse response = instance.read(server); - if (response.isSuccess()) { - lwM2mObjectInstances.add((LwM2mObjectInstance) response.getContent()); - } - } - return ReadResponse.success(new LwM2mObject(getId(), lwM2mObjectInstances)); - } - - // Manage Instance case - LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); - if (instance == null) - return ReadResponse.notFound(); - - if (path.getResourceId() == null) { - return instance.read(server); - } - - // Manage Resource case - if (path.getResourceInstanceId() == null) { - return instance.read(server, path.getResourceId()); - } - - // Manage Resource Instance case - return instance.read(server, path.getResourceId(), path.getResourceInstanceId()); - } - - @Override - protected BootstrapReadResponse doRead(LwM2mServer server, BootstrapReadRequest request) { - // Basic implementation we delegate to classic Read Request - ReadResponse response = doRead(server, - new ReadRequest(request.getContentFormat(), request.getPath(), request.getCoapRequest())); - return new BootstrapReadResponse(response.getCode(), response.getContent(), response.getErrorMessage()); - } - - @Override - protected ObserveResponse doObserve(final LwM2mServer server, final ObserveRequest request) { - final LwM2mPath path = request.getPath(); - - // Manage Object case - if (path.isObject()) { - List lwM2mObjectInstances = new ArrayList<>(); - for (LwM2mInstanceEnabler instance : instances.values()) { - ReadResponse response = instance.observe(server); - if (response.isSuccess()) { - lwM2mObjectInstances.add((LwM2mObjectInstance) response.getContent()); - } - } - return ObserveResponse.success(new LwM2mObject(getId(), lwM2mObjectInstances)); - } - - // Manage Instance case - final LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); - if (instance == null) - return ObserveResponse.notFound(); - - if (path.getResourceId() == null) { - return instance.observe(server); - } - - // Manage Resource case - if (path.getResourceInstanceId() == null) { - return instance.observe(server, path.getResourceId()); - } - - // Manage Resource Instance case - return instance.observe(server, path.getResourceId(), path.getResourceInstanceId()); - } - - @Override - protected WriteResponse doWrite(LwM2mServer server, WriteRequest request) { - LwM2mPath path = request.getPath(); - - // Manage Instance case - LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); - if (instance == null) - return WriteResponse.notFound(); - - if (path.isObjectInstance()) { - return instance.write(server, request.isReplaceRequest(), (LwM2mObjectInstance) request.getNode()); - } - - // Manage Resource case - if (path.getResourceInstanceId() == null) { - return instance.write(server, request.isReplaceRequest(), path.getResourceId(), - (LwM2mResource) request.getNode()); - } - - // Manage Resource Instance case - return instance.write(server, false, path.getResourceId(), path.getResourceInstanceId(), - ((LwM2mResourceInstance) request.getNode())); - } - - @Override - protected BootstrapWriteResponse doWrite(LwM2mServer server, BootstrapWriteRequest request) { - LwM2mPath path = request.getPath(); - - // Manage Object case - if (path.isObject()) { - for (LwM2mObjectInstance instanceNode : ((LwM2mObject) request.getNode()).getInstances().values()) { - LwM2mInstanceEnabler instanceEnabler = instances.get(instanceNode.getId()); - if (instanceEnabler == null) { - doCreate(server, new CreateRequest(path.getObjectId(), instanceNode)); - } else { - doWrite(server, new WriteRequest(Mode.REPLACE, path.getObjectId(), instanceEnabler.getId(), - instanceNode.getResources().values())); - } - } - return BootstrapWriteResponse.success(); - } - - // Manage Instance case - if (path.isObjectInstance()) { - LwM2mObjectInstance instanceNode = (LwM2mObjectInstance) request.getNode(); - LwM2mInstanceEnabler instanceEnabler = instances.get(path.getObjectInstanceId()); - if (instanceEnabler == null) { - doCreate(server, new CreateRequest(path.getObjectId(), instanceNode)); - } else { - doWrite(server, new WriteRequest(Mode.REPLACE, request.getContentFormat(), path.getObjectId(), - path.getObjectInstanceId(), instanceNode.getResources().values())); - } - return BootstrapWriteResponse.success(); - } - - // Manage resource case - LwM2mResource resource = (LwM2mResource) request.getNode(); - LwM2mInstanceEnabler instanceEnabler = instances.get(path.getObjectInstanceId()); - if (instanceEnabler == null) { - doCreate(server, new CreateRequest(path.getObjectId(), - new LwM2mObjectInstance(path.getObjectInstanceId(), resource))); - } else { - instanceEnabler.write(server, true, path.getResourceId(), resource); - } - return BootstrapWriteResponse.success(); - } - - @Override - protected ExecuteResponse doExecute(LwM2mServer server, ExecuteRequest request) { - LwM2mPath path = request.getPath(); - LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); - if (instance == null) { - return ExecuteResponse.notFound(); - } - return instance.execute(server, path.getResourceId(), request.getArguments()); - } - - @Override - protected DeleteResponse doDelete(LwM2mServer server, DeleteRequest request) { - LwM2mInstanceEnabler deletedInstance = instances.remove(request.getPath().getObjectInstanceId()); - if (deletedInstance != null) { - deletedInstance.onDelete(server); - fireInstancesRemoved(deletedInstance.getId()); - return DeleteResponse.success(); - } - return DeleteResponse.notFound(); - } - - @Override - public BootstrapDeleteResponse doDelete(LwM2mServer server, BootstrapDeleteRequest request) { - if (request.getPath().isRoot() || request.getPath().isObject()) { - if (id == LwM2mId.SECURITY) { - // For security object, we clean everything except bootstrap Server account. - - // Get bootstrap account and store removed instances ids - Entry bootstrapServerAccount = null; - int[] instanceIds = new int[instances.size()]; - int i = 0; - for (Entry instance : instances.entrySet()) { - if (ServersInfoExtractor.isBootstrapServer(instance.getValue())) { - bootstrapServerAccount = instance; - } else { - // Store instance ids - instanceIds[i] = instance.getKey(); - i++; - } - } - // Clear everything - instances.clear(); - - // Put bootstrap account again - if (bootstrapServerAccount != null) { - instances.put(bootstrapServerAccount.getKey(), bootstrapServerAccount.getValue()); - } - - fireInstancesRemoved(instanceIds); - return BootstrapDeleteResponse.success(); - } else if (id == LwM2mId.OSCORE) { - // For OSCORE object, we clean everything except OSCORE object link to bootstrap Server account. - - // Get bootstrap account - LwM2mObjectInstance bootstrapInstance = ServersInfoExtractor.getBootstrapSecurityInstance( - getLwm2mClient().getObjectTree().getObjectEnabler(LwM2mId.SECURITY)); - // Get OSCORE instance ID associated to it - Integer bootstrapOscoreInstanceId = bootstrapInstance != null - ? ServersInfoExtractor.getOscoreSecurityMode(bootstrapInstance) - : null; - - // if bootstrap server use OSCORE, - // search the OSCORE instance for this ID and store removed instances ids - if (bootstrapOscoreInstanceId != null) { - Entry bootstrapServerOscore = null; - int[] instanceIds = new int[instances.size()]; - int i = 0; - for (Entry instance : instances.entrySet()) { - if (bootstrapOscoreInstanceId.equals(instance.getKey())) { - bootstrapServerOscore = instance; - } else { - // Store instance ids - instanceIds[i] = instance.getKey(); - i++; - } - } - - // Clear everything - instances.clear(); - - // Put bootstrap OSCORE instance again - if (bootstrapServerOscore != null) { - instances.put(bootstrapServerOscore.getKey(), bootstrapServerOscore.getValue()); - } - fireInstancesRemoved(instanceIds); - return BootstrapDeleteResponse.success(); - } - // else delete everything. - } - - // In all other cases, just delete everything - instances.clear(); - // fired instances removed - int[] instanceIds = new int[instances.size()]; - int i = 0; - for (Entry instance : instances.entrySet()) { - instanceIds[i] = instance.getKey(); - i++; - } - fireInstancesRemoved(instanceIds); - - return BootstrapDeleteResponse.success(); - } else if (request.getPath().isObjectInstance()) { - if (id == LwM2mId.SECURITY) { - // For security object, deleting bootstrap Server account is not allowed - LwM2mInstanceEnabler instance = instances.get(request.getPath().getObjectInstanceId()); - if (instance == null) { - return BootstrapDeleteResponse - .badRequest(String.format("Instance %s not found", request.getPath())); - } else if (ServersInfoExtractor.isBootstrapServer(instance)) { - return BootstrapDeleteResponse.badRequest("bootstrap server can not be deleted"); - } - } else if (id == LwM2mId.OSCORE) { - // For OSCORE object, deleting instance linked to Bootstrap account is not allowed - - // Get bootstrap instance - LwM2mObjectInstance bootstrapInstance = ServersInfoExtractor.getBootstrapSecurityInstance( - getLwm2mClient().getObjectTree().getObjectEnabler(LwM2mId.SECURITY)); - // Get OSCORE instance ID associated to it - Integer bootstrapOscoreInstanceId = bootstrapInstance != null - ? ServersInfoExtractor.getOscoreSecurityMode(bootstrapInstance) - : null; - - if (bootstrapOscoreInstanceId != null - && bootstrapOscoreInstanceId.equals(request.getPath().getObjectInstanceId())) { - return BootstrapDeleteResponse - .badRequest("OSCORE instance linked to bootstrap server can not be deleted"); - } - } - if (null != instances.remove(request.getPath().getObjectInstanceId())) { - fireInstancesRemoved(request.getPath().getObjectInstanceId()); - return BootstrapDeleteResponse.success(); - } else { - return BootstrapDeleteResponse.badRequest(String.format("Instance %s not found", request.getPath())); - } - } - return BootstrapDeleteResponse.badRequest(String.format("unexcepted path %s", request.getPath())); - } - - protected void listenInstance(LwM2mInstanceEnabler instance, final int instanceId) { - instance.addResourceListener(new ResourceListener() { - @Override - public void resourceChanged(LwM2mPath... paths) { - for (LwM2mPath path : paths) { - if (!isValid(instanceId, path)) { - LOG.warn("InstanceEnabler ({}) of object ({}) try to raise a change of {} which seems invalid.", - instanceId, getId(), path); - } - } - fireResourcesChanged(paths); - } - }); - } - - protected boolean isValid(int instanceId, LwM2mPath pathToValidate) { - if (!(pathToValidate.isResource() || pathToValidate.isResourceInstance())) - return false; - - if (pathToValidate.getObjectId() != getId()) { - return false; - } - - if (pathToValidate.getObjectInstanceId() != instanceId) { - return false; - } - - return true; - } - - @Override - public ContentFormat getDefaultEncodingFormat(DownlinkRequest request) { - return defaultContentFormat; - } - - @Override - public void init(LwM2mClient client, LinkFormatHelper linkFormatHelper) { - super.init(client, linkFormatHelper); - this.tbLinkFormatHelper = linkFormatHelper; - for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { - instanceEnabler.setLwM2mClient(client); - } - } - - @Override - public void destroy() { - for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { - if (instanceEnabler instanceof Destroyable) { - ((Destroyable) instanceEnabler).destroy(); - } else if (instanceEnabler instanceof Stoppable) { - ((Stoppable) instanceEnabler).stop(); - } - } - } - - @Override - public void start() { - for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { - if (instanceEnabler instanceof Startable) { - ((Startable) instanceEnabler).start(); - } - } - } - - @Override - public void stop() { - for (LwM2mInstanceEnabler instanceEnabler : instances.values()) { - if (instanceEnabler instanceof Stoppable) { - ((Stoppable) instanceEnabler).stop(); - } - } - } - - @Override - public synchronized WriteAttributesResponse writeAttributes(LwM2mServer server, WriteAttributesRequest request) { - // execute is not supported for bootstrap - if (server.isLwm2mBootstrapServer()) { - return WriteAttributesResponse.methodNotAllowed(); - } -// return WriteAttributesResponse.internalServerError("not implemented"); - return doWriteAttributes(server, request); - } - - /** - * Class Attributes - * - pmin (def = 0(sec)) Integer Resource/Object Instance/Object Readable Resource - * - pmax (def = -- ) Integer Resource/Object Instance/Object Readable Resource - * - Greater Than gt (def = -- ) Float Resource Numerical&Readable Resource - * - Less Than lt (def = -- ) Float Resource Numerical&Readable Resource - * - Step st (def = -- ) Float Resource Numerical&Readable Resource - */ - public WriteAttributesResponse doWriteAttributes(LwM2mServer server, WriteAttributesRequest request) { - LwM2mPath lwM2mPath = request.getPath(); - LwM2mAttributeSet attributeSet = lwM2mAttributes.get(lwM2mPath); - Map> attributes = new HashMap<>(); - - for (LwM2mAttribute attr : request.getAttributes().getLwM2mAttributes()) { - if (attr.getName().equals("pmax") || attr.getName().equals("pmin")) { - if (lwM2mPath.isObject() || lwM2mPath.isObjectInstance() || lwM2mPath.isResource()) { - attributes.put(attr.getName(), attr); - } else { - return WriteAttributesResponse.badRequest("Attribute " + attr.getName() + " can be used for only Resource/Object Instance/Object."); - } - } else if (attr.getName().equals("gt") || attr.getName().equals("lt") || attr.getName().equals("st")) { - if (lwM2mPath.isResource()) { - attributes.put(attr.getName(), attr); - } else { - return WriteAttributesResponse.badRequest("Attribute " + attr.getName() + " can be used for only Resource."); - } - } - } - if (attributes.size() > 0) { - if (attributeSet == null) { - attributeSet = new LwM2mAttributeSet(attributes.values()); - } else { - Iterable> lwM2mAttributeIterable = attributeSet.getLwM2mAttributes(); - Map> attributesOld = new HashMap<>(); - for (LwM2mAttribute attr : lwM2mAttributeIterable) { - attributesOld.put(attr.getName(), attr); - } - attributesOld.putAll(attributes); - attributeSet = new LwM2mAttributeSet(attributesOld.values()); - } - lwM2mAttributes.put(lwM2mPath, attributeSet); - return WriteAttributesResponse.success(); - } - return WriteAttributesResponse.internalServerError("not implemented"); - } - - @Override - public synchronized DiscoverResponse discover(LwM2mServer server, DiscoverRequest request) { - - if (server.isLwm2mBootstrapServer()) { - // discover is not supported for bootstrap - return DiscoverResponse.methodNotAllowed(); - } - - if (id == LwM2mId.SECURITY || id == LwM2mId.OSCORE) { - return DiscoverResponse.notFound(); - } - return doDiscover(server, request); - - } - - protected DiscoverResponse doDiscover(LwM2mServer server, DiscoverRequest request) { - - LwM2mPath path = request.getPath(); - if (path.isObject()) { - LwM2mLink[] ObjectLinks = linkAddUpdateAttributes(this.tbLinkFormatHelper.getObjectDescription(server, this, null), server); - return DiscoverResponse.success(ObjectLinks); - - } else if (path.isObjectInstance()) { - // Manage discover on instance - if (!getAvailableInstanceIds().contains(path.getObjectInstanceId())) - return DiscoverResponse.notFound(); - - LwM2mLink[] instanceLink = linkAddUpdateAttributes(this.tbLinkFormatHelper.getInstanceDescription(server, this, path.getObjectInstanceId(), null), server); - return DiscoverResponse.success(instanceLink); - - } else if (path.isResource()) { - // Manage discover on resource - if (!getAvailableInstanceIds().contains(path.getObjectInstanceId())) - return DiscoverResponse.notFound(); - - ResourceModel resourceModel = getObjectModel().resources.get(path.getResourceId()); - if (resourceModel == null) - return DiscoverResponse.notFound(); - - if (!getAvailableResourceIds(path.getObjectInstanceId()).contains(path.getResourceId())) - return DiscoverResponse.notFound(); - - LwM2mLink[] resourceLink = linkAddUpdateAttributes(this.tbLinkFormatHelper.getResourceDescription(server, - this, path.getObjectInstanceId(), path.getResourceId(), null), server); - return DiscoverResponse.success(resourceLink); - } - return DiscoverResponse.badRequest(null); - } - - private LwM2mLink[] linkAddUpdateAttributes(LwM2mLink[] links, LwM2mServer server) { - ArrayList resourceLinkList = new ArrayList<>(); - for (LwM2mLink link : links) { - - LwM2mAttributeSet lwM2mAttributeSetDop = null; - if (this.lwM2mAttributes.get(link.getPath()) != null) { - lwM2mAttributeSetDop = this.lwM2mAttributes.get(link.getPath()); - } - LwM2mAttribute resourceAttributeDim = getResourceAttributes(server, link.getPath()); - - Map> attributes = new HashMap<>(); - if (link.getAttributes() != null) { - for (LwM2mAttribute attr : link.getAttributes().getLwM2mAttributes()) { - attributes.put(attr.getName(), attr); - } - } - if (lwM2mAttributeSetDop != null) { - for (LwM2mAttribute attr : lwM2mAttributeSetDop.getLwM2mAttributes()) { - attributes.put(attr.getName(), attr); - } - } - if (resourceAttributeDim != null) { - attributes.put(resourceAttributeDim.getName(), resourceAttributeDim); - } - resourceLinkList.add(new LwM2mLink(link.getRootPath(), link.getPath(), attributes.values())); - } - return resourceLinkList.toArray(LwM2mLink[]::new); - } - - protected LwM2mAttribute getResourceAttributes(LwM2mServer server, LwM2mPath path) { - ResourceModel resourceModel = getObjectModel().resources.get(path.getResourceId()); - if (path.isResource() && resourceModel.multiple) { - return getResourceAttributeDim(path, server); - } - return null; - } - - protected LwM2mAttribute getResourceAttributeDim(LwM2mPath path, LwM2mServer server) { - LwM2mInstanceEnabler instance = instances.get(path.getObjectInstanceId()); - try { - ReadResponse readResponse = instance.read(server, path.getResourceId()); - if (readResponse.getCode().getCode() == 205 && readResponse.getContent() instanceof LwM2mMultipleResource) { - long valueDim = ((LwM2mMultipleResource) readResponse.getContent()).getInstances().size(); - return LwM2mAttributes.create(LwM2mAttributes.DIMENSION, valueDim); - } else { - return null; - } - } catch (Exception e) { - return null; - } - } - -} - diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbObjectsInitializer.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbObjectsInitializer.java deleted file mode 100644 index eff5ac962e..0000000000 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/TbObjectsInitializer.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright © 2016-2024 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.transport.lwm2m.client; - -import org.eclipse.leshan.client.resource.BaseInstanceEnablerFactory; -import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler; -import org.eclipse.leshan.client.resource.LwM2mObjectEnabler; -import org.eclipse.leshan.client.resource.ObjectsInitializer; -import org.eclipse.leshan.core.model.LwM2mModel; -import org.eclipse.leshan.core.model.ObjectModel; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class TbObjectsInitializer extends ObjectsInitializer { - - - public TbObjectsInitializer(LwM2mModel model) { - super(model); - } - - public List create(int... objectId) { - List enablers = new ArrayList<>(); - for (int anObjectId : objectId) { - LwM2mObjectEnabler objectEnabler = create(anObjectId); - if (objectEnabler != null) - enablers.add(objectEnabler); - } - return enablers; - } - - public LwM2mObjectEnabler create(int objectId) { - ObjectModel objectModel = model.getObjectModel(objectId); - if (objectModel == null) { - throw new IllegalArgumentException( - "Cannot create object for id " + objectId + " because no model is defined for this id."); - } - return createNodeEnabler(objectModel); - } - - protected LwM2mObjectEnabler createNodeEnabler(ObjectModel objectModel) { - Map instances = new HashMap<>(); - LwM2mInstanceEnabler[] newInstances = createInstances(objectModel); - for (LwM2mInstanceEnabler instance : newInstances) { - // set id if not already set - if (instance.getId() == null) { - int id = BaseInstanceEnablerFactory.generateNewInstanceId(instances.keySet()); - instance.setId(id); - } - instance.setModel(objectModel); - instances.put(instance.getId(), instance); - } - return new TbLwm2mObjectEnabler(objectModel.id, objectModel, instances, getFactoryFor(objectModel), - getContentFormat(objectModel.id)); - } -} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java index bd3ca0642a..dae47c039f 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -110,9 +110,6 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg @Before public void startInitRPC() throws Exception { - if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationDiscoverWriteAttributesTest")){ - isWriteAttribute = true; - } if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationWriteCborTest")){ supportFormatOnly_SenMLJSON_SenMLCBOR = true; } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java index 9518010f6a..f34b32a97a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java @@ -39,6 +39,7 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_6; public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegrationTest { @@ -171,6 +172,17 @@ public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegration assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText()); } + @Test + public void testDiscoverRequestCannotTargetResourceInstance_Return_INTERNAL_SERVER_ERROR() throws Exception { + // ResourceInstanceId + String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6 + "/1"; + String actualResult = sendDiscover(expectedPath); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.INTERNAL_SERVER_ERROR.getName(), rpcActualResult.get("result").asText()); + String expected = "InvalidRequestException: Discover request cannot target resource instance path: /3/0/6/1"; + assertTrue(rpcActualResult.get("error").asText().contains(expected)); + } + private String sendDiscover(String path) throws Exception { String setRpcRequest = "{\"method\": \"Discover\", \"params\": {\"id\": \"" + path + "\"}}"; return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverWriteAttributesTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverWriteAttributesTest.java index c44048849e..b3bff83426 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverWriteAttributesTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverWriteAttributesTest.java @@ -19,12 +19,12 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import org.eclipse.leshan.core.ResponseCode; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_6; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_7; @@ -32,69 +32,22 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcLwM2MIntegrationTest { /** - * WriteAttributes {"id":"/3_1.2/0/6","attributes":{"pmax":100, "pmin":10}} - * if not implemented: - * {"result":"INTERNAL_SERVER_ERROR","error":"not implemented"} - * if implemented: - * {"result":"BAD_REQUEST","error":"Attribute pmax can be used for only Resource/Object Instance/Object."} + * Class Attributes + * - dim (0-65535) Integer: Multiple-Instance Resource; R, Number of instances existing for a Multiple-Instance Resource + * Class Attributes + * - pmin (def = 0(sec)) Integer: Object; Object Instance; Resource; Resource Instance; RW, Readable Resource + * - pmax (def = -- ) Integer: Object; Object Instance; Resource; Resource Instance; RW, Readable Resource + * - Greater Than gt (def = -- ) Float: Resource; Resource Instance; RW, Numerical&Readable Resource + * - Less Than lt (def = -- ) Float: Resource; Resource Instance; RW, Numerical&Readable Resource + * - Step st (def = -- ) Float: Resource; Resource Instance; RW, Numerical&Readable Resource */ - @Test - public void testWriteAttributesResourceWithParametersByResourceInstanceId_Result_BAD_REQUEST() throws Exception { - String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6 + "/1"; - String expectedValue = "{\"pmax\":100, \"pmin\":10}"; - String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expected = "Attribute pmax can be used for only Resource/Object Instance/Object."; - String actual = rpcActualResult.get("error").asText(); - assertTrue(actual.equals(expected)); - } - /** - * WriteAttributes {"id":"/3_1.2/0/6","attributes":{"pmax":100, "pmin":10}} - * if not implemented: - * {"result":"INTERNAL_SERVER_ERROR","error":"not implemented"} - * if implemented: - * {"result":"BAD_REQUEST","error":"Attribute pmax can be used for only Resource/Object Instance/Object."} - */ - @Test - public void testWriteAttributeResourceDimWithParametersByResourceId_Result_BAD_REQUEST() throws Exception { - String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6; - String expectedValue = "{\"dim\":3}"; - String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expected = "Attribute dim is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request."; - String actual = rpcActualResult.get("error").asText(); - assertTrue(actual.equals(expected)); - } - - @Test - public void testWriteAttributesResourceVerWithParametersById_Result_BAD_REQUEST() throws Exception { - String expectedPath = objectIdVer_3; - String expectedValue = "{\"ver\":1.3}"; - String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expected = "Attribute ver is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request."; - String actual = rpcActualResult.get("error").asText(); - assertTrue(actual.equals(expected)); - } - - @Test - public void testWriteAttributesResourceServerUriWithParametersById_Result_BAD_REQUEST() throws Exception { - String expectedPath = objectInstanceIdVer_1; - String actualResult = sendRPCReadById(expectedPath); - String expectedValue = "{\"uri\":\"coaps://localhost:5690\"}"; - actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expected = "Attribute uri is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request."; - String actual = rpcActualResult.get("error").asText(); - assertTrue(actual.equals(expected)); - } /** + * Class Attributes + * Object Version ver Object + * Provide the version of the associated Object. + * "ver" only for objectId * Class Attributes * Dimension dim Integer [0:255] * Number of instances existing for a Multiple-Instance Resource @@ -106,46 +59,34 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL * Integer * 0..7 * WriteAttributes implemented: Discover {"id":"3/0/6"} -> 'dim' = 3 - * "ver" only for objectId */ @Test public void testReadDIM_3_0_6_Only_R() throws Exception { - String path = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6; - String actualResult = sendDiscover(path); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String expected = ";dim=3"; - assertTrue(rpcActualResult.get("value").asText().contains(expected)); - - } - - - /** - * Class Attributes - * Object Version ver Object - * Provide the version of the associated Object. - * "ver" only for objectId - */ - @Test - public void testReadVer() throws Exception { String path = objectIdVer_3; String actualResult = sendDiscover(path); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); String expected = ";ver=1.2"; assertTrue(rpcActualResult.get("value").asText().contains(expected)); + expected = ";dim=3"; + assertTrue(rpcActualResult.get("value").asText().contains(expected)); + expected = ";dim=3"; + assertTrue(rpcActualResult.get("value").asText().contains(expected)); + expected = ";dim=3"; + assertTrue(rpcActualResult.get("value").asText().contains(expected)); + expected = ";dim=1"; + assertTrue(rpcActualResult.get("value").asText().contains(expected)); } /** * WriteAttributes {"id":"/3/0/14","attributes":{"pmax":100, "pmin":10}} - * if not implemented: - * {"result":"INTERNAL_SERVER_ERROR","error":"not implemented"} - * if implemented: * {"result":"CHANGED"} + * result changed: + * */ @Test public void testWriteAttributesResourceWithParametersById_Result_CHANGED() throws Exception { - String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14; + String expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6; String expectedValue = "{\"pmax\":100, \"pmin\":10}"; String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); @@ -154,41 +95,33 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL actualResult = sendDiscover(expectedPath); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String expected = ";pmax=100;pmin=10"; + String expected = ";pmax=100;pmin=10;dim=3"; assertTrue(rpcActualResult.get("value").asText().contains(expected)); } - /** - * Class Attributes - * Minimum/Maximum Period pmin/pmax - * Notes: The Minimum Period Attribute: - * -- indicates the minimum time in seconds the LwM2M Client MUST wait between two notifications. If a notification of an observed Resource is supposed to be generated but it is before pmin expiry, notification MUST be sent as soon as pmin expires. In the absence of this parameter, the Minimum Period is defined by the Default Minimum Period set in the LwM2M Server Account. - * Notes: The Maximum Period Attribute: - * -- indicates the maximum time in seconds the LwM2M Client MAY wait between two notifications. When this "Maximum Period" expires after the last notification, a new notification MUST be sent. In the absence of this parameter, the "Maximum Period" is defined by the Default Maximum Period when set in the LwM2M Server Account or considered as 0 otherwise. The value of 0, means pmax MUST be ignored. The maximum period parameter MUST be greater than the minimum period parameter otherwise pmax will be ignored for the Resource to which such inconsistent timing conditions are applied. - * Greater Than gt Resource - * Less Than lt Resource - * Step st Resource - * - * Object Id = 1 - * Default Minimum Period Id = 2 300 or 0 - * Default Maximum Period Id = 3 6000 or "-" - * ;pmax=65, , <3/0/2>, , , - * <3/0/6>;dim=8,<3/0/7>;gt=50;lt=42.2;st=0.5,<3/0/8>;... - */ @Test - public void testWriteAttributesPeriodLtGt() throws Exception { + public void testWriteAttributesResourceVerWithParametersById_Result_BAD_REQUEST() throws Exception { + String expectedPath = objectIdVer_3; + String expectedValue = "{\"ver\":1.3}"; + String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expected = "Attribute ver is of class PROPERTIES but only NOTIFICATION attribute can be used in WRITE ATTRIBUTE request."; + String actual = rpcActualResult.get("error").asText(); + assertTrue(actual.equals(expected)); + } + + @Test + public void testWriteAttributesObjectInstanceResourcePeriodLtGt_Return_CHANGED() throws Exception { String expectedPath = objectInstanceIdVer_3; - String expectedValue = "{\"pmax\":60}"; + String expectedValue = "{\"pmax\":65, \"pmin\":5}"; String actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); - expectedPath = objectInstanceIdVer_3; - expectedValue = "{\"pmax\":65}"; - actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_7; - expectedValue = "{\"gt\":50.0, \"lt\":42.2, \"st\":0.5}"; + String expectedValueStr = "gt=50;lt=42.2;st=0.5"; + JsonUtils.parse("{" + expectedValueStr + "}").toString(); + expectedValue = JsonUtils.parse("{" + expectedValueStr + "}").toString(); actualResult = sendRPCExecuteWithValueById(expectedPath, expectedValue); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText()); @@ -198,11 +131,11 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); String actualValue = rpcActualResult.get("value").asText(); - String expected = ";ver=1.2,;pmax=65"; + String expected = ";ver=1.2,;pmax=65;pmin=5"; assertTrue(actualValue.contains(expected)); expected = ";dim=3"; assertTrue(actualValue.contains(expected)); - expected = ";st=0.5;lt=42.2;gt=50"; + expected = ";" + expectedValueStr + ";dim=3"; assertTrue(actualValue.contains(expected)); // ObjectInstanceId expectedPath = objectInstanceIdVer_3; @@ -210,30 +143,23 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); actualValue = rpcActualResult.get("value").asText(); - expected = ";pmax=65"; + expected = ";pmax=65;pmin=5"; assertTrue(actualValue.contains(expected)); - expected = ";dim=3,;st=0.5;lt=42.2;gt=50"; + expected = ";" + expectedValueStr + ";dim=3"; assertTrue(actualValue.contains(expected)); // ResourceId expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6; actualResult = sendDiscover(expectedPath); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - expected = ";dim=3"; + expected = ";dim=3,,,"; assertTrue(rpcActualResult.get("value").asText().contains(expected)); expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_7; actualResult = sendDiscover(expectedPath); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - expected = ";st=0.5;lt=42.2;gt=50"; + expected = ";" + expectedValueStr; assertTrue(rpcActualResult.get("value").asText().contains(expected)); - // ResourceInstanceId - expectedPath = objectInstanceIdVer_3 + "/" + RESOURCE_ID_6 + "/1"; - actualResult = sendDiscover(expectedPath); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.INTERNAL_SERVER_ERROR.getName(), rpcActualResult.get("result").asText()); - expected = "InvalidRequestException: Discover request cannot target resource instance path: /3/0/6/1"; - assertTrue(rpcActualResult.get("error").asText().contains(expected)); } private String sendRPCExecuteWithValueById(String path, String value) throws Exception { @@ -241,11 +167,6 @@ public class RpcLwm2mIntegrationDiscoverWriteAttributesTest extends AbstractRpcL return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); } - private String sendRPCReadById(String path) throws Exception { - String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); - } - private String sendDiscover(String path) throws Exception { String setRpcRequest = "{\"method\": \"Discover\", \"params\": {\"id\": \"" + path + "\"}}"; return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); From ec8dd5f49176081d63459dbc289bff24aec332ec Mon Sep 17 00:00:00 2001 From: nick Date: Wed, 9 Oct 2024 19:47:38 +0300 Subject: [PATCH 16/25] lwm2m: update tests lwm2mAttributes --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 79 +++++++-------- .../lwm2m/client/LwM2mTemperatureSensor.java | 2 +- .../ota/AbstractOtaLwM2MIntegrationTest.java | 86 +++++++++++++---- ...est.java => Ota5LwM2MIntegrationTest.java} | 96 +++++-------------- .../ota/sql/Ota9LwM2MIntegrationTest.java | 73 ++++++++++++++ ...bstractRpcLwM2MIntegrationObserveTest.java | 9 +- .../rpc/AbstractRpcLwM2MIntegrationTest.java | 67 ++++++++----- ...cLwm2MIntegrationObserveCompositeTest.java | 62 +++++------- .../sql/RpcLwm2mIntegrationDiscoverTest.java | 15 +++ .../sql/RpcLwm2mIntegrationObserveTest.java | 20 ++-- ...wm2mIntegrationReadCollectedValueTest.java | 5 - .../AbstractSecurityLwM2MIntegrationTest.java | 14 +-- ...rityLwM2MIntegrationDtlsCidLengthTest.java | 7 +- .../AbstractLwM2MIntegrationDiffPortTest.java | 5 +- .../security/sql/PskLwm2mIntegrationTest.java | 5 +- .../security/sql/RpkLwM2MIntegrationTest.java | 15 +-- .../sql/X509_NoTrustLwM2MIntegrationTest.java | 14 +-- .../lwm2m/server/client/LwM2mClient.java | 2 +- 18 files changed, 327 insertions(+), 249 deletions(-) rename application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/{OtaLwM2MIntegrationTest.java => Ota5LwM2MIntegrationTest.java} (51%) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota9LwM2MIntegrationTest.java diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 1acf5165fd..bee2d59ce7 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -25,9 +25,9 @@ import org.eclipse.leshan.client.LeshanClient; import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.core.ResponseCode; import org.junit.After; -import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; +import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.test.context.TestPropertySource; @@ -54,6 +54,7 @@ import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingC import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential; import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.NoSecLwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataPageLink; import org.thingsboard.server.common.data.query.EntityDataQuery; @@ -70,9 +71,9 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd; import org.thingsboard.server.transport.AbstractTransportIntegrationTest; import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; +import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; -import java.io.IOException; import java.net.ServerSocket; import java.util.ArrayList; import java.util.Arrays; @@ -107,7 +108,10 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfil public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportIntegrationTest { @SpyBean - LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest; + protected LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest; + + @SpyBean + protected DefaultLwM2mUplinkMsgHandler defaultUplinkMsgHandlerTest; @Autowired private LwM2mClientContext clientContextTest; @@ -117,7 +121,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte public static final int securityPort = 5686; public static final int portBs = 5687; public static final int securityPortBs = 5688; - public static final int[] SERVERS_PORT_NUMBERS = {port, securityPort, portBs, securityPortBs}; public static final String host = "localhost"; public static final String hostBs = "localhost"; @@ -172,7 +175,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte protected final Set expectedStatusesRegistrationLwm2mSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS)); protected final Set expectedStatusesRegistrationLwm2mSuccessUpdate = new HashSet<>(Arrays.asList(ON_INIT, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS, ON_UPDATE_STARTED, ON_UPDATE_SUCCESS)); protected final Set expectedStatusesRegistrationBsSuccess = new HashSet<>(Arrays.asList(ON_BOOTSTRAP_STARTED, ON_BOOTSTRAP_SUCCESS, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS)); - protected DeviceProfile deviceProfile; protected ScheduledExecutorService executor; protected LwM2MTestClient lwM2MTestClient; private String[] resources; @@ -185,14 +187,11 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte } @After - public void after() { + public void after() throws Exception { clientDestroy(); - executor.shutdownNow(); - } - - @AfterClass - public static void afterClass() { - awaitServersDestroy(); + if (executor != null && !executor.isShutdown()) { + executor.shutdownNow(); + } } private void init() throws Exception { @@ -217,8 +216,8 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte String endpoint, boolean queueMode) throws Exception { Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS, getBootstrapServerCredentialsNoSec(NONE)); - createDeviceProfile(transportConfiguration); - Device device = createDevice(deviceCredentials, endpoint); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + endpoint, transportConfiguration); + Device device = createLwm2mDevice(deviceCredentials, endpoint, deviceProfile.getId()); SingleEntityFilter sef = new SingleEntityFilter(); sef.setSingleEntity(device.getId()); @@ -254,29 +253,30 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte } - protected void createDeviceProfile(Lwm2mDeviceProfileTransportConfiguration transportConfiguration) throws Exception { - deviceProfile = new DeviceProfile(); - deviceProfile.setName("LwM2M"); - deviceProfile.setType(DeviceProfileType.DEFAULT); - deviceProfile.setTenantId(tenantId); - deviceProfile.setTransportType(DeviceTransportType.LWM2M); - deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); - deviceProfile.setDescription(deviceProfile.getName()); + protected DeviceProfile createLwm2mDeviceProfile(String name, Lwm2mDeviceProfileTransportConfiguration transportConfiguration) throws Exception { + DeviceProfile lwm2mDeviceProfile = new DeviceProfile(); + lwm2mDeviceProfile.setName(name); + lwm2mDeviceProfile.setType(DeviceProfileType.DEFAULT); + lwm2mDeviceProfile.setTenantId(tenantId); + lwm2mDeviceProfile.setTransportType(DeviceTransportType.LWM2M); + lwm2mDeviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); + lwm2mDeviceProfile.setDescription(name); DeviceProfileData deviceProfileData = new DeviceProfileData(); deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null)); deviceProfileData.setTransportConfiguration(transportConfiguration); - deviceProfile.setProfileData(deviceProfileData); + lwm2mDeviceProfile.setProfileData(deviceProfileData); - deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); - Assert.assertNotNull(deviceProfile); + lwm2mDeviceProfile = doPost("/api/deviceProfile", lwm2mDeviceProfile, DeviceProfile.class); + Assert.assertNotNull(lwm2mDeviceProfile); + return lwm2mDeviceProfile; } - protected Device createDevice(LwM2MDeviceCredentials credentials, String endpoint) throws Exception { + protected Device createLwm2mDevice(LwM2MDeviceCredentials credentials, String endpoint, DeviceProfileId deviceProfileId) throws Exception { Device device = new Device(); device.setName(endpoint); - device.setDeviceProfileId(deviceProfile.getId()); + device.setDeviceProfileId(deviceProfileId); device.setTenantId(tenantId); device = doPost("/api/device", device, Device.class); Assert.assertNotNull(device); @@ -384,25 +384,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte return credentials; } - private static void awaitServersDestroy() { - await("One of servers ports number is not free") - .atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS) - .until(() -> isServerPortsAvailable() == null); - } - - private static String isServerPortsAvailable() { - for (int port : SERVERS_PORT_NUMBERS) { - try (ServerSocket serverSocket = new ServerSocket(port)) { - serverSocket.close(); - Assert.assertEquals(true, serverSocket.isClosed()); - } catch (IOException e) { - log.warn(String.format("Port %n still in use", port)); - return (String.format("Port %n still in use", port)); - } - } - return null; - } - private static void awaitClientDestroy(LeshanClient leshanClient) { await("Destroy LeshanClient: delete All is registered Servers.") .atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS) @@ -455,4 +436,10 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte return JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); } + protected long countUpdateReg() { + return Mockito.mockingDetails(defaultUplinkMsgHandlerTest) + .getInvocations().stream() + .filter(invocation -> invocation.getMethod().getName().equals("updatedReg")) + .count(); + } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java index 35d36749c9..9f96956a49 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java @@ -76,7 +76,7 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr @Override public synchronized ReadResponse read(LwM2mServer identity, int resourceId) { log.trace("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); - if (this.registeredServer == null) { + if (this.registeredServer == null && this.leshanClient != null && getId() == 12) { try { Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0 = Instant.now().toEpochMilli(); this.registeredServer = this.leshanClient.getRegisteredServers().values().iterator().next(); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/AbstractOtaLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/AbstractOtaLwM2MIntegrationTest.java index 60e335e32a..6008c0352a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/AbstractOtaLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/AbstractOtaLwM2MIntegrationTest.java @@ -15,17 +15,31 @@ */ package org.thingsboard.server.transport.lwm2m.ota; +import com.fasterxml.jackson.core.type.TypeReference; +import lombok.extern.slf4j.Slf4j; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; +import java.util.Comparator; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.rest.client.utils.RestJsonConverter.toTimeseries; import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; +@Slf4j @DaoSqlTest public abstract class AbstractOtaLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @@ -33,9 +47,10 @@ public abstract class AbstractOtaLwM2MIntegrationTest extends AbstractLwM2MInteg protected static final String CLIENT_ENDPOINT_WITHOUT_FW_INFO = "WithoutFirmwareInfoDevice"; protected static final String CLIENT_ENDPOINT_OTA5 = "Ota5_Device"; protected static final String CLIENT_ENDPOINT_OTA9 = "Ota9_Device"; + protected List expectedStatuses; - protected final String OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA = + protected final String OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA5 = " {\n" + " \"keyName\": {\n" + @@ -43,22 +58,14 @@ public abstract class AbstractOtaLwM2MIntegrationTest extends AbstractLwM2MInteg " \"/5_1.2/0/5\": \"updateResult\",\n" + " \"/5_1.2/0/6\": \"pkgname\",\n" + " \"/5_1.2/0/7\": \"pkgversion\",\n" + - " \"/5_1.2/0/9\": \"firmwareUpdateDeliveryMethod\",\n" + - " \"/9_1.1/0/0\": \"pkgname\",\n" + - " \"/9_1.1/0/1\": \"pkgversion\",\n" + - " \"/9_1.1/0/7\": \"updateState\",\n" + - " \"/9_1.1/0/9\": \"updateResult\"\n" + + " \"/5_1.2/0/9\": \"firmwareUpdateDeliveryMethod\"\n" + " },\n" + " \"observe\": [\n" + " \"/5_1.2/0/3\",\n" + " \"/5_1.2/0/5\",\n" + " \"/5_1.2/0/6\",\n" + " \"/5_1.2/0/7\",\n" + - " \"/5_1.2/0/9\",\n" + - " \"/9_1.1/0/0\",\n" + - " \"/9_1.1/0/1\",\n" + - " \"/9_1.1/0/7\",\n" + - " \"/9_1.1/0/9\"\n" + + " \"/5_1.2/0/9\"\n" + " ],\n" + " \"attribute\": [],\n" + " \"telemetry\": [\n" + @@ -66,7 +73,28 @@ public abstract class AbstractOtaLwM2MIntegrationTest extends AbstractLwM2MInteg " \"/5_1.2/0/5\",\n" + " \"/5_1.2/0/6\",\n" + " \"/5_1.2/0/7\",\n" + - " \"/5_1.2/0/9\",\n" + + " \"/5_1.2/0/9\"\n" + + " ],\n" + + " \"attributeLwm2m\": {}\n" + + " }"; + + protected final String OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA9 = + + " {\n" + + " \"keyName\": {\n" + + " \"/9_1.1/0/0\": \"pkgname\",\n" + + " \"/9_1.1/0/1\": \"pkgversion\",\n" + + " \"/9_1.1/0/7\": \"updateState\",\n" + + " \"/9_1.1/0/9\": \"updateResult\"\n" + + " },\n" + + " \"observe\": [\n" + + " \"/9_1.1/0/0\",\n" + + " \"/9_1.1/0/1\",\n" + + " \"/9_1.1/0/7\",\n" + + " \"/9_1.1/0/9\"\n" + + " ],\n" + + " \"attribute\": [],\n" + + " \"telemetry\": [\n" + " \"/9_1.1/0/0\",\n" + " \"/9_1.1/0/1\",\n" + " \"/9_1.1/0/7\",\n" + @@ -79,14 +107,14 @@ public abstract class AbstractOtaLwM2MIntegrationTest extends AbstractLwM2MInteg setResources(this.RESOURCES_OTA); } - protected OtaPackageInfo createFirmware() throws Exception { + protected OtaPackageInfo createFirmware(String version, DeviceProfileId deviceProfileId) throws Exception { String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; OtaPackageInfo firmwareInfo = new OtaPackageInfo(); - firmwareInfo.setDeviceProfileId(deviceProfile.getId()); + firmwareInfo.setDeviceProfileId(deviceProfileId); firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle("My firmware"); - firmwareInfo.setVersion("v1.0"); + firmwareInfo.setVersion(version); OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); @@ -95,11 +123,11 @@ public abstract class AbstractOtaLwM2MIntegrationTest extends AbstractLwM2MInteg return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256"); } - protected OtaPackageInfo createSoftware() throws Exception { + protected OtaPackageInfo createSoftware(DeviceProfileId deviceProfileId) throws Exception { String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; OtaPackageInfo swInfo = new OtaPackageInfo(); - swInfo.setDeviceProfileId(deviceProfile.getId()); + swInfo.setDeviceProfileId(deviceProfileId); swInfo.setType(SOFTWARE); swInfo.setTitle("My sw"); swInfo.setVersion("v1.0"); @@ -117,4 +145,28 @@ public abstract class AbstractOtaLwM2MIntegrationTest extends AbstractLwM2MInteg setJwtToken(postRequest); return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackageInfo.class); } + + + protected Device getDeviceFromAPI(UUID deviceId) throws Exception { + final Device device = doGet("/api/device/" + deviceId, Device.class); + log.trace("Fetched device by API for deviceId {}, device is {}", deviceId, device); + return device; + } + + protected List getFwSwStateTelemetryFromAPI(UUID deviceId, String type_state) throws Exception { + final List tsKvEntries = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?orderBy=ASC&keys=" + type_state + "&startTs=0&endTs=" + System.currentTimeMillis(), new TypeReference<>() { + })); + log.warn("Fetched telemetry by API for deviceId {}, list size {}, tsKvEntries {}", deviceId, tsKvEntries.size(), tsKvEntries); + return tsKvEntries; + } + + protected boolean predicateForStatuses(List ts) { + List statuses = ts.stream() + .sorted(Comparator.comparingLong(TsKvEntry::getTs)) + .map(KvEntry::getValueAsString) + .map(OtaPackageUpdateStatus::valueOf) + .collect(Collectors.toList()); + log.warn("{}", statuses); + return statuses.containsAll(expectedStatuses); + } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota5LwM2MIntegrationTest.java similarity index 51% rename from application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota5LwM2MIntegrationTest.java index b78e738d72..2a54bdef34 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota5LwM2MIntegrationTest.java @@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j; import org.junit.Assert; import org.junit.Test; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.kv.KvEntry; @@ -29,9 +30,7 @@ import org.thingsboard.server.transport.lwm2m.ota.AbstractOtaLwM2MIntegrationTes import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.List; -import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -45,24 +44,21 @@ import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.INIT import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.QUEUED; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING; -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; @Slf4j -public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { - - private List expectedStatuses; +public class Ota5LwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { @Test - public void testFirmwareUpdateWithClientWithoutFirmwareOtaInfoFromProfile() throws Exception { + public void testFirmwareUpdateWithClientWithoutFirmwareOtaInfoFromProfile_IsNotSupported() throws Exception { Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS, getBootstrapServerCredentialsNoSec(NONE)); - createDeviceProfile(transportConfiguration); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + this.CLIENT_ENDPOINT_WITHOUT_FW_INFO, transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_WITHOUT_FW_INFO)); - final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); + final Device device = createLwm2mDevice(deviceCredentials, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO, deviceProfile.getId()); createNewClient(SECURITY_NO_SEC, null, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO); awaitObserveReadAll(0, device.getId().getId().toString()); - device.setFirmwareId(createFirmware().getId()); + device.setFirmwareId(createFirmware("5.1", deviceProfile.getId()).getId()); final Device savedDevice = doPost("/api/device", device, Device.class); Thread.sleep(1000); @@ -78,81 +74,37 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { Assert.assertEquals(expectedStatuses, statuses); } + /** + * /5/0/5 -> Update Result (Res); 5/0/3 -> State; + * => ((Res>=0 && Res<=9) && State=0) + * => Write to Package/Write to Package URI -> DOWNLOADING ((Res>=0 && Res<=9) && State=1) + * => Download Finished -> DOWNLOADED ((Res==0 || Res=8) && State=2) + * => Executable resource Update is triggered / Initiate Firmware Update -> UPDATING (Res=0 && State=3) + * => Update Successful [Res==1] + * => Start / Res=0 -> "IDLE" .... + * @throws Exception + */ @Test - public void testFirmwareUpdateByObject5() throws Exception { - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA, getBootstrapServerCredentialsNoSec(NONE)); - createDeviceProfile(transportConfiguration); + public void testFirmwareUpdateByObject5_Ok() throws Exception { + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA5, getBootstrapServerCredentialsNoSec(NONE)); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + this.CLIENT_ENDPOINT_OTA5, transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA5)); - final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA5); + final Device device = createLwm2mDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA5, deviceProfile.getId()); createNewClient(SECURITY_NO_SEC, null, false, this.CLIENT_ENDPOINT_OTA5); - awaitObserveReadAll(9, device.getId().getId().toString()); + awaitObserveReadAll(5, device.getId().getId().toString()); - device.setFirmwareId(createFirmware().getId()); + device.setFirmwareId(createFirmware("fw.v.1.5.0-update", deviceProfile.getId()).getId()); final Device savedDevice = doPost("/api/device", device, Device.class); assertThat(savedDevice).as("saved device").isNotNull(); assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice); expectedStatuses = Arrays.asList(QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, UPDATING, UPDATED); - List ts = await("await on timeseries") + List ts = await("await on timeseries for FW") .atMost(TIMEOUT, TimeUnit.SECONDS) - .until(() -> toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + - savedDevice.getId().getId() + "/values/timeseries?orderBy=ASC&keys=fw_state&startTs=0&endTs=" + - System.currentTimeMillis(), new TypeReference<>() { - })), this::predicateForStatuses); + .until(() -> getFwSwStateTelemetryFromAPI(device.getId().getId(), "fw_state"), this::predicateForStatuses); log.warn("Object5: Got the ts: {}", ts); } - /** - * This is the example how to use the AWAITILITY instead Thread.sleep() - * Test will finish as fast as possible, but will await until TIMEOUT if a build machine is busy or slow - * Check the detailed log output to learn how Awaitility polling the API and when exactly expected result appears - * */ - @Test - public void testSoftwareUpdateByObject9() throws Exception { - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA, getBootstrapServerCredentialsNoSec(NONE)); - createDeviceProfile(transportConfiguration); - LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA9)); - final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA9); - createNewClient(SECURITY_NO_SEC, null, false, this.CLIENT_ENDPOINT_OTA9); - awaitObserveReadAll(9, device.getId().getId().toString()); - - device.setSoftwareId(createSoftware().getId()); - final Device savedDevice = doPost("/api/device", device, Device.class); //sync call - - assertThat(savedDevice).as("saved device").isNotNull(); - assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice); - - expectedStatuses = List.of( - QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED); - - List ts = await("await on timeseries") - .atMost(TIMEOUT, TimeUnit.SECONDS) - .until(() -> getSwStateTelemetryFromAPI(device.getId().getId()), this::predicateForStatuses); - log.warn("Object9: Got the ts: {}", ts); - } - - private Device getDeviceFromAPI(UUID deviceId) throws Exception { - final Device device = doGet("/api/device/" + deviceId, Device.class); - log.trace("Fetched device by API for deviceId {}, device is {}", deviceId, device); - return device; - } - - private List getSwStateTelemetryFromAPI(UUID deviceId) throws Exception { - final List tsKvEntries = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?orderBy=ASC&keys=sw_state&startTs=0&endTs=" + System.currentTimeMillis(), new TypeReference<>() { - })); - log.warn("Fetched telemetry by API for deviceId {}, list size {}, tsKvEntries {}", deviceId, tsKvEntries.size(), tsKvEntries); - return tsKvEntries; - } - - private boolean predicateForStatuses(List ts) { - List statuses = ts.stream() - .sorted(Comparator.comparingLong(TsKvEntry::getTs)) - .map(KvEntry::getValueAsString) - .map(OtaPackageUpdateStatus::valueOf) - .collect(Collectors.toList()); - log.warn("{}", statuses); - return statuses.containsAll(expectedStatuses); - } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota9LwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota9LwM2MIntegrationTest.java new file mode 100644 index 0000000000..90807de02c --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/Ota9LwM2MIntegrationTest.java @@ -0,0 +1,73 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.ota.sql; + +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.transport.lwm2m.ota.AbstractOtaLwM2MIntegrationTest; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADING; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.INITIATED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.QUEUED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE; + +@Slf4j +public class Ota9LwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { + + /** + * => Start -> INITIAL (State=0) -> DOWNLOAD STARTED; + * => PKG / URI Write -> DOWNLOAD STARTED (Res=1 (Downloading) && State=1) -> DOWNLOADED + * => PKG Written -> DOWNLOADED (Res=1 Initial && State=2) -> DELIVERED; + * => PKG integrity verified -> DELIVERED (Res=3 (Successfully Downloaded and package integrity verified) && State=3) -> INSTALLED; + * => Install -> INSTALLED (Res=2 SW successfully installed) && State=4) -> Start + * + * */ + @Test + public void testSoftwareUpdateByObject9() throws Exception { + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA9, getBootstrapServerCredentialsNoSec(NONE)); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + this.CLIENT_ENDPOINT_OTA9, transportConfiguration); + LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA9)); + final Device device = createLwm2mDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA9, deviceProfile.getId()); + createNewClient(SECURITY_NO_SEC, null, false, this.CLIENT_ENDPOINT_OTA9); + awaitObserveReadAll(4, device.getId().getId().toString()); + + device.setSoftwareId(createSoftware(deviceProfile.getId()).getId()); + final Device savedDevice = doPost("/api/device", device, Device.class); //sync call + + assertThat(savedDevice).as("saved device").isNotNull(); + assertThat(getDeviceFromAPI(device.getId().getId())).as("fetched device").isEqualTo(savedDevice); + + expectedStatuses = List.of( + QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED); + List ts = await("await on timeseries") + .atMost(TIMEOUT, TimeUnit.SECONDS) + .until(() -> getFwSwStateTelemetryFromAPI(device.getId().getId(), "sw_state"), this::predicateForStatuses); + log.warn("Object9: Got the ts: {}", ts); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserveTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserveTest.java index ea9814c1d0..7195b0d229 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserveTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationObserveTest.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.transport.lwm2m.rpc; -import org.junit.Before; import org.thingsboard.server.dao.service.DaoSqlTest; +import static org.junit.Assert.assertTrue; @DaoSqlTest public abstract class AbstractRpcLwM2MIntegrationObserveTest extends AbstractRpcLwM2MIntegrationTest{ @@ -26,9 +26,8 @@ public abstract class AbstractRpcLwM2MIntegrationObserveTest extends AbstractRpc setResources(this.RESOURCES_RPC_MULTIPLE_19); } - @Before - public void initTest () throws Exception { - awaitObserveReadAll(4, deviceId); + protected void sendRpcObserveWithContainsLwM2mSingleResource(String params) throws Exception { + String rpcActualResult = sendRpcObserveOkWithResultValue("Observe", params); + assertTrue(rpcActualResult.contains("LwM2mSingleResource") || rpcActualResult.contains("LwM2mMultipleResource")); } - } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java index dae47c039f..99dd9d1d71 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -22,13 +22,13 @@ import org.junit.Before; import org.mockito.Mockito; import org.springframework.boot.test.mock.mockito.SpyBean; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; -import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler; import java.util.List; import java.util.Set; @@ -98,9 +98,6 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg protected String idVer_19_0_0; - @SpyBean - protected DefaultLwM2mUplinkMsgHandler defaultUplinkMsgHandlerTest; - @SpyBean protected LwM2mTransportServerHelper lwM2mTransportServerHelperTest; @@ -110,13 +107,19 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg @Before public void startInitRPC() throws Exception { - if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationWriteCborTest")){ + if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationWriteCborTest")) { supportFormatOnly_SenMLJSON_SenMLCBOR = true; } - initRpc(false); + if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationObserveTest")) { + initRpc(0); + } else if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationReadCollectedValueTest")) { + initRpc(3303); + } else { + initRpc(1); + } } - protected void initRpc(boolean isCollected) throws Exception { + protected void initRpc(int typeConfigProfile) throws Exception { String endpoint = DEVICE_ENDPOINT_RPC_PREF + endpointSequence.incrementAndGet(); createNewClient(SECURITY_NO_SEC, null, true, endpoint); expectedObjects = ConcurrentHashMap.newKeySet(); @@ -151,7 +154,7 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg idVer_3_0_0 = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0; idVer_3_0_9 = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_9; - id_3_0_9 = fromVersionedIdToObjectId(idVer_3_0_9); + id_3_0_9 = fromVersionedIdToObjectId(idVer_3_0_9); idVer_19_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0; String ATTRIBUTES_TELEMETRY_WITH_PARAMS_RPC_WITH_OBSERVE = @@ -182,6 +185,28 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg " }"; String TELEMETRY_WITH_PARAMS_RPC_WITHOUT_OBSERVE = + " {\n" + + " \"keyName\": {\n" + + " \"" + idVer_3_0_9 + "\": \"" + RESOURCE_ID_NAME_3_9 + "\",\n" + + " \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\": \"" + RESOURCE_ID_NAME_3_14 + "\",\n" + + " \"" + idVer_19_0_0 + "\": \"" + RESOURCE_ID_NAME_19_0_0 + "\",\n" + + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\": \"" + RESOURCE_ID_NAME_19_1_0 + "\",\n" + + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2 + "\": \"" + RESOURCE_ID_NAME_19_0_2 + "\"\n" + + " },\n" + + " \"observe\": [\n" + + " ],\n" + + " \"attribute\": [\n" + + " \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\",\n" + + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2 + "\"\n" + + " ],\n" + + " \"telemetry\": [\n" + + " \"" + idVer_3_0_9 + "\",\n" + + " \"" + idVer_19_0_0 + "\",\n" + + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\"\n" + + " ],\n" + + " \"attributeLwm2m\": {}\n" + + " }"; + String TELEMETRY_WITH_PARAMS_RPC_COLLECTED_VALUE = " {\n" + " \"keyName\": {\n" + " \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\": \"" + RESOURCE_ID_NAME_3303_12_5700 + "\"\n" + @@ -194,15 +219,19 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg " \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\"\n" + " ],\n" + " \"attributeLwm2m\": {}\n" + - " }" ; - - CONFIG_PROFILE_WITH_PARAMS_RPC = isCollected ? TELEMETRY_WITH_PARAMS_RPC_WITHOUT_OBSERVE : ATTRIBUTES_TELEMETRY_WITH_PARAMS_RPC_WITH_OBSERVE; - + " }"; + CONFIG_PROFILE_WITH_PARAMS_RPC = + switch (typeConfigProfile) { + case 0 -> ATTRIBUTES_TELEMETRY_WITH_PARAMS_RPC_WITH_OBSERVE; + case 1 -> TELEMETRY_WITH_PARAMS_RPC_WITHOUT_OBSERVE; + case 3303 -> TELEMETRY_WITH_PARAMS_RPC_COLLECTED_VALUE; + default -> throw new IllegalStateException("Unexpected value: " + typeConfigProfile); + }; Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(CONFIG_PROFILE_WITH_PARAMS_RPC, getBootstrapServerCredentialsNoSec(NONE)); - createDeviceProfile(transportConfiguration); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + endpoint, transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(endpoint)); - final Device device = createDevice(deviceCredentials, endpoint); + final Device device = createLwm2mDevice(deviceCredentials, endpoint, deviceProfile.getId()); deviceId = device.getId().getId().toString(); lwM2MTestClient.start(true); @@ -248,14 +277,7 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg log.trace("updateRegAtLeastOnceAfterAction: newInvocationCount [{}]", newInvocationCount.get()); } - protected long countUpdateReg() { - return Mockito.mockingDetails(defaultUplinkMsgHandlerTest) - .getInvocations().stream() - .filter(invocation -> invocation.getMethod().getName().equals("updatedReg")) - .count(); - } - - protected long countSendParametersOnThingsboardTelemetryResource(String rezName) { + protected long countSendParametersOnThingsboardTelemetryResource(String rezName) { return Mockito.mockingDetails(lwM2mTransportServerHelperTest) .getInvocations().stream() .filter(invocation -> @@ -268,5 +290,4 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg ) .count(); } - } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java index a1ad762aaa..9e483a6f18 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2MIntegrationObserveCompositeTest.java @@ -58,7 +58,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveCompositeAnyResources_Result_CONTENT_Value_LwM2mSingleResource_LwM2mResourceInstance() throws Exception { - sendObserveCancelAllWithAwait(deviceId); String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; @@ -81,7 +80,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveComposite_ObjectInstanceWithOtherObjectResourceInstance_Result_CONTENT_Ok() throws Exception { - sendObserveCancelAllWithAwait(deviceId); String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0; String expectedIdVer5_0 = objectInstanceIdVer_5; String expectedIds = "[\"" + expectedIdVer19_1_0 + "\", \"" + expectedIdVer5_0 + "\"]"; @@ -100,7 +98,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveReadAll_AfterCompositeObservation_WithResourceNotReadable_Result_CONTENT_ObserveResourceNotReadableIsNull() throws Exception { - sendObserveCancelAllWithAwait(deviceId); String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_2 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2; String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_2 + "\"]"; @@ -120,7 +117,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveComposite_Result_BAD_REQUEST_ONE_PATH_CONTAINCE_OTHER() throws Exception { - sendObserveCancelAllWithAwait(deviceId); String expectedIdVer5_0 = objectInstanceIdVer_5; String expectedIdVer5_0_2 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2; String expectedIds = "[\"" + expectedIdVer5_0 + "\", \"" + expectedIdVer5_0_2 + "\"]"; @@ -133,7 +129,7 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt } /** - * Previous -> "3/0/9", "19/0/2", "19/1/0", "19/0/0", All only SingleObservation; + * Previous -> "3/0/9" SingleObservation; * if at least one of the resource objectIds (Composite) in SingleObservation or CompositeObservation is already registered - return BAD REQUEST * ObserveComposite {"ids":["5/0/7", "5/0/5", "5/0/3", "3/0/9"]} * @throws Exception @@ -145,13 +141,8 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); String actualValues = rpcActualResultReadAll.get("value").asText(); - String expectedIdVer19_0_2 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2; - String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0; - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_9))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_0_2))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_1_0))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_19_0_0))); - // Send Observe composite with "/3/0/9" + assertTrue(actualValues.contains("[]")); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9); String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; @@ -167,9 +158,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); actualValues = rpcActualResultReadAll.get("value").asText(); assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_9))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_0_2))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_1_0))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_19_0_0))); } /** * Previous -> ["5/0/7", "5/0/5", "5/0/3"], CompositeObservation * @@ -206,12 +194,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); actualValues = rpcActualResultReadAll.get("value").asText(); - String expectedIdVer19_0_2 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2; - String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0; - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_9))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_0_2))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_1_0))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_19_0_0))); assertTrue(actualValues.contains("CompositeObservation:")); assertTrue(actualValues.contains(expectedId5_0_7)); assertTrue(actualValues.contains(expectedId5_0_5)); @@ -224,8 +206,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveCompositeAnyResources_Result_CONTENT_Value_LwM2mSingleResource_LwM2mMultipleResource() throws Exception { - sendObserveCancelAllWithAwait(deviceId); - String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; @@ -248,8 +228,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveCompositeWithKeyName_Result_CONTENT_Value_SingleResources() throws Exception { - sendObserveCancelAllWithAwait(deviceId); - String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9; String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14; String expectedKey19_0_0 = RESOURCE_ID_NAME_19_0_0; @@ -274,6 +252,7 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveCompositeWithKeyName_IfLeastOneResourceIsAlreadyRegistered_return_BadRequest() throws Exception { + sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9); String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9; String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14; String expectedKey19_0_0 = RESOURCE_ID_NAME_19_0_0; @@ -292,8 +271,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveReadAll_AfterbserveCancelAllAndCompositeObservation_Result_CONTENT_Value_CompositeObservation_Only() throws Exception { - sendObserveCancelAllWithAwait(deviceId); - String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9; String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14; String expectedKey19_0_0 = RESOURCE_ID_NAME_19_0_0; @@ -323,7 +300,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveCancelAllThenObserveCompositeAnyResources_Result_CONTENT_CancelObserveComposite_This_Result_Content_Count_1() throws Exception { - sendObserveCancelAllWithAwait(deviceId); // ObserveComposite String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; @@ -349,7 +325,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveCompositeFiveResources_Result_CONTENT_CancelObserveComposite_TwoAnyResource_Result_BadRequest() throws Exception { - sendObserveCancelAllWithAwait(deviceId); // ObserveComposite five String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; @@ -377,7 +352,6 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveOneObjectAnyResources_Result_CONTENT_Cancel_OneResourceFromObjectAnyResource_Result_BAD_REQUEST_Cancel_OneObject_Result_CONTENT() throws Exception { - sendObserveCancelAllWithAwait(deviceId); // ObserveComposite String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0; @@ -412,17 +386,25 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt String idVer_19_0_2 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2; String id_19_0_2 = fromVersionedIdToObjectId(idVer_19_0_2); - // 1 - "ObserveReadAll": at least one update value of all resources we observe - after connection + // 1 - Verify after start String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - String rpcActualVValuesReadAll = rpcActualResultReadAll.get("value").asText(); - ArrayNode rpcactualValues = JacksonUtil.fromString(rpcActualVValuesReadAll, ArrayNode.class); - assertEquals(rpcactualValues.size(), 4); - assertTrue(actualResultReadAll.contains("SingleObservation:" + id_3_0_9)); - assertTrue(actualResultReadAll.contains("SingleObservation:" + id_19_1_0)); - assertTrue(actualResultReadAll.contains("SingleObservation:" + id_19_0_2)); - assertTrue(actualResultReadAll.contains("SingleObservation:" + id_19_0_0)); + String actualValues = rpcActualResultReadAll.get("value").asText(); + assertTrue(actualValues.contains("[]")); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_19_0_0); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_19_1_0); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_19_0_2); + + actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); + rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); + actualValues = rpcActualResultReadAll.get("value").asText(); + assertTrue(actualValues.contains("SingleObservation:" + id_3_0_9)); + assertTrue(actualValues.contains("SingleObservation:" + id_19_1_0)); + assertTrue(actualValues.contains("SingleObservation:" + id_19_0_2)); + assertTrue(actualValues.contains("SingleObservation:" + id_19_0_0)); long initAttrTelemetryAtCount = countUpdateAttrTelemetryAll(); long initAttrTelemetryAtCount_3_0_9 = countUpdateAttrTelemetryResource(idVer_3_0_9); long initAttrTelemetryAtCount_19_0_0 = countUpdateAttrTelemetryResource(idVer_19_0_0); @@ -441,8 +423,8 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - rpcActualVValuesReadAll = rpcActualResultReadAll.get("value").asText(); - rpcactualValues = JacksonUtil.fromString(rpcActualVValuesReadAll, ArrayNode.class); + String rpcActualVValuesReadAll = rpcActualResultReadAll.get("value").asText(); + ArrayNode rpcactualValues = JacksonUtil.fromString(rpcActualVValuesReadAll, ArrayNode.class); assertEquals(rpcactualValues.size(), 0); // 2.1 - ObserveComposite: observeCancelAll verify" initAttrTelemetryAtCount = countUpdateAttrTelemetryAll(); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java index f34b32a97a..14a3044a59 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java @@ -22,6 +22,8 @@ import org.eclipse.leshan.core.link.Link; import org.eclipse.leshan.core.link.LinkParseException; import org.eclipse.leshan.core.node.LwM2mPath; import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.test.context.event.annotation.BeforeTestClass; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.transport.lwm2m.config.TbLwM2mVersion; @@ -30,8 +32,10 @@ import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTes import java.util.Arrays; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -44,6 +48,11 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegrationTest { + @BeforeEach + public void beforeTest () throws Exception { + testInit(); + } + /** * DiscoverAll * @@ -202,4 +211,10 @@ public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegration return null; } } + + public void testInit() throws Exception { + await("Update Registration at-least-once after start") + .atMost(50, TimeUnit.SECONDS) + .until(() -> countUpdateReg() > 0); + } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java index a665f7f53c..6518e3b2f0 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java @@ -20,14 +20,12 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.LwM2m.Version; import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.node.LwM2mPath; -import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.server.registration.Registration; +import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserveTest; -import java.util.Optional; - import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -45,13 +43,15 @@ import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fr @Slf4j public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationObserveTest { + @Before + public void setupObserveTest() throws Exception { + awaitObserveReadAll(4, deviceId); + } + + @Test public void testObserveReadAll_Count_4_CancelAll_Count_0_Ok() throws Exception { - String actualValuesReadAll = sendRpcObserveOkWithResultValue("ObserveReadAll", null); - assertEquals(4, actualValuesReadAll.split(",").length); sendObserveCancelAllWithAwait(deviceId); - actualValuesReadAll = sendRpcObserveOkWithResultValue("ObserveReadAll", null); - assertEquals("[]", actualValuesReadAll); } /** @@ -344,11 +344,5 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); return rpcActualResult.get("value").asText(); } - - private void sendRpcObserveWithContainsLwM2mSingleResource(String params) throws Exception { - String rpcActualResult = sendRpcObserveOkWithResultValue("Observe", params); - assertTrue(rpcActualResult.contains("LwM2mSingleResource")); - assertEquals(Optional.of(1).get(), Optional.ofNullable(getCntObserveAll(deviceId)).get()); - } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java index d76f2f5400..f1efa1feea 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java @@ -37,11 +37,6 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID @Slf4j public class RpcLwm2mIntegrationReadCollectedValueTest extends AbstractRpcLwM2MIntegrationTest { - @Before - public void startInitRPC() throws Exception { - initRpc(true); - } - /** * Read {"id":"/3303/12/5700"} * Trigger a Send operation from the client with multiple values for the same resource as a payload diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java index e787d26265..76df8ca449 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java @@ -25,6 +25,7 @@ import org.junit.Assert; import org.springframework.test.web.servlet.MvcResult; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.credentials.lwm2m.AbstractLwM2MClientSecurityCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; @@ -41,6 +42,7 @@ import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBo import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.PSKLwM2MBootstrapServerCredential; import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.RPKLwM2MBootstrapServerCredential; import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.X509LwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.dao.service.DaoSqlTest; @@ -203,8 +205,8 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M boolean isAwaitObserveReadAll, LwM2MClientState finishState, boolean isStartLw) throws Exception { - createDeviceProfile(transportConfiguration); - final Device device = createDevice(deviceCredentials, endpoint); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + endpoint, transportConfiguration); + final Device device = createLwm2mDevice(deviceCredentials, endpoint, deviceProfile.getId()); createNewClient(security, securityBs, true, endpoint); lwM2MTestClient.start(isStartLw); if (isAwaitObserveReadAll) { @@ -248,8 +250,8 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M Set expectedStatusesLwm2m, Set expectedStatusesBs) throws Exception { - createDeviceProfile(transportConfiguration); - final Device device = createDevice(deviceCredentials, endpoint); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + endpoint, transportConfiguration); + final Device device = createLwm2mDevice(deviceCredentials, endpoint, deviceProfile.getId()); String deviceIdStr = device.getId().getId().toString(); createNewClient(security, securityBs, true, endpoint); lwM2MTestClient.start(true); @@ -446,10 +448,10 @@ public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2M return bootstrapCredentials; } - protected MvcResult createDeviceWithMvcResult(LwM2MDeviceCredentials credentials, String endpoint) throws Exception { + protected MvcResult createDeviceWithMvcResult(LwM2MDeviceCredentials credentials, String endpoint, DeviceProfileId deviceProfileId) throws Exception { Device device = new Device(); device.setName(endpoint); - device.setDeviceProfileId(deviceProfile.getId()); + device.setDeviceProfileId(deviceProfileId); device.setTenantId(tenantId); device = doPost("/api/device", device, Device.class); Assert.assertNotNull(device); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java index 291149b7e8..5ff7fe4225 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/cid/AbstractSecurityLwM2MIntegrationDtlsCidLengthTest.java @@ -20,7 +20,7 @@ import org.eclipse.californium.elements.config.Configuration; import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpoint; import org.eclipse.leshan.client.californium.endpoint.CaliforniumClientEndpointsProvider; import org.junit.Assert; -import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; @@ -49,9 +49,8 @@ public abstract class AbstractSecurityLwM2MIntegrationDtlsCidLengthTest extends protected void basicTestConnectionDtlsCidLength(Integer clientDtlsCidLength, Integer serverDtlsCidLength) throws Exception { - createDeviceProfile(transportConfiguration); - final Device device = createDevice(deviceCredentials, clientEndpoint); - device.getId().getId().toString(); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + clientEndpoint, transportConfiguration); + createLwm2mDevice(deviceCredentials, clientEndpoint, deviceProfile.getId()); createNewClient(security, null, true, clientEndpoint, clientDtlsCidLength); lwM2MTestClient.start(true); await(awaitAlias) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/AbstractLwM2MIntegrationDiffPortTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/AbstractLwM2MIntegrationDiffPortTest.java index 9e9a38925c..83bf32081e 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/AbstractLwM2MIntegrationDiffPortTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/diffPort/AbstractLwM2MIntegrationDiffPortTest.java @@ -23,6 +23,7 @@ import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.registration.RegistrationUpdate; import org.junit.Assert; import org.springframework.boot.test.mock.mockito.SpyBean; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; @@ -59,8 +60,8 @@ public abstract class AbstractLwM2MIntegrationDiffPortTest extends AbstractSecur return invocation.callRealMethod(); }).when(registrationStoreTest).updateRegistration(any(RegistrationUpdate.class)); - createDeviceProfile(transportConfiguration); - createDevice(deviceCredentials, clientEndpoint); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + clientEndpoint, transportConfiguration); + createLwm2mDevice(deviceCredentials, clientEndpoint, deviceProfile.getId()); createNewClient(security, null, true, clientEndpoint); lwM2MTestClient.start(true); await(awaitAlias) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java index e5c9cbf469..ac1c0866de 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java @@ -20,6 +20,7 @@ import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.core.util.Hex; import org.junit.Test; import org.springframework.test.web.servlet.MvcResult; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; @@ -76,9 +77,9 @@ public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTes clientCredentials.setIdentity(identity); clientCredentials.setKey(keyPsk); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE)); - createDeviceProfile(transportConfiguration); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + clientEndpoint, transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false); - MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint); + MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint, deviceProfile.getId()); assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus()); String msgExpected = "Key must be HexDec format: 32, 64, 128 characters!"; assertTrue(result.getResponse().getContentAsString().contains(msgExpected)); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java index c33d4b5059..af0be89feb 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java @@ -21,6 +21,7 @@ import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.core.util.Hex; import org.junit.Test; import org.springframework.test.web.servlet.MvcResult; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredential; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; @@ -33,6 +34,7 @@ import static org.eclipse.leshan.client.object.Security.rpk; import static org.eclipse.leshan.client.object.Security.rpkBootstrap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.PSK; import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.RPK; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH; @@ -75,10 +77,11 @@ public class RpkLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTes RPKClientCredential clientCredentials = new RPKClientCredential(); clientCredentials.setEndpoint(clientEndpoint); clientCredentials.setKey(Hex.encodeHexString(certificate.getPublicKey().getEncoded())); - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(RPK, NONE)); + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE)); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + clientEndpoint, transportConfiguration); + LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, RPK, false); - createDeviceProfile(transportConfiguration); - MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint); + MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint, deviceProfile.getId()); assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus()); String msgExpected = "LwM2M client RPK key must be in standard [RFC7250] and support only EC algorithm and then encoded to Base64 format!"; assertTrue(result.getResponse().getContentAsString().contains(msgExpected)); @@ -92,10 +95,10 @@ public class RpkLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTes RPKClientCredential clientCredentials = new RPKClientCredential(); clientCredentials.setEndpoint(clientEndpoint); clientCredentials.setKey(Base64.encodeBase64String(certificate.getPublicKey().getEncoded())); - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(RPK, NONE)); + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE)); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + clientEndpoint, transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, RPK, true); - createDeviceProfile(transportConfiguration); - MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint); + MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint, deviceProfile.getId()); assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus()); String msgExpected = "Bootstrap server client RPK secret key must be in PKCS#8 format (DER encoding, standard [RFC5958]) and then encoded to Base64 format!"; assertTrue(result.getResponse().getContentAsString().contains(msgExpected)); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java index 072dea9a6e..743c007fdb 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java @@ -20,6 +20,7 @@ import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.core.util.Hex; import org.junit.Test; import org.springframework.test.web.servlet.MvcResult; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; @@ -33,6 +34,7 @@ import static org.eclipse.leshan.client.object.Security.x509; import static org.eclipse.leshan.client.object.Security.x509Bootstrap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.PSK; import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.X509; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH; @@ -76,10 +78,10 @@ public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MInteg X509ClientCredential clientCredentials = new X509ClientCredential(); clientCredentials.setEndpoint(clientEndpoint); clientCredentials.setCert(Hex.encodeHexString(certificate.getEncoded())); - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, NONE)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false); - createDeviceProfile(transportConfiguration); - MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint); + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE)); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + clientEndpoint, transportConfiguration); + MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint, deviceProfile.getId()); assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus()); String msgExpected = "LwM2M client X509 certificate must be in DER-encoded X509v3 format and support only EC algorithm and then encoded to Base64 format!"; assertTrue(result.getResponse().getContentAsString().contains(msgExpected)); @@ -93,10 +95,10 @@ public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MInteg X509ClientCredential clientCredentials = new X509ClientCredential(); clientCredentials.setEndpoint(clientEndpoint); clientCredentials.setCert(Base64.getEncoder().encodeToString(certificate.getEncoded())); - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, NONE)); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, true); - createDeviceProfile(transportConfiguration); - MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint); + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE)); + DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + clientEndpoint, transportConfiguration); + MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint, deviceProfile.getId()); assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus()); String msgExpected = "Bootstrap server client X509 secret key must be in PKCS#8 format (DER encoding, standard [RFC5958]) and then encoded to Base64 format!"; assertTrue(result.getResponse().getContentAsString().contains(msgExpected)); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index 5672ae33c3..713d5b9fa8 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -449,7 +449,7 @@ public class LwM2mClient { } public LwM2m.Version getSupportedObjectVersion(Integer objectid) { - return this.supportedClientObjects.get(objectid); + return this.supportedClientObjects != null ? this.supportedClientObjects.get(objectid) : null; } private void setSupportedClientObjects(){ From 5f7e8db05d72d7356eb4218664e3f035e9222ea5 Mon Sep 17 00:00:00 2001 From: ViacheslavKlimov Date: Fri, 11 Oct 2024 13:42:54 +0300 Subject: [PATCH 17/25] Hide mail sender error details --- .../server/controller/AdminController.java | 10 +++++++- .../server/controller/AuthController.java | 8 +++++-- .../server/controller/BaseController.java | 2 +- .../server/controller/UserController.java | 6 ++++- .../entitiy/user/DefaultUserService.java | 2 +- .../service/mail/DefaultMailService.java | 23 +++++++++---------- .../mfa/provider/impl/EmailTwoFaProvider.java | 6 ++++- 7 files changed, 38 insertions(+), 19 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java index a2115360d8..d65cc58d87 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java @@ -235,7 +235,15 @@ public class AdminController extends BaseController { } } String email = getCurrentUser().getEmail(); - mailService.sendTestMail(adminSettings.getJsonValue(), email); + try { + mailService.sendTestMail(adminSettings.getJsonValue(), email); + } catch (ThingsboardException e) { + String error = e.getMessage(); + if (e.getCause() != null) { + error += ": " + e.getCause().getMessage(); // showing actual underlying error for testing purposes + } + throw new ThingsboardException(error, e.getErrorCode()); + } } } diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index 17f7930eff..254f5ed013 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -215,7 +215,7 @@ public class AuthController extends BaseController { try { mailService.sendAccountActivatedEmail(loginUrl, email); } catch (Exception e) { - log.info("Unable to send account activation email [{}]", e.getMessage()); + log.warn("Unable to send account activation email [{}]", e.getMessage()); } } @@ -254,7 +254,11 @@ public class AuthController extends BaseController { String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); String loginUrl = String.format("%s/login", baseUrl); String email = user.getEmail(); - mailService.sendPasswordWasResetEmail(loginUrl, email); + try { + mailService.sendPasswordWasResetEmail(loginUrl, email); + } catch (Exception e) { + log.warn("Couldn't send password was reset email: {}", e.getMessage()); + } eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId())); diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index d266c7e6cc..4f60b2f6a1 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -398,7 +398,7 @@ public abstract class BaseController { || exception instanceof DataValidationException || cause instanceof IncorrectParameterException) { return new ThingsboardException(exception.getMessage(), ThingsboardErrorCode.BAD_REQUEST_PARAMS); } else if (exception instanceof MessagingException) { - return new ThingsboardException("Unable to send mail: " + exception.getMessage(), ThingsboardErrorCode.GENERAL); + return new ThingsboardException("Unable to send mail", ThingsboardErrorCode.GENERAL); } else if (exception instanceof AsyncRequestTimeoutException) { return new ThingsboardException("Request timeout", ThingsboardErrorCode.GENERAL); } else if (exception instanceof DataAccessException) { diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index a71bd4dd38..d6f69f02d5 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -230,7 +230,11 @@ public class UserController extends BaseController { accessControlService.checkPermission(securityUser, Resource.USER, Operation.READ, user.getId(), user); UserActivationLink activationLink = tbUserService.getActivationLink(securityUser.getTenantId(), securityUser.getCustomerId(), user.getId(), request); - mailService.sendActivationEmail(activationLink.value(), activationLink.ttlMs(), email); + try { + mailService.sendActivationEmail(activationLink.value(), activationLink.ttlMs(), email); + } catch (Exception e) { + throw new ThingsboardException("Couldn't send user activation email", ThingsboardErrorCode.GENERAL); + } } @ApiOperation(value = "Get activation link (getActivationLink)", diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java index ad4b7c097e..4e9e8e954c 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java @@ -60,7 +60,7 @@ public class DefaultUserService extends AbstractTbEntityService implements TbUse mailService.sendActivationEmail(activationLink.value(), activationLink.ttlMs(), savedUser.getEmail()); } catch (ThingsboardException e) { userService.deleteUser(tenantId, savedUser); - throw e; + throw new ThingsboardException("Couldn't send user activation email", ThingsboardErrorCode.GENERAL); } } logEntityActionService.logEntityAction(tenantId, savedUser.getId(), savedUser, customerId, actionType, user); diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 1603720450..d850dca43d 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -443,17 +443,16 @@ public class DefaultMailService implements MailService { } } - private void sendMailWithTimeout(JavaMailSender mailSender, MimeMessage msg, long timeout) { + private void sendMailWithTimeout(JavaMailSender mailSender, MimeMessage msg, long timeout) throws ThingsboardException { var submittedMail = Futures.withTimeout( mailExecutorService.submit(() -> mailSender.send(msg)), timeout, TimeUnit.MILLISECONDS, timeoutScheduler); try { submittedMail.get(timeout, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { - log.debug("Error during mail submission", e); throw new RuntimeException("Timeout!"); } catch (Exception e) { - throw new RuntimeException(ExceptionUtils.getRootCause(e)); + throw new ThingsboardException("Unable to send mail", ExceptionUtils.getRootCause(e), ThingsboardErrorCode.GENERAL); } } @@ -463,20 +462,20 @@ public class DefaultMailService implements MailService { Template template = freemarkerConfig.getTemplate(templateLocation); return FreeMarkerTemplateUtils.processTemplateIntoString(template, model); } catch (Exception e) { - throw handleException(e); + log.warn("Failed to process mail template: {}", ExceptionUtils.getRootCauseMessage(e)); + throw new ThingsboardException("Failed to process mail template: " + e.getMessage(), e, ThingsboardErrorCode.GENERAL); } } - protected ThingsboardException handleException(Exception exception) { - String message; + protected ThingsboardException handleException(Throwable exception) { + if (exception instanceof ThingsboardException thingsboardException) { + return thingsboardException; + } if (exception instanceof NestedRuntimeException) { - message = ((NestedRuntimeException) exception).getMostSpecificCause().getMessage(); - } else { - message = exception.getMessage(); + exception = ((NestedRuntimeException) exception).getMostSpecificCause(); } - log.warn("Unable to send mail: {}", message); - return new ThingsboardException(String.format("Unable to send mail: %s", message), - ThingsboardErrorCode.GENERAL); + log.warn("Unable to send mail: {}", exception.getMessage()); + return new ThingsboardException("Unable to send mail: " + exception.getMessage(), ThingsboardErrorCode.GENERAL); } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/EmailTwoFaProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/EmailTwoFaProvider.java index 4add550090..7cdbc9d54f 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/EmailTwoFaProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/mfa/provider/impl/EmailTwoFaProvider.java @@ -57,7 +57,11 @@ public class EmailTwoFaProvider extends OtpBasedTwoFaProvider Date: Fri, 11 Oct 2024 14:26:19 +0300 Subject: [PATCH 18/25] coap: fix bug coap_processTwoWayRpcTest --- .../rpc/AbstractCoapServerSideRpcIntegrationTest.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java index 384bfe4772..c43d90d8af 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java @@ -114,6 +114,13 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC CoapTestCallback callbackCoap = new TestCoapCallbackForRPC(client, false, protobuf); CoapObserveRelation observeRelation = client.getObserveRelation(callbackCoap); + String awaitAlias = "await Two Way Rpc (client.getObserveRelation)"; + await(awaitAlias) + .atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS) + .until(() -> processTwoWayRpcTestWithAwait(callbackCoap, observeRelation, expectedResponseResult)); + } + + private boolean processTwoWayRpcTestWithAwait(CoapTestCallback callbackCoap, CoapObserveRelation observeRelation, String expectedResponseResult) throws Exception { String awaitAlias = "await Two Way Rpc (client.getObserveRelation)"; await(awaitAlias) .atMost(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS) @@ -146,7 +153,7 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC validateTwoWayStateChangedNotification(callbackCoap, expectedResponseResult, actualResult); observeRelation.proactiveCancel(); - assertTrue(observeRelation.isCanceled()); + return observeRelation.isCanceled(); } protected void processOnLoadResponse(CoapResponse response, CoapTestClient client) { From 8ac3e5c7e1511e9a0c4d87ce5a43544a0cdeaae6 Mon Sep 17 00:00:00 2001 From: nick Date: Sun, 13 Oct 2024 10:05:33 +0300 Subject: [PATCH 19/25] coap: fix bug lwm2m_fix_bug_test_CollectedValue --- .../lwm2m/client/LwM2MTestClient.java | 6 +- .../lwm2m/client/LwM2mTemperatureSensor.java | 47 ++++---- .../rpc/AbstractRpcLwM2MIntegrationTest.java | 33 ++++-- ...wm2mIntegrationReadCollectedValueTest.java | 105 ++++++++++++++++++ .../rpc/sql/RpcLwm2mIntegrationReadTest.java | 69 ------------ 5 files changed, 160 insertions(+), 100 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java index 655edc6db6..796f3e09c5 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -137,6 +137,8 @@ public class LwM2MTestClient { private Map clientDtlsCid; private LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest; private LwM2mClientContext clientContext; + private LwM2mTemperatureSensor lwM2mTemperatureSensor12; + public void init(Security security, Security securityBs, int port, boolean isRpc, LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler, LwM2mClientContext clientContext, boolean isWriteAttribute, Integer cIdLength, boolean queueMode, @@ -189,7 +191,7 @@ public class LwM2MTestClient { locationParams.getPos(); initializer.setInstancesForObject(LOCATION, new LwM2mLocation(locationParams.getLatitude(), locationParams.getLongitude(), locationParams.getScaleFactor(), executor, OBJECT_INSTANCE_ID_0)); LwM2mTemperatureSensor lwM2mTemperatureSensor0 = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_0); - LwM2mTemperatureSensor lwM2mTemperatureSensor12 = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_12); + lwM2mTemperatureSensor12 = new LwM2mTemperatureSensor(executor, OBJECT_INSTANCE_ID_12); initializer.setInstancesForObject(TEMPERATURE_SENSOR, lwM2mTemperatureSensor0, lwM2mTemperatureSensor12); List enablers = initializer.createAll(); @@ -315,7 +317,6 @@ public class LwM2MTestClient { clientDtlsCid = new HashMap<>(); clientStates.add(ON_INIT); leshanClient = builder.build(); - lwM2mTemperatureSensor12.setLeshanClient(leshanClient); LwM2mClientObserver observer = new LwM2mClientObserver() { @Override @@ -452,6 +453,7 @@ public class LwM2MTestClient { if (isStartLw) { this.awaitClientAfterStartConnectLw(); } + lwM2mTemperatureSensor12.setLeshanClient(leshanClient); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java index 4f594ed4c0..657216196a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mTemperatureSensor.java @@ -37,6 +37,7 @@ import java.util.List; import java.util.Random; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; + import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS; @@ -50,14 +51,17 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr private double maxMeasuredValue = currentTemp; private LeshanClient leshanClient; - private int cntRead_5700; private int cntIdentitySystem; protected static final Random RANDOM = new Random(); private static final List supportedResources = Arrays.asList(5601, 5602, 5700, 5701); - public LwM2mTemperatureSensor() { + private LwM2mServer registeredServer; + private ManualDataSender sender; + + private int resourceIdForSendCollected = 5700; + public LwM2mTemperatureSensor() { } public LwM2mTemperatureSensor(ScheduledExecutorService executorService, Integer id) { @@ -72,26 +76,33 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr @Override public synchronized ReadResponse read(LwM2mServer identity, int resourceId) { - log.info("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + log.trace("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + if (this.registeredServer == null) { + try { + Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0 = Instant.now().toEpochMilli(); + this.registeredServer = this.leshanClient.getRegisteredServers().values().iterator().next(); + this.sender = (ManualDataSender) this.leshanClient.getSendService().getDataSender(ManualDataSender.DEFAULT_NAME); + this.sender.collectData(Arrays.asList(getPathForCollectedValue(resourceIdForSendCollected))); + } catch (Exception e) { + log.error("[{}] Sender for SendCollected", e.toString()); + e.printStackTrace(); + } + } switch (resourceId) { case 5601: return ReadResponse.success(resourceId, getTwoDigitValue(minMeasuredValue)); case 5602: return ReadResponse.success(resourceId, getTwoDigitValue(maxMeasuredValue)); case 5700: - if (identity == LwM2mServer.SYSTEM) { // return value for ForCollectedValue + if (identity == LwM2mServer.SYSTEM) { + double val5700 = cntIdentitySystem == 0 ? RESOURCE_ID_3303_12_5700_VALUE_0 : RESOURCE_ID_3303_12_5700_VALUE_1; cntIdentitySystem++; - return ReadResponse.success(resourceId, cntIdentitySystem == 1 ? - RESOURCE_ID_3303_12_5700_VALUE_0 : RESOURCE_ID_3303_12_5700_VALUE_1); - } - cntRead_5700++; - if (cntRead_5700 == 1) { // read value after start - return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp)); + return ReadResponse.success(resourceId, val5700); } else { - if (this.getId() == 12 && this.leshanClient != null) { + if (cntIdentitySystem == 1 && this.getId() == 12 && this.leshanClient != null) { sendCollected(); } - return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp)); + return super.read(identity, resourceId); } case 5701: return ReadResponse.success(resourceId, UNIT_CELSIUS); @@ -163,14 +174,10 @@ public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destr private void sendCollected() { try { - int resourceId = 5700; - LwM2mServer registeredServer = this.leshanClient.getRegisteredServers().values().iterator().next(); - ManualDataSender sender = this.leshanClient.getSendService().getDataSender(ManualDataSender.DEFAULT_NAME, - ManualDataSender.class); - sender.collectData(Arrays.asList(getPathForCollectedValue(resourceId))); - Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0 = Instant.now().toEpochMilli(); - Thread.sleep(RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS); - sender.collectData(Arrays.asList(getPathForCollectedValue(resourceId))); + if ((Instant.now().toEpochMilli() - Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0) < RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS) { + Thread.sleep(RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS); + } + sender.collectData(Arrays.asList(getPathForCollectedValue(resourceIdForSendCollected))); Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_1 = Instant.now().toEpochMilli(); sender.sendCollectedData(registeredServer, ContentFormat.SENML_JSON, 1000, false); } catch (InterruptedException e) { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java index d0b86fdab0..bd3ca0642a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java @@ -71,7 +71,7 @@ import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fr public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { protected final LinkParser linkParser = new DefaultLwM2mLinkParser(); - protected String OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC; + protected String CONFIG_PROFILE_WITH_PARAMS_RPC; public Set expectedObjects; public Set expectedObjectIdVers; public Set expectedInstances; @@ -116,10 +116,10 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg if (this.getClass().getSimpleName().equals("RpcLwm2mIntegrationWriteCborTest")){ supportFormatOnly_SenMLJSON_SenMLCBOR = true; } - initRpc(); + initRpc(false); } - private void initRpc () throws Exception { + protected void initRpc(boolean isCollected) throws Exception { String endpoint = DEVICE_ENDPOINT_RPC_PREF + endpointSequence.incrementAndGet(); createNewClient(SECURITY_NO_SEC, null, true, endpoint); expectedObjects = ConcurrentHashMap.newKeySet(); @@ -157,15 +157,14 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg id_3_0_9 = fromVersionedIdToObjectId(idVer_3_0_9); idVer_19_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0; - OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC = + String ATTRIBUTES_TELEMETRY_WITH_PARAMS_RPC_WITH_OBSERVE = " {\n" + " \"keyName\": {\n" + " \"" + idVer_3_0_9 + "\": \"" + RESOURCE_ID_NAME_3_9 + "\",\n" + " \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\": \"" + RESOURCE_ID_NAME_3_14 + "\",\n" + " \"" + idVer_19_0_0 + "\": \"" + RESOURCE_ID_NAME_19_0_0 + "\",\n" + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\": \"" + RESOURCE_ID_NAME_19_1_0 + "\",\n" + - " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2 + "\": \"" + RESOURCE_ID_NAME_19_0_2 + "\",\n" + - " \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\": \"" + RESOURCE_ID_NAME_3303_12_5700 + "\"\n" + + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2 + "\": \"" + RESOURCE_ID_NAME_19_0_2 + "\"\n" + " },\n" + " \"observe\": [\n" + " \"" + idVer_3_0_9 + "\",\n" + @@ -180,13 +179,29 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg " \"telemetry\": [\n" + " \"" + idVer_3_0_9 + "\",\n" + " \"" + idVer_19_0_0 + "\",\n" + - " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\",\n" + - " \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\"\n" + + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\"\n" + " ],\n" + " \"attributeLwm2m\": {}\n" + " }"; - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC, getBootstrapServerCredentialsNoSec(NONE)); + String TELEMETRY_WITH_PARAMS_RPC_WITHOUT_OBSERVE = + " {\n" + + " \"keyName\": {\n" + + " \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\": \"" + RESOURCE_ID_NAME_3303_12_5700 + "\"\n" + + " },\n" + + " \"observe\": [\n" + + " ],\n" + + " \"attribute\": [\n" + + " ],\n" + + " \"telemetry\": [\n" + + " \"" + objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + RESOURCE_ID_5700 + "\"\n" + + " ],\n" + + " \"attributeLwm2m\": {}\n" + + " }" ; + + CONFIG_PROFILE_WITH_PARAMS_RPC = isCollected ? TELEMETRY_WITH_PARAMS_RPC_WITHOUT_OBSERVE : ATTRIBUTES_TELEMETRY_WITH_PARAMS_RPC_WITH_OBSERVE; + + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(CONFIG_PROFILE_WITH_PARAMS_RPC, getBootstrapServerCredentialsNoSec(NONE)); createDeviceProfile(transportConfiguration); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(endpoint)); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java new file mode 100644 index 0000000000..3281d5f78c --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadCollectedValueTest.java @@ -0,0 +1,105 @@ +/** + * Copyright © 2016-2024 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.lwm2m.rpc.sql; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; + +import java.util.concurrent.atomic.AtomicReference; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.awaitility.Awaitility.await; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_1; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_0; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_1; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3303_12_5700; +import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS; + +@Slf4j +public class RpcLwm2mIntegrationReadCollectedValueTest extends AbstractRpcLwM2MIntegrationTest { + + @Before + public void startInitRPC() throws Exception { + initRpc(true); + } + + /** + * Read {"id":"/3303/12/5700"} + * Trigger a Send operation from the client with multiple values for the same resource as a payload + * acked "[{"bn":"/3303/12/5700","bt":1724".. 116 bytes] + * 2 values for the resource /3303/12/5700 should be stored with: + * - timestamps1 = Instance.now() + RESOURCE_ID_VALUE_3303_12_5700_1 + * - timestamps2 = (timestamps1 + 3 sec) + RESOURCE_ID_VALUE_3303_12_5700_2 + * @throws Exception + */ + @Test + public void testReadSingleResource_sendFromClient_CollectedValue() throws Exception { + // init test + int cntValues = 2; + int resourceId = 5700; + String expectedIdVer = objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + resourceId; + sendRPCById(expectedIdVer); + + // verify time start/end send CollectedValue; + await().atMost(40, SECONDS).until(() -> RESOURCE_ID_3303_12_5700_TS_0 > 0 + && RESOURCE_ID_3303_12_5700_TS_1 > 0); + + // verify result read: verify count value: 1-2: send CollectedValue; + AtomicReference actualValues = new AtomicReference<>(); + await().atMost(40, SECONDS).until(() -> { + actualValues.set(doGetAsync( + "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" + + RESOURCE_ID_NAME_3303_12_5700 + + "&startTs=" + (RESOURCE_ID_3303_12_5700_TS_0 - RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS) + + "&endTs=" + (RESOURCE_ID_3303_12_5700_TS_1 + RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS) + + "&interval=0&limit=100&useStrictDataTypes=false", + ObjectNode.class)); + return actualValues.get() != null && actualValues.get().size() > 0 + && actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700).size() >= cntValues && verifyTs(actualValues); + }); + } + + private boolean verifyTs(AtomicReference actualValues) { + String expectedVal_0 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_0); + String expectedVal_1 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_1); + ArrayNode actual = (ArrayNode) actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700); + long actualTS0 = 0; + long actualTS1 = 0; + for (JsonNode tsNode : actual) { + if (tsNode.get("value").asText().equals(expectedVal_0)) { + actualTS0 = tsNode.get("ts").asLong(); + } else if (tsNode.get("value").asText().equals(expectedVal_1)) { + actualTS1 = tsNode.get("ts").asLong(); + } + } + return actualTS0 >= RESOURCE_ID_3303_12_5700_TS_0 + && actualTS1 <= RESOURCE_ID_3303_12_5700_TS_1 + && (actualTS1 - actualTS0) >= RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS; + } + + private String sendRPCById(String path) throws Exception { + String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + } +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java index 1ab4893ec4..5dfe06b6f4 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java @@ -15,23 +15,14 @@ */ package org.thingsboard.server.transport.lwm2m.rpc.sql; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.collections4.map.HashedMap; import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.node.LwM2mPath; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; -import java.time.Instant; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; - -import static java.util.concurrent.TimeUnit.SECONDS; -import static org.awaitility.Awaitility.await; import static org.eclipse.leshan.core.LwM2mId.SERVER; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -39,24 +30,17 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_11; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_0; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_TS_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_3; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3303_12_5700; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_0; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3303_12_5700_VALUE_1; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS; @Slf4j public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest { @@ -228,59 +212,6 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest assertTrue(actualValues.contains(expected19_1_0)); } - - /** - * Read {"id":"/3303/12/5700"} - * Trigger a Send operation from the client with multiple values for the same resource as a payload - * acked "[{"bn":"/3303/12/5700","bt":1724".. 116 bytes] - * 2 values for the resource /3303/12/5700 should be stored with: - * - timestamps1 = Instance.now() + RESOURCE_ID_VALUE_3303_12_5700_1 - * - timestamps2 = (timestamps1 + 3 sec) + RESOURCE_ID_VALUE_3303_12_5700_2 - * @throws Exception - */ - @Test - public void testReadSingleResource_sendFromClient_CollectedValue() throws Exception { - // init test - long startTs = Instant.now().toEpochMilli(); - int cntValues = 4; - int resourceId = 5700; - String expectedIdVer = objectIdVer_3303 + "/" + OBJECT_INSTANCE_ID_12 + "/" + resourceId; - sendRPCById(expectedIdVer); - // verify result read: verify count value: 1-2: send CollectedValue; 3 - response for read; - long endTs = Instant.now().toEpochMilli() + RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS * 4; - String expectedVal_1 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_0); - String expectedVal_2 = String.valueOf(RESOURCE_ID_3303_12_5700_VALUE_1); - AtomicReference actualValues = new AtomicReference<>(); - await().atMost(40, SECONDS).until(() -> { - actualValues.set(doGetAsync( - "/api/plugins/telemetry/DEVICE/" + deviceId + "/values/timeseries?keys=" - + RESOURCE_ID_NAME_3303_12_5700 - + "&startTs=" + startTs - + "&endTs=" + endTs - + "&interval=0&limit=100&useStrictDataTypes=false", - ObjectNode.class)); - // verify cntValues - return actualValues.get() != null && actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700).size() == cntValues; - }); - // verify ts - ArrayNode actual = (ArrayNode) actualValues.get().get(RESOURCE_ID_NAME_3303_12_5700); - Map keyTsMaps = new HashedMap(); - for (JsonNode tsNode: actual) { - if (tsNode.get("value").asText().equals(expectedVal_1) || tsNode.get("value").asText().equals(expectedVal_2)) { - keyTsMaps.put(tsNode.get("value").asText(), tsNode.get("ts").asLong()); - } - } - assertTrue(keyTsMaps.size() == 2); - long actualTS0 = keyTsMaps.get(expectedVal_1).longValue(); - long actualTS1 = keyTsMaps.get(expectedVal_2).longValue(); - assertTrue(actualTS0 > 0); - assertTrue(actualTS1 > 0); - assertTrue(actualTS1 > actualTS0); - assertTrue((actualTS1 - actualTS0) >= RESOURCE_ID_VALUE_3303_12_5700_DELTA_TS); - assertTrue(actualTS0 <= RESOURCE_ID_3303_12_5700_TS_0); - assertTrue(actualTS1 <= RESOURCE_ID_3303_12_5700_TS_1); - } - /** * ReadComposite {"keys":["batteryLevel", "UtfOffset", "dataDescription"]} */ From cbb9d621a73918ffa3982a81b15d61c7b743b2ac Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 16 Oct 2024 01:45:41 +0200 Subject: [PATCH 20/25] automaticaly set schema version based on app version --- .../install/SqlTsDatabaseSchemaService.java | 27 ++++++++++++++++--- .../main/resources/sql/schema-entities.sql | 12 --------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java index 66af786c83..48ae646480 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java @@ -15,8 +15,10 @@ */ package org.thingsboard.server.service.install; -import org.springframework.beans.factory.annotation.Value; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.info.BuildProperties; import org.springframework.context.annotation.Profile; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.thingsboard.server.dao.util.SqlTsDao; @@ -25,8 +27,11 @@ import org.thingsboard.server.dao.util.SqlTsDao; @Profile("install") public class SqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService implements TsDatabaseSchemaService { - @Value("${sql.postgres.ts_key_value_partitioning:MONTHS}") - private String partitionType; + @Autowired + private BuildProperties buildProperties; + + @Autowired + private JdbcTemplate jdbcTemplate; public SqlTsDatabaseSchemaService() { super("schema-ts-psql.sql", null); @@ -36,5 +41,21 @@ public class SqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService public void createDatabaseSchema() throws Exception { super.createDatabaseSchema(); executeQuery("CREATE TABLE IF NOT EXISTS ts_kv_indefinite PARTITION OF ts_kv DEFAULT;"); + + Long schemaVersion = jdbcTemplate.queryForList("SELECT schema_version FROM tb_schema_settings", Long.class).stream().findFirst().orElse(null); + + if (schemaVersion == null) { + jdbcTemplate.execute("INSERT INTO tb_schema_settings (schema_version) VALUES (" + getSchemaVersion() + ")"); + } + } + + private int getSchemaVersion() { + String[] versionParts = buildProperties.getVersion().replaceAll("[^\\d.]", "").split("\\."); + + int major = Integer.parseInt(versionParts[0]); + int minor = Integer.parseInt(versionParts[1]); + int patch = versionParts.length > 2 ? Integer.parseInt(versionParts[2]) : 0; + + return major * 1000000 + minor * 1000 + patch; } } \ No newline at end of file diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 9c95f385f8..13d50beaa1 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -20,18 +20,6 @@ CREATE TABLE IF NOT EXISTS tb_schema_settings CONSTRAINT tb_schema_settings_pkey PRIMARY KEY (schema_version) ); -CREATE OR REPLACE PROCEDURE insert_tb_schema_settings() - LANGUAGE plpgsql AS -$$ -BEGIN - IF (SELECT COUNT(*) FROM tb_schema_settings) = 0 THEN - INSERT INTO tb_schema_settings (schema_version) VALUES (3006004); - END IF; -END; -$$; - -call insert_tb_schema_settings(); - CREATE TABLE IF NOT EXISTS admin_settings ( id uuid NOT NULL CONSTRAINT admin_settings_pkey PRIMARY KEY, tenant_id uuid NOT NULL, From 4fe05fded352c4f504072f1cb7eb0f8afd07635c Mon Sep 17 00:00:00 2001 From: Artem Dzhereleiko Date: Thu, 17 Oct 2024 10:27:10 +0300 Subject: [PATCH 21/25] UI: Fixed row and column auto calc --- .../home/components/widget/lib/scada/scada-symbol.models.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts index c6be0f46d1..41611d3f6e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts @@ -220,8 +220,8 @@ export interface ScadaSymbolMetadata { export const emptyMetadata = (width?: number, height?: number): ScadaSymbolMetadata => ({ title: '', - widgetSizeX: width ? width/100 : 3, - widgetSizeY: height ? height/100 : 3, + widgetSizeX: width ? Math.max(Math.round(width/100), 1) : 3, + widgetSizeY: height ? Math.max(Math.round(height/100), 1) : 3, tags: [], behavior: [], properties: [] From eb796351aa49b2f9454088cfb88bca8856cc343b Mon Sep 17 00:00:00 2001 From: dashevchenko Date: Thu, 17 Oct 2024 12:26:35 +0300 Subject: [PATCH 22/25] updated postgres from 15 to 16 --- .../install/centos/instructions.md | 22 +++++++++---------- .../install/docker/instructions.md | 2 +- .../install/ubuntu/instructions.md | 2 +- .../instructions/upgrade/docker/upgrade_db.md | 2 +- docker/docker-compose.hybrid.yml | 2 +- docker/docker-compose.postgres.yml | 2 +- msa/tb/docker-cassandra/Dockerfile | 2 +- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/application/src/main/data/json/edge/instructions/install/centos/instructions.md b/application/src/main/data/json/edge/instructions/install/centos/instructions.md index bcc1967dfd..86c5acad56 100644 --- a/application/src/main/data/json/edge/instructions/install/centos/instructions.md +++ b/application/src/main/data/json/edge/instructions/install/centos/instructions.md @@ -56,13 +56,13 @@ sudo yum update sudo yum -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm # Install packages sudo yum -y install epel-release yum-utils -sudo yum-config-manager --enable pgdg15 -sudo yum install postgresql15-server postgresql15 +sudo yum-config-manager --enable pgdg16 +sudo yum install postgresql16-server postgresql16 postgresql16-contrib # Initialize your PostgreSQL DB -sudo /usr/pgsql-15/bin/postgresql-15-setup initdb -sudo systemctl start postgresql-15 +sudo /usr/pgsql-16/bin/postgresql-16-setup initdb +sudo systemctl start postgresql-16 # Optional: Configure PostgreSQL to start on boot -sudo systemctl enable --now postgresql-15 +sudo systemctl enable --now postgresql-16 {:copy-code} ``` @@ -74,12 +74,12 @@ sudo systemctl enable --now postgresql-15 sudo yum -y install https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm # Install packages sudo dnf -qy module disable postgresql -sudo dnf -y install postgresql15 postgresql15-server +sudo dnf -y install postgresql16 postgresql16-server postgresql16-contrib # Initialize your PostgreSQL DB -sudo /usr/pgsql-15/bin/postgresql-15-setup initdb -sudo systemctl start postgresql-15 +sudo /usr/pgsql-16/bin/postgresql-16-setup initdb +sudo systemctl start postgresql-16 # Optional: Configure PostgreSQL to start on boot -sudo systemctl enable --now postgresql-15 +sudo systemctl enable --now postgresql-16 {:copy-code} ``` @@ -101,7 +101,7 @@ After configuring the password, edit the pg_hba.conf to use MD5 authentication w Edit pg_hba.conf file: ```bash -sudo nano /var/lib/pgsql/15/data/pg_hba.conf +sudo nano /var/lib/pgsql/16/data/pg_hba.conf {:copy-code} ``` @@ -121,7 +121,7 @@ host all all 127.0.0.1/32 md5 Finally, you should restart the PostgreSQL service to initialize the new configuration: ```bash -sudo systemctl restart postgresql-15.service +sudo systemctl restart postgresql-16.service {:copy-code} ``` diff --git a/application/src/main/data/json/edge/instructions/install/docker/instructions.md b/application/src/main/data/json/edge/instructions/install/docker/instructions.md index 9910124b3e..23f484f2ec 100644 --- a/application/src/main/data/json/edge/instructions/install/docker/instructions.md +++ b/application/src/main/data/json/edge/instructions/install/docker/instructions.md @@ -38,7 +38,7 @@ services: ${EXTRA_HOSTS} postgres: restart: always - image: "postgres:15" + image: "postgres:16" ports: - "5432" environment: diff --git a/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md b/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md index fe9601443f..992b5e2ee2 100644 --- a/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md +++ b/application/src/main/data/json/edge/instructions/install/ubuntu/instructions.md @@ -49,7 +49,7 @@ echo "deb http://apt.postgresql.org/pub/repos/apt/ ${RELEASE}"-pgdg main | sudo # install and launch the postgresql service: sudo apt update -sudo apt -y install postgresql-15 +sudo apt -y install postgresql-16 sudo service postgresql start {:copy-code} ``` diff --git a/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md b/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md index d922fa9155..a594ebe4e7 100644 --- a/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md +++ b/application/src/main/data/json/edge/instructions/upgrade/docker/upgrade_db.md @@ -21,7 +21,7 @@ services: entrypoint: upgrade-tb-edge.sh postgres: restart: always - image: "postgres:15" + image: "postgres:16" ports: - "5432" environment: diff --git a/docker/docker-compose.hybrid.yml b/docker/docker-compose.hybrid.yml index 43f1e81cd1..5bffa9fb1d 100644 --- a/docker/docker-compose.hybrid.yml +++ b/docker/docker-compose.hybrid.yml @@ -19,7 +19,7 @@ version: '3.0' services: postgres: restart: always - image: "postgres:15" + image: "postgres:16" ports: - "5432" environment: diff --git a/docker/docker-compose.postgres.yml b/docker/docker-compose.postgres.yml index 780d2ccefd..50844fa2eb 100644 --- a/docker/docker-compose.postgres.yml +++ b/docker/docker-compose.postgres.yml @@ -19,7 +19,7 @@ version: '3.0' services: postgres: restart: always - image: "postgres:15" + image: "postgres:16" ports: - "5432" environment: diff --git a/msa/tb/docker-cassandra/Dockerfile b/msa/tb/docker-cassandra/Dockerfile index 18071b249b..27613fe161 100644 --- a/msa/tb/docker-cassandra/Dockerfile +++ b/msa/tb/docker-cassandra/Dockerfile @@ -16,7 +16,7 @@ FROM thingsboard/openjdk17:bookworm-slim -ENV PG_MAJOR=15 +ENV PG_MAJOR=16 ENV DATA_FOLDER=/data From 5093df9b62aaa9dc90c431fe0b95929308302929 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 17 Oct 2024 19:33:57 +0200 Subject: [PATCH 23/25] refactored --- .../install/ThingsboardInstallService.java | 1 + .../install/EntityDatabaseSchemaService.java | 2 ++ .../SqlEntityDatabaseSchemaService.java | 30 +++++++++++++++++++ .../install/SqlTsDatabaseSchemaService.java | 25 ---------------- 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 029a27f510..f27f17ca96 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -170,6 +170,7 @@ public class ThingsboardInstallService { log.info("Installing DataBase schema for entities..."); entityDatabaseSchemaService.createDatabaseSchema(); + entityDatabaseSchemaService.createSchemaVersion(); entityDatabaseSchemaService.createOrUpdateViewsAndFunctions(); entityDatabaseSchemaService.createOrUpdateDeviceInfoView(persistToTelemetry); diff --git a/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java index 68f19e7a02..02241367a1 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/EntityDatabaseSchemaService.java @@ -23,4 +23,6 @@ public interface EntityDatabaseSchemaService extends DatabaseSchemaService { void createCustomerTitleUniqueConstraintIfNotExists(); + void createSchemaVersion(); + } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java index 5e1357a48e..3bb8b0e58f 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlEntityDatabaseSchemaService.java @@ -16,7 +16,10 @@ package org.thingsboard.server.service.install; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.info.BuildProperties; import org.springframework.context.annotation.Profile; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; @Service @@ -29,6 +32,11 @@ public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaSer public static final String SCHEMA_ENTITIES_IDX_PSQL_ADDON_SQL = "schema-entities-idx-psql-addon.sql"; public static final String SCHEMA_VIEWS_AND_FUNCTIONS_SQL = "schema-views-and-functions.sql"; + @Autowired + private BuildProperties buildProperties; + @Autowired + private JdbcTemplate jdbcTemplate; + public SqlEntityDatabaseSchemaService() { super(SCHEMA_ENTITIES_SQL, SCHEMA_ENTITIES_IDX_SQL); } @@ -59,4 +67,26 @@ public class SqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaSer "ALTER TABLE customer ADD CONSTRAINT customer_title_unq_key UNIQUE(tenant_id, title); END IF; END; $$;", "create 'customer_title_unq_key' constraint if it doesn't already exist!"); } + + @Override + public void createSchemaVersion() { + try { + Long schemaVersion = jdbcTemplate.queryForList("SELECT schema_version FROM tb_schema_settings", Long.class).stream().findFirst().orElse(null); + if (schemaVersion == null) { + jdbcTemplate.execute("INSERT INTO tb_schema_settings (schema_version) VALUES (" + getSchemaVersion() + ")"); + } + } catch (Exception e) { + log.warn("Failed to create schema version [{}]!", buildProperties.getVersion(), e); + } + } + + private int getSchemaVersion() { + String[] versionParts = buildProperties.getVersion().replaceAll("[^\\d.]", "").split("\\."); + + int major = Integer.parseInt(versionParts[0]); + int minor = Integer.parseInt(versionParts[1]); + int patch = versionParts.length > 2 ? Integer.parseInt(versionParts[2]) : 0; + + return major * 1000000 + minor * 1000 + patch; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java index 48ae646480..428101ee05 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlTsDatabaseSchemaService.java @@ -15,10 +15,7 @@ */ package org.thingsboard.server.service.install; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.info.BuildProperties; import org.springframework.context.annotation.Profile; -import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.thingsboard.server.dao.util.SqlTsDao; @@ -27,12 +24,6 @@ import org.thingsboard.server.dao.util.SqlTsDao; @Profile("install") public class SqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService implements TsDatabaseSchemaService { - @Autowired - private BuildProperties buildProperties; - - @Autowired - private JdbcTemplate jdbcTemplate; - public SqlTsDatabaseSchemaService() { super("schema-ts-psql.sql", null); } @@ -41,21 +32,5 @@ public class SqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService public void createDatabaseSchema() throws Exception { super.createDatabaseSchema(); executeQuery("CREATE TABLE IF NOT EXISTS ts_kv_indefinite PARTITION OF ts_kv DEFAULT;"); - - Long schemaVersion = jdbcTemplate.queryForList("SELECT schema_version FROM tb_schema_settings", Long.class).stream().findFirst().orElse(null); - - if (schemaVersion == null) { - jdbcTemplate.execute("INSERT INTO tb_schema_settings (schema_version) VALUES (" + getSchemaVersion() + ")"); - } - } - - private int getSchemaVersion() { - String[] versionParts = buildProperties.getVersion().replaceAll("[^\\d.]", "").split("\\."); - - int major = Integer.parseInt(versionParts[0]); - int minor = Integer.parseInt(versionParts[1]); - int patch = versionParts.length > 2 ? Integer.parseInt(versionParts[2]) : 0; - - return major * 1000000 + minor * 1000 + patch; } } \ No newline at end of file From a9bb238ab31a80ee513431674cd5028432934e55 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 18 Oct 2024 16:33:19 +0300 Subject: [PATCH 24/25] UI: Dashboard widget copy/paste and scada symbol animation for chrome fixes. --- .../src/app/core/services/dashboard-utils.service.ts | 10 ++++++---- .../layout/dashboard-layout.component.ts | 5 +++-- .../components/dashboard-page/layout/layout.models.ts | 6 ++++-- .../home/components/dashboard/dashboard.component.ts | 4 ++-- .../components/widget/lib/scada/scada-symbol.models.ts | 6 +++--- .../modules/home/models/dashboard-component.models.ts | 7 ++++--- ui-ngx/src/app/shared/models/jquery-event.models.ts | 6 ++++++ ui-ngx/src/app/shared/models/widget.models.ts | 2 ++ 8 files changed, 30 insertions(+), 16 deletions(-) diff --git a/ui-ngx/src/app/core/services/dashboard-utils.service.ts b/ui-ngx/src/app/core/services/dashboard-utils.service.ts index f0341410bc..235924f98f 100644 --- a/ui-ngx/src/app/core/services/dashboard-utils.service.ts +++ b/ui-ngx/src/app/core/services/dashboard-utils.service.ts @@ -636,7 +636,7 @@ export class DashboardUtilsService { targetLayout: DashboardLayoutId, widget: Widget, originalColumns?: number, - originalSize?: {sizeX: number; sizeY: number}, + originalSize?: WidgetSize, row?: number, column?: number, breakpoint = 'default'): void { @@ -661,8 +661,8 @@ export class DashboardUtilsService { mobileHeight: widget.config.mobileHeight, mobileHide: widget.config.mobileHide, desktopHide: widget.config.desktopHide, - preserveAspectRatio: widget.config.preserveAspectRatio, - resizable: widget.config.resizable + preserveAspectRatio: originalSize ? originalSize.preserveAspectRatio : widget.config.preserveAspectRatio, + resizable: originalSize ? originalSize.resizable : widget.config.resizable }; if (isUndefined(originalColumns)) { originalColumns = 24; @@ -1065,7 +1065,9 @@ export class DashboardUtilsService { const widgetLayout = layout.widgets[widget.id]; return { sizeX: widgetLayout.sizeX, - sizeY: widgetLayout.sizeY + sizeY: widgetLayout.sizeY, + preserveAspectRatio: widgetLayout.preserveAspectRatio, + resizable: widgetLayout.resizable }; } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.ts index c04a2af3df..52742c54c4 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.ts @@ -39,6 +39,7 @@ import { map } from 'rxjs/operators'; import { displayGrids } from 'angular-gridster2/lib/gridsterConfig.interface'; import { BreakpointId, LayoutType, ViewFormatType } from '@shared/models/dashboard.models'; import { isNotEmptyStr } from '@core/utils'; +import { TbContextMenuEvent } from '@shared/models/jquery-event.models'; @Component({ selector: 'tb-dashboard-layout', @@ -319,12 +320,12 @@ export class DashboardLayoutComponent extends PageComponent implements ILayoutCo this.layoutCtx.dashboardCtrl.copyWidgetReference($event, this.layoutCtx, widget); } - pasteWidget($event: Event) { + pasteWidget($event: TbContextMenuEvent | KeyboardEvent) { const pos = this.dashboard.getEventGridPosition($event); this.layoutCtx.dashboardCtrl.pasteWidget($event, this.layoutCtx, pos); } - pasteWidgetReference($event: Event) { + pasteWidgetReference($event: TbContextMenuEvent | KeyboardEvent) { const pos = this.dashboard.getEventGridPosition($event); this.layoutCtx.dashboardCtrl.pasteWidgetReference($event, this.layoutCtx, pos); } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/layout.models.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/layout.models.ts index 3ea9f553a8..7b54261945 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/layout.models.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/layout.models.ts @@ -14,13 +14,15 @@ /// limitations under the License. /// +import { TbContextMenuEvent } from '@shared/models/jquery-event.models'; + export interface ILayoutController { reload(); resetHighlight(); highlightWidget(widgetId: string, delay?: number); selectWidget(widgetId: string, delay?: number); - pasteWidget($event: MouseEvent); - pasteWidgetReference($event: MouseEvent); + pasteWidget($event: TbContextMenuEvent | KeyboardEvent); + pasteWidgetReference($event: TbContextMenuEvent | KeyboardEvent); } export enum LayoutWidthType { diff --git a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts index fd031fa90f..7169e4a20b 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard/dashboard.component.ts @@ -529,7 +529,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo return dashboardWidget ? dashboardWidget.widget : null; } - getEventGridPosition(event: Event): WidgetPosition { + getEventGridPosition(event: TbContextMenuEvent | KeyboardEvent): WidgetPosition { const pos: WidgetPosition = { row: 0, column: 0 @@ -537,7 +537,7 @@ export class DashboardComponent extends PageComponent implements IDashboardCompo const parentElement = $(this.gridster.el); let pageX = 0; let pageY = 0; - if (event instanceof MouseEvent) { + if ('pageX' in event && 'pageY' in event) { pageX = event.pageX; pageY = event.pageY; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts index 41611d3f6e..6d3e9fde82 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/scada/scada-symbol.models.ts @@ -1168,13 +1168,13 @@ class CssScadaSymbolAnimation implements ScadaSymbolAnimation { private element: Element, duration = 1000) { this._duration = duration; - this.fixPatternAnimationForChromeBelow128(); + this.fixPatternAnimationForChrome(); } - private fixPatternAnimationForChromeBelow128(): void { + private fixPatternAnimationForChrome(): void { try { const userAgent = window.navigator.userAgent; - if (+(/Chrome\/(\d+)/i.exec(userAgent)[1]) <= 127) { + if (+(/Chrome\/(\d+)/i.exec(userAgent)[1]) > 0) { if (this.svgShape.defs().findOne('pattern') && !this.svgShape.defs().findOne('pattern.empty-animation')) { this.svgShape.defs().add(SVG('')); this.svgShape.style() diff --git a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts index 65c6c23004..b4132bf882 100644 --- a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts @@ -42,6 +42,7 @@ import { enumerable } from '@shared/decorators/enumerable'; import { UtilsService } from '@core/services/utils.service'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { ComponentStyle, iconStyle, textStyle } from '@shared/models/widget-settings.models'; +import { TbContextMenuEvent } from '@shared/models/jquery-event.models'; export interface WidgetsData { widgets: Array; @@ -56,11 +57,11 @@ export interface ContextMenuItem { } export interface DashboardContextMenuItem extends ContextMenuItem { - action: (contextMenuEvent: MouseEvent) => void; + action: (contextMenuEvent: TbContextMenuEvent) => void; } export interface WidgetContextMenuItem extends ContextMenuItem { - action: (contextMenuEvent: MouseEvent, widget: Widget) => void; + action: (contextMenuEvent: TbContextMenuEvent, widget: Widget) => void; } export interface DashboardCallbacks { @@ -94,7 +95,7 @@ export interface IDashboardComponent { highlightWidget(widgetId: string, delay?: number); selectWidget(widgetId: string, delay?: number); getSelectedWidget(): Widget; - getEventGridPosition(event: Event): WidgetPosition; + getEventGridPosition(event: TbContextMenuEvent | KeyboardEvent): WidgetPosition; notifyGridsterOptionsChanged(); pauseChangeNotifications(); resumeChangeNotifications(); diff --git a/ui-ngx/src/app/shared/models/jquery-event.models.ts b/ui-ngx/src/app/shared/models/jquery-event.models.ts index b893da758c..a054210672 100644 --- a/ui-ngx/src/app/shared/models/jquery-event.models.ts +++ b/ui-ngx/src/app/shared/models/jquery-event.models.ts @@ -19,6 +19,8 @@ import Timeout = NodeJS.Timeout; export interface TbContextMenuEvent extends Event { clientX: number; clientY: number; + pageX: number; + pageY: number; ctrlKey: boolean; metaKey: boolean; } @@ -41,6 +43,8 @@ export const initCustomJQueryEvents = () => { const event = $.Event('tbcontextmenu', { clientX: touch.clientX, clientY: touch.clientY, + pageX: touch.pageX, + pageY: touch.pageY, ctrlKey: false, metaKey: false, originalEvent: e @@ -59,6 +63,8 @@ export const initCustomJQueryEvents = () => { const event = $.Event('tbcontextmenu', { clientX: e.originalEvent.clientX, clientY: e.originalEvent.clientY, + pageX: e.originalEvent.pageX, + pageY: e.originalEvent.pageY, ctrlKey: e.originalEvent.ctrlKey, metaKey: e.originalEvent.metaKey, originalEvent: e diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 429bb79e35..dc3a94052b 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -836,6 +836,8 @@ export interface WidgetPosition { export interface WidgetSize { sizeX: number; sizeY: number; + preserveAspectRatio: boolean; + resizable: boolean; } export interface IWidgetSettingsComponent { From 40c5f9d66d08c7d1ca5e2f4cdb26e9b2b6f198d3 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 18 Oct 2024 17:51:07 +0300 Subject: [PATCH 25/25] UI: Initialize widget component in angular zone. Force detect widget changes in angular zone. --- .../components/widget/widget.component.ts | 20 +++++++++++++------ .../home/models/widget-component.models.ts | 16 +++++++++++++-- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts index b58e9fe85b..d72b896862 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts @@ -956,11 +956,15 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.widgetContext.hiddenData = subscription.hiddenData; this.widgetContext.timeWindow = subscription.timeWindow; this.widgetContext.defaultSubscription = subscription; - createSubscriptionSubject.next(); - createSubscriptionSubject.complete(); + this.ngZone.run(() => { + createSubscriptionSubject.next(); + createSubscriptionSubject.complete(); + }); }, (err) => { - createSubscriptionSubject.error(err); + this.ngZone.run(() => { + createSubscriptionSubject.error(err); + }); } ); } else if (this.widget.type === widgetType.rpc) { @@ -1013,11 +1017,15 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI this.createSubscription(options).subscribe( (subscription) => { this.widgetContext.defaultSubscription = subscription; - createSubscriptionSubject.next(); - createSubscriptionSubject.complete(); + this.ngZone.run(() => { + createSubscriptionSubject.next(); + createSubscriptionSubject.complete(); + }); }, (err) => { - createSubscriptionSubject.error(err); + this.ngZone.run(() => { + createSubscriptionSubject.error(err); + }); } ); this.detectChanges(); diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 673bc21edb..9e739cafb4 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -415,7 +415,13 @@ export class WidgetContext { this.dashboardWidget.updateWidgetParams(); } try { - this.changeDetectorValue.detectChanges(); + if (this.ngZone) { + this.ngZone.run(() => { + this.changeDetectorValue?.detectChanges(); + }); + } else { + this.changeDetectorValue?.detectChanges(); + } } catch (e) { // console.log(e); } @@ -425,7 +431,13 @@ export class WidgetContext { detectContainerChanges() { if (!this.destroyed) { try { - this.containerChangeDetectorValue.detectChanges(); + if (this.ngZone) { + this.ngZone.run(() => { + this.containerChangeDetectorValue?.detectChanges(); + }); + } else { + this.containerChangeDetectorValue?.detectChanges(); + } } catch (e) { // console.log(e); }