diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java index e54f93be8f..d9469bd2db 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java @@ -37,13 +37,10 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; -import java.util.ArrayList; import java.util.List; +import java.util.UUID; @Service @AllArgsConstructor @@ -176,20 +173,20 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb } @Override - public List unassignDeletedUserAlarms(TenantId tenantId, UserId userId, String userTitle, long unassignTs) { - List totalAlarmIds = new ArrayList<>(); - PageLink pageLink = new PageLink(100, 0, null, new SortOrder("id", SortOrder.Direction.ASC)); - while (true) { - PageData pageData = alarmService.findAlarmIdsByAssigneeId(tenantId, userId, pageLink); - List alarmIds = pageData.getData(); - if (alarmIds.isEmpty()) { - break; + public void unassignDeletedUserAlarms(TenantId tenantId, UserId userId, String userTitle, List alarms, long unassignTs) { + for (UUID alarmId : alarms) { + log.trace("[{}] Unassigning alarm {} from user {}", tenantId, alarmId, userId); + AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(tenantId, new AlarmId(alarmId), unassignTs); + if (!result.isSuccessful()) { + log.error("[{}] Cannot unassign alarm {} from user {}", tenantId, alarmId, userId); + continue; + } + if (result.isModified()) { + String comment = String.format("Alarm was unassigned because user %s - was deleted", userTitle); + addSystemAlarmComment(result.getAlarm(), null, "ASSIGN", comment); + logEntityActionService.logEntityAction(result.getAlarm().getTenantId(), result.getAlarm().getOriginator(), result.getAlarm(), result.getAlarm().getCustomerId(), ActionType.ALARM_UNASSIGNED, null); } - processAlarmsUnassignment(tenantId, userId, userTitle, alarmIds, unassignTs); - totalAlarmIds.addAll(alarmIds); - pageLink = pageLink.nextPageLink(); } - return totalAlarmIds; } @Override @@ -204,22 +201,6 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb return ts > 0 ? ts : System.currentTimeMillis(); } - private void processAlarmsUnassignment(TenantId tenantId, UserId userId, String userTitle, List alarmIds, long unassignTs) { - for (AlarmId alarmId : alarmIds) { - log.trace("[{}] Unassigning alarm {} userId {}", tenantId, alarmId, userId); - AlarmApiCallResult result = alarmSubscriptionService.unassignAlarm(tenantId, alarmId, unassignTs); - if (!result.isSuccessful()) { - log.error("[{}] Cannot unassign alarm {} userId {}", tenantId, alarmId, userId); - continue; - } - if (result.isModified()) { - String comment = String.format("Alarm was unassigned because user %s - was deleted", userTitle); - addSystemAlarmComment(result.getAlarm(), null, "ASSIGN", comment); - logEntityActionService.logEntityAction(result.getAlarm().getTenantId(), result.getAlarm().getOriginator(), result.getAlarm(), result.getAlarm().getCustomerId(), ActionType.ALARM_UNASSIGNED, null); - } - } - } - private void addSystemAlarmComment(Alarm alarm, User user, String subType, String commentText) { addSystemAlarmComment(alarm, user, subType, commentText, null); } @@ -245,4 +226,5 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb log.error("Failed to save alarm comment", e); } } + } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java index c975291b58..7ba9bfc87c 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/TbAlarmService.java @@ -19,11 +19,11 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import java.util.List; +import java.util.UUID; public interface TbAlarmService { @@ -41,7 +41,7 @@ public interface TbAlarmService { AlarmInfo unassign(Alarm alarm, long unassignTs, User user) throws ThingsboardException; - List unassignDeletedUserAlarms(TenantId tenantId, UserId userId, String userTitle, long unassignTs); + void unassignDeletedUserAlarms(TenantId tenantId, UserId userId, String userTitle, List alarms, long unassignTs); Boolean delete(Alarm alarm, User user); } diff --git a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsUnassignTaskProcessor.java b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsUnassignTaskProcessor.java index ba596da500..9d6eb2860d 100644 --- a/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsUnassignTaskProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/housekeeper/processor/AlarmsUnassignTaskProcessor.java @@ -21,22 +21,46 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.housekeeper.AlarmsUnassignHousekeeperTask; import org.thingsboard.server.common.data.housekeeper.HousekeeperTaskType; import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.util.TbPair; +import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.service.entitiy.alarm.TbAlarmService; import java.util.List; +import java.util.UUID; @Component @RequiredArgsConstructor @Slf4j public class AlarmsUnassignTaskProcessor extends HousekeeperTaskProcessor { - private final TbAlarmService alarmService; + private final TbAlarmService tbAlarmService; + private final AlarmService alarmService; @Override public void process(AlarmsUnassignHousekeeperTask task) throws Exception { - List alarms = alarmService.unassignDeletedUserAlarms(task.getTenantId(), (UserId) task.getEntityId(), task.getUserTitle(), task.getTs()); - log.debug("[{}][{}] Unassigned {} alarms", task.getTenantId(), task.getEntityId(), alarms.size()); + TenantId tenantId = task.getTenantId(); + UserId userId = (UserId) task.getEntityId(); + if (task.getAlarms() == null) { + AlarmId lastId = null; + long lastCreatedTime = 0; + while (true) { + List> alarms = alarmService.findAlarmIdsByAssigneeId(tenantId, userId, lastCreatedTime, lastId, 64); + if (alarms.isEmpty()) { + break; + } + housekeeperClient.submitTask(new AlarmsUnassignHousekeeperTask(tenantId, userId, task.getUserTitle(), alarms.stream().map(TbPair::getFirst).toList())); + + TbPair last = alarms.get(alarms.size() - 1); + lastId = new AlarmId(last.getFirst()); + lastCreatedTime = last.getSecond(); + log.debug("[{}][{}] Submitted task for unassigning {} alarms", tenantId, userId, alarms.size()); + } + } else { + tbAlarmService.unassignDeletedUserAlarms(tenantId, userId, task.getUserTitle(), task.getAlarms(), task.getTs()); + log.debug("[{}][{}] Unassigned {} alarms", tenantId, userId, task.getAlarms().size()); + } } @Override diff --git a/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java b/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java index 902eef918f..60a0ac3bf6 100644 --- a/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java @@ -37,7 +37,6 @@ import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.edge.EdgeService; @@ -46,7 +45,6 @@ import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; -import java.util.Collections; import java.util.List; import java.util.UUID; @@ -164,16 +162,13 @@ public class DefaultTbAlarmServiceTest { AlarmInfo alarm = new AlarmInfo(); alarm.setId(new AlarmId(UUID.randomUUID())); - when(alarmService.findAlarmIdsByAssigneeId(any(), any(), any())) - .thenReturn(new PageData<>(List.of(alarm.getId()), 0, 1, false)) - .thenReturn(new PageData<>(Collections.EMPTY_LIST, 0, 0, false)); when(alarmSubscriptionService.unassignAlarm(any(), any(), anyLong())) .thenReturn(AlarmApiCallResult.builder().successful(true).modified(true).alarm(alarm).build()); User user = new User(); user.setEmail("testEmail@gmail.com"); user.setId(new UserId(UUID.randomUUID())); - service.unassignDeletedUserAlarms(new TenantId(UUID.randomUUID()), user.getId(), user.getTitle(), System.currentTimeMillis()); + service.unassignDeletedUserAlarms(new TenantId(UUID.randomUUID()), user.getId(), user.getTitle(), List.of(alarm.getUuidId()), System.currentTimeMillis()); ObjectNode commentNode = JacksonUtil.newObjectNode(); commentNode.put("subtype", "ASSIGN"); diff --git a/application/src/test/java/org/thingsboard/server/service/housekeeper/HousekeeperServiceTest.java b/application/src/test/java/org/thingsboard/server/service/housekeeper/HousekeeperServiceTest.java index 06740e2851..d3b1916def 100644 --- a/application/src/test/java/org/thingsboard/server/service/housekeeper/HousekeeperServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/housekeeper/HousekeeperServiceTest.java @@ -55,7 +55,6 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.msg.TbNodeConnectionType; -import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; @@ -63,6 +62,7 @@ import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.alarm.AlarmService; @@ -184,23 +184,32 @@ public class HousekeeperServiceTest extends AbstractControllerTest { Device device = createDevice("test", "test"); UserId userId = customerUserId; createRelatedData(userId); - Alarm alarm = Alarm.builder() - .type("test") - .tenantId(tenantId) - .originator(device.getId()) - .severity(AlarmSeverity.MAJOR) - .build(); - alarm = doPost("/api/alarm", alarm, Alarm.class); - AlarmId alarmId = alarm.getId(); - alarm = doPost("/api/alarm/" + alarmId + "/assign/" + userId, "", Alarm.class); - assertThat(alarm.getAssigneeId()).isEqualTo(userId); - assertThat(alarmService.findAlarmIdsByAssigneeId(tenantId, userId, new PageLink(100)).getData()).isNotEmpty(); + + List alarms = new ArrayList<>(); + int count = 112; + for (int i = 0; i < count; i++) { + Alarm alarm = Alarm.builder() + .type("test" + i) + .tenantId(tenantId) + .originator(device.getId()) + .severity(AlarmSeverity.MAJOR) + .build(); + alarm = doPost("/api/alarm", alarm, Alarm.class); + AlarmId alarmId = alarm.getId(); + alarm = doPost("/api/alarm/" + alarmId + "/assign/" + userId, "", Alarm.class); + assertThat(alarm.getAssigneeId()).isEqualTo(userId); + alarms.add(alarmId); + } + List assignedAlarms = alarmService.findAlarmIdsByAssigneeId(tenantId, userId, 0, null, 5000).stream() + .map(TbPair::getFirst).map(AlarmId::new).toList(); + assertThat(assignedAlarms).size().isEqualTo(count); + assertThat(assignedAlarms).containsAll(alarms); doDelete("/api/user/" + userId).andExpect(status().isOk()); - await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { + await().atMost(TIMEOUT, TimeUnit.SECONDS).untilAsserted(() -> { verifyNoRelatedData(userId); - assertThat(alarmService.findAlarmById(tenantId, alarmId).getAssigneeId()).isNull(); + assertThat(alarmService.findAlarmIdsByAssigneeId(tenantId, userId, 0, null, 5000)).size().isZero(); }); } 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 8423d5e40a..c9bf8d0afe 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 @@ -410,27 +410,36 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte } protected void awaitObserveReadAll(int cntObserve, String deviceIdStr) throws Exception { - await("ObserveReadAll after start client/test: countObserve " + cntObserve) + await("ObserveReadAll: countObserve " + cntObserve) .atMost(40, TimeUnit.SECONDS) .until(() -> cntObserve == getCntObserveAll(deviceIdStr)); } protected Integer getCntObserveAll(String deviceIdStr) throws Exception { - String actualResultBefore = sendObserve("ObserveReadAll", null, deviceIdStr); - ObjectNode rpcActualResultBefore = JacksonUtil.fromString(actualResultBefore, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultBefore.get("result").asText()); - JsonElement element = JsonUtils.parse(rpcActualResultBefore.get("value").asText()); + String actualResult = sendObserveOK("ObserveReadAll", null, deviceIdStr); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + JsonElement element = JsonUtils.parse(rpcActualResult.get("value").asText()); return element.isJsonArray() ? ((JsonArray)element).size() : null; } - protected void sendCancelObserveAllWithAwait(String deviceIdStr) throws Exception { - String actualResultCancelAll = sendObserve("ObserveCancelAll", null, deviceIdStr); + protected void sendObserveCancelAllWithAwait(String deviceIdStr) throws Exception { + String actualResultCancelAll = sendObserveOK("ObserveCancelAll", null, deviceIdStr); ObjectNode rpcActualResultCancelAll = JacksonUtil.fromString(actualResultCancelAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultCancelAll.get("result").asText()); awaitObserveReadAll(0, deviceId); } - protected String sendObserve(String method, String params, String deviceIdStr) throws Exception { + protected String sendRpcObserveOkWithResultValue(String method, String params) throws Exception { + String actualResultReadAll = sendRpcObserveOk(method, params); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + return rpcActualResult.get("value").asText(); + } + protected String sendRpcObserveOk(String method, String params) throws Exception { + return sendObserveOK(method, params, deviceId); + } + protected String sendObserveOK(String method, String params, String deviceIdStr) throws Exception { String sendRpcRequest; if (params == null) { sendRpcRequest = "{\"method\": \"" + method + "\"}"; @@ -441,4 +450,9 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte return doPostAsync("/api/plugins/rpc/twoway/" + deviceIdStr, sendRpcRequest, String.class, status().isOk()); } + protected ObjectNode sendRpcObserveWithResult(String method, String params) throws Exception { + String actualResultReadAll = sendRpcObserveOk(method, params); + return JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); + } + } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java index b2ed3cc297..c627c77cd4 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java @@ -48,6 +48,7 @@ public class Lwm2mTestHelper { public static final String RESOURCE_ID_NAME_3_9 = "batteryLevel"; public static final String RESOURCE_ID_NAME_3_14 = "UtfOffset"; public static final String RESOURCE_ID_NAME_19_0_0 = "dataRead"; + public static final String RESOURCE_ID_NAME_19_0_2 = "dataCreationTime"; public static final String RESOURCE_ID_NAME_19_1_0 = "dataWrite"; public static final String RESOURCE_ID_NAME_19_0_3 = "dataDescription"; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java index 88385a067e..3efd6365dd 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java @@ -27,8 +27,10 @@ import org.eclipse.leshan.core.response.WriteResponse; import javax.security.auth.Destroyable; import java.sql.Time; +import java.time.Instant; +import java.time.LocalTime; +import java.time.ZoneId; import java.util.Arrays; -import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -85,7 +87,8 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements fireResourceChange(0); fireResourceChange(2); } - , 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN + , 1, 1, TimeUnit.SECONDS); // 1 sec +// , 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN } catch (Throwable e) { log.error("[{}]Throwable", e.toString()); e.printStackTrace(); @@ -123,6 +126,7 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements switch (resourceId) { case 0: if (setData(value, replace)) { + fireResourceChange(resourceId); return WriteResponse.success(); } else { WriteResponse.badRequest("Invalidate value ..."); @@ -132,7 +136,7 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements fireResourceChange(resourceId); return WriteResponse.success(); case 2: - setTimestamp(((Date) value.getValue()).getTime()); + setTimestamp(); fireResourceChange(resourceId); return WriteResponse.success(); case 3: @@ -177,12 +181,15 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements return this.description; } - private void setTimestamp(long time) { - this.timestamp = new Time(time); + private void setTimestamp() { + long currentTimeMillis = System.currentTimeMillis(); + this.timestamp = new Time(currentTimeMillis); } private Time getTimestamp() { - return this.timestamp != null ? this.timestamp : new Time(new Date().getTime()); + LocalTime localTime = LocalTime.ofInstant(Instant.now(), ZoneId.systemDefault()); + this.timestamp = Time.valueOf(localTime); + return this.timestamp; } private boolean setData(LwM2mResource value, boolean replace) { 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 1c0aa2b637..370c2249df 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 @@ -58,7 +58,7 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl executorService.scheduleWithFixedDelay(() -> { fireResourceChange(9); } - , 1, 1, TimeUnit.SECONDS); // 30 MIN + , 1, 1, TimeUnit.SECONDS); // 2 sec // , 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN } catch (Throwable e) { log.error("[{}]Throwable", e.toString()); @@ -169,8 +169,9 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl } private int getBatteryLevel() { - return randomIterator.nextInt(); -// return 42; + int valBattery = randomIterator.nextInt(); + log.trace("Send from client [3/0/9] val: [{}]", valBattery); + return valBattery; } private long getMemoryFree() { 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 3cda275e13..ea9814c1d0 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 @@ -28,7 +28,7 @@ public abstract class AbstractRpcLwM2MIntegrationObserveTest extends AbstractRpc @Before public void initTest () throws Exception { - awaitObserveReadAll(2, deviceId); + awaitObserveReadAll(4, deviceId); } } 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 b5f23c3e9a..1f61a08e15 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 @@ -42,13 +42,16 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INST import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0; 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_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_2; 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_3_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources; +import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; @DaoSqlTest public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @@ -74,7 +77,11 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg protected String objectIdVer_3303; protected static AtomicInteger endpointSequence = new AtomicInteger(); protected static String DEVICE_ENDPOINT_RPC_PREF = "deviceEndpointRpc"; + + protected String idVer_3_0_0; protected String idVer_3_0_9; + protected String id_3_0_9; + protected String idVer_19_0_0; public AbstractRpcLwM2MIntegrationTest() { @@ -125,7 +132,9 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg objectInstanceIdVer_5 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).startsWith("/" + FIRMWARE)).findFirst().get(); objectInstanceIdVer_9 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).startsWith("/" + SOFTWARE_MANAGEMENT)).findFirst().get(); + 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); idVer_19_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0; OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC = @@ -134,14 +143,18 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg " \"" + 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_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" + " \"" + idVer_3_0_9 + "\",\n" + - " \"" + idVer_19_0_0 + "\"\n" + + " \"" + idVer_19_0_0 + "\",\n" + + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\",\n" + + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_2 + "\"\n" + " ],\n" + " \"attribute\": [\n" + - " \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\"\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" + 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 94ee1eb48e..cf78c58288 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 @@ -15,12 +15,22 @@ */ package org.thingsboard.server.transport.lwm2m.rpc.sql; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.ResponseCode; import org.junit.Test; +import org.mockito.Mockito; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserveTest; +import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import static org.awaitility.Awaitility.await; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -29,21 +39,25 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INST import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_15; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_5; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_7; 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_2; 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_3_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; +@Slf4j public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MIntegrationObserveTest { + @SpyBean + DefaultLwM2mUplinkMsgHandler defaultUplinkMsgHandlerTest; + /** * ObserveComposite {"ids":["5/0/7", "5/0/5", "5/0/3", "3/0/9", "19/1/0/0"]} - Ok @@ -51,13 +65,13 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveCompositeAnyResources_Result_CONTENT_Value_LwM2mSingleResource_LwM2mResourceInstance() throws Exception { - sendCancelObserveAllWithAwait(deviceId); + sendObserveCancelAllWithAwait(deviceId); String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; - String expectedIdVer5_0_5= objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; + String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; 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; String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); String actualValues = rpcActualResult.get("value").asText(); @@ -74,30 +88,30 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveComposite_ObjectInstanceWithOtherObjectResourceInstance_Result_CONTENT_Ok() throws Exception { - sendCancelObserveAllWithAwait(deviceId); + 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 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String actual= rpcActualResult.get("value").asText(); + String actual = rpcActualResult.get("value").asText(); assertTrue(actual.contains(fromVersionedIdToObjectId(expectedIdVer19_1_0) + "=LwM2mMultipleResource")); assertTrue(actual.contains(fromVersionedIdToObjectId(expectedIdVer5_0) + "=LwM2mObjectInstance")); } /** * ObserveComposite {"ids":["5/0/7", "5/0/2"]} - Ok - * "5/0/2" - Execute^ result == null + * "5/0/2" - Execute result == null * @throws Exception */ @Test - public void testObserveCompositeAnyResources_Result_CONTENT_Value_LwM2mSingleResource_If_Error_Null() throws Exception { -// sendCancelObserveAllWithAwait(deviceId); + 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 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); String actualValues = rpcActualResult.get("value").asText(); @@ -113,34 +127,102 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveComposite_Result_BAD_REQUEST_ONE_PATH_CONTAINCE_OTHER() throws Exception { - sendCancelObserveAllWithAwait(deviceId); + sendObserveCancelAllWithAwait(deviceId); String expectedIdVer5_0 = objectInstanceIdVer_5; String expectedIdVer5_0_2 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2; String expectedIds = "[\"" + expectedIdVer5_0 + "\", \"" + expectedIdVer5_0_2 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String actual= rpcActualResult.get("error").asText(); + String actual = rpcActualResult.get("error").asText(); String expected = "Invalid path list : /5/0 and /5/0/2 are overlapped paths"; assertTrue(expected.equals(actual)); } /** - * Previous -> "3/0/9" - * ObserveComposite {"ids":["5/0/7", "5/0/5", "5/0/3", "3/0/9"]} - CONTENT + * Previous -> "3/0/9", "19/0/2", "19/1/0", "19/0/0", All only 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 */ @Test - public void testObserveCompositeThereAreObservationOneResource_Result_CONTENT_Value_ObservationAddIfAbsent() throws Exception { + public void testObserveComposite_IfLeastOneResourceIsAlreadyRegistered_return_BadRequest() throws Exception { + // Verify after start + String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); + 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" String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; - String expectedIdVer5_0_5= objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; + String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + assertTrue(rpcActualResult.get("error").asText().contains(fromVersionedIdToObjectId(idVer_3_0_9))); + assertTrue(rpcActualResult.get("error").asText().contains("is already registered")); + // verify after send Observe composite + 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:" + 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 * + * if the resource SingleObservation is already registered in CompositeObservation - return BAD REQUEST + * SingleObservation {"id":["5/0/7"} + * @throws Exception + */ + @Test + public void testObserveSingle_IfResourceIsAlreadyRegisteredInComposite_return_BadRequest() throws Exception { + // Send Observe Composite + String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; + String expectedId5_0_7 = fromVersionedIdToObjectId(expectedIdVer5_0_7); + String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; + String expectedId5_0_5 = fromVersionedIdToObjectId(expectedIdVer5_0_5); + String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; + String expectedId5_0_3 = fromVersionedIdToObjectId(expectedIdVer5_0_3); + + String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\"]"; + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String expectedResult = "/3/0/9=LwM2mSingleResource [id=9"; - assertTrue(rpcActualResult.get("value").asText().contains(expectedResult)); + String actualValues = rpcActualResult.get("value").asText(); + assertTrue(actualValues.contains(expectedId5_0_7)); + assertTrue(actualValues.contains(expectedId5_0_5)); + assertTrue(actualValues.contains(expectedId5_0_3)); + // Send Observe Single + actualResult = sendObserve("Observe", expectedIdVer5_0_7); + rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + assertTrue(rpcActualResult.get("error").asText().contains(expectedId5_0_7)); + assertTrue(rpcActualResult.get("error").asText().contains("is already registered")); + // verify after send Observe Single + String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); + 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)); + assertTrue(actualValues.contains(expectedId5_0_3)); } /** @@ -149,14 +231,14 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveCompositeAnyResources_Result_CONTENT_Value_LwM2mSingleResource_LwM2mMultipleResource() throws Exception { - sendCancelObserveAllWithAwait(deviceId); + sendObserveCancelAllWithAwait(deviceId); String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; - String expectedIdVer5_0_5= objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; + String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0; String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); String actualValues = rpcActualResult.get("value").asText(); @@ -173,7 +255,7 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt */ @Test public void testObserveCompositeWithKeyName_Result_CONTENT_Value_SingleResources() throws Exception { - sendCancelObserveAllWithAwait(deviceId); + sendObserveCancelAllWithAwait(deviceId); String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9; String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14; @@ -198,18 +280,16 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt * @throws Exception */ @Test - public void testObserveCompositeWithKeyNameThereAreObservationOneResource_Result_CONTENT_Value_ObservationAddIfAbsent() throws Exception { + public void testObserveCompositeWithKeyName_IfLeastOneResourceIsAlreadyRegistered_return_BadRequest() throws Exception { 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; String expectedKey19_1_0 = RESOURCE_ID_NAME_19_1_0; String expectedKeys = "[\"" + expectedKey3_0_9 + "\", \"" + expectedKey3_0_14 + "\", \"" + expectedKey19_0_0 + "\", \"" + expectedKey19_1_0 + "\"]"; - String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0; String actualResult = sendCompositeRPCByKeys("ObserveComposite", expectedKeys); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String actual = rpcActualResult.get("value").asText(); - assertTrue(actual.contains(fromVersionedIdToObjectId(expectedIdVer19_1_0) + "=LwM2mMultipleResource")); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + assertTrue(rpcActualResult.get("error").asText().contains("is already registered")); } /** @@ -218,8 +298,8 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt * @throws Exception */ @Test - public void testObserveReadAll_AfterCompositeObservation_Result_CONTENT_Value_SingleObservation_Only() throws Exception { - sendCancelObserveAllWithAwait(deviceId); + 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; @@ -232,76 +312,15 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - String actualValues = rpcActualResultReadAll.get("value").asText(); + String actualValues = rpcActualResultReadAll.get("value").asText(); String expectedIdVer3_0_14 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14; 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(expectedIdVer3_0_14))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_1_0))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_19_0_0))); - } - - /** - * ObserveReadAll - * {"result":"CONTENT","value":"{"result":"CONTENT","value":"["SingleObservation:/3/0/9","SingleObservation:/3/0/14","SingleObservation:/19/1/0/0","SingleObservation:/19/0/0"]"} - Ok - * @throws Exception - */ - @Test - public void testObserveReadAll_Result_CONTENT_Value_SingleObservation_Only() throws Exception { - sendCancelObserveAllWithAwait(deviceId); - - String expectedIdVer3_0_14 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14; - String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0; - String actualResult3_0_9 = sendObserve("Observe", idVer_3_0_9); - ObjectNode rpcActualResult3_0_9 = JacksonUtil.fromString(actualResult3_0_9, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult3_0_9.get("result").asText()); - String actualResult3_0_14 = sendObserve("Observe", expectedIdVer3_0_14); - ObjectNode rpcActualResult3_0_14 = JacksonUtil.fromString(actualResult3_0_14, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult3_0_14.get("result").asText()); - String actualResult19_1_0_0 = sendObserve("Observe", expectedIdVer19_1_0_0); - ObjectNode rpcActualResult19_1_0_0 = JacksonUtil.fromString(actualResult19_1_0_0, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult19_1_0_0.get("result").asText()); - String actualResult19_0_0 = sendObserve("Observe", idVer_19_0_0); - ObjectNode rpcActualResult19_0_0 = JacksonUtil.fromString(actualResult19_0_0, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult19_0_0.get("result").asText()); - String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); - ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - String actualValues = rpcActualResultReadAll.get("value").asText(); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_9))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer3_0_14))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer19_1_0_0))); - assertTrue(actualValues.contains("SingleObservation:" + fromVersionedIdToObjectId(idVer_19_0_0))); - } - - /** - * ObserveReadAll - * {"result":"CONTENT","value":"[\"CompositeObservation: [/19/1/0\",\"/19/0/0\",\"/3/0/14\",\"/3/0/9]\"]"} - Ok - * @throws Exception - */ - @Test - public void testObserveReadAll_AfterCompositeObservation_WithResourceNotReadable_Result_CONTENT_Value_SingleObservation_Only() throws Exception { - sendCancelObserveAllWithAwait(deviceId); - - String expectedIdVer5_0_7 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_7; - String expectedIdVer5_0_2= objectInstanceIdVer_5 + "/" + RESOURCE_ID_2; - String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; - String expectedIdVer19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0; - String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_2 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String actualValues = rpcActualResult.get("value").asText(); - - assertTrue(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer5_0_2) + "=null")); - - String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); - ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - actualValues = rpcActualResultReadAll.get("value").asText(); - - assertFalse(actualValues.contains(fromVersionedIdToObjectId(expectedIdVer5_0_2))); - + assertTrue(actualValues.contains("CompositeObservation:")); + assertFalse(actualValues.contains("SingleObservation")); + assertTrue(actualValues.contains(Objects.requireNonNull(fromVersionedIdToObjectId(idVer_3_0_9)))); + assertTrue(actualValues.contains(Objects.requireNonNull(fromVersionedIdToObjectId(expectedIdVer3_0_14)))); + assertTrue(actualValues.contains(Objects.requireNonNull(fromVersionedIdToObjectId(expectedIdVer19_1_0)))); + assertTrue(actualValues.contains(Objects.requireNonNull(fromVersionedIdToObjectId(idVer_19_0_0)))); } /** @@ -310,166 +329,179 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt * @throws Exception */ @Test - public void testObserveCompositeAnyResources_Result_CONTENT_CancelObserveComposite_This_Result_Content_Count_5() throws Exception { - sendCancelObserveAllWithAwait(deviceId); - // ObserveComposite + 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; + String expectedIdVer5_0_5 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_5; 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; String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - // ObserveCompositeCancel - actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertEquals("5", rpcActualResult.get("value").asText()); - - assertEquals(0, (Object) getCntObserveAll(deviceId)); - } - - /** - * ObserveComposite {"ids":["/3", "/5/0/3", "/19/1/0/0"]} - Ok - * ObserveCompositeCancel {"ids":["/3", "/5/0/3", "/19/1/0/0"]} - Ok - * @throws Exception - */ - @Test - public void testObserveCompositeOneObjectAnyResources_Result_CONTENT_CancelObserveComposite_This_Result_Content_Count_3() throws Exception { - sendCancelObserveAllWithAwait(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; - String expectedIds = "[\"" + idVer_3_0_9 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - // ObserveCompositeCancel - actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); + // ObserveCompositeCancel + actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertEquals("3", rpcActualResult.get("value").asText()); + assertEquals("1", rpcActualResult.get("value").asText()); assertEquals(0, (Object) getCntObserveAll(deviceId)); } /** * ObserveComposite {"ids":["/3/0/9", "/5/0/5", "/5/0/3", "/5/0/7", "/19/1/0/0"]} - Ok - * ObserveCompositeCancel {"ids":["/5", "/19/1/0/0"]} - Ok - * last Observation + * ObserveCompositeCancel {"ids":["/5", "/19/1/0/0"]} - BadRequest * @throws Exception */ @Test - public void testObserveCompositeAnyResources_Result_CONTENT_CancelObserveComposite_OneObjectAnyResource_Result_Content_Count_4() throws Exception { - sendCancelObserveAllWithAwait(deviceId); - // ObserveComposite + 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; String expectedIdVer5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedIdVer3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; String expectedIdVer19_1_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "/" + RESOURCE_INSTANCE_ID_0; - String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String expectedIds = "[\"" + expectedIdVer5_0_7 + "\", \"" + expectedIdVer5_0_5 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - awaitObserveReadAll(5, deviceId); + awaitObserveReadAll(1, deviceId); - // ObserveCompositeCancel + // ObserveCompositeCancel two expectedIds = "[\"" + objectInstanceIdVer_5 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; - actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); + actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertEquals("4", rpcActualResult.get("value").asText()); // CNT = 4 ("/5/0/5", "/5/0/3", "/5/0/7", "/19/1/0/0"9) - - String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); - ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - String actualValues = rpcActualResultReadAll.get("value").asText(); - assertEquals("[\"SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer3_0_9) + "\"]", actualValues); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + assertTrue(rpcActualResult.get("error").asText().contains(objectInstanceIdVer_5)); // CNT = 4 ("/5/0/5", "/5/0/3", "/5/0/7", "/19/1/0/0"9) + assertTrue(rpcActualResult.get("error").asText().contains(expectedIdVer19_1_0_0)); // CNT = 4 ("/5/0/5", "/5/0/3", "/5/0/7", "/19/1/0/0"9) } /** * ObserveComposite {"ids":["/3", "/5/0/3", "/19/1/0/0"]} - Ok - * ObserveCompositeCancel {"ids":["/3/0/9", "/5/0/3", "/19/1/0/0"} -> BAD_REQUEST - * ObserveCompositeCancel {"ids":["/3"} -> CONTENT + * ObserveCompositeCancel {"ids":["/19/1/0/0", "/3/0/9"} -> BAD_REQUEST */ @Test public void testObserveOneObjectAnyResources_Result_CONTENT_Cancel_OneResourceFromObjectAnyResource_Result_BAD_REQUEST_Cancel_OneObject_Result_CONTENT() throws Exception { - sendCancelObserveAllWithAwait(deviceId); - // ObserveComposite - sendCancelObserveAllWithAwait(deviceId); + 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; - String expectedIds = "[\"" + objectIdVer_3 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); + String expectedIds = "[\"" + objectIdVer_3 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; + String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - // ObserveCompositeCancel - expectedIds = "[\"" + expectedIdVer19_1_0_0 + "\", \"" + idVer_3_0_9 + "\"]"; - actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); + // ObserveCompositeCancel + String sendIds = "[\"" + expectedIdVer19_1_0_0 + "\", \"" + idVer_3_0_9 + "\"]"; + actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", sendIds); rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expectedValue = "for observation path [" + fromVersionedIdToObjectId(objectIdVer_3) + "], that includes this observation path [" + fromVersionedIdToObjectId(idVer_3_0_9); + String expectedValue = "Could not find active Observe Composite component with paths: [/19_1.1/1/0/0, /3_1.2/0/9]"; assertTrue(rpcActualResult.get("error").asText().contains(expectedValue)); - // ObserveCompositeCancel - expectedIds = "[\"" + objectIdVer_3 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; - actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertEquals("2", rpcActualResult.get("value").asText()); - + // "ObserveReadAll" String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - String actualValues = rpcActualResultReadAll.get("value").asText(); - assertEquals("[\"SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer5_0_3) + "\"]", actualValues); + String actualValues = rpcActualResultReadAll.get("value").asText(); + assertTrue(actualValues.contains("CompositeObservation:")); } - /** - * ObserveComposite {"ids":["/3/0/9", "/3/0/14", "/5/0/3", "/3/0/15", "/19/1/0/0"]} - Ok - * ObserveCancel {"id":"/3/0/9"} -> INTERNAL_SERVER_ERROR - * ObserveCompositeCancel {"ids":["/3/0/9", "/19/1/0/0", "/3]} - Ok - * last Observation - * @throws Exception - */ @Test - public void testObserveCompositeAnyResources_Result_CONTENT_CancelObserveComposite_OneResource_OneObjectAnyResource_Result_Content_Count_4() throws Exception { - sendCancelObserveAllWithAwait(deviceId); - // ObserveComposite - sendCancelObserveAllWithAwait(deviceId); - String expectedIdVer3_0_14 = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14; - String expectedIdVer3_0_15= objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_15; - 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; - String expectedIds = "[\"" + idVer_3_0_9 + "\", \"" + expectedIdVer3_0_14 + "\", \"" + expectedIdVer5_0_3 + "\", \"" + expectedIdVer3_0_15 + "\", \"" + expectedIdVer19_1_0_0 + "\"]"; - String actualResult = sendCompositeRPCByIds("ObserveComposite", expectedIds); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - // ObserveCompositeCancel - expectedIds = "[\"" + idVer_3_0_9 + "\", \"" + expectedIdVer19_1_0_0 + "\", \"" + objectIdVer_3 + "\"]"; - actualResult = sendCompositeRPCByIds("ObserveCompositeCancel", expectedIds); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertEquals("4", rpcActualResult.get("value").asText()); - + public void testObserveCompositeResource_Update_After_Registration_UpdateRegistration() throws Exception { + String id_3_0_9 = fromVersionedIdToObjectId(idVer_3_0_9); + String id_19_0_0 = fromVersionedIdToObjectId(idVer_19_0_0); + String idVer_19_1_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0; + String id_19_1_0 = fromVersionedIdToObjectId(idVer_19_1_0); + 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 String actualResultReadAll = sendCompositeRPCByKeys("ObserveReadAll", null); ObjectNode rpcActualResultReadAll = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultReadAll.get("result").asText()); - String actualValues = rpcActualResultReadAll.get("value").asText(); - assertEquals("[\"SingleObservation:" + fromVersionedIdToObjectId(expectedIdVer5_0_3) + "\"]", actualValues); + 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)); + long initAttrTelemetryAtCount = countUpdateAttrTelemetryAll(); + long initAttrTelemetryAtCount_3_0_9 = countUpdateAttrTelemetryResource(idVer_3_0_9); + long initAttrTelemetryAtCount_19_0_0 = countUpdateAttrTelemetryResource(idVer_19_0_0); + long initAttrTelemetryAtCount_19_1_0 = countUpdateAttrTelemetryResource(idVer_19_1_0); + long initAttrTelemetryAtCount_19_0_2 = countUpdateAttrTelemetryResource(idVer_19_0_2); + updateRegAtLeastOnceAfterAction(); + updateAttrTelemetryAllAtLeastOnceAfterAction(initAttrTelemetryAtCount); + updateAttrTelemetryResourceAtLeastOnceAfterAction(initAttrTelemetryAtCount_3_0_9, idVer_3_0_9); + updateAttrTelemetryResourceAtLeastOnceAfterAction(initAttrTelemetryAtCount_19_0_0, idVer_19_0_0); + updateAttrTelemetryResourceAtLeastOnceAfterAction(initAttrTelemetryAtCount_19_1_0, idVer_19_1_0); + updateAttrTelemetryResourceAtLeastOnceAfterAction(initAttrTelemetryAtCount_19_0_2, idVer_19_0_2); + + // 2 - "ObserveReadAll": No update of all resources we are observing - after "ObserveReadCancelAll" + sendObserveCancelAllWithAwait(deviceId); + updateRegAtLeastOnceAfterAction(); + 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); + assertEquals(rpcactualValues.size(), 0); + // 2.1 - ObserveComposite: observeCancelAll verify" + initAttrTelemetryAtCount = countUpdateAttrTelemetryAll(); + initAttrTelemetryAtCount_3_0_9 = countUpdateAttrTelemetryResource(idVer_3_0_9); + initAttrTelemetryAtCount_19_0_0 = countUpdateAttrTelemetryResource(idVer_19_0_0); + initAttrTelemetryAtCount_19_1_0 = countUpdateAttrTelemetryResource(idVer_19_1_0); + initAttrTelemetryAtCount_19_0_2 = countUpdateAttrTelemetryResource(idVer_19_0_2); + updateRegAtLeastOnceAfterAction(); + assertEquals(countUpdateAttrTelemetryAll(), initAttrTelemetryAtCount); + assertEquals(countUpdateAttrTelemetryResource(idVer_3_0_9), initAttrTelemetryAtCount_3_0_9); + assertEquals(countUpdateAttrTelemetryResource(idVer_19_0_0), initAttrTelemetryAtCount_19_0_0); + assertEquals(countUpdateAttrTelemetryResource(idVer_19_1_0), initAttrTelemetryAtCount_19_1_0); + assertEquals(countUpdateAttrTelemetryResource(idVer_19_0_2), initAttrTelemetryAtCount_19_0_2); + + // 3 - ObserveComposite: at least one update value of all resources we observe - after ObserveComposite" + String expectedKeys = "[\"" + RESOURCE_ID_NAME_3_9 + "\", \"" + RESOURCE_ID_NAME_19_0_0 + "\", \"" + RESOURCE_ID_NAME_19_0_2 + "\", \"" + RESOURCE_ID_NAME_19_1_0 + "\"]"; + String actualResult = sendCompositeRPCByKeys("ObserveComposite", expectedKeys); + assertTrue(actualResult.contains(id_3_0_9 + "=LwM2mSingleResource")); + assertTrue(actualResult.contains(id_19_0_0 + "=LwM2mMultipleResource")); + assertTrue(actualResult.contains(id_19_1_0 + "=LwM2mMultipleResource")); + assertTrue(actualResult.contains(id_19_0_2 + "=LwM2mSingleResource")); + // 3.1 - ObserveComposite: - verify"); + 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); + assertEquals(rpcactualValues.size(), 1); + assertFalse(actualResultReadAll.contains("SingleObservation")); + assertTrue(actualResultReadAll.contains("CompositeObservation:")); + assertTrue(actualResultReadAll.contains(id_19_0_2)); + assertTrue(actualResultReadAll.contains(id_19_1_0)); + assertTrue(actualResultReadAll.contains(id_19_0_0)); + assertTrue(actualResultReadAll.contains(id_3_0_9)); + initAttrTelemetryAtCount = countUpdateAttrTelemetryAll(); + initAttrTelemetryAtCount_3_0_9 = countUpdateAttrTelemetryResource(idVer_3_0_9); + initAttrTelemetryAtCount_19_0_0 = countUpdateAttrTelemetryResource(idVer_19_0_0); + initAttrTelemetryAtCount_19_1_0 = countUpdateAttrTelemetryResource(idVer_19_1_0); + initAttrTelemetryAtCount_19_0_2 = countUpdateAttrTelemetryResource(idVer_19_0_2); + updateRegAtLeastOnceAfterAction(); + updateAttrTelemetryAllAtLeastOnceAfterAction(initAttrTelemetryAtCount); + updateAttrTelemetryResourceAtLeastOnceAfterAction(initAttrTelemetryAtCount_3_0_9, idVer_3_0_9); + updateAttrTelemetryResourceAtLeastOnceAfterAction(initAttrTelemetryAtCount_19_0_0, idVer_19_0_0); + updateAttrTelemetryResourceAtLeastOnceAfterAction(initAttrTelemetryAtCount_19_1_0, idVer_19_1_0); + updateAttrTelemetryResourceAtLeastOnceAfterAction(initAttrTelemetryAtCount_19_0_2, idVer_19_0_2); } - private String sendObserve(String method, String params) throws Exception { String sendRpcRequest; if (params == null) { sendRpcRequest = "{\"method\": \"" + method + "\"}"; - } - else { + } else { sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + params + "\"}}"; } return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, sendRpcRequest, String.class, status().isOk()); @@ -481,7 +513,71 @@ public class RpcLwm2MIntegrationObserveCompositeTest extends AbstractRpcLwM2MInt } private String sendCompositeRPCByKeys(String method, String keys) throws Exception { - String setRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"keys\":" + keys + "}}"; - return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); + String sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"keys\":" + keys + "}}"; + return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, sendRpcRequest, String.class, status().isOk()); + } + + private long countUpdateAttrTelemetryAll() { + return Mockito.mockingDetails(defaultUplinkMsgHandlerTest) + .getInvocations().stream() + .filter(invocation -> invocation.getMethod().getName().equals("updateAttrTelemetry")) + .count(); + } + + private void updateAttrTelemetryAllAtLeastOnceAfterAction(long initialInvocationCount) { + AtomicLong newInvocationCount = new AtomicLong(initialInvocationCount); + log.warn("countUpdateAttrTelemetryAllAtLeastOnceAfterAction: initialInvocationCount [{}]", initialInvocationCount); + await("Update AttrTelemetryAll at-least-once after action") + .atMost(50, TimeUnit.SECONDS) + .until(() -> { + newInvocationCount.set(countUpdateAttrTelemetryAll()); + return newInvocationCount.get() > initialInvocationCount; + }); + log.warn("countUpdateAttrTelemetryAllAtLeastOnceAfterAction: newInvocationCount [{}]", newInvocationCount.get()); + } + + + private long countUpdateAttrTelemetryResource(String idVerRez) { + return Mockito.mockingDetails(defaultUplinkMsgHandlerTest) + .getInvocations().stream() + .filter(invocation -> + invocation.getMethod().getName().equals("updateAttrTelemetry") && + invocation.getArguments().length > 1 && + idVerRez.equals(invocation.getArguments()[1]) + ) + .count(); + } + + + private void updateAttrTelemetryResourceAtLeastOnceAfterAction(long initialInvocationCount, String idVerRez) { + AtomicLong newInvocationCount = new AtomicLong(initialInvocationCount); + log.warn("countUpdateAttrTelemetryResourceAtLeastOnceAfterAction: initialInvocationCount [{}]", initialInvocationCount); + await("Update AttrTelemetryResource at-least-once after action") + .atMost(50, TimeUnit.SECONDS) + .until(() -> { + newInvocationCount.set(countUpdateAttrTelemetryResource(idVerRez)); + return newInvocationCount.get() > initialInvocationCount; + }); + log.warn("countUpdateAttrTelemetryResourceAtLeastOnceAfterAction: newInvocationCount [{}]", newInvocationCount.get()); + } + + private long countUpdateReg() { + return Mockito.mockingDetails(defaultUplinkMsgHandlerTest) + .getInvocations().stream() + .filter(invocation -> invocation.getMethod().getName().equals("updatedReg")) + .count(); + } + + private void updateRegAtLeastOnceAfterAction() { + long initialInvocationCount = countUpdateReg(); + AtomicLong newInvocationCount = new AtomicLong(initialInvocationCount); + log.warn("updateRegAtLeastOnceAfterAction: initialInvocationCount [{}]", initialInvocationCount); + await("Update Registration at-least-once after action") + .atMost(50, TimeUnit.SECONDS) + .until(() -> { + newInvocationCount.set(countUpdateReg()); + return newInvocationCount.get() > initialInvocationCount; + }); + log.warn("updateRegAtLeastOnceAfterAction: newInvocationCount [{}]", newInvocationCount.get()); } } 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 e1717c9b61..9518010f6a 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 @@ -23,6 +23,8 @@ import org.eclipse.leshan.core.link.LinkParseException; import org.eclipse.leshan.core.node.LwM2mPath; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.transport.lwm2m.config.TbLwM2mVersion; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationTest; import java.util.Arrays; @@ -33,9 +35,10 @@ import java.util.stream.Collectors; 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.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; +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.utils.LwM2MTransportUtil.convertObjectIdToVerId; public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegrationTest { @@ -172,4 +175,19 @@ public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegration String setRpcRequest = "{\"method\": \"Discover\", \"params\": {\"id\": \"" + path + "\"}}"; return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk()); } + + private String convertObjectIdToVerId(String path, String ver) { + ver = ver != null ? ver : TbLwM2mVersion.VERSION_1_0.getVersion().toString(); + try { + String[] keyArray = path.split(LWM2M_SEPARATOR_PATH); + if (keyArray.length > 1) { + keyArray[1] = keyArray[1] + LWM2M_SEPARATOR_KEY + ver; + return StringUtils.join(keyArray, LWM2M_SEPARATOR_PATH); + } else { + return path; + } + } catch (Exception e) { + return null; + } + } } 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 5a849e70e4..ce2622c4db 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 @@ -25,7 +25,6 @@ import org.eclipse.leshan.server.registration.Registration; import org.junit.Test; import org.mockito.Mockito; import org.springframework.boot.test.mock.mockito.SpyBean; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.transport.lwm2m.rpc.AbstractRpcLwM2MIntegrationObserveTest; import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler; @@ -38,10 +37,10 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; 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.RESOURCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_2; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_3; -import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9; import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.fromVersionedIdToObjectId; @Slf4j @@ -51,16 +50,12 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO DefaultLwM2mUplinkMsgHandler defaultUplinkMsgHandlerTest; @Test - public void testObserveReadAll_Count_2_CancelAll_Count_0_Ok() throws Exception { - String actualResultReadAll = sendRpcObserve("ObserveReadAll", null); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String actualValuesReadAll = rpcActualResult.get("value").asText(); - assertEquals(2, actualValuesReadAll.split(",").length); - String expected = "\"SingleObservation:/19/0/0\""; - assertTrue(actualValuesReadAll.contains(expected)); - expected = "\"SingleObservation:/3/0/9\""; - assertTrue(actualValuesReadAll.contains(expected)); + 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); } /** @@ -69,16 +64,11 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserveOneResource_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception { - sendCancelObserveAllWithAwait(deviceId); - String idVer_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; - String actualResult = sendRpcObserve("Observe", idVer_3_0_9); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource")); - assertEquals(Optional.of(1).get(), Optional.ofNullable(getCntObserveAll(deviceId)).get()); + sendObserveCancelAllWithAwait(deviceId); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9); int cntUpdate = 3; - verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate)) + verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate)) .onUpdateValueAfterReadResponse(Mockito.any(Registration.class), eq(idVer_3_0_9), Mockito.any(ReadResponse.class)); } @@ -88,15 +78,12 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserveOneObjectInstance_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception { - sendCancelObserveAllWithAwait(deviceId); + sendObserveCancelAllWithAwait(deviceId); String idVer_3_0 = objectInstanceIdVer_3; - String actualResult = sendRpcObserve("Observe", idVer_3_0); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource")); - assertEquals(Optional.of(1).get(), Optional.ofNullable(getCntObserveAll(deviceId)).get()); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0); + int cntUpdate = 3; - verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate)) + verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate)) .updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9)); } @@ -106,19 +93,15 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserveOneObject_Result_CONTENT_Value_Count_3_After_Cancel_Count_2() throws Exception { - sendCancelObserveAllWithAwait(deviceId); + sendObserveCancelAllWithAwait(deviceId); String idVer_3_0 = objectInstanceIdVer_3; - String actualResult = sendRpcObserve("Observe", idVer_3_0); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource")); - assertEquals(Optional.of(1).get(), Optional.ofNullable(getCntObserveAll(deviceId)).get()); + sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0); + int cntUpdate = 3; - verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate)) + verify(defaultUplinkMsgHandlerTest, timeout(10000).times(cntUpdate)) .updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9)); } - /** * Repeated request on Observe * Observe {"id":"/3_1.2/0/0"} @@ -126,15 +109,10 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserveRepeated_Result_CONTENT_AddIfAbsent() throws Exception { - String idVer_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; - String actualResult = sendRpcObserve("Observe", idVer_3_0_0); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - actualResult = sendRpcObserve("Observe", idVer_3_0_0); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + sendRpcObserveOkWithResultValue("Observe", idVer_3_0_0); + String rpcActualResult = sendRpcObserveOkWithResultValue("Observe", idVer_3_0_0); String expected = "LwM2mSingleResource [id=0"; - assertTrue(rpcActualResult.get("value").asText().contains(expected)); + assertTrue(rpcActualResult.contains(expected)); } /** @@ -143,15 +121,14 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserveWithBadVersion_Result_BadRequest_ErrorMsg_BadVersionMustBe_Ver() throws Exception { - String expectedInstance = (String) expectedInstances.stream().filter(path -> !((String)path).contains("_")).findFirst().get(); + String expectedInstance = (String) expectedInstances.stream().filter(path -> !((String) path).contains("_")).findFirst().get(); LwM2mPath expectedPath = new LwM2mPath(expectedInstance); int expectedResource = lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey(); String ver = lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().version; String expectedId = "/" + expectedPath.getObjectId() + "_" + Version.MAX + "/" + expectedPath.getObjectInstanceId() + "/" + expectedResource; - String actualResult = sendRpcObserve("Observe", expectedId); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + ObjectNode rpcActualResult = sendRpcObserveWithResult("Observe", expectedId); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); - String expected = "Specified resource id " + expectedId +" is not valid version! Must be version: " + ver; + String expected = "Specified resource id " + expectedId + " is not valid version! Must be version: " + ver; assertEquals(expected, rpcActualResult.get("error").asText()); } @@ -162,10 +139,9 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserveNoImplementedInstanceOnDevice_Result_NotFound() throws Exception { - String objectInstanceIdVer = (String) expectedObjectIdVers.stream().filter(path -> ((String)path).contains("/" + ACCESS_CONTROL)).findFirst().get(); + String objectInstanceIdVer = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + ACCESS_CONTROL)).findFirst().get(); String expected = objectInstanceIdVer + "/" + OBJECT_INSTANCE_ID_0; - String actualResult = sendRpcObserve("Observe", expected); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + ObjectNode rpcActualResult = sendRpcObserveWithResult("Observe", expected); assertEquals(ResponseCode.NOT_FOUND.getName(), rpcActualResult.get("result").asText()); } @@ -177,8 +153,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO @Test public void testObserveNoImplementedResourceOnDeviceValueNull_Result_BadRequest() throws Exception { String expected = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_3; - String actualResult = sendRpcObserve("Observe", expected); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + ObjectNode rpcActualResult = sendRpcObserveWithResult("Observe", expected); String expectedValue = "value MUST NOT be null"; assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); assertEquals(expectedValue, rpcActualResult.get("error").asText()); @@ -191,9 +166,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO @Test public void testObserveResourceNotRead_Result_METHOD_NOT_ALLOWED() throws Exception { String expectedId = objectInstanceIdVer_5 + "/" + RESOURCE_ID_0; - sendRpcObserve("Observe", expectedId); - String actualResult = sendRpcObserve("Observe", expectedId); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + ObjectNode rpcActualResult = sendRpcObserveWithResult("Observe", expectedId); assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActualResult.get("result").asText()); } @@ -204,9 +177,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO @Test public void testObserveExecuteResource_Result_METHOD_NOT_ALLOWED() throws Exception { String expectedId = objectInstanceIdVer_5 + "/" + RESOURCE_ID_2; - sendRpcObserve("Observe", expectedId); - String actual = sendRpcObserve("Observe", expectedId); - ObjectNode rpcActual = JacksonUtil.fromString(actual, ObjectNode.class); + ObjectNode rpcActual = sendRpcObserveWithResult("Observe", expectedId); assertEquals(ResponseCode.METHOD_NOT_ALLOWED.getName(), rpcActual.get("result").asText()); } @@ -217,100 +188,80 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserveRepeatedRequestObserveOnDevice_Result_CONTENT_PutIfAbsent() throws Exception { - String idVer_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; - String actualResult = sendRpcObserve("Observe", idVer_3_0_0); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - actualResult = sendRpcObserve("Observe", idVer_3_0_0); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + sendRpcObserveOkWithResultValue("Observe", idVer_3_0_0); + String rpcActualResult = sendRpcObserveOkWithResultValue("Observe", idVer_3_0_0); String expected = "LwM2mSingleResource [id=0"; - assertTrue(rpcActualResult.get("value").asText().contains(expected)); + assertTrue(rpcActualResult.contains(expected)); } /** - * Observe {"id":["3"]} - Ok * PreviousObservation contains "3/0/9" + * Observe {"id":["19"]} - Bad Request + * Observe {"id":["19/0"]} - Bad Request + * Observe {"id":["19/1"]} - Ok * @throws Exception */ @Test - public void testObserve_Result_CONTENT_ONE_PATH_PreviousObservation_CONTAINCE_OTHER_CurrentObservation() throws Exception { - sendCancelObserveAllWithAwait(deviceId); - // "3/0/9" - String idVer_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; - String actualResult3_0_9 = sendRpcObserve("Observe", idVer_3_0_9); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult3_0_9, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - // "3" - String actualResult3 = sendRpcObserve("Observe", objectIdVer_3); - rpcActualResult = JacksonUtil.fromString(actualResult3, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - // PreviousObservation "3/0/9" change to CurrentObservation "3" - String actualResultReadAll = sendRpcObserve("ObserveReadAll", null); - rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String actualValuesReadAll = rpcActualResult.get("value").asText(); - assertEquals(1, actualValuesReadAll.split(",").length); - String expected = "\"SingleObservation:/3\""; - assertTrue(actualValuesReadAll.contains(expected)); - } - - /** - * Observe {"id":["3/0/9"]} - Ok - * PreviousObservation contains "3" - * @throws Exception - */ - @Test - public void testObserve_Result_CONTENT_ONE_PATH_CurrentObservation_CONTAINCE_OTHER_PreviousObservation() throws Exception { - sendCancelObserveAllWithAwait(deviceId); - - // "3" - String actualResult3 = sendRpcObserve("Observe", objectIdVer_3); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult3, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - - // "3/0/0"; WARN: - Token collision ? existing observation [/3] includes input observation [/3/0/0] - String idVer_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; - String actualResult3_0_0 = sendRpcObserve("Observe", idVer_3_0_0); - rpcActualResult = JacksonUtil.fromString(actualResult3_0_0, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - - String actualResultReadAll = sendRpcObserve("ObserveReadAll", null); - rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); + public void testObserves_OverlappedPaths_FirstResource_SecondObjectOrInstance() throws Exception { + sendObserveCancelAllWithAwait(deviceId); + // "19/0/0" + sendRpcObserveOkWithResultValue("Observe", idVer_19_0_0); + // PreviousObservation "19/0/0" change to CurrentObservation "19" - object + ObjectNode rpcActualResult = sendRpcObserveWithResult("Observe", objectIdVer_19); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expected = "Resource [" + fromVersionedIdToObjectId(objectIdVer_19) + "] conflict with is already registered as SingleObservation [" + fromVersionedIdToObjectId(idVer_19_0_0) + "]."; + assertEquals(expected, rpcActualResult.get("error").asText()); + // Verify ObserveReadAll + String actualValuesReadAll = sendRpcObserveOkWithResultValue("ObserveReadAll", null); + String expectedReadAll = "[\"SingleObservation:/19/0/0\"]"; + assertEquals(expectedReadAll, actualValuesReadAll); + // PreviousObservation "19/0/0" change to CurrentObservation "19/0" - instance + String expectedIdVer19_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0; + rpcActualResult = sendRpcObserveWithResult("Observe", expectedIdVer19_0); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + expected = "Resource [" + fromVersionedIdToObjectId(expectedIdVer19_0) + "] conflict with is already registered as SingleObservation [" + fromVersionedIdToObjectId(idVer_19_0_0) + "]."; + assertEquals(expected, rpcActualResult.get("error").asText()); + // Verify ObserveReadAll + actualValuesReadAll = sendRpcObserveOkWithResultValue("ObserveReadAll", null); + assertEquals(expectedReadAll, actualValuesReadAll); + // PreviousObservation "19/0/0" add CurrentObservation "19/1" - instance + String expectedIdVer19_1 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1; + rpcActualResult = sendRpcObserveWithResult("Observe", expectedIdVer19_1); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String actualValuesReadAll = rpcActualResult.get("value").asText(); - assertEquals(1, actualValuesReadAll.split(",").length); - String expected = "\"SingleObservation:/3\""; - assertTrue(actualValuesReadAll.contains(expected)); + assertTrue(rpcActualResult.get("value").asText().contains("LwM2mObjectInstance")); + // Verify ObserveReadAll + actualValuesReadAll = sendRpcObserveOkWithResultValue("ObserveReadAll", null); + assertTrue(actualValuesReadAll.contains("SingleObservation:/19/1")); + assertTrue(actualValuesReadAll.contains("SingleObservation:/19/0/0")); + // PreviousObservation "19/1/"- instance change to CurrentObservation "19/1/0" - resource + String expectedIdVer19_1_0 = expectedIdVer19_1 + "/" + RESOURCE_ID_0; + rpcActualResult = sendRpcObserveWithResult("Observe", expectedIdVer19_1_0); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + expected = "Resource [" + fromVersionedIdToObjectId(expectedIdVer19_1_0) + "] conflict with is already registered as SingleObservation [" + fromVersionedIdToObjectId(expectedIdVer19_1) + "]."; + assertEquals(expected, rpcActualResult.get("error").asText()); + // Verify ObserveReadAll + actualValuesReadAll = sendRpcObserveOkWithResultValue("ObserveReadAll", null); + assertTrue(actualValuesReadAll.contains("SingleObservation:/19/1")); + assertTrue(actualValuesReadAll.contains("SingleObservation:/19/0/0")); } - /** * Observe {"id":"/3/0/9"} * ObserveCancel {"id":"/3/0/9"} */ @Test public void testObserveResource_ObserveCancelResource_Result_CONTENT_Count_1() throws Exception { - sendCancelObserveAllWithAwait(deviceId); + sendObserveCancelAllWithAwait(deviceId); - String expectedId_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; - sendRpcObserve("Observe", expectedId_3_0_9); - String actualResultReadAll = sendRpcObserve("ObserveReadAll", null); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String actualValuesReadAll = rpcActualResult.get("value").asText(); + String actualValuesReadAll = sendRpcObserveReadAllWithResult(idVer_3_0_9); assertEquals(1, actualValuesReadAll.split(",").length); - String expected = "\"SingleObservation:" + fromVersionedIdToObjectId(expectedId_3_0_9) + "\""; + String expected = "\"SingleObservation:" + id_3_0_9 + "\""; assertTrue(actualValuesReadAll.contains(expected)); // cancel observe "/3_1.2/0/9" - String actualResult = sendRpcObserve("ObserveCancel", expectedId_3_0_9); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertEquals("1", rpcActualResult.get("value").asText()); + sendRpcObserveOkWithResultValue("ObserveCancel", idVer_3_0_9); } - /** * Observe {"id":"/3"} * ObserveCancel {"id":"/3/0/9"} -> INTERNAL_SERVER_ERROR @@ -318,64 +269,90 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationO */ @Test public void testObserveObject_ObserveCancelOneResource_Result_INTERNAL_SERVER_ERROR_Than_Cancel_ObserveObject_Result_CONTENT_Count_1() throws Exception { - sendCancelObserveAllWithAwait(deviceId); + sendObserveCancelAllWithAwait(deviceId); - String expectedId_3 = objectIdVer_3; - sendRpcObserve("Observe", expectedId_3); - String actualResultReadAll = sendRpcObserve("ObserveReadAll", null); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String actualValuesReadAll = rpcActualResult.get("value").asText(); + String actualValuesReadAll = sendRpcObserveReadAllWithResult(objectIdVer_3); assertEquals(1, actualValuesReadAll.split(",").length); - String expected = "\"SingleObservation:" + fromVersionedIdToObjectId(expectedId_3) + "\""; + String expected = "\"SingleObservation:" + fromVersionedIdToObjectId(objectIdVer_3) + "\""; assertTrue(actualValuesReadAll.contains(expected)); - // cancel observe "/3_1.2/0/9" - String expectedId_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; - String actualResult = sendRpcObserve("ObserveCancel", expectedId_3_0_9); - String expectedValue = "for observation path [" + fromVersionedIdToObjectId(objectIdVer_3) + "], that includes this observation path [" + fromVersionedIdToObjectId(expectedId_3_0_9); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + // cancel observe "/3_1.2/0/9" + ObjectNode rpcActualResult = sendRpcObserveWithResult("ObserveCancel", idVer_3_0_9); + String expectedValue = "Could not find active Observe component with path: " + idVer_3_0_9; assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); assertTrue(rpcActualResult.get("error").asText().contains(expectedValue)); - // cancel observe "/3_1.2" - actualResult = sendRpcObserve("ObserveCancel", expectedId_3); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertEquals("1", rpcActualResult.get("value").asText()); + // cancel observe "/3_1.2" + sendRpcObserveOkWithResultValue("ObserveCancel", objectIdVer_3); } /** * Observe {"id":"/3/0/0"} * Observe {"id":"/3/0/9"} - * ObserveCancel {"id":"/3"} - Ok, cnt = 2 + * ObserveCancel {"id":"/3"} - Bad + * ObserveCancel {"/3/0/0"} - Ok + * ObserveCancel {"/3/0/9"} - Ok + * */ @Test public void testObserveResource_ObserveCancelObject_Result_CONTENT_Count_1() throws Exception { - sendCancelObserveAllWithAwait(deviceId); - String expectedId_3_0_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; - sendRpcObserve("Observe", expectedId_3_0_0); - String expectedId_3_0_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; - sendRpcObserve("Observe", expectedId_3_0_9); - String actualResultReadAll = sendRpcObserve("ObserveReadAll", null); - ObjectNode rpcActualResult = JacksonUtil.fromString(actualResultReadAll, ObjectNode.class); - assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - String actualValuesReadAll = rpcActualResult.get("value").asText(); - assertEquals(2, actualValuesReadAll.split(",").length); - String expected_3_0_0 = "\"SingleObservation:" + fromVersionedIdToObjectId(expectedId_3_0_0) + "\""; - String expected_3_0_9 = "\"SingleObservation:" + fromVersionedIdToObjectId(expectedId_3_0_9) + "\""; - assertTrue(actualValuesReadAll.contains(expected_3_0_0)); - assertTrue(actualValuesReadAll.contains(expected_3_0_9)); - - // cancel observe "/3_1.2" - String expectedId_3 = objectIdVer_3; - String actualResult = sendRpcObserve("ObserveCancel", expectedId_3); - rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); + sendObserveCancelAllWithAwait(deviceId); + sendRpcObserveWithWithTwoResource(idVer_3_0_0, idVer_3_0_9); + String rpcActualResul = sendRpcObserveOkWithResultValue("ObserveReadAll", null); + assertEquals(2, rpcActualResul.split(",").length); + String expected_3_0_0 = "\"SingleObservation:" + fromVersionedIdToObjectId(idVer_3_0_0) + "\""; + String expected_3_0_9 = "\"SingleObservation:" + id_3_0_9 + "\""; + assertTrue(rpcActualResul.contains(expected_3_0_0)); + assertTrue(rpcActualResul.contains(expected_3_0_9)); + // cancel observe "/3_1.2" + ObjectNode rpcActualResult = sendRpcObserveWithResult("ObserveCancel", objectIdVer_3); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + String expected = "Could not find active Observe component with path: " + objectIdVer_3; + assertEquals(expected, rpcActualResult.get("error").asText()); + // Verify ObserveReadAll + rpcActualResul = sendRpcObserveOkWithResultValue("ObserveReadAll", null); + String expectedReadAll = "[\"SingleObservation:/19/0/0\"]"; + assertTrue(rpcActualResul.contains(expected_3_0_0)); + assertTrue(rpcActualResul.contains(expected_3_0_9)); + } + + /** + * ObserveCancelAll + * Observe {"id":"3_1.2/0/9"} + * updateRegistration + * idResources_3_1.2/0/9 => updateAttrTelemetry >= 10 times + */ + @Test + public void testObserveResource_Update_AfterUpdateRegistration() throws Exception { + sendObserveCancelAllWithAwait(deviceId); + + int cntUpdate = 3; + verify(defaultUplinkMsgHandlerTest, timeout(50000).atLeast(cntUpdate)) + .updatedReg(Mockito.any(Registration.class)); + + sendRpcObserveWithContainsLwM2mSingleResource(idVer_3_0_9); + + cntUpdate = 10; + verify(defaultUplinkMsgHandlerTest, timeout(50000).atLeast(cntUpdate)) + .updateAttrTelemetry(Mockito.any(Registration.class), eq(idVer_3_0_9)); + } + + private void sendRpcObserveWithWithTwoResource(String expectedId_1, String expectedId_2) throws Exception { + sendRpcObserveOk("Observe", expectedId_1); + sendRpcObserveOk("Observe", expectedId_2); + } + + private String sendRpcObserveReadAllWithResult(String params) throws Exception { + sendRpcObserveOk("Observe", params); + ObjectNode rpcActualResult = sendRpcObserveWithResult("ObserveReadAll", null); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); - assertEquals("2", rpcActualResult.get("value").asText()); + return rpcActualResult.get("value").asText(); } - private String sendRpcObserve(String method, String params) throws Exception { - return sendObserve(method, params, deviceId); + 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/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java index 5bf6b95853..256d10465d 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java @@ -107,7 +107,7 @@ public interface AlarmService extends EntityDaoService { PageData findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection orderedEntityIds); - PageData findAlarmIdsByAssigneeId(TenantId tenantId, UserId userId, PageLink pageLink); + List> findAlarmIdsByAssigneeId(TenantId tenantId, UserId userId, long createdTimeOffset, AlarmId idOffset, int limit); List> findAlarmIdsByOriginatorId(TenantId tenantId, EntityId originatorId, long createdTimeOffset, AlarmId idOffset, int limit); @@ -118,4 +118,5 @@ public interface AlarmService extends EntityDaoService { long countAlarmsByQuery(TenantId tenantId, CustomerId customerId, AlarmCountQuery query); PageData findAlarmTypesByTenantId(TenantId tenantId, PageLink pageLink); + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/AlarmsUnassignHousekeeperTask.java b/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/AlarmsUnassignHousekeeperTask.java index 5911418dc3..b84e8cd742 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/AlarmsUnassignHousekeeperTask.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/housekeeper/AlarmsUnassignHousekeeperTask.java @@ -21,6 +21,11 @@ import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import lombok.ToString; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; + +import java.util.List; +import java.util.UUID; @Data @ToString(callSuper = true) @@ -29,10 +34,21 @@ import org.thingsboard.server.common.data.User; public class AlarmsUnassignHousekeeperTask extends HousekeeperTask { private String userTitle; + private List alarms; protected AlarmsUnassignHousekeeperTask(User user) { - super(user.getTenantId(), user.getId(), HousekeeperTaskType.UNASSIGN_ALARMS); - this.userTitle = user.getTitle(); + this(user.getTenantId(), user.getId(), user.getTitle(), null); + } + + public AlarmsUnassignHousekeeperTask(TenantId tenantId, UserId userId, String userTitle, List alarms) { + super(tenantId, userId, HousekeeperTaskType.UNASSIGN_ALARMS); + this.userTitle = userTitle; + this.alarms = alarms; + } + + @Override + public String getDescription() { + return super.getDescription() + (alarms != null ? " (" + alarms + ")" : ""); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java index 77e427fc77..80221eb5ea 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java @@ -68,9 +68,8 @@ import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.registration.Registration; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.profile.lwm2m.ObjectAttributes; -import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; -import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; @@ -88,14 +87,11 @@ import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import java.util.Arrays; import java.util.Collection; import java.util.Date; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; -import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.RejectedExecutionException; import java.util.function.Function; @@ -177,24 +173,32 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } } + /** + * if resource in CompositeObservation is already registered - return BAD REQUEST + */ @Override public void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback callback) { try { validateVersionedId(client, request); LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); - ObserveRequest downlink; - ContentFormat contentFormat = getReadRequestContentFormat(client, request, modelProvider); - if (resultIds.isResourceInstance()) { - downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), resultIds.getResourceInstanceId()); - } else if (resultIds.isResource()) { - downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); - } else if (resultIds.isObjectInstance()) { - downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId()); + String resourceExisting = checkResourceSingleObservationForExisting(client, resultIds.toString()); + if (StringUtils.isNotBlank(resourceExisting)) { + callback.onValidationError(request.toString(), resourceExisting); } else { - downlink = new ObserveRequest(contentFormat, resultIds.getObjectId()); + ObserveRequest downlink; + ContentFormat contentFormat = getReadRequestContentFormat(client, request, modelProvider); + if (resultIds.isResourceInstance()) { + downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), resultIds.getResourceInstanceId()); + } else if (resultIds.isResource()) { + downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); + } else if (resultIds.isObjectInstance()) { + downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId()); + } else { + downlink = new ObserveRequest(contentFormat, resultIds.getObjectId()); + } + log.info("[{}] Send observation: {}.", client.getEndpoint(), request.getVersionedId()); + sendSimpleRequest(client, downlink, request.getTimeout(), callback); } - log.info("[{}] Send observation: {}.", client.getEndpoint(), request.getVersionedId()); - sendSimpleRequest(client, downlink, request.getTimeout(), callback); } catch (InvalidRequestException e) { callback.onValidationError(request.toString(), e.getMessage()); } @@ -208,19 +212,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im if (observation instanceof SingleObservation) { paths.add("SingleObservation:" + ((SingleObservation) observation).getPath().toString()); } else { - List listPath = ((CompositeObservation) observation).getPaths(); - List pathsComposite = listPath.stream().map(lwM2mPath -> (lwM2mPath.toString())).collect(Collectors.toList()); - Set pathsCompositeSort = new TreeSet<>(); - if (pathsComposite.size() == 1) { - pathsCompositeSort.add("CompositeObservation: [" + pathsComposite.get(0) + "]"); - } else if (pathsComposite.size() > 1) { - List sort = new LinkedList<>(); - sort.addAll(pathsComposite); - sort.set(0, "CompositeObservation: [" + sort.get(0)); - sort.set(pathsComposite.size()-1, sort.get(pathsComposite.size()-1) + "]"); - pathsCompositeSort = new LinkedHashSet<>(sort); - } - paths.addAll(pathsCompositeSort); + paths.add("CompositeObservation: " + ((CompositeObservation) observation).getPaths().toString()); } }); @@ -234,10 +226,15 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im public void sendObserveCompositeRequest(LwM2mClient client, TbLwM2MObserveCompositeRequest request, DownlinkRequestCallback callback) { try { - log.trace("[{}] Send Composite observation: [{}].", client.getEndpoint(), request.getObjectIds()); ContentFormat compositeContentFormat = this.findFirstContentFormatForComposite(client.getClientSupportContentFormats()); - ObserveCompositeRequest downlink = new ObserveCompositeRequest(compositeContentFormat, compositeContentFormat, request.getObjectIds()); - sendCompositeRequest(client, downlink, this.config.getTimeout(), callback); + String resourceExisting = checkResourceForExistingComposite(client, request.getObjectIds()); + if (StringUtils.isNotBlank(resourceExisting)) { + callback.onValidationError(request.toString(), resourceExisting); + } else { + ObserveCompositeRequest downlink = new ObserveCompositeRequest(compositeContentFormat, compositeContentFormat, request.getObjectIds()); + log.trace("[{}] Send ObserveComposite: {}.", client.getEndpoint(), request.getVersionedIds()); + sendCompositeRequest(client, downlink, this.config.getTimeout(), callback); + } } catch (InvalidRequestException e) { callback.onValidationError(request.toString(), e.getMessage()); } @@ -246,44 +243,18 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im @Override public void sendCancelObserveCompositeRequest(LwM2mClient client, TbLwM2MCancelObserveCompositeRequest request, DownlinkRequestCallback callback) { try { - Set observations = context.getServer().getObservationService().getObservations(client.getRegistration()); - List listPath = LwM2mPath.getLwM2mPathList(Arrays.asList(request.getObjectIds())); - Optional observationOpt = Optional.ofNullable(observations.stream().filter(observation -> observation instanceof CompositeObservation && ((CompositeObservation) observation).getPaths().equals(listPath)).findFirst().orElse(null)); - int cnt = 0; - if (observationOpt.isPresent()) { - cnt = context.getServer().getObservationService().cancelCompositeObservations(client.getRegistration(), request.getObjectIds()); + log.trace("[{}] Send CancelObserveComposite: {}.", client.getEndpoint(), request.getVersionedIds()); + int cnt = context.getServer().getObservationService().cancelCompositeObservations(client.getRegistration(), request.getObjectIds()); + if (cnt != 0) { callback.onSuccess(request, cnt); } else { - Set lwPaths = new HashSet<>(); - for (Observation obs : observations) { - LwM2mPath lwPathObs = ((SingleObservation) obs).getPath(); - for (LwM2mPath nodePath : listPath) { - String validNodePath = validatePathObserveCancelAny(nodePath, lwPathObs, client); - if (validNodePath != null) lwPaths.add(validNodePath); - } - }; - for (String nodePath : lwPaths) { - cnt += context.getServer().getObservationService().cancelObservations(client.getRegistration(), nodePath); - } + callback.onValidationError(request.toString(), "Could not find active Observe Composite component with paths: " + Arrays.toString(request.getVersionedIds())); } - callback.onSuccess(request, cnt); - } catch (ThingsboardException e){ + } catch (InvalidRequestException e) { callback.onValidationError(request.toString(), e.getMessage()); } } - private String validatePathObserveCancelAny(LwM2mPath nodePath, LwM2mPath lwPathObs, LwM2mClient client) throws ThingsboardException { - if (nodePath.equals(lwPathObs) || lwPathObs.startWith(nodePath)) { // nodePath = "3", lwPathObs = "3/0/9": cancel for tne all lwPathObs - return lwPathObs.toString(); - } else if (!nodePath.equals(lwPathObs) && nodePath.startWith(lwPathObs)) { - String errorMsg = String.format( - "Unexpected error: There is registration with Endpoint %s for observation path [%s], that includes this observation path [%s]", - client.getRegistration().getEndpoint(), lwPathObs, nodePath); - throw new ThingsboardException(errorMsg, ThingsboardErrorCode.BAD_REQUEST_PARAMS); - } - return null; - } - @Override public void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback> callback) { callback.onSuccess(request, Arrays.stream(client.getRegistration().getSortedObjectLinks()).map(Link::toCoreLinkFormat).collect(Collectors.toList())); @@ -334,22 +305,15 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im @Override public void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback callback) { - try{ - validateVersionedId(client, request); - Set observations = context.getServer().getObservationService().getObservations(client.getRegistration()); - int observeCancelCnt = 0; - Set lwPaths = new HashSet<>(); - for (Observation obs : observations) { - LwM2mPath lwPathObs = ((SingleObservation) obs).getPath(); - LwM2mPath nodePath = new LwM2mPath(request.getObjectId()); - String validNodePath = validatePathObserveCancelAny(nodePath, lwPathObs, client); - if (validNodePath != null) lwPaths.add(validNodePath); - }; - for (String nodePath : lwPaths) { - observeCancelCnt += context.getServer().getObservationService().cancelObservations(client.getRegistration(), nodePath); + try { + log.trace("[{}] Send CancelObserve {}.", client.getEndpoint(), request.getVersionedId()); + int cnt = context.getServer().getObservationService().cancelObservations(client.getRegistration(), request.getObjectId()); + if (cnt != 0) { + callback.onSuccess(request, cnt); + } else { + callback.onValidationError(request.toString(), "Could not find active Observe component with path: " + request.getVersionedId()); } - callback.onSuccess(request, observeCancelCnt); - } catch (ThingsboardException e){ + } catch (InvalidRequestException e) { callback.onValidationError(request.toString(), e.getMessage()); } } @@ -416,7 +380,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im addAttribute(attributes, OBJECT_VERSION, params.getVer()); // Attachment.OBJECT } - return new LwM2mAttributeSet(attributes); + return new LwM2mAttributeSet(attributes); } @Override @@ -702,13 +666,13 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im addAttribute(attributes, attribute, value, null, null); } - private static void addAttribute(List> attributes, LwM2mAttributeModel attribute, T value, Function converter) { + private static void addAttribute(List> attributes, LwM2mAttributeModel attribute, T value, Function converter) { addAttribute(attributes, attribute, value, null, converter); } - private static void addAttribute(List> attributes, LwM2mAttributeModel attributeName, T value, Predicate filter, Function converter) { + private static void addAttribute(List> attributes, LwM2mAttributeModel attributeName, T value, Predicate filter, Function converter) { if (value != null && ((filter == null) || filter.test(value))) { - T valueConvert = (T) converter != null ? (T) converter.apply(value) : value; + T valueConvert = (T) converter != null ? (T) converter.apply(value) : value; attributes.add(new LwM2mAttribute<>(attributeName, valueConvert)); } } @@ -746,12 +710,12 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im if (resourceModel != null && (pathIds.isResourceInstance() || (pathIds.isResource() && !resourceModel.multiple))) { ContentFormat[] desiredFormats; if (OBJLNK.equals(resourceModel.type)) { - desiredFormats = new ContentFormat[]{ContentFormat.LINK, ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON}; + desiredFormats = new ContentFormat[]{ContentFormat.LINK, ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON}; } else if (OPAQUE.equals(resourceModel.type)) { - desiredFormats = new ContentFormat[]{ContentFormat.OPAQUE, ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON}; - } else { - desiredFormats = new ContentFormat[]{ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON}; - } + desiredFormats = new ContentFormat[]{ContentFormat.OPAQUE, ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON}; + } else { + desiredFormats = new ContentFormat[]{ContentFormat.CBOR, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON}; + } return findFirstContentFormatForComp(client.getClientSupportContentFormats(), client.getDefaultContentFormat(), desiredFormats); } else { return getContentFormatForComplex(client); @@ -775,6 +739,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im throw new RuntimeException("The version " + client.getRegistration().getLwM2mVersion() + " is not supported!"); } } + private String toString(R request) { try { return request != null ? request.toString() : ""; @@ -784,7 +749,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } } - private ContentFormat findFirstContentFormatForComposite (Set clientSupportContentFormats) { + private ContentFormat findFirstContentFormatForComposite(Set clientSupportContentFormats) { ContentFormat contentFormat = findFirstContentFormatForComp(clientSupportContentFormats, null, ContentFormat.SENML_CBOR, ContentFormat.SENML_JSON); if (contentFormat != null) { return contentFormat; @@ -792,8 +757,9 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im throw new RuntimeException("This device does not support Composite Operation"); } } + private static ContentFormat findFirstContentFormatForComp(Set clientSupportContentFormats, ContentFormat defaultValue, ContentFormat... desiredFormats) { - List desiredFormatsList = Arrays.asList(desiredFormats); + List desiredFormatsList = Arrays.asList(desiredFormats); for (ContentFormat c : clientSupportContentFormats) { if (desiredFormatsList.contains(c)) { return c; @@ -801,4 +767,61 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } return defaultValue; } + + /** + * Check if at least one of the resource objectIds (Composite) in SingleObservation or CompositeObservation is already registered + * @param objectIds + * @return + */ + private String checkResourceForExistingComposite(LwM2mClient client, String[] objectIds) { + List objectIdsList = Arrays.asList(objectIds); + Set observations = context.getServer().getObservationService().getObservations(client.getRegistration()); + for (Observation observation : observations) { + if (observation instanceof SingleObservation singleObs) { + String idSingleOb = singleObs.getPath().toString(); + if (objectIdsList.contains(idSingleOb)) { + return "Resource [" + idSingleOb + "] is already registered as SingleObservation."; + } + } else if (observation instanceof CompositeObservation compObs) { + String paths = compObs.getPaths().toString(); + for (String idCompOb : objectIds) { + if (paths.contains(idCompOb)) { + return "Resource [" + idCompOb + "] is already registered in CompositeObservation."; + } + } + } + } + return null; + } + + /** + * Check if the resource SingleObservation is already registered in CompositeObservation + * Check if the resource SingleObservation is already registered in SingleObservation and (not equals path + * @param objectId + * @return + */ + private String checkResourceSingleObservationForExisting(LwM2mClient client, String objectId) { + Set observations = context.getServer().getObservationService().getObservations(client.getRegistration()); + for (Observation observation : observations) { + if (observation instanceof SingleObservation singleObs) { + LwM2mPath pathSingleOb = singleObs.getPath(); + LwM2mPath pathObjectId = new LwM2mPath(objectId); + if (!pathSingleOb.toString().equals(objectId)) { + List paths = Arrays.asList(pathSingleOb, pathObjectId); + try { + LwM2mPath.validateNotOverlapping(paths); + } catch (IllegalArgumentException e){ + return "Resource [" + objectId + "] conflict with is already registered as SingleObservation [" + pathSingleOb + "]."; + } + } + } + else if (observation instanceof CompositeObservation compObs) { + String paths = compObs.getPaths().toString(); + if (paths.contains(objectId)) { + return "Resource [" + objectId + "] is already registered in CompositeObservation."; + } + } + } + return null; + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemoryRegistrationStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemoryRegistrationStore.java index 7479bf23ca..45616a4b91 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemoryRegistrationStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbInMemoryRegistrationStore.java @@ -15,32 +15,22 @@ */ package org.thingsboard.server.transport.lwm2m.server.store; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.coap.Token; -import org.eclipse.californium.core.network.RandomTokenGenerator; import org.eclipse.californium.core.network.TokenGenerator; -import org.eclipse.californium.core.network.TokenGenerator.Scope; import org.eclipse.leshan.core.Destroyable; import org.eclipse.leshan.core.Startable; import org.eclipse.leshan.core.Stoppable; -import org.eclipse.leshan.core.model.ObjectModel; -import org.eclipse.leshan.core.model.ResourceModel; -import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.observation.CompositeObservation; import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.observation.ObservationIdentifier; import org.eclipse.leshan.core.observation.SingleObservation; import org.eclipse.leshan.core.peer.LwM2mIdentity; -import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.server.registration.Deregistration; import org.eclipse.leshan.server.registration.ExpirationListener; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.registration.RegistrationUpdate; import org.eclipse.leshan.server.registration.UpdatedRegistration; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2mVersionedModelProvider; @@ -48,7 +38,6 @@ import org.thingsboard.server.transport.lwm2m.server.LwM2mVersionedModelProvider import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -59,13 +48,9 @@ import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import static org.eclipse.leshan.core.californium.ObserveUtil.CTX_CF_OBERSATION; -import static org.eclipse.leshan.core.californium.ObserveUtil.extractSerializedObservation; - @Slf4j public class TbInMemoryRegistrationStore implements RegistrationStore, Startable, Stoppable, Destroyable { @@ -105,7 +90,7 @@ public class TbInMemoryRegistrationStore implements RegistrationStore, Startable this.schedExecutor = schedExecutor; this.cleanPeriod = cleanPeriodInSec; this.modelProvider = modelProvider; - this.config = config; + this.config = config; } /* *************** Leshan Registration API **************** */ @@ -252,138 +237,60 @@ public class TbInMemoryRegistrationStore implements RegistrationStore, Startable List removed = new ArrayList<>(); try { lock.writeLock().lock(); - if (!regsByRegId.containsKey(registrationId)) { throw new IllegalStateException(String.format( "can not add observation %s there is no registration with id %s", observation, registrationId)); } - - if (observation instanceof SingleObservation) { - if (validateObserveResource(((SingleObservation)observation).getPath(), registrationId)) { - updateSingleObservation(registrationId, (SingleObservation) observation, addIfAbsent, removed); - // cancel existing observations for the same path and registration id. - cancelObservation (observation, registrationId, removed); - } - } else { - ContentFormat ct = ((CompositeObservation) observation).getResponseContentFormat(); - Map ctx = observation.getContext(); - String serializedObservation = extractSerializedObservation(observation); - JsonNode nodeSerObs = JacksonUtil.toJsonNode(serializedObservation); - ((CompositeObservation)observation).getPaths().forEach(path -> { - if (validateObserveResource(path, registrationId)) { - String serializedObs = createSerializedSingleObservation(nodeSerObs, path.toString()); - Observation singleObservation = createSingleObservation(registrationId, path, ct, ctx, serializedObs, getTokenGenerator()); - updateSingleObservation(registrationId, (SingleObservation) singleObservation, addIfAbsent, removed); - // cancel existing observations for the same path and registration id. - cancelObservation (singleObservation, registrationId, removed); - } - }); - } - + updateObservation(registrationId, observation, addIfAbsent, removed); } finally { lock.writeLock().unlock(); } - return removed; } - private boolean validateObserveResource(LwM2mPath path, String registrationId){ - // check if the resource is readable. - if (path.isResource() || path.isResourceInstance()) { - ObjectModel objectModel = modelProvider.getObjectModel(getRegistration(registrationId)).getObjectModel(path.getObjectId()); - ResourceModel resourceModel = objectModel == null ? null : objectModel.resources.get(path.getResourceId()); - if (resourceModel == null) { - return false; - } else if (!resourceModel.operations.isReadable()) { - return false; - } else if (path.isResourceInstance() && !resourceModel.multiple) { - return false; - } - } - return true; - } - - private void updateSingleObservation (String registrationId, SingleObservation observation, boolean addIfAbsent, List removed) { - // Absorption by existing Observations - + private void updateObservation(String registrationId, Observation observation, boolean addIfAbsent, List removed) { + // Absorption by existing Observations Observation previousObservation = null; - SingleObservation existingObservation = null; - ObservationIdentifier id = observation.getId(); if (addIfAbsent) { if (!obsByToken.containsKey(id)) { - existingObservation = validateByAbsorptionExistingObservations(observation); - if (existingObservation == null) { - obsByToken.put(id, observation); - } else if (!existingObservation.getPath().equals(observation.getPath())){ - obsByToken.put(id, observation); - previousObservation = obsByToken.get(existingObservation.getId()); - } + previousObservation = obsByToken.put(id, observation); + } else { + obsByToken.put(id, observation); } } else { previousObservation = obsByToken.put(id, observation); } + if (!tokensByRegId.containsKey(registrationId)) { tokensByRegId.put(registrationId, new HashSet()); } - - if (existingObservation == null || !existingObservation.getPath().equals(observation.getPath())) { - tokensByRegId.get(registrationId).add(id); - } + tokensByRegId.get(registrationId).add(id); // log any collisions - if (addIfAbsent && previousObservation != null) { - if (!existingObservation.getPath().equals(observation.getPath())) { - removed.add(previousObservation); - log.warn("Token collision ? observation [{}] will be replaced by observation [{}], that this observation includes input observation [{}]!", - previousObservation, observation, observation); - } else { - log.warn("Token collision ? existing observation [{}] includes input observation [{}]", - existingObservation, observation); - } + if (previousObservation != null) { + removed.add(previousObservation); + log.warn("Token collision ? observation [{}] will be replaced by observation [{}] ", + previousObservation, observation); } - } - - private SingleObservation validateByAbsorptionExistingObservations (SingleObservation observation) { - LwM2mPath pathObservation = observation.getPath(); - AtomicReference result = new AtomicReference<>(); - obsByToken.values().stream().forEach(obs -> { - LwM2mPath pathObs = ((SingleObservation)obs).getPath(); - if ((!pathObservation.equals(pathObs) && pathObs.startWith(pathObservation)) || // pathObs = "3/0/9"-> pathObservation = "3" - (pathObservation.equals(pathObs) && !observation.getId().equals(obs.getId()))) { - result.set((SingleObservation)obs); - } else if (!pathObservation.equals(pathObs) && pathObservation.startWith(pathObs)) { // pathObs = "3" -> pathObservation = "3/0/9" - result.set(observation); + // cancel existing observations for the same path and registration id. + for (Observation obs : unsafeGetObservations(registrationId)) { + if (areTheSamePaths(observation, obs) && !observation.getId().equals(obs.getId())) { + unsafeRemoveObservation(obs.getId()); + removed.add(obs); } - }); - return result.get(); - } - - private TokenGenerator getTokenGenerator(){ - if (this.tokenGenerator == null) { - this.tokenGenerator = new RandomTokenGenerator(config.getCoapConfig()); } - return this.tokenGenerator; } - public static SingleObservation createSingleObservation(String registrationId, LwM2mPath target, ContentFormat ct, - Map ctx, String serializedObservation, TokenGenerator tokenGenerator) { - Token token = tokenGenerator.createToken(Scope.SHORT_TERM); - Map protocolData = Collections.emptyMap(); - if (serializedObservation != null) { - protocolData = new HashMap<>(); - protocolData.put(CTX_CF_OBERSATION, serializedObservation); + private boolean areTheSamePaths(Observation observation, Observation obs) { + if (observation instanceof SingleObservation && obs instanceof SingleObservation) { + return ((SingleObservation) observation).getPath().equals(((SingleObservation) obs).getPath()); } - return new SingleObservation(new ObservationIdentifier(token.getBytes()), registrationId, target, ct, ctx, protocolData); - } - - public static String createSerializedSingleObservation(JsonNode nodeSerObs, String path){ - if (nodeSerObs.has("context")){ - ((ObjectNode) nodeSerObs.get("context")).put("leshan-path", path + "\n"); - return JacksonUtil.toString(nodeSerObs); + if (observation instanceof CompositeObservation && obs instanceof CompositeObservation) { + return ((CompositeObservation) observation).getPaths().equals(((CompositeObservation) obs).getPaths()); } - return null; + return false; } @Override @@ -391,6 +298,12 @@ public class TbInMemoryRegistrationStore implements RegistrationStore, Startable try { lock.writeLock().lock(); Observation observation = unsafeGetObservation(observationId); + if (observation instanceof SingleObservation){ + log.trace("(SingleObservation) removeObservation: [{}]", ((SingleObservation)observation).getPath()); + } else { + log.trace("(CompositeObservation) removeObservation: [{}]", ((CompositeObservation)observation).getPaths()); + } + if (observation != null && registrationId.equals(observation.getRegistrationId())) { unsafeRemoveObservation(observationId); return observation; @@ -467,24 +380,6 @@ public class TbInMemoryRegistrationStore implements RegistrationStore, Startable return obs; } - private void cancelObservation (Observation observation, String registrationId, List removed) { - for (Observation obs : unsafeGetObservations(registrationId)) { - cancelExistingObservation(observation, obs, removed); - } - } - - private void cancelExistingObservation(Observation observation, Observation obs, List removed) { - LwM2mPath pathObservation = ((SingleObservation)observation).getPath(); - LwM2mPath pathObs = ((SingleObservation)obs).getPath(); - if ((!pathObservation.equals(pathObs) && pathObs.startWith(pathObservation)) || // pathObservation = "3", pathObs = "3/0/9" - (pathObservation.equals(pathObs) && !observation.getId().equals(obs.getId()))) { - unsafeRemoveObservation(obs.getId()); - removed.add(obs); - } else if (!pathObservation.equals(pathObs) && pathObservation.startWith(pathObs)) { // pathObservation = "3/0/9", pathObs = "3" - unsafeRemoveObservation(observation.getId()); - } - } - private void unsafeRemoveObservation(ObservationIdentifier observationId) { Observation removed = obsByToken.remove(observationId); if (removed != null) { @@ -497,7 +392,6 @@ public class TbInMemoryRegistrationStore implements RegistrationStore, Startable } } - /** * CancelAllObservation * @param registrationId diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java index 7b87b43650..d9dfd94f41 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.transport.lwm2m.server.store; -import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.coap.Token; import org.eclipse.californium.core.network.RandomTokenGenerator; @@ -25,17 +24,13 @@ import org.eclipse.californium.core.network.serialization.UdpDataSerializer; import org.eclipse.leshan.core.Destroyable; import org.eclipse.leshan.core.Startable; import org.eclipse.leshan.core.Stoppable; -import org.eclipse.leshan.core.model.ObjectModel; -import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.observation.CompositeObservation; import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.observation.ObservationIdentifier; import org.eclipse.leshan.core.observation.SingleObservation; import org.eclipse.leshan.core.peer.LwM2mIdentity; -import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.util.Validate; -import org.eclipse.leshan.server.redis.RedisRegistrationStore; import org.eclipse.leshan.server.redis.serialization.ObservationSerDes; import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes; import org.eclipse.leshan.server.registration.Deregistration; @@ -44,15 +39,12 @@ import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.RegistrationStore; import org.eclipse.leshan.server.registration.RegistrationUpdate; import org.eclipse.leshan.server.registration.UpdatedRegistration; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.data.redis.connection.RedisClusterConnection; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.ScanOptions; import org.springframework.integration.redis.util.RedisLockRegistry; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2mVersionedModelProvider; @@ -65,18 +57,13 @@ import java.util.Collections; import java.util.Iterator; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.Lock; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.eclipse.leshan.core.californium.ObserveUtil.extractSerializedObservation; -import static org.thingsboard.server.transport.lwm2m.server.store.TbInMemoryRegistrationStore.createSerializedSingleObservation; -import static org.thingsboard.server.transport.lwm2m.server.store.TbInMemoryRegistrationStore.createSingleObservation; @Slf4j public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startable, Stoppable, Destroyable { @@ -86,8 +73,6 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab /** Defaut Extra time for registration lifetime in seconds */ public static final long DEFAULT_GRACE_PERIOD = 0; - private static final Logger LOG = LoggerFactory.getLogger(RedisRegistrationStore.class); - // Redis key prefixes public static final String REG_EP = "REG:EP:"; // (Endpoint => Registration) private static final String REG_EP_REGID_IDX = "EP:REGID:"; // secondary index key (Registration ID => Endpoint) @@ -278,6 +263,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab return getRegistration(connection, registrationId); } } + private Registration getRegistration(RedisConnection connection, String registrationId) { byte[] ep = connection.get(toRegIdKey(registrationId)); if (ep == null) { @@ -491,31 +477,10 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab Lock lock = null; String lockKey = toLockKey(ep); - try { lock = redisLock.obtain(lockKey); lock.lock(); - if (observation instanceof SingleObservation) { - if (validateObserveResource(((SingleObservation)observation).getPath(), registrationId)) { - updateSingleObservation(registrationId, (SingleObservation)observation, addIfAbsent, removed, connection); - // cancel existing observations for the same path and registration id. - cancelObservation(observation, registrationId, removed, connection); - } - } else { - ContentFormat ct = ((CompositeObservation) observation).getResponseContentFormat(); - Map ctx = observation.getContext(); - String serializedObservation = extractSerializedObservation(observation); - JsonNode nodeSerObs = JacksonUtil.toJsonNode(serializedObservation); - ((CompositeObservation)observation).getPaths().forEach(path -> { - if (validateObserveResource(path, registrationId)) { - String serializedObs = createSerializedSingleObservation(nodeSerObs, path.toString()); - SingleObservation singleObservation = createSingleObservation(registrationId, path, ct, ctx, serializedObs, getTokenGenerator()); - updateSingleObservation(registrationId, singleObservation, addIfAbsent, removed, connection); - // cancel existing observations for the same path and registration id. - cancelObservation (singleObservation, registrationId, removed, connection); - } - }); - } + updateObservation(registrationId, observation, addIfAbsent, removed, connection); } finally { if (lock != null) { lock.unlock(); @@ -524,43 +489,21 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab } return removed; } - - private boolean validateObserveResource(LwM2mPath path, String registrationId){ - // check if the resource is readable. - if (path.isResource() || path.isResourceInstance()) { - ObjectModel objectModel = modelProvider.getObjectModel(getRegistration(registrationId)).getObjectModel(path.getObjectId()); - ResourceModel resourceModel = objectModel == null ? null : objectModel.resources.get(path.getResourceId()); - if (resourceModel == null) { - return false; - } else if (!resourceModel.operations.isReadable()) { - return false; - } else if (path.isResourceInstance() && !resourceModel.multiple) { - return false; - } - } - return true; - } - - private void updateSingleObservation (String registrationId, SingleObservation observation, boolean addIfAbsent, - List removed, RedisConnection connection) { + private void updateObservation(String registrationId, Observation observation, boolean addIfAbsent, + List removed, RedisConnection connection) { // Add and Get previous observation byte[] previousValue; byte[] key = toKey(OBS_TKN, observation.getId().getBytes()); byte[] serializeObs = serializeObs(observation); // we analyze the present previous value - SingleObservation existingObservation = null; - - if (addIfAbsent){ + if (addIfAbsent) { previousValue = connection.stringCommands().get(key); if (previousValue == null) { - existingObservation = validateByAbsorptionExistingObservations(observation, connection); - if (existingObservation == null){ - connection.stringCommands().set(key, serializeObs); - } else if(!existingObservation.getPath().equals(observation.getPath())) { - connection.stringCommands().set(key, serializeObs); - previousValue = serializeObs(existingObservation); - } + connection.stringCommands().set(key, serializeObs); + previousValue = serializeObs; + } else { + connection.stringCommands().set(key, serializeObs); } } else { previousValue = connection.stringCommands().getSet(key, serializeObs); @@ -570,25 +513,38 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab connection.listCommands().lPush(toKey(OBS_TKNS_REGID_IDX, registrationId), observation.getId().getBytes()); // log any collisions - if (addIfAbsent && previousValue != null) { - if (!existingObservation.getPath().equals(observation.getPath())) { - Observation previousObservation = deserializeObs(previousValue); - removed.add(previousObservation); - LOG.warn("Token collision ? observation [{}] will be replaced by observation [{}], that this observation includes input observation [{}]!", - previousObservation, observation, observation); - } else { - LOG.warn("Token collision ? existing observation [{}] includes input observation [{}]", - existingObservation, observation); + Observation previousObservation; + if (previousValue != null && previousValue.length != 0) { + previousObservation = deserializeObs(previousValue); + log.warn("Token collision ? observation [{}] will be replaced by observation [{}] ", + previousObservation, observation); + } + + // cancel existing observations for the same path and registration id. + for (Observation obs : getObservations(connection, registrationId)) { + if (areTheSamePaths(observation, obs) && !observation.getId().equals(obs.getId())) { + removed.add(obs); + unsafeRemoveObservation(connection, registrationId, obs.getId().getBytes()); } } } + private boolean areTheSamePaths(Observation observation, Observation obs) { + if (observation instanceof SingleObservation && obs instanceof SingleObservation) { + return ((SingleObservation) observation).getPath().equals(((SingleObservation) obs).getPath()); + } + if (observation instanceof CompositeObservation && obs instanceof CompositeObservation) { + return ((CompositeObservation) observation).getPaths().equals(((CompositeObservation) obs).getPaths()); + } + return false; + } @Override public Collection getObservations(String registrationId) { try (var connection = connectionFactory.getConnection()) { return getObservations(connection, registrationId); } } + @Override public Observation getObservation(String registrationId, ObservationIdentifier observationId) { return getObservations(registrationId).stream().filter( @@ -651,25 +607,6 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab return result; } - private SingleObservation validateByAbsorptionExistingObservations(SingleObservation observation, RedisConnection connection) { - LwM2mPath pathObservation = observation.getPath(); - AtomicReference result = new AtomicReference<>(); - Collection observations = getObservations(connection, observation.getRegistrationId()); - observations.stream().forEach(obs -> { - LwM2mPath pathObs = ((SingleObservation)obs).getPath(); - if ((!pathObservation.equals(pathObs) && pathObs.startWith(pathObservation)) || // pathObs = "3/0/9"-> pathObservation = "3" - (pathObservation.equals(pathObs) && !observation.getId().equals(obs.getId()))) { - result.set((SingleObservation)obs); - } else if (!pathObservation.equals(pathObs) && pathObservation.startWith(pathObs)) { // pathObs = "3" -> pathObservation = "3/0/9" - result.set(observation); - } - }); - return result.get(); - - - } - - @Override public Collection removeObservations(String registrationId) { try (var connection = connectionFactory.getConnection()) { @@ -707,7 +644,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab /* *************** Observation utility functions **************** */ - private TokenGenerator getTokenGenerator(){ + private TokenGenerator getTokenGenerator() { if (this.tokenGenerator == null) { this.tokenGenerator = new RandomTokenGenerator(config.getCoapConfig()); } @@ -748,8 +685,8 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab } private void cancelExistingObservation(RedisConnection connection, Observation observation, Observation obs, List removed) { - LwM2mPath pathObservation = ((SingleObservation)observation).getPath(); - LwM2mPath pathObs = ((SingleObservation)obs).getPath(); + LwM2mPath pathObservation = ((SingleObservation) observation).getPath(); + LwM2mPath pathObs = ((SingleObservation) obs).getPath(); if ((!pathObservation.equals(pathObs) && pathObs.startWith(pathObservation)) || // pathObservation = "3", pathObs = "3/0/9" (pathObservation.equals(pathObs) && !observation.getId().equals(obs.getId()))) { unsafeRemoveObservation(connection, obs.getRegistrationId(), obs.getId().getBytes()); @@ -800,7 +737,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab try { schedExecutor.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { - LOG.warn("Destroying RedisRegistrationStore was interrupted.", e); + log.warn("Destroying RedisRegistrationStore was interrupted.", e); } } @@ -821,7 +758,7 @@ public class TbLwM2mRedisRegistrationStore implements RegistrationStore, Startab } } } catch (Exception e) { - LOG.warn("Unexpected Exception while registration cleaning", e); + log.warn("Unexpected Exception while registration cleaning", e); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java index 5be27be03d..df1a86d2eb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java @@ -372,21 +372,19 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl LwM2mPath path = instant.getKey(); LwM2mNode node = instant.getValue(); LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); - String stringPath = convertObjectIdToVersionedId(path.toString(), lwM2MClient); - ObjectModel objectModelVersion = lwM2MClient.getObjectModel(stringPath, modelProvider); + ObjectModel objectModelVersion = lwM2MClient.getObjectModel(path.toString(), modelProvider); if (objectModelVersion != null) { if (node instanceof LwM2mObject) { LwM2mObject lwM2mObject = (LwM2mObject) node; - this.updateObjectResourceValue(lwM2MClient, lwM2mObject, stringPath, 0); + this.updateObjectResourceValue(lwM2MClient, lwM2mObject, path.toString(), 0); } else if (node instanceof LwM2mObjectInstance) { LwM2mObjectInstance lwM2mObjectInstance = (LwM2mObjectInstance) node; - this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, stringPath, 0); + this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, path.toString(), 0); } else if (node instanceof LwM2mResource) { LwM2mResource lwM2mResource = (LwM2mResource) node; - this.updateResourcesValue(lwM2MClient, lwM2mResource, stringPath, Mode.UPDATE, 0); + this.updateResourcesValue(lwM2MClient, lwM2mResource, path.toString(), Mode.UPDATE, 0); } } - tryAwake(lwM2MClient); } } @@ -569,7 +567,6 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl } private void updateObjectInstanceResourceValue(LwM2mClient client, LwM2mObjectInstance lwM2mObjectInstance, String pathIdVer, int code) { - LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); lwM2mObjectInstance.getResources().forEach((resourceId, resource) -> { String pathRez = pathIdVer + "/" + resourceId; this.updateResourcesValue(client, resource, pathRez, Mode.UPDATE, code); @@ -584,11 +581,12 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl * #4 updateAttrTelemetry * @param lwM2MClient - Registration LwM2M Client * @param lwM2mResource - LwM2mSingleResource response.getContent() - * @param path - resource + * @param stringPath - resource * @param mode - Replace, Update */ - private void updateResourcesValue(LwM2mClient lwM2MClient, LwM2mResource lwM2mResource, String path, Mode mode, int code) { + private void updateResourcesValue(LwM2mClient lwM2MClient, LwM2mResource lwM2mResource, String stringPath, Mode mode, int code) { Registration registration = lwM2MClient.getRegistration(); + String path = convertObjectIdToVersionedId(stringPath, lwM2MClient); if (lwM2MClient.saveResourceValue(path, lwM2mResource, modelProvider, mode)) { if (path.equals(convertObjectIdToVersionedId(FW_NAME_ID, lwM2MClient))) { otaService.onCurrentFirmwareNameUpdate(lwM2MClient, (String) lwM2mResource.getValue()); @@ -632,17 +630,17 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl * @param registration - Registration LwM2M Client */ public void updateAttrTelemetry(Registration registration, String path) { + log.trace("UpdateAttrTelemetry paths [{}]", path); try { ResultsAddKeyValueProto results = this.getParametersFromProfile(registration, path); - if (path.equals("/3_1.2/0/9")) { - log.info("UpdateTelemetry paths [{}] key: [{}] value [{}]", path, results.getResultTelemetries().get(0).getKey(), results.getResultTelemetries().get(0).getLongV()); - } SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(registration); if (results != null && sessionInfo != null) { if (results.getResultAttributes().size() > 0) { + log.trace("UpdateAttribute paths [{}] value [{}]", path, results.getResultAttributes().get(0).toString()); this.helper.sendParametersOnThingsboardAttribute(results.getResultAttributes(), sessionInfo); } if (results.getResultTelemetries().size() > 0) { + log.trace("UpdateTelemetry paths [{}] value [{}]", path, results.getResultTelemetries().get(0).toString()); this.helper.sendParametersOnThingsboardTelemetry(results.getResultTelemetries(), sessionInfo); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java index e2e31f2768..6b90ae21b6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2MTransportUtil.java @@ -156,21 +156,19 @@ public class LwM2MTransportUtil { } public static String convertObjectIdToVersionedId(String path, LwM2mClient lwM2MClient) { - String ver = String.valueOf(lwM2MClient.getSupportedObjectVersion(new LwM2mPath(path).getObjectId())); - return convertObjectIdToVerId(path, ver); - } - public static String convertObjectIdToVerId(String path, String ver) { - ver = ver != null ? ver : TbLwM2mVersion.VERSION_1_0.getVersion().toString(); - try { - String[] keyArray = path.split(LWM2M_SEPARATOR_PATH); - if (keyArray.length > 1) { - keyArray[1] = keyArray[1] + LWM2M_SEPARATOR_KEY + ver; + String[] keyArray = path.split(LWM2M_SEPARATOR_PATH); + if (keyArray.length > 1) { + try { + Integer objectId = Integer.valueOf((keyArray[1].split(LWM2M_SEPARATOR_KEY))[0]); + String ver = String.valueOf(lwM2MClient.getSupportedObjectVersion(objectId)); + ver = ver != null ? ver : TbLwM2mVersion.VERSION_1_0.getVersion().toString(); + keyArray[1] = String.valueOf(keyArray[1]).contains(LWM2M_SEPARATOR_KEY) ? keyArray[1] : keyArray[1] + LWM2M_SEPARATOR_KEY + ver; return StringUtils.join(keyArray, LWM2M_SEPARATOR_PATH); - } else { - return path; + } catch (Exception e) { + return null; } - } catch (Exception e) { - return null; + } else { + return path; } } @@ -214,20 +212,20 @@ public class LwM2MTransportUtil { } public static Map convertMultiResourceValuesFromRpcBody(Object value, ResourceModel.Type type, String versionedId) throws Exception { - String valueJsonStr = JacksonUtil.toString(value); - JsonElement element = JsonUtils.parse(valueJsonStr); - return convertMultiResourceValuesFromJson(element, type, versionedId); + String valueJsonStr = JacksonUtil.toString(value); + JsonElement element = JsonUtils.parse(valueJsonStr); + return convertMultiResourceValuesFromJson(element, type, versionedId); } public static Map convertMultiResourceValuesFromJson(JsonElement newValProto, ResourceModel.Type type, String versionedId) { Map newValues = new HashMap<>(); newValProto.getAsJsonObject().entrySet().forEach((obj) -> { - newValues.put(Integer.valueOf(obj.getKey()), convertValueByTypeResource (obj.getValue().getAsString(), type, versionedId)); + newValues.put(Integer.valueOf(obj.getKey()), convertValueByTypeResource(obj.getValue().getAsString(), type, versionedId)); }); return newValues; } - public static Object convertValueByTypeResource (String value, ResourceModel.Type type, String versionedId) { + public static Object convertValueByTypeResource(String value, ResourceModel.Type type, String versionedId) { return LwM2mValueConverterImpl.getInstance().convertValue(value, STRING, type, new LwM2mPath(fromVersionedIdToObjectId(versionedId))); } @@ -356,7 +354,7 @@ public class LwM2MTransportUtil { serverCoapConfig.setTransient(DTLS_CONNECTION_ID_LENGTH); serverCoapConfig.setTransient(DTLS_CONNECTION_ID_NODE_ID); serverCoapConfig.set(DTLS_CONNECTION_ID_LENGTH, cIdLength); - if ( cIdLength > 4) { + if (cIdLength > 4) { serverCoapConfig.set(DTLS_CONNECTION_ID_NODE_ID, 0); } else { serverCoapConfig.set(DTLS_CONNECTION_ID_NODE_ID, null); diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java index 570256e98c..07b670f835 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java @@ -79,7 +79,7 @@ public interface AlarmDao extends Dao { PageData findAlarmsIdsByEndTsBeforeAndTenantId(Long time, TenantId tenantId, PageLink pageLink); - PageData findAlarmIdsByAssigneeId(TenantId tenantId, UUID userId, PageLink pageLink); + PageData> findAlarmIdsByAssigneeId(TenantId tenantId, UserId userId, long createdTimeOffset, AlarmId idOffset, int limit); PageData> findAlarmIdsByOriginatorId(TenantId tenantId, EntityId originatorId, long createdTimeOffset, AlarmId idOffset, int limit); diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index f0fc18c827..7e9cabf5b4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -308,10 +308,10 @@ public class BaseAlarmService extends AbstractCachedEntityService findAlarmIdsByAssigneeId(TenantId tenantId, UserId userId, PageLink pageLink) { - log.trace("[{}] Executing findAlarmIdsByAssigneeId [{}]", tenantId, userId); + public List> findAlarmIdsByAssigneeId(TenantId tenantId, UserId userId, long createdTimeOffset, AlarmId idOffset, int limit) { + log.trace("[{}] Executing findAlarmIdsByAssigneeId [{}][{}]", tenantId, userId, idOffset); validateId(userId, id -> "Incorrect userId " + id); - return alarmDao.findAlarmIdsByAssigneeId(tenantId, userId.getId(), pageLink); + return alarmDao.findAlarmIdsByAssigneeId(tenantId, userId, createdTimeOffset, idOffset, limit).getData(); } @Override @@ -476,4 +476,5 @@ public class BaseAlarmService extends AbstractCachedEntityService { @Query(value = "SELECT a FROM AlarmInfoEntity a WHERE a.tenantId = :tenantId AND a.id = :alarmId") AlarmInfoEntity findAlarmInfoById(@Param("tenantId") UUID tenantId, @Param("alarmId") UUID alarmId); - @Query("SELECT a.id FROM AlarmEntity a WHERE a.tenantId = :tenantId AND a.assigneeId = :assigneeId") - Page findAlarmIdsByAssigneeId(@Param("tenantId") UUID tenantId, @Param("assigneeId") UUID assigneeId, Pageable pageable); + // using Slice so that count query is not executed + @Query("SELECT new org.thingsboard.server.common.data.util.TbPair(a.id, a.createdTime) " + + "FROM AlarmEntity a WHERE a.tenantId = :tenantId AND a.assigneeId = :assigneeId") + Slice> findAlarmIdsByAssigneeId(@Param("tenantId") UUID tenantId, + @Param("assigneeId") UUID assigneeId, + Pageable pageable); + + // using Slice so that count query is not executed + @Query("SELECT new org.thingsboard.server.common.data.util.TbPair(a.id, a.createdTime) " + + "FROM AlarmEntity a WHERE a.tenantId = :tenantId AND a.assigneeId = :assigneeId " + + "AND (a.createdTime > :createdTimeOffset OR " + + "(a.createdTime = :createdTimeOffset AND a.id > :idOffset))") + Slice> findAlarmIdsByAssigneeId(@Param("tenantId") UUID tenantId, + @Param("assigneeId") UUID assigneeId, + @Param("createdTimeOffset") long createdTimeOffset, + @Param("idOffset") UUID idOffset, + Pageable pageable); // using Slice so that count query is not executed @Query("SELECT new org.thingsboard.server.common.data.util.TbPair(a.id, a.createdTime) " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index 133eb70521..b4c1bf7913 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -296,9 +296,15 @@ public class JpaAlarmDao extends JpaAbstractDao implements A } @Override - public PageData findAlarmIdsByAssigneeId(TenantId tenantId, UUID userId, PageLink pageLink) { - return DaoUtil.pageToPageData(alarmRepository.findAlarmIdsByAssigneeId(tenantId.getId(), userId, DaoUtil.toPageable(pageLink))) - .mapData(AlarmId::new); + public PageData> findAlarmIdsByAssigneeId(TenantId tenantId, UserId userId, long createdTimeOffset, AlarmId idOffset, int limit) { + Slice> result; + Pageable pageRequest = toPageable(new PageLink(limit), List.of(SortOrder.of("createdTime", ASC), SortOrder.of("id", ASC))); + if (idOffset == null) { + result = alarmRepository.findAlarmIdsByAssigneeId(tenantId.getId(), userId.getId(), pageRequest); + } else { + result = alarmRepository.findAlarmIdsByAssigneeId(tenantId.getId(), userId.getId(), createdTimeOffset, idOffset.getId(), pageRequest); + } + return DaoUtil.pageToPageData(result); } @Override diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractLwm2mClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractLwm2mClientTest.java deleted file mode 100644 index 34d78abfb4..0000000000 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractLwm2mClientTest.java +++ /dev/null @@ -1,319 +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.msa; - -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.io.IOUtils; -import org.eclipse.leshan.client.object.Security; -import org.eclipse.leshan.core.util.Hex; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.common.util.ThingsBoardExecutors; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.DeviceProfileProvisionType; -import org.thingsboard.server.common.data.DeviceProfileType; -import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.ResourceType; -import org.thingsboard.server.common.data.TbResource; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; -import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential; -import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential; -import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKBootstrapClientCredential; -import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential; -import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; -import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; -import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration; -import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration; -import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.common.data.security.DeviceCredentialsType; -import org.thingsboard.server.msa.connectivity.lwm2m.LwM2MTestClient; -import org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState; - -import java.net.ServerSocket; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; -import static org.eclipse.leshan.client.object.Security.psk; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_ENDPOINT_NO_SEC; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_ENDPOINT_PSK; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_LWM2M_SETTINGS; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_PSK_IDENTITY; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.CLIENT_PSK_KEY; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_INIT; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.OBSERVE_ATTRIBUTES_WITHOUT_PARAMS; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.SECURE_URI; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.SECURITY_NO_SEC; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.resources; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.shortServerId; - -@Slf4j -public class AbstractLwm2mClientTest extends AbstractContainerTest{ - protected ScheduledExecutorService executor; - - protected Security security; - - protected final PageLink pageLink = new PageLink(30); - protected TenantId tenantId; - protected DeviceProfile lwm2mDeviceProfile; - protected Device lwM2MDeviceTest; - protected LwM2MTestClient lwM2MTestClient; - - public final Set expectedStatusesRegistrationLwm2mSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS)); - - public void connectLwm2mClientNoSec() throws Exception { - LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(CLIENT_ENDPOINT_NO_SEC)); - basicTestConnection(SECURITY_NO_SEC, - deviceCredentials, - CLIENT_ENDPOINT_NO_SEC, - "TestConnection Lwm2m NoSec (msa)"); - } - - public void connectLwm2mClientPsk() throws Exception { - String clientEndpoint = CLIENT_ENDPOINT_PSK; - String identity = CLIENT_PSK_IDENTITY; - String keyPsk = CLIENT_PSK_KEY; - PSKClientCredential clientCredentials = new PSKClientCredential(); - clientCredentials.setEndpoint(clientEndpoint); - clientCredentials.setIdentity(identity); - clientCredentials.setKey(keyPsk); - Security security = psk(SECURE_URI, - shortServerId, - identity.getBytes(StandardCharsets.UTF_8), - Hex.decodeHex(keyPsk.toCharArray())); - LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecurePsk(clientCredentials); - - basicTestConnection(security, - deviceCredentials, - clientEndpoint, - "TestConnection Lwm2m Rpc (msa)"); - } - - public void basicTestConnection(Security security, - LwM2MDeviceCredentials deviceCredentials, - String clientEndpoint, String alias) throws Exception { - // create lwm2mClient and lwM2MDevice - lwM2MDeviceTest = createDeviceWithCredentials(deviceCredentials, clientEndpoint); - lwM2MTestClient = createNewClient(security, clientEndpoint, executor); - - LwM2MClientState finishState = ON_REGISTRATION_SUCCESS; - await(alias + " - " + ON_REGISTRATION_STARTED) - .atMost(40, TimeUnit.SECONDS) - .until(() -> { - log.warn("msa basicTestConnection started -> finishState: [{}] states: {}", finishState, lwM2MTestClient.getClientStates()); - return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_STARTED); - }); - await(alias + " - " + ON_UPDATE_SUCCESS) - .atMost(40, TimeUnit.SECONDS) - .until(() -> { - log.warn("msa basicTestConnection update -> finishState: [{}] states: {}", finishState, lwM2MTestClient.getClientStates()); - return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_UPDATE_SUCCESS); - }); - assertThat(lwM2MTestClient.getClientStates()).containsAll(expectedStatusesRegistrationLwm2mSuccess); - } - - public LwM2MTestClient createNewClient(Security security, - String endpoint, ScheduledExecutorService executor) throws Exception { - this.executor = executor; - LwM2MTestClient lwM2MTestClient = new LwM2MTestClient(endpoint); - try (ServerSocket socket = new ServerSocket(0)) { - int clientPort = socket.getLocalPort(); - lwM2MTestClient.init(security, clientPort); - } - return lwM2MTestClient; - } - - protected void destroyAfter(){ - clientDestroy(); - deviceDestroy(); - deviceProfileDestroy(); - if (executor != null) { - executor.shutdown(); - } - } - - protected void clientDestroy() { - try { - if (lwM2MTestClient != null) { - lwM2MTestClient.destroy(); - } - } catch (Exception e) { - log.error("Failed client Destroy", e); - } - } - protected void deviceDestroy() { - try { - if (lwM2MDeviceTest != null) { - testRestClient.deleteDeviceIfExists(lwM2MDeviceTest.getId()); - } - } catch (Exception e) { - log.error("Failed device Delete", e); - } - } - - protected void initTest(String deviceProfileName) throws Exception { - if (executor != null) { - executor.shutdown(); - } - executor = ThingsBoardExecutors.newScheduledThreadPool(10, "test-scheduled-" + deviceProfileName); - - lwm2mDeviceProfile = getDeviceProfile(deviceProfileName); - tenantId = lwm2mDeviceProfile.getTenantId(); - - for (String resourceName : resources) { - TbResource lwModel = new TbResource(); - lwModel.setResourceType(ResourceType.LWM2M_MODEL); - lwModel.setTitle(resourceName); - lwModel.setFileName(resourceName); - lwModel.setTenantId(tenantId); - byte[] bytes = IOUtils.toByteArray(AbstractLwm2mClientTest.class.getClassLoader().getResourceAsStream("lwm2m-registry/" + resourceName)); - lwModel.setData(bytes); - testRestClient.postTbResourceIfNotExists(lwModel); - } - } - - protected DeviceProfile getDeviceProfile(String deviceProfileName) throws Exception { - DeviceProfile deviceProfile = getDeviceProfileIfExists(deviceProfileName); - if (deviceProfile == null) { - deviceProfile = testRestClient.postDeviceProfile(createDeviceProfile(deviceProfileName)); - } - return deviceProfile; - } - - protected DeviceProfile getDeviceProfileIfExists(String deviceProfileName) throws Exception { - return testRestClient.getDeviceProfiles(pageLink).getData().stream() - .filter(x -> x.getName().equals(deviceProfileName)) - .findFirst() - .orElse(null); - } - - - protected DeviceProfile createDeviceProfile(String deviceProfileName) throws Exception { - DeviceProfile deviceProfile = new DeviceProfile(); - deviceProfile.setName(deviceProfileName); - deviceProfile.setType(DeviceProfileType.DEFAULT); - deviceProfile.setTransportType(DeviceTransportType.LWM2M); - deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); - deviceProfile.setDescription(deviceProfile.getName()); - - DeviceProfileData deviceProfileData = new DeviceProfileData(); - deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); - deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null)); - deviceProfileData.setTransportConfiguration(getTransportConfiguration()); - deviceProfile.setProfileData(deviceProfileData); - return deviceProfile; - } - - protected void deviceProfileDestroy(){ - try { - if (lwm2mDeviceProfile != null) { - testRestClient.deleteDeviceProfileIfExists(lwm2mDeviceProfile); - } - } catch (Exception e) { - log.error("Failed deviceProfile Delete", e); - } - } - - protected Device createDeviceWithCredentials(LwM2MDeviceCredentials deviceCredentials, String clientEndpoint) throws Exception { - Device device = createDevice(deviceCredentials, clientEndpoint); - return device; - } - - protected Device createDevice(LwM2MDeviceCredentials credentials, String clientEndpoint) throws Exception { - Device device = testRestClient.getDeviceByNameIfExists(clientEndpoint); - if (device == null) { - device = new Device(); - device.setName(clientEndpoint); - device.setDeviceProfileId(lwm2mDeviceProfile.getId()); - device.setTenantId(tenantId); - device = testRestClient.postDevice("", device); - } - - DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); - deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); - deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials)); - deviceCredentials = testRestClient.postDeviceCredentials(deviceCredentials); - assertThat(deviceCredentials).isNotNull(); - return device; - } - - protected LwM2MDeviceCredentials getDeviceCredentialsNoSec(LwM2MClientCredential clientCredentials) { - LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials(); - credentials.setClient(clientCredentials); - LwM2MBootstrapClientCredentials bootstrapCredentials = new LwM2MBootstrapClientCredentials(); - NoSecBootstrapClientCredential serverCredentials = new NoSecBootstrapClientCredential(); - bootstrapCredentials.setBootstrapServer(serverCredentials); - bootstrapCredentials.setLwm2mServer(serverCredentials); - credentials.setBootstrap(bootstrapCredentials); - return credentials; - } - - public NoSecClientCredential createNoSecClientCredentials(String clientEndpoint) { - NoSecClientCredential clientCredentials = new NoSecClientCredential(); - clientCredentials.setEndpoint(clientEndpoint); - return clientCredentials; - } - - protected Lwm2mDeviceProfileTransportConfiguration getTransportConfiguration() { - List bootstrapServerCredentials = new ArrayList<>(); - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration(); - TelemetryMappingConfiguration observeAttrConfiguration = JacksonUtil.fromString(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, TelemetryMappingConfiguration.class); - OtherConfiguration clientLwM2mSettings = JacksonUtil.fromString(CLIENT_LWM2M_SETTINGS, OtherConfiguration.class); - transportConfiguration.setBootstrapServerUpdateEnable(true); - transportConfiguration.setObserveAttr(observeAttrConfiguration); - transportConfiguration.setClientLwM2mSettings(clientLwM2mSettings); - transportConfiguration.setBootstrap(bootstrapServerCredentials); - return transportConfiguration; - } - - protected LwM2MDeviceCredentials getDeviceCredentialsSecurePsk(LwM2MClientCredential clientCredentials) { - LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials(); - credentials.setClient(clientCredentials); - LwM2MBootstrapClientCredentials bootstrapCredentials; - bootstrapCredentials = getBootstrapClientCredentialsPsk(clientCredentials); - credentials.setBootstrap(bootstrapCredentials); - return credentials; - } - - private LwM2MBootstrapClientCredentials getBootstrapClientCredentialsPsk(LwM2MClientCredential clientCredentials) { - LwM2MBootstrapClientCredentials bootstrapCredentials = new LwM2MBootstrapClientCredentials(); - PSKBootstrapClientCredential serverCredentials = new PSKBootstrapClientCredential(); - if (clientCredentials != null) { - serverCredentials.setClientSecretKey(((PSKClientCredential) clientCredentials).getKey()); - serverCredentials.setClientPublicKeyOrId(((PSKClientCredential) clientCredentials).getIdentity()); - } - bootstrapCredentials.setBootstrapServer(serverCredentials); - bootstrapCredentials.setLwm2mServer(serverCredentials); - return bootstrapCredentials; - } -} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java index c76627d3a1..87d466e11a 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java @@ -16,6 +16,7 @@ package org.thingsboard.server.msa; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import io.restassured.RestAssured; import io.restassured.common.mapper.TypeRef; import io.restassured.config.HeaderConfig; @@ -116,6 +117,16 @@ public class TestRestClient { .as(Device.class); } + public ObjectNode postRpcLwm2mParams(String deviceIdStr, String body) { + return given().spec(requestSpec).body(body) + .post("/api/plugins/rpc/twoway/" + deviceIdStr) + .then() + .statusCode(HTTP_OK) + .extract() + .as(ObjectNode.class); + } + + public Device getDeviceByName(String deviceName) { return given().spec(requestSpec).pathParam("deviceName", deviceName) .get("/api/tenant/devices?deviceName={deviceName}") diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/AbstractLwm2mClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/AbstractLwm2mClientTest.java new file mode 100644 index 0000000000..bb5c642e91 --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/AbstractLwm2mClientTest.java @@ -0,0 +1,493 @@ +/** + * 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.msa.connectivity.lwm2m; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.Sets; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.io.IOUtils; +import org.eclipse.leshan.client.object.Security; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.util.Hex; +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; +import org.testng.Assert; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.DeviceProfileType; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.ResourceType; +import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKBootstrapClientCredential; +import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential; +import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.msa.AbstractContainerTest; +import org.thingsboard.server.msa.WsClient; +import org.thingsboard.server.msa.connectivity.lwm2m.client.LwM2MTestClient; +import org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState; +import org.thingsboard.server.msa.mapper.WsTelemetryResponse; + +import java.net.ServerSocket; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.eclipse.leshan.client.object.Security.psk; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.testng.AssertJUnit.assertEquals; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.CLIENT_ENDPOINT_PSK; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.CLIENT_LWM2M_SETTINGS; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.CLIENT_PSK_IDENTITY; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.CLIENT_PSK_KEY; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_INIT; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.OBSERVE_ATTRIBUTES_WITH_PARAMS; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_0; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_2; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.RESOURCE_ID_NAME_3_14; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.SECURE_URI; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.SECURITY_NO_SEC; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.resources; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.shortServerId; + +@Slf4j +public class AbstractLwm2mClientTest extends AbstractContainerTest { + protected ScheduledExecutorService executor; + + protected Security security; + + protected final PageLink pageLink = new PageLink(30); + protected TenantId tenantId; + + public final Set expectedStatusesRegistrationLwm2mSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS)); + + public void createLwm2mDevicesForConnectNoSec(String name, Lwm2mDevicesForTest devicesForTest) throws Exception { + String clientEndpoint = name + "-" + RandomStringUtils.randomAlphanumeric(7); + LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint)); + Device lwM2MDeviceTest = createDeviceWithCredentials(deviceCredentials, clientEndpoint, devicesForTest.getLwm2mDeviceProfile().getId()); + LwM2MTestClient lwM2MTestClient = createNewClient(SECURITY_NO_SEC, clientEndpoint, executor); + devicesForTest.setLwM2MDeviceTest(lwM2MDeviceTest); + devicesForTest.setLwM2MTestClient(lwM2MTestClient); + } + + public void createLwm2mDevicesForConnectPsk(Lwm2mDevicesForTest devicesForTest) throws Exception { + String clientEndpoint = CLIENT_ENDPOINT_PSK + "-" + RandomStringUtils.randomAlphanumeric(7); + String identity = CLIENT_PSK_IDENTITY; + String keyPsk = CLIENT_PSK_KEY; + PSKClientCredential clientCredentials = new PSKClientCredential(); + clientCredentials.setEndpoint(clientEndpoint); + clientCredentials.setIdentity(identity); + clientCredentials.setKey(keyPsk); + Security security = psk(SECURE_URI, + shortServerId, + identity.getBytes(StandardCharsets.UTF_8), + Hex.decodeHex(keyPsk.toCharArray())); + LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecurePsk(clientCredentials); + Device lwM2MDeviceTest = createDeviceWithCredentials(deviceCredentials, clientEndpoint, devicesForTest.getLwm2mDeviceProfile().getId()); + LwM2MTestClient lwM2MTestClient = createNewClient(security, clientEndpoint, executor); + devicesForTest.setLwM2MDeviceTest(lwM2MDeviceTest); + devicesForTest.setLwM2MTestClient(lwM2MTestClient); + } + + /** + * Observe {"id":"/3/0/0"} + * Observe {"id":"/3/0/9"} + * ObserveCancel {"id":"/3"} - Bad + * ObserveCancel {"/3/0/0"} - Ok + * ObserveCancelAl - Ok + * + * @param lwM2MTestClient + * @param deviceId + * @throws Exception + */ + public void observeResource_Update_AfterUpdateRegistration_test(LwM2MTestClient lwM2MTestClient, DeviceId deviceId) throws Exception { + awaitUpdateRegistrationSuccess(lwM2MTestClient, 5); + sendCancelObserveAllWithAwait(deviceId.toString()); + awaitUpdateRegistrationSuccess(lwM2MTestClient, 1); + long tsBefore = Instant.now().toEpochMilli(); + String param = "/3_1.2/0/9"; + sendRpcObserveWithContainsLwM2mSingleResource(param, deviceId.toString(), 1); + awaitUpdateRegistrationSuccess(lwM2MTestClient, 1); + checkLatestTelemetryUploaded(deviceId, tsBefore, RESOURCE_ID_NAME_3_9); + param = "/3_1.2/0/0"; + sendRpcObserveWithContainsLwM2mSingleResource(param, deviceId.toString(), 2); + awaitUpdateRegistrationSuccess(lwM2MTestClient, 2); + param = "/3_1.2"; + String expected = "Could not find active Observe component with path: " + param; + String actual = sendObserveCancel_BadRequest("ObserveCancel", param, deviceId.toString()); + assertEquals(expected, actual); + param = "/3_1.2/0/0"; + sendObserveCancel_Ok("ObserveCancel", param, deviceId.toString()); + sendCancelObserveAllWithAwait(deviceId.toString()); + awaitUpdateRegistrationSuccess(lwM2MTestClient, 1); + } + + public void observeCompositeResource_Update_AfterUpdateRegistration_test(LwM2MTestClient lwM2MTestClient, DeviceId deviceId) throws Exception { + String id_3_0_9 = "/3/0/9"; + String id_3_0_14 = "/3/0/14"; + String id_19_0_0 = "/19/0/0"; + String id_19_1_0 = "/19/1/0"; + String id_19_0_2 = "/19/0/2"; + + awaitUpdateRegistrationSuccess(lwM2MTestClient, 5); + sendCancelObserveAllWithAwait(deviceId.toString()); + awaitUpdateRegistrationSuccess(lwM2MTestClient, 1); + long tsBefore = Instant.now().toEpochMilli(); + String expectedKeys = "[\"" + RESOURCE_ID_NAME_3_9 + "\", \"" + RESOURCE_ID_NAME_3_14 + "\", \"" + RESOURCE_ID_NAME_19_0_0 + "\", \"" + RESOURCE_ID_NAME_19_0_2 + "\", \"" +RESOURCE_ID_NAME_19_1_0 + "\"]"; + String actualResult = sendRpcObserveCompositeWithResultValue(expectedKeys, deviceId.toString()); + assertTrue(actualResult.contains(id_3_0_9 + "=LwM2mSingleResource")); + assertTrue(actualResult.contains(id_3_0_14 + "=LwM2mSingleResource")); + assertTrue(actualResult.contains(id_19_0_0 + "=LwM2mMultipleResource")); + assertTrue(actualResult.contains(id_19_1_0 + "=LwM2mMultipleResource")); + assertTrue(actualResult.contains(id_19_0_2 + "=LwM2mSingleResource")); + // ObserveComposite: - verify + ObjectNode rpcActualResultBefore = sendRpcObserve("ObserveReadAll", null, deviceId.toString()); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultBefore.get("result").asText()); + JsonElement element = JsonParser.parseString(rpcActualResultBefore.get("value").asText()); + assertEquals(1, ((JsonArray) element).size()); + actualResult = ((JsonArray) element).asList().get(0).getAsString(); + assertTrue(actualResult.contains("CompositeObservation:")); + checkLatestTelemetryUploaded(deviceId, tsBefore, RESOURCE_ID_NAME_3_9, RESOURCE_ID_NAME_3_14, + RESOURCE_ID_NAME_19_0_0, RESOURCE_ID_NAME_19_0_2, RESOURCE_ID_NAME_19_1_0); + + awaitUpdateRegistrationSuccess(lwM2MTestClient, 1); + sendCancelObserveAllWithAwait(deviceId.toString()); + awaitUpdateRegistrationSuccess(lwM2MTestClient, 1); + assertEquals(0, (Object) Optional.ofNullable(getCntObserveAll(deviceId.toString())).get()); + } + + + public void basicTestConnection(LwM2MTestClient lwM2MTestClient, String alias) throws Exception { + LwM2MClientState finishState = ON_REGISTRATION_SUCCESS; + await(alias + " - " + ON_REGISTRATION_STARTED) + .atMost(40, TimeUnit.SECONDS) + .until(() -> { + log.warn("msa basicTestConnection started -> finishState: [{}] states: {}", finishState, lwM2MTestClient.getClientStates()); + return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_REGISTRATION_STARTED); + }); + await(alias + " - " + ON_UPDATE_SUCCESS) + .atMost(40, TimeUnit.SECONDS) + .until(() -> { + log.warn("msa basicTestConnection update -> finishState: [{}] states: {}", finishState, lwM2MTestClient.getClientStates()); + return lwM2MTestClient.getClientStates().contains(finishState) || lwM2MTestClient.getClientStates().contains(ON_UPDATE_SUCCESS); + }); + assertThat(lwM2MTestClient.getClientStates()).containsAll(expectedStatusesRegistrationLwm2mSuccess); + + } + + + public LwM2MTestClient createNewClient(Security security, + String endpoint, ScheduledExecutorService executor) throws Exception { + this.executor = executor; + LwM2MTestClient lwM2MTestClient = new LwM2MTestClient(executor, endpoint); + try (ServerSocket socket = new ServerSocket(0)) { + int clientPort = socket.getLocalPort(); + lwM2MTestClient.init(security, clientPort); + } + return lwM2MTestClient; + } + + protected void destroyAfter(Lwm2mDevicesForTest devicesForTest) { + clientDestroy(devicesForTest.getLwM2MTestClient()); + deviceDestroy(devicesForTest.getLwM2MDeviceTest()); + deviceProfileDestroy(devicesForTest.getLwm2mDeviceProfile()); + if (executor != null) { + executor.shutdown(); + } + } + + protected void clientDestroy(LwM2MTestClient lwM2MTestClient) { + try { + if (lwM2MTestClient != null) { + lwM2MTestClient.destroy(); + } + } catch (Exception e) { + log.error("Failed client Destroy", e); + } + } + + protected void deviceDestroy(Device lwM2MDeviceTest) { + try { + if (lwM2MDeviceTest != null) { + testRestClient.deleteDeviceIfExists(lwM2MDeviceTest.getId()); + } + } catch (Exception e) { + log.error("Failed device Delete", e); + } + } + + protected DeviceProfile initTest(String deviceProfileName) throws Exception { + if (executor != null) { + executor.shutdown(); + } + executor = Executors.newScheduledThreadPool(10, ThingsBoardThreadFactory.forName("test-scheduled-" + deviceProfileName)); + + DeviceProfile lwm2mDeviceProfile = getDeviceProfile(deviceProfileName); + tenantId = lwm2mDeviceProfile.getTenantId(); + + for (String resourceName : resources) { + TbResource lwModel = new TbResource(); + lwModel.setResourceType(ResourceType.LWM2M_MODEL); + lwModel.setTitle(resourceName); + lwModel.setFileName(resourceName); + lwModel.setTenantId(tenantId); + byte[] bytes = IOUtils.toByteArray(AbstractLwm2mClientTest.class.getClassLoader().getResourceAsStream("lwm2m-registry/" + resourceName)); + lwModel.setData(bytes); + testRestClient.postTbResourceIfNotExists(lwModel); + } + return lwm2mDeviceProfile; + } + + protected DeviceProfile getDeviceProfile(String deviceProfileName) throws Exception { + DeviceProfile deviceProfile = getDeviceProfileIfExists(deviceProfileName); + if (deviceProfile == null) { + deviceProfile = testRestClient.postDeviceProfile(createDeviceProfile(deviceProfileName)); + } + return deviceProfile; + } + + protected DeviceProfile getDeviceProfileIfExists(String deviceProfileName) throws Exception { + return testRestClient.getDeviceProfiles(pageLink).getData().stream() + .filter(x -> x.getName().equals(deviceProfileName)) + .findFirst() + .orElse(null); + } + + + protected DeviceProfile createDeviceProfile(String deviceProfileName) throws Exception { + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setName(deviceProfileName); + deviceProfile.setType(DeviceProfileType.DEFAULT); + deviceProfile.setTransportType(DeviceTransportType.LWM2M); + deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); + deviceProfile.setDescription(deviceProfile.getName()); + + DeviceProfileData deviceProfileData = new DeviceProfileData(); + deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); + deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null)); + deviceProfileData.setTransportConfiguration(getTransportConfiguration()); + deviceProfile.setProfileData(deviceProfileData); + return deviceProfile; + } + + protected void deviceProfileDestroy(DeviceProfile lwm2mDeviceProfile) { + try { + if (lwm2mDeviceProfile != null) { + testRestClient.deleteDeviceProfileIfExists(lwm2mDeviceProfile); + } + } catch (Exception e) { + log.error("Failed deviceProfile Delete", e); + } + } + + protected Device createDeviceWithCredentials(LwM2MDeviceCredentials deviceCredentials, String clientEndpoint, DeviceProfileId profileId) throws Exception { + Device device = createDevice(deviceCredentials, clientEndpoint, profileId); + return device; + } + + protected Device createDevice(LwM2MDeviceCredentials credentials, String clientEndpoint, DeviceProfileId profileId) throws Exception { + Device device = testRestClient.getDeviceByNameIfExists(clientEndpoint); + if (device == null) { + device = new Device(); + device.setName(clientEndpoint); + device.setDeviceProfileId(profileId); + device.setTenantId(tenantId); + device = testRestClient.postDevice("", device); + } + + DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); + deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); + deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials)); + deviceCredentials = testRestClient.postDeviceCredentials(deviceCredentials); + assertThat(deviceCredentials).isNotNull(); + return device; + } + + protected LwM2MDeviceCredentials getDeviceCredentialsNoSec(LwM2MClientCredential clientCredentials) { + LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials(); + credentials.setClient(clientCredentials); + LwM2MBootstrapClientCredentials bootstrapCredentials = new LwM2MBootstrapClientCredentials(); + NoSecBootstrapClientCredential serverCredentials = new NoSecBootstrapClientCredential(); + bootstrapCredentials.setBootstrapServer(serverCredentials); + bootstrapCredentials.setLwm2mServer(serverCredentials); + credentials.setBootstrap(bootstrapCredentials); + return credentials; + } + + public NoSecClientCredential createNoSecClientCredentials(String clientEndpoint) { + NoSecClientCredential clientCredentials = new NoSecClientCredential(); + clientCredentials.setEndpoint(clientEndpoint); + return clientCredentials; + } + + protected Lwm2mDeviceProfileTransportConfiguration getTransportConfiguration() { + List bootstrapServerCredentials = new ArrayList<>(); + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration(); + TelemetryMappingConfiguration observeAttrConfiguration = JacksonUtil.fromString(OBSERVE_ATTRIBUTES_WITH_PARAMS, TelemetryMappingConfiguration.class); + OtherConfiguration clientLwM2mSettings = JacksonUtil.fromString(CLIENT_LWM2M_SETTINGS, OtherConfiguration.class); + transportConfiguration.setBootstrapServerUpdateEnable(true); + transportConfiguration.setObserveAttr(observeAttrConfiguration); + transportConfiguration.setClientLwM2mSettings(clientLwM2mSettings); + transportConfiguration.setBootstrap(bootstrapServerCredentials); + return transportConfiguration; + } + + protected LwM2MDeviceCredentials getDeviceCredentialsSecurePsk(LwM2MClientCredential clientCredentials) { + LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials(); + credentials.setClient(clientCredentials); + LwM2MBootstrapClientCredentials bootstrapCredentials; + bootstrapCredentials = getBootstrapClientCredentialsPsk(clientCredentials); + credentials.setBootstrap(bootstrapCredentials); + return credentials; + } + + private LwM2MBootstrapClientCredentials getBootstrapClientCredentialsPsk(LwM2MClientCredential clientCredentials) { + LwM2MBootstrapClientCredentials bootstrapCredentials = new LwM2MBootstrapClientCredentials(); + PSKBootstrapClientCredential serverCredentials = new PSKBootstrapClientCredential(); + if (clientCredentials != null) { + serverCredentials.setClientSecretKey(((PSKClientCredential) clientCredentials).getKey()); + serverCredentials.setClientPublicKeyOrId(((PSKClientCredential) clientCredentials).getIdentity()); + } + bootstrapCredentials.setBootstrapServer(serverCredentials); + bootstrapCredentials.setLwm2mServer(serverCredentials); + return bootstrapCredentials; + } + + protected String sendObserveCancel_BadRequest(String method, String params, String deviceIdStr) throws Exception { + ObjectNode rpcActualResult = sendRpcObserve(method, params, deviceIdStr); + assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); + return rpcActualResult.get("error").asText(); + } + + protected void sendObserveCancel_Ok(String method, String params, String deviceIdStr) throws Exception { + ObjectNode rpcActualResult = sendRpcObserve(method, params, deviceIdStr); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + assertEquals("1", rpcActualResult.get("value").asText()); + } + + protected void sendCancelObserveAllWithAwait(String deviceIdStr) throws Exception { + ObjectNode rpcActualResultCancelAll = sendRpcObserve("ObserveCancelAll", null, deviceIdStr); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultCancelAll.get("result").asText()); + awaitObserveReadAll(0, deviceIdStr); + } + + protected void awaitObserveReadAll(int cntObserve, String deviceIdStr) throws Exception { + await("ObserveReadAll after start client/test: countObserve " + cntObserve) + .atMost(40, TimeUnit.SECONDS) + .until(() -> cntObserve == getCntObserveAll(deviceIdStr)); + } + + protected void awaitUpdateRegistrationSuccess(LwM2MTestClient lwM2MTestClient, int cntUpdate) throws Exception { + cntUpdate = cntUpdate + lwM2MTestClient.getCountUpdateRegistrationSuccess(); + int finalCntUpdate = cntUpdate; + await("Update Registration client: countUpdateSuccess " + finalCntUpdate) + .atMost(40, TimeUnit.SECONDS) + .until(() -> finalCntUpdate <= lwM2MTestClient.getCountUpdateRegistrationSuccess()); + } + + protected void awaitObserveReadResource_3_0_9(int cntRead, String deviceIdStr) throws Exception { + await("Read value 3/0/9 after start observe: countRead " + cntRead) + .atMost(40, TimeUnit.SECONDS) + .until(() -> cntRead == getCntObserveAll(deviceIdStr)); + } + + protected Integer getCntObserveAll(String deviceIdStr) throws Exception { + ObjectNode rpcActualResultBefore = sendRpcObserve("ObserveReadAll", null, deviceIdStr); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultBefore.get("result").asText()); + JsonElement element = JsonParser.parseString(rpcActualResultBefore.get("value").asText()); + return element.isJsonArray() ? ((JsonArray) element).size() : null; + } + + private void sendRpcObserveWithContainsLwM2mSingleResource(String params, String deviceIdStr, int cnt) throws Exception { + String rpcActualResult = sendRpcObserveWithResultValue(params, deviceIdStr); + assertTrue(rpcActualResult.contains("LwM2mSingleResource")); + assertEquals(Optional.of(cnt).get(), Optional.ofNullable(getCntObserveAll(deviceIdStr)).get()); + } + + private String sendRpcObserveWithResultValue(String params, String deviceIdStr) throws Exception { + ObjectNode rpcActualResult = sendRpcObserve("Observe", params, deviceIdStr); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + return rpcActualResult.get("value").asText(); + } + + private String sendRpcObserveCompositeWithResultValue(String params, String deviceIdStr) throws Exception { + ObjectNode rpcActualResult = sendRpcObserveComposite(params, deviceIdStr); + assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); + return rpcActualResult.get("value").asText(); + } + + protected ObjectNode sendRpcObserve(String method, String params, String deviceIdStr) throws Exception { + String sendRpcRequest; + if (params == null) { + sendRpcRequest = "{\"method\": \"" + method + "\"}"; + } else { + sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"id\": \"" + params + "\"}}"; + } + return testRestClient.postRpcLwm2mParams(deviceIdStr, sendRpcRequest); + } + + protected ObjectNode sendRpcObserveComposite(String keys, String deviceIdStr) throws Exception { + String method = "ObserveComposite"; + String sendRpcRequest = "{\"method\": \"" + method + "\", \"params\": {\"keys\":" + keys + "}}"; + return testRestClient.postRpcLwm2mParams(deviceIdStr, sendRpcRequest); + } + + public void checkLatestTelemetryUploaded(DeviceId deviceId, long tsBefore, String... keyNames) throws Exception { + WsClient wsClient = subscribeToWebSocket(deviceId, "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); + WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); + wsClient.closeBlocking(); + Assert.assertEquals(actualLatestTelemetry.getData().size(), 1); + String actualKeyName = actualLatestTelemetry.getLatestValues().keySet().toArray()[0].toString(); + Assert.assertTrue(Sets.newHashSet(keyNames).contains(actualKeyName)); + Assert.assertTrue((Long) actualLatestTelemetry.getDataValuesByKey(actualKeyName).get(0) > tsBefore); + } +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mDevicesForTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mDevicesForTest.java new file mode 100644 index 0000000000..b1e85c0e38 --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mDevicesForTest.java @@ -0,0 +1,35 @@ +/** + * 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.msa.connectivity.lwm2m; + + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.msa.connectivity.lwm2m.client.LwM2MTestClient; + +@Slf4j +@Data +public class Lwm2mDevicesForTest { + + Device lwM2MDeviceTest; + LwM2MTestClient lwM2MTestClient; + DeviceProfile lwm2mDeviceProfile; + public Lwm2mDevicesForTest(DeviceProfile deviceProfile) { + this.lwm2mDeviceProfile = deviceProfile; + } +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/FwLwM2MDevice.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/FwLwM2MDevice.java similarity index 98% rename from msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/FwLwM2MDevice.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/FwLwM2MDevice.java index 6cf96d043a..fdd2b0fb7c 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/FwLwM2MDevice.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/FwLwM2MDevice.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.msa.connectivity.lwm2m; +package org.thingsboard.server.msa.connectivity.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2MTestClient.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2MTestClient.java similarity index 76% rename from msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2MTestClient.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2MTestClient.java index c9f193704b..ad1294a637 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2MTestClient.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2MTestClient.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.msa.connectivity.lwm2m; +package org.thingsboard.server.msa.connectivity.lwm2m.client; import lombok.Data; import lombok.extern.slf4j.Slf4j; @@ -44,13 +44,14 @@ import org.eclipse.leshan.core.model.LwM2mModel; import org.eclipse.leshan.core.model.ObjectLoader; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.StaticModel; +import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.codec.DefaultLwM2mDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mEncoder; import org.eclipse.leshan.core.request.BootstrapRequest; import org.eclipse.leshan.core.request.DeregisterRequest; import org.eclipse.leshan.core.request.RegisterRequest; import org.eclipse.leshan.core.request.UpdateRequest; -import org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState; +import org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState; import java.io.IOException; import java.net.InetSocketAddress; @@ -60,6 +61,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; @@ -70,33 +72,35 @@ import static org.eclipse.leshan.core.LwM2mId.DEVICE; import static org.eclipse.leshan.core.LwM2mId.FIRMWARE; import static org.eclipse.leshan.core.LwM2mId.SECURITY; import static org.eclipse.leshan.core.LwM2mId.SERVER; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_FAILURE; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_STARTED; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_SUCCESS; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_TIMEOUT; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_FAILURE; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_STARTED; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_SUCCESS; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_TIMEOUT; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_EXPECTED_ERROR; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_INIT; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_FAILURE; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_TIMEOUT; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_FAILURE; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_STARTED; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_TIMEOUT; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.resources; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.serverId; -import static org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mTestHelper.shortServerId; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_FAILURE; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_STARTED; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_SUCCESS; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_TIMEOUT; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_FAILURE; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_STARTED; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_SUCCESS; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_TIMEOUT; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_EXPECTED_ERROR; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_INIT; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_FAILURE; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_TIMEOUT; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_FAILURE; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_STARTED; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_TIMEOUT; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.resources; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.serverId; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.shortServerId; @Slf4j @Data public class LwM2MTestClient { + private final ScheduledExecutorService executor; private final String endpoint; private LeshanClient leshanClient; private SimpleLwM2MDevice lwM2MDevice; @@ -104,8 +108,13 @@ public class LwM2MTestClient { private Set clientStates; private FwLwM2MDevice fwLwM2MDevice; + + private LwM2mBinaryAppDataContainer lwM2MBinaryAppDataContainer; private Map clientDtlsCid; + private int countUpdateRegistrationSuccess; + private int countReadObserveAfterUpdateRegistrationSuccess; + public void init(Security security, int clientPort) throws InvalidDDFFileException, IOException { assertThat(leshanClient).as("client already initialized").isNull(); @@ -123,9 +132,12 @@ public class LwM2MTestClient { lwm2mServer.setId(serverId); initializer.setInstancesForObject(SERVER, lwm2mServer); - initializer.setInstancesForObject(DEVICE, lwM2MDevice = new SimpleLwM2MDevice()); + SimpleLwM2MDevice simpleLwM2MDevice = new SimpleLwM2MDevice(executor); + initializer.setInstancesForObject(DEVICE, lwM2MDevice = simpleLwM2MDevice); initializer.setClassForObject(ACCESS_CONTROL, DummyInstanceEnabler.class); initializer.setInstancesForObject(FIRMWARE, fwLwM2MDevice = new FwLwM2MDevice()); + initializer.setInstancesForObject(BINARY_APP_DATA_CONTAINER, lwM2MBinaryAppDataContainer = new LwM2mBinaryAppDataContainer(executor, 0), + new LwM2mBinaryAppDataContainer(executor, 1)); List enablers = initializer.createAll(); @@ -218,6 +230,7 @@ public class LwM2MTestClient { clientDtlsCid = new HashMap<>(); clientStates.add(ON_INIT); leshanClient = builder.build(); + simpleLwM2MDevice.setLwM2MTestClient(this); LwM2mClientObserver observer = new LwM2mClientObserver() { @Override @@ -248,7 +261,7 @@ public class LwM2MTestClient { @Override public void onRegistrationSuccess(LwM2mServer server, RegisterRequest request, String registrationID) { clientStates.add(ON_REGISTRATION_SUCCESS); - } + } @Override public void onRegistrationFailure(LwM2mServer server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { @@ -268,6 +281,8 @@ public class LwM2MTestClient { @Override public void onUpdateSuccess(LwM2mServer server, UpdateRequest request) { clientStates.add(ON_UPDATE_SUCCESS); + countUpdateRegistrationSuccess++; + countReadObserveAfterUpdateRegistrationSuccess = 0; } @Override @@ -319,6 +334,12 @@ public class LwM2MTestClient { public void objectAdded(LwM2mObjectEnabler object) { log.info("Object {} v{} enabled.", object.getId(), object.getObjectModel().version); } + + @Override + public void resourceChanged(LwM2mPath... paths) { + countReadObserveAfterUpdateRegistrationSuccess++; + log.trace("resourceChanged paths {} cntReadObserve {} cntUpdateSuccess {} .", paths, countReadObserveAfterUpdateRegistrationSuccess, countUpdateRegistrationSuccess); + } }); leshanClient.start(); @@ -334,5 +355,8 @@ public class LwM2MTestClient { if (fwLwM2MDevice != null) { fwLwM2MDevice.destroy(); } + if (lwM2MBinaryAppDataContainer != null) { + lwM2MBinaryAppDataContainer.destroy(); + } } } \ No newline at end of file diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mBinaryAppDataContainer.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mBinaryAppDataContainer.java new file mode 100644 index 0000000000..d8f598fa93 --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mBinaryAppDataContainer.java @@ -0,0 +1,230 @@ +/** + * 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.msa.connectivity.lwm2m.client; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.client.resource.BaseInstanceEnabler; +import org.eclipse.leshan.client.servers.LwM2mServer; +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.LwM2mResource; +import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.core.response.WriteResponse; + +import javax.security.auth.Destroyable; +import java.sql.Time; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + + +@Slf4j +public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements Destroyable { + + /** + * id = 0 + * Multiple + * base64 + */ + + /** + * Example1: + * InNlcnZpY2VJZCI6Ik1ldGVyIiwNCiJzZXJ2aWNlRGF0YSI6ew0KImN1cnJlbnRSZWFka + * W5nIjoiNDYuMyIsDQoic2lnbmFsU3RyZW5ndGgiOjE2LA0KImRhaWx5QWN0aXZpdHlUaW1lIjo1NzA2DQo= + * "serviceId":"Meter", + * "serviceData":{ + * "currentReading":"46.3", + * "signalStrength":16, + * "dailyActivityTime":5706 + */ + + /** + * Example2: + * InNlcnZpY2VJZCI6IldhdGVyTWV0ZXIiLA0KImNtZCI6IlNFVF9URU1QRVJBVFVSRV9SRUFEX + * 1BFUklPRCIsDQoicGFyYXMiOnsNCiJ2YWx1ZSI6NA0KICAgIH0sDQoNCg0K + * "serviceId":"WaterMeter", + * "cmd":"SET_TEMPERATURE_READ_PERIOD", + * "paras":{ + * "value":4 + * }, + */ + + Map data; + private Integer priority = 0; + private Time timestamp; + private String description; + private String dataFormat; + private Integer appID = -1; + private static final List supportedResources = Arrays.asList(0, 1, 2, 3, 4, 5); + + public LwM2mBinaryAppDataContainer() { + } + + public LwM2mBinaryAppDataContainer(ScheduledExecutorService executorService, Integer id) { + try { + if (id != null) this.setId(id); + executorService.scheduleWithFixedDelay(() -> { + fireResourceChange(0); + fireResourceChange(2); + } + , 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN + } catch (Throwable e) { + log.error("[{}]Throwable", e.toString()); + e.printStackTrace(); + } + } + + @Override + public ReadResponse read(LwM2mServer identity, int resourceId) { + try { + switch (resourceId) { + case 0: + ReadResponse response = ReadResponse.success(resourceId, getData(), ResourceModel.Type.OPAQUE); + return response; + case 1: + return ReadResponse.success(resourceId, getPriority()); + case 2: + return ReadResponse.success(resourceId, getTimestamp()); + case 3: + return ReadResponse.success(resourceId, getDescription()); + case 4: + return ReadResponse.success(resourceId, getDataFormat()); + case 5: + return ReadResponse.success(resourceId, getAppID()); + default: + return super.read(identity, resourceId); + } + } catch (Exception e) { + return ReadResponse.badRequest(e.getMessage()); + } + } + + @Override + public WriteResponse write(LwM2mServer identity, boolean replace, int resourceId, LwM2mResource value) { + log.info("Write on Device resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + switch (resourceId) { + case 0: + if (setData(value, replace)) { + return WriteResponse.success(); + } else { + WriteResponse.badRequest("Invalidate value ..."); + } + case 1: + setPriority((Integer) (value.getValue() instanceof Long ? ((Long) value.getValue()).intValue() : value.getValue())); + fireResourceChange(resourceId); + return WriteResponse.success(); + case 2: + setTimestamp(((Date) value.getValue()).getTime()); + fireResourceChange(resourceId); + return WriteResponse.success(); + case 3: + setDescription((String) value.getValue()); + fireResourceChange(resourceId); + return WriteResponse.success(); + case 4: + setDataFormat((String) value.getValue()); + fireResourceChange(resourceId); + return WriteResponse.success(); + case 5: + setAppID((Integer) value.getValue()); + fireResourceChange(resourceId); + return WriteResponse.success(); + default: + return super.write(identity, replace, resourceId, value); + } + } + + private Integer getAppID() { + return this.appID; + } + + private void setAppID(Integer appId) { + this.appID = appId; + } + + private void setDataFormat(String value) { + this.dataFormat = value; + } + + private String getDataFormat() { + return this.dataFormat == null ? "OPAQUE" : this.dataFormat; + } + + private void setDescription(String value) { + this.description = value; + } + + private String getDescription() { +// return this.description == null ? "meter reading" : this.description; + return this.description; + } + + private void setTimestamp(long time) { + this.timestamp = new Time(time); + } + + private Time getTimestamp() { + return this.timestamp != null ? this.timestamp : new Time(new Date().getTime()); + } + + private boolean setData(LwM2mResource value, boolean replace) { + try { + if (value instanceof LwM2mMultipleResource) { + if (replace || this.data == null) { + this.data = new HashMap<>(); + } + value.getInstances().values().forEach(v -> { + this.data.put(v.getId(), (byte[]) v.getValue()); + }); + return true; + } else { + return false; + } + } catch (Exception e) { + return false; + } + } + + private Map getData() { + if (data == null) { + this.data = new HashMap<>(); + this.data.put(0, new byte[]{(byte) 0xAC}); + } + return data; + } + + @Override + public List getAvailableResourceIds(ObjectModel model) { + return supportedResources; + } + + @Override + public void destroy() { + } + + private int getPriority() { + return this.priority; + } + + private void setPriority(int value) { + this.priority = value; + } +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mTemperatureSensor.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mTemperatureSensor.java new file mode 100644 index 0000000000..2ad6fdc8d1 --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mTemperatureSensor.java @@ -0,0 +1,190 @@ +/** + * 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.msa.connectivity.lwm2m.client; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.client.LeshanClient; +import org.eclipse.leshan.client.resource.BaseInstanceEnabler; +import org.eclipse.leshan.client.send.ManualDataSender; +import org.eclipse.leshan.client.servers.LwM2mServer; +import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.request.argument.Arguments; +import org.eclipse.leshan.core.response.ExecuteResponse; +import org.eclipse.leshan.core.response.ReadResponse; + +import javax.security.auth.Destroyable; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +@Slf4j +public class LwM2mTemperatureSensor extends BaseInstanceEnabler implements Destroyable { + + private static final String UNIT_CELSIUS = "cel"; + private double currentTemp = 20d; + private double minMeasuredValue = currentTemp; + private double maxMeasuredValue = currentTemp; + + private LeshanClient leshanClient; + private List containingValues; + protected static final Random RANDOM = new Random(); + private static final List supportedResources = Arrays.asList(5601, 5602, 5700, 5701); + + public LwM2mTemperatureSensor() { + + } + + public LwM2mTemperatureSensor(ScheduledExecutorService executorService, Integer id) { + try { + if (id != null) this.setId(id); + executorService.scheduleWithFixedDelay(this::adjustTemperature, 2000, 2000, TimeUnit.MILLISECONDS); + } catch (Throwable e) { + log.error("[{}]Throwable", e.toString()); + e.printStackTrace(); + } + } + + @Override + public synchronized ReadResponse read(LwM2mServer identity, int resourceId) { + log.info("Read on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + switch (resourceId) { + case 5601: + return ReadResponse.success(resourceId, getTwoDigitValue(minMeasuredValue)); + case 5602: + return ReadResponse.success(resourceId, getTwoDigitValue(maxMeasuredValue)); + case 5700: + if (identity == LwM2mServer.SYSTEM) { + setTemperature(); + setData(); + return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp)); + } else if (this.getId() == 12 && this.leshanClient != null) { + containingValues = new ArrayList<>(); + sendCollected(5700); + return ReadResponse.success(resourceId, getData()); + } else { + return ReadResponse.success(resourceId, getTwoDigitValue(currentTemp)); + } + case 5701: + return ReadResponse.success(resourceId, UNIT_CELSIUS); + default: + return super.read(identity, resourceId); + } + } + + @Override + public synchronized ExecuteResponse execute(LwM2mServer identity, int resourceId, Arguments arguments) { + log.info("Execute on Temperature resource /[{}]/[{}]/[{}]", getModel().id, getId(), resourceId); + switch (resourceId) { + case 5605: + resetMinMaxMeasuredValues(); + return ExecuteResponse.success(); + default: + return super.execute(identity, resourceId, arguments); + } + } + + private double getTwoDigitValue(double value) { + BigDecimal toBeTruncated = BigDecimal.valueOf(value); + return toBeTruncated.setScale(2, RoundingMode.HALF_UP).doubleValue(); + } + + private void adjustTemperature() { + setTemperature(); + Integer changedResource = adjustMinMaxMeasuredValue(currentTemp); + fireResourceChange(5700); + if (changedResource != null) { + fireResourceChange(changedResource); + } + } + + private void setTemperature(){ + float delta = (RANDOM.nextInt(20) - 10) / 10f; + currentTemp += delta; + } + private synchronized Integer adjustMinMaxMeasuredValue(double newTemperature) { + if (newTemperature > maxMeasuredValue) { + maxMeasuredValue = newTemperature; + return 5602; + } else if (newTemperature < minMeasuredValue) { + minMeasuredValue = newTemperature; + return 5601; + } else { + return null; + } + } + + private void resetMinMaxMeasuredValues() { + minMeasuredValue = currentTemp; + maxMeasuredValue = currentTemp; + } + + @Override + public List getAvailableResourceIds(ObjectModel model) { + return supportedResources; + } + + protected void setLeshanClient(LeshanClient leshanClient){ + this.leshanClient = leshanClient; + } + + @Override + public void destroy() { + } + + private void sendCollected(int resourceId) { + try { + 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))); + Thread.sleep(1000); + sender.collectData(Arrays.asList(getPathForCollectedValue(resourceId))); + sender.sendCollectedData(registeredServer, ContentFormat.SENML_JSON, 1000, false); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + private LwM2mPath getPathForCollectedValue(int resourceId) { + return new LwM2mPath(3303, this.getId(), resourceId); + } + + private double getData() { + if (containingValues.size() > 1) { + Integer t0 = Math.toIntExact(Math.round(containingValues.get(0) * 100)); + Integer t1 = Math.toIntExact(Math.round(containingValues.get(1) * 100)); + long to_t1 = (((long) t0) << 32) | (t1 & 0xffffffffL); + return Double.longBitsToDouble(to_t1); + } else { + return currentTemp; + } + + } + + private void setData() { + if (containingValues == null){ + containingValues = new ArrayList<>(); + } + containingValues.add(getTwoDigitValue(currentTemp)); + } +} + diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2mValueConverterImpl.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mValueConverterImpl.java similarity index 99% rename from msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2mValueConverterImpl.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mValueConverterImpl.java index b49d54f27e..bb76569685 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/LwM2mValueConverterImpl.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/LwM2mValueConverterImpl.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.msa.connectivity.lwm2m; +package org.thingsboard.server.msa.connectivity.lwm2m.client; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.ResourceModel.Type; diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mTestHelper.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/Lwm2mTestHelper.java similarity index 73% rename from msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mTestHelper.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/Lwm2mTestHelper.java index dfb4c71a1e..c8200342e2 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/Lwm2mTestHelper.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/Lwm2mTestHelper.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.msa.connectivity.lwm2m; +package org.thingsboard.server.msa.connectivity.lwm2m.client; import org.eclipse.californium.elements.config.Configuration; import org.eclipse.leshan.client.object.Security; @@ -25,7 +25,7 @@ import static org.eclipse.leshan.client.object.Security.noSec; public class Lwm2mTestHelper { // Models - public static final String[] resources = new String[]{ "0.xml", "1.xml", "2.xml", "3.xml", "5.xml"}; + public static final String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml", "5.xml", "19.xml"}; public static final int serverId = 1; public static final int port = 5685; @@ -44,16 +44,41 @@ public class Lwm2mTestHelper { public static final String CLIENT_PSK_IDENTITY = "SOME_PSK_ID"; public static final String CLIENT_PSK_KEY = "73656372657450534b73656372657450"; + public static final int BINARY_APP_DATA_CONTAINER = 19; + + public static final String RESOURCE_ID_NAME_3_9 = "batteryLevel"; + public static final String RESOURCE_ID_NAME_3_14 = "UtfOffset"; + public static final String RESOURCE_ID_NAME_19_0_0 = "dataRead"; + public static final String RESOURCE_ID_NAME_19_0_2 = "dataCreationTime"; + public static final String RESOURCE_ID_NAME_19_1_0 = "dataWrite"; + + public static String OBSERVE_ATTRIBUTES_WITH_PARAMS = - public static final String OBSERVE_ATTRIBUTES_WITHOUT_PARAMS = " {\n" + - " \"keyName\": {},\n" + - " \"observe\": [],\n" + - " \"attribute\": [],\n" + - " \"telemetry\": [],\n" + + " \"keyName\": {\n" + + " \"/3_1.2/0/9\": \"batteryLevel\",\n" + + " \"/3_1.2/0/14\": \"UtfOffset\",\n" + + " \"/19_1.1/0/0\": \"dataRead\",\n" + + " \"/19_1.1/1/0\": \"dataWrite\",\n" + + " \"/19_1.1/0/2\": \"dataCreationTime\"\n" + + " },\n" + + " \"observe\": [\n" + + " \"/3_1.2/0/9\",\n" + + " \"/19_1.1/0/0\",\n" + + " \"/19_1.1/1/0\",\n" + + " \"/19_1.1/0/2\"\n" + + " ],\n" + + " \"attribute\": [\n" + + " \"/3_1.2/0/14\",\n" + + " \"/19_1.1/0/2\"\n" + + " ],\n" + + " \"telemetry\": [\n" + + " \"/3_1.2/0/9\",\n" + + " \"/19_1.1/0/0\",\n" + + " \"/19_1.1/1/0\"\n" + + " ],\n" + " \"attributeLwm2m\": {}\n" + " }"; - public static final String CLIENT_LWM2M_SETTINGS = " {\n" + " \"edrxCycle\": null,\n" + @@ -67,10 +92,6 @@ public class Lwm2mTestHelper { " \"clientOnlyObserveAfterConnect\": 1\n" + " }"; - public static final int BINARY_APP_DATA_CONTAINER = 19; - public static final int OBJECT_INSTANCE_ID_0 = 0; - public static final int OBJECT_INSTANCE_ID_1 = 1; - public enum LwM2MClientState { ON_INIT(0, "onInit"), @@ -91,8 +112,8 @@ public class Lwm2mTestHelper { ON_DEREGISTRATION_FAILURE(14, "onDeregistrationFailure"), ON_DEREGISTRATION_TIMEOUT(15, "onDeregistrationTimeout"), ON_EXPECTED_ERROR(16, "onUnexpectedError"), - ON_READ_CONNECTION_ID (17, "onReadConnection"), - ON_WRITE_CONNECTION_ID (18, "onWriteConnection"); + ON_READ_CONNECTION_ID(17, "onReadConnection"), + ON_WRITE_CONNECTION_ID(18, "onWriteConnection"); public int code; public String type; @@ -125,7 +146,7 @@ public class Lwm2mTestHelper { serverCoapConfig.setTransient(DTLS_CONNECTION_ID_LENGTH); serverCoapConfig.setTransient(DTLS_CONNECTION_ID_NODE_ID); serverCoapConfig.set(DTLS_CONNECTION_ID_LENGTH, cIdLength); - if ( cIdLength > 4) { + if (cIdLength > 4) { serverCoapConfig.set(DTLS_CONNECTION_ID_NODE_ID, 0); } else { serverCoapConfig.set(DTLS_CONNECTION_ID_NODE_ID, null); diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/SimpleLwM2MDevice.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/SimpleLwM2MDevice.java similarity index 79% rename from msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/SimpleLwM2MDevice.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/SimpleLwM2MDevice.java index d2aa1af4d2..eefeb4bdf8 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/SimpleLwM2MDevice.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/client/SimpleLwM2MDevice.java @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.msa.connectivity.lwm2m; +package org.thingsboard.server.msa.connectivity.lwm2m.client; +import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.client.resource.BaseInstanceEnabler; import org.eclipse.leshan.client.servers.LwM2mServer; @@ -36,8 +37,11 @@ import java.util.Map; import java.util.PrimitiveIterator; import java.util.Random; import java.util.TimeZone; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; @Slf4j +@Data public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyable { @@ -46,17 +50,30 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl private static final int max = 50; 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); - + private int countReadObserveAfterUpdateRegistrationSuccess_3_0_9; + private int countUpdateRegistrationSuccessLast; + private LwM2MTestClient lwM2MTestClient; public SimpleLwM2MDevice() { } - + public SimpleLwM2MDevice(ScheduledExecutorService executorService) { + try { + executorService.scheduleWithFixedDelay(() -> { + fireResourceChange(9); + } + , 1, 1, TimeUnit.SECONDS); // 30 MIN +// , 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN + } catch (Throwable e) { + log.error("[{}]Throwable", e.toString()); + e.printStackTrace(); + } + } @Override public ReadResponse read(LwM2mServer identity, int resourceId) { if (!identity.isSystem()) - log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); + log.trace("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); switch (resourceId) { case 0: return ReadResponse.success(resourceId, getManufacturer()); @@ -155,8 +172,15 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl } private int getBatteryLevel() { + int batteryLevel = randomIterator.nextInt(); + if (countUpdateRegistrationSuccessLast != this.lwM2MTestClient.getCountUpdateRegistrationSuccess()) { + countUpdateRegistrationSuccessLast = this.lwM2MTestClient.getCountUpdateRegistrationSuccess(); + countReadObserveAfterUpdateRegistrationSuccess_3_0_9 = 0; + } + countReadObserveAfterUpdateRegistrationSuccess_3_0_9++; + this.lwM2MTestClient.setCountReadObserveAfterUpdateRegistrationSuccess(countReadObserveAfterUpdateRegistrationSuccess_3_0_9); + log.info("Read on Device resource /{}/{}/9 batteryLevel = {}, cntReadAfterUpdateReg [{}] ", getModel().id, getId(), batteryLevel, countReadObserveAfterUpdateRegistrationSuccess_3_0_9); return randomIterator.nextInt(); -// return 42; } private long getMemoryFree() { @@ -212,6 +236,10 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl return supportedResources; } + protected void setLwM2MTestClient(LwM2MTestClient lwM2MTestClient){ + this.lwM2MTestClient = lwM2MTestClient; + } + @Override public void destroy() { } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/rpc/Lwm2mObserveCompositeTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/rpc/Lwm2mObserveCompositeTest.java new file mode 100644 index 0000000000..117e3c5b96 --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/rpc/Lwm2mObserveCompositeTest.java @@ -0,0 +1,52 @@ +/** + * 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.msa.connectivity.lwm2m.rpc; + +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.thingsboard.server.msa.DisableUIListeners; +import org.thingsboard.server.msa.connectivity.lwm2m.AbstractLwm2mClientTest; +import org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mDevicesForTest; + +import static org.thingsboard.server.msa.ui.utils.Const.TENANT_EMAIL; +import static org.thingsboard.server.msa.ui.utils.Const.TENANT_PASSWORD; + +@DisableUIListeners +public class Lwm2mObserveCompositeTest extends AbstractLwm2mClientTest { + + private Lwm2mDevicesForTest lwm2mDevicesForTest; + + private final static String name = "lwm2m-NoSec-ObserveComposite"; + + @BeforeMethod + public void setUp() throws Exception { + testRestClient.login(TENANT_EMAIL, TENANT_PASSWORD); + this.lwm2mDevicesForTest = new Lwm2mDevicesForTest(initTest(name + "-profile" + RandomStringUtils.randomAlphanumeric(7))); + } + + @AfterMethod + public void tearDown() { + destroyAfter(this.lwm2mDevicesForTest); + } + + @Test + public void testObserveResource_Update_AfterUpdateRegistration() throws Exception { + createLwm2mDevicesForConnectNoSec( name + "-" + RandomStringUtils.randomAlphanumeric(7), this.lwm2mDevicesForTest ); + observeCompositeResource_Update_AfterUpdateRegistration_test(this.lwm2mDevicesForTest.getLwM2MTestClient(), this.lwm2mDevicesForTest.getLwM2MDeviceTest().getId()); + } +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/rpc/Lwm2mObserveTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/rpc/Lwm2mObserveTest.java new file mode 100644 index 0000000000..07e8780aa0 --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/rpc/Lwm2mObserveTest.java @@ -0,0 +1,52 @@ +/** + * 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.msa.connectivity.lwm2m.rpc; + +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.thingsboard.server.msa.connectivity.lwm2m.AbstractLwm2mClientTest; +import org.thingsboard.server.msa.DisableUIListeners; +import org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mDevicesForTest; + +import static org.thingsboard.server.msa.ui.utils.Const.TENANT_EMAIL; +import static org.thingsboard.server.msa.ui.utils.Const.TENANT_PASSWORD; + +@DisableUIListeners +public class Lwm2mObserveTest extends AbstractLwm2mClientTest { + + private Lwm2mDevicesForTest lwm2mDevicesForTest; + + private final static String name = "lwm2m-NoSec-Observe"; + + @BeforeMethod + public void setUp() throws Exception { + testRestClient.login(TENANT_EMAIL, TENANT_PASSWORD); + this.lwm2mDevicesForTest = new Lwm2mDevicesForTest(initTest(name + "-profile" + RandomStringUtils.randomAlphanumeric(7))); + } + + @AfterMethod + public void tearDown() { + destroyAfter(this.lwm2mDevicesForTest); + } + + @Test + public void testObserveResource_Update_AfterUpdateRegistration() throws Exception { + createLwm2mDevicesForConnectNoSec( name + "-" + RandomStringUtils.randomAlphanumeric(7), this.lwm2mDevicesForTest ); + observeResource_Update_AfterUpdateRegistration_test(this.lwm2mDevicesForTest.getLwM2MTestClient(), this.lwm2mDevicesForTest.getLwM2MDeviceTest().getId()); + } +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientNoSecTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/security/Lwm2mClientNoSecTest.java similarity index 60% rename from msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientNoSecTest.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/security/Lwm2mClientNoSecTest.java index 15658140b0..c614dedf2f 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientNoSecTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/security/Lwm2mClientNoSecTest.java @@ -13,33 +13,38 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.msa.connectivity; +package org.thingsboard.server.msa.connectivity.lwm2m.security; +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import org.thingsboard.server.msa.AbstractLwm2mClientTest; +import org.thingsboard.server.msa.connectivity.lwm2m.AbstractLwm2mClientTest; import org.thingsboard.server.msa.DisableUIListeners; +import org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mDevicesForTest; +import static org.thingsboard.server.msa.connectivity.lwm2m.client.Lwm2mTestHelper.CLIENT_ENDPOINT_NO_SEC; import static org.thingsboard.server.msa.ui.utils.Const.TENANT_EMAIL; import static org.thingsboard.server.msa.ui.utils.Const.TENANT_PASSWORD; @DisableUIListeners public class Lwm2mClientNoSecTest extends AbstractLwm2mClientTest { + private Lwm2mDevicesForTest lwm2mDevicesForTest; @BeforeMethod public void setUp() throws Exception { testRestClient.login(TENANT_EMAIL, TENANT_PASSWORD); - initTest("lwm2m-NoSec"); + this.lwm2mDevicesForTest = new Lwm2mDevicesForTest(initTest("lwm2m-NoSec-profile" + "-" + RandomStringUtils.randomAlphanumeric(7))); } @AfterMethod public void tearDown() { - destroyAfter(); + destroyAfter(this.lwm2mDevicesForTest); } @Test public void connectLwm2mClientNoSecWithLwm2mServer() throws Exception { - connectLwm2mClientNoSec(); + createLwm2mDevicesForConnectNoSec(CLIENT_ENDPOINT_NO_SEC, this.lwm2mDevicesForTest); + basicTestConnection(this.lwm2mDevicesForTest.getLwM2MTestClient(), "TestConnection Lwm2m NoSec (msa)"); } } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientPskTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/security/Lwm2mClientPskTest.java similarity index 64% rename from msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientPskTest.java rename to msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/security/Lwm2mClientPskTest.java index fbd8139d8f..9eab67f999 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/Lwm2mClientPskTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/lwm2m/security/Lwm2mClientPskTest.java @@ -13,13 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.msa.connectivity; +package org.thingsboard.server.msa.connectivity.lwm2m.security; +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import org.thingsboard.server.msa.AbstractLwm2mClientTest; +import org.thingsboard.server.msa.connectivity.lwm2m.AbstractLwm2mClientTest; import org.thingsboard.server.msa.DisableUIListeners; +import org.thingsboard.server.msa.connectivity.lwm2m.Lwm2mDevicesForTest; import static org.thingsboard.server.msa.ui.utils.Const.TENANT_EMAIL; import static org.thingsboard.server.msa.ui.utils.Const.TENANT_PASSWORD; @@ -27,19 +29,21 @@ import static org.thingsboard.server.msa.ui.utils.Const.TENANT_PASSWORD; @DisableUIListeners public class Lwm2mClientPskTest extends AbstractLwm2mClientTest { + private Lwm2mDevicesForTest lwm2mDevicesForTest; @BeforeMethod public void setUp() throws Exception { testRestClient.login(TENANT_EMAIL, TENANT_PASSWORD); - initTest("lwm2m-Psk"); + this.lwm2mDevicesForTest = new Lwm2mDevicesForTest(initTest("lwm2m-Psk-profile" + "-" + RandomStringUtils.randomAlphanumeric(7))); } @AfterMethod public void tearDown() { - destroyAfter(); + destroyAfter(this.lwm2mDevicesForTest); } @Test public void connectLwm2mClientPskWithLwm2mServer() throws Exception { - connectLwm2mClientPsk(); + createLwm2mDevicesForConnectPsk(this.lwm2mDevicesForTest); + basicTestConnection(this.lwm2mDevicesForTest.getLwM2MTestClient(), "TestConnection Lwm2m Rpc (msa)"); } } diff --git a/msa/black-box-tests/src/test/resources/lwm2m-registry/19.xml b/msa/black-box-tests/src/test/resources/lwm2m-registry/19.xml new file mode 100644 index 0000000000..ffa21864b7 --- /dev/null +++ b/msa/black-box-tests/src/test/resources/lwm2m-registry/19.xml @@ -0,0 +1,144 @@ + + + + + BinaryAppDataContainer + + 19 + urn:oma:lwm2m:oma:19:1.1 + 1.1 + 1.1 + Multiple + Optional + + Data + RW + Multiple + Mandatory + Opaque + + + + + Data Priority + RW + Single + Optional + Integer + 1 bytes + + + + Data Creation Time + RW + Single + Optional + Time + + + + + Data Description + RW + Single + Optional + String + 32 bytes + + + + Data Format + RW + Single + Optional + String + 32 bytes + + + + App ID + RW + Single + Optional + Integer + 2 bytes + + + + + + diff --git a/msa/black-box-tests/src/test/resources/lwm2m-registry/3303.xml b/msa/black-box-tests/src/test/resources/lwm2m-registry/3303.xml new file mode 100644 index 0000000000..a6e5406b08 --- /dev/null +++ b/msa/black-box-tests/src/test/resources/lwm2m-registry/3303.xml @@ -0,0 +1,103 @@ + + + + + Temperature + This IPSO object should be used with a temperature sensor to report a temperature measurement. It also provides resources for minimum/maximum measured values and the minimum/maximum range that can be measured by the temperature sensor. An example measurement unit is degrees Celsius. + 3303 + urn:oma:lwm2m:ext:3303 + 1.0 + 1.0 + Multiple + Optional + + + Sensor Value + R + Single + Mandatory + Float + + + Last or Current Measured Value from the Sensor + + + Min Measured Value + R + Single + Optional + Float + + + The minimum value measured by the sensor since power ON or reset + + + Max Measured Value + R + Single + Optional + Float + + + The maximum value measured by the sensor since power ON or reset + + + Min Range Value + R + Single + Optional + Float + + + The minimum value that can be measured by the sensor + + + Max Range Value + R + Single + Optional + Float + + + The maximum value that can be measured by the sensor + + + Sensor Units + R + Single + Optional + String + + + Measurement Units Definition. + + + Reset Min and Max Measured Values + E + Single + Optional + + + + Reset the Min and Max Measured Values to Current Value + + + + +