Browse Source

Merge with master

pull/6199/head
Igor Kulikov 4 years ago
parent
commit
3af76140cd
  1. 18
      application/src/main/data/json/system/widget_bundles/maps.json
  2. 50
      application/src/main/data/upgrade/3.3.3/schema_event_ttl_procedure.sql
  3. 184
      application/src/main/java/org/apache/kafka/common/network/NetworkReceive.java
  4. 14
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  5. 2
      application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java
  6. 4
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  7. 70
      application/src/main/java/org/thingsboard/server/controller/EdgeController.java
  8. 4
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  9. 1
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  10. 119
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
  11. 110
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeLicenseService.java
  12. 6
      application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java
  13. 2
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  14. 8
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
  15. 2
      application/src/main/java/org/thingsboard/server/service/importing/BulkImportColumnType.java
  16. 20
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  17. 148
      application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java
  18. 210
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  19. 32
      application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java
  20. 5
      application/src/main/resources/thingsboard.yml
  21. 6
      application/src/test/java/org/thingsboard/server/actors/stats/StatsActorTest.java
  22. 2
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  23. 61
      application/src/test/java/org/thingsboard/server/controller/AbstractWebsocketTest.java
  24. 2
      application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
  25. 2
      application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
  26. 378
      application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java
  27. 43
      application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
  28. 23
      application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
  29. 86
      application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java
  30. 1
      application/src/test/java/org/thingsboard/server/service/ttl/EventsCleanUpServiceTest.java
  31. 315
      application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
  32. 106
      application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java
  33. 181
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java
  34. 7
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java
  35. 235
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/Lwm2mServer.java
  36. 26
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java
  37. 162
      application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java
  38. 168
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java
  39. 2
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java
  40. 7
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java
  41. 78
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java
  42. 25
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java
  43. 324
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java
  44. 76
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java
  45. 91
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java
  46. 115
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java
  47. 112
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java
  48. 69
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java
  49. 8
      application/src/test/resources/application-test.properties
  50. 0
      application/src/test/resources/logback-test.xml
  51. 299
      application/src/test/resources/lwm2m/credentials/shell/lwM2M_cfssl_chain_trusts_and_clients_for_test.sh
  52. 65
      application/src/test/resources/lwm2m/credentials/shell/lwm2m_cfssl_chain_for_test_All.sh
  53. 298
      application/src/test/resources/lwm2m/credentials/shell/lwm2m_cfssl_chain_server_for_test.sh
  54. 8
      common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java
  55. 20
      common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java
  56. 13
      common/data/src/main/java/org/thingsboard/server/common/data/exception/ThingsboardKafkaClientError.java
  57. 4
      common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java
  58. 6
      common/edge-api/src/main/proto/edge.proto
  59. 11
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java
  60. 56
      common/message/src/test/java/org/thingsboard/server/common/msg/TbMsgMetaDataTest.java
  61. 3
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java
  62. 12
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java
  63. 21
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java
  64. 10
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
  65. 10
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MNetworkConfig.java
  66. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java
  67. 21
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  68. 3
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java
  69. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java
  70. 6
      dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java
  71. 22
      dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java
  72. 26
      dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java
  73. 2
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  74. 14
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEdgeEntity.java
  75. 91
      dao/src/main/java/org/thingsboard/server/dao/sql/event/AbstractEventInsertRepository.java
  76. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/event/EventCleanupRepository.java
  77. 72
      dao/src/main/java/org/thingsboard/server/dao/sql/event/EventInsertRepository.java
  78. 54
      dao/src/main/java/org/thingsboard/server/dao/sql/event/HsqlEventCleanupRepository.java
  79. 63
      dao/src/main/java/org/thingsboard/server/dao/sql/event/HsqlEventInsertRepository.java
  80. 121
      dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java
  81. 12
      dao/src/main/java/org/thingsboard/server/dao/sql/event/PsqlEventCleanupRepository.java
  82. 53
      dao/src/main/java/org/thingsboard/server/dao/sql/event/PsqlEventInsertRepository.java
  83. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDao.java
  84. 14
      dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantRepository.java
  85. 7
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantDao.java
  86. 15
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
  87. 27
      dao/src/main/resources/sql/schema-entities.sql
  88. 3
      dao/src/test/java/org/thingsboard/server/dao/AbstractJpaDaoTest.java
  89. 2
      dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
  90. 4
      dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java
  91. 2
      dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java
  92. 33
      dao/src/test/java/org/thingsboard/server/dao/service/event/BaseEventServiceTest.java
  93. 29
      dao/src/test/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDaoTest.java
  94. 16
      dao/src/test/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDaoTest.java
  95. 3
      pom.xml
  96. 30
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java
  97. 70
      tools/src/main/shell/lwm2m/lwm2m_cfssl_chain_all_for_test.sh
  98. 10
      ui-ngx/src/app/core/http/entity.service.ts
  99. 6
      ui-ngx/src/app/core/utils.ts
  100. 19
      ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.ts

18
application/src/main/data/json/system/widget_bundles/maps.json

File diff suppressed because one or more lines are too long

50
application/src/main/data/upgrade/3.3.3/schema_event_ttl_procedure.sql

@ -0,0 +1,50 @@
--
-- Copyright © 2016-2022 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.
--
DROP PROCEDURE IF EXISTS public.cleanup_events_by_ttl(bigint, bigint, bigint);
CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(
IN regular_events_start_ts bigint,
IN regular_events_end_ts bigint,
IN debug_events_start_ts bigint,
IN debug_events_end_ts bigint,
INOUT deleted bigint)
LANGUAGE plpgsql AS
$$
DECLARE
ttl_deleted_count bigint DEFAULT 0;
debug_ttl_deleted_count bigint DEFAULT 0;
BEGIN
IF regular_events_start_ts > 0 AND regular_events_end_ts > 0 THEN
EXECUTE format(
'WITH deleted AS (DELETE FROM event WHERE id in (SELECT id from event WHERE ts > %L::bigint AND ts < %L::bigint AND ' ||
'(event_type != %L::varchar AND event_type != %L::varchar)) RETURNING *) ' ||
'SELECT count(*) FROM deleted', regular_events_start_ts, regular_events_end_ts,
'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into ttl_deleted_count;
END IF;
IF debug_events_start_ts > 0 AND debug_events_end_ts > 0 THEN
EXECUTE format(
'WITH deleted AS (DELETE FROM event WHERE id in (SELECT id from event WHERE ts > %L::bigint AND ts < %L::bigint AND ' ||
'(event_type = %L::varchar OR event_type = %L::varchar)) RETURNING *) ' ||
'SELECT count(*) FROM deleted', debug_events_start_ts, debug_events_end_ts,
'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into debug_ttl_deleted_count;
END IF;
RAISE NOTICE 'Events removed by ttl: %', ttl_deleted_count;
RAISE NOTICE 'Debug Events removed by ttl: %', debug_ttl_deleted_count;
deleted := ttl_deleted_count + debug_ttl_deleted_count;
END
$$;

184
application/src/main/java/org/apache/kafka/common/network/NetworkReceive.java

@ -0,0 +1,184 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/*
* Content of this file was modified to addresses the issue https://issues.apache.org/jira/browse/KAFKA-4090
*
*/
package org.apache.kafka.common.network;
import org.apache.kafka.common.memory.MemoryPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.thingsboard.server.common.data.exception.ThingsboardKafkaClientError;
import java.io.EOFException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.ScatteringByteChannel;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* A size delimited Receive that consists of a 4 byte network-ordered size N followed by N bytes of content
*/
public class NetworkReceive implements Receive {
public final static String UNKNOWN_SOURCE = "";
public final static int UNLIMITED = -1;
public final static int TB_MAX_REQUESTED_BUFFER_SIZE = 100 * 1024 * 1024;
public final static int TB_LOG_REQUESTED_BUFFER_SIZE = 10 * 1024 * 1024;
private static final Logger log = LoggerFactory.getLogger(NetworkReceive.class);
private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
private final String source;
private final ByteBuffer size;
private final int maxSize;
private final MemoryPool memoryPool;
private int requestedBufferSize = -1;
private ByteBuffer buffer;
public NetworkReceive(String source, ByteBuffer buffer) {
this.source = source;
this.buffer = buffer;
this.size = null;
this.maxSize = TB_MAX_REQUESTED_BUFFER_SIZE;
this.memoryPool = MemoryPool.NONE;
}
public NetworkReceive(String source) {
this.source = source;
this.size = ByteBuffer.allocate(4);
this.buffer = null;
this.maxSize = TB_MAX_REQUESTED_BUFFER_SIZE;
this.memoryPool = MemoryPool.NONE;
}
public NetworkReceive(int maxSize, String source) {
this.source = source;
this.size = ByteBuffer.allocate(4);
this.buffer = null;
this.maxSize = getMaxSize(maxSize);
this.memoryPool = MemoryPool.NONE;
}
public NetworkReceive(int maxSize, String source, MemoryPool memoryPool) {
this.source = source;
this.size = ByteBuffer.allocate(4);
this.buffer = null;
this.maxSize = getMaxSize(maxSize);
this.memoryPool = memoryPool;
}
public NetworkReceive() {
this(UNKNOWN_SOURCE);
}
@Override
public String source() {
return source;
}
@Override
public boolean complete() {
return !size.hasRemaining() && buffer != null && !buffer.hasRemaining();
}
public long readFrom(ScatteringByteChannel channel) throws IOException {
int read = 0;
if (size.hasRemaining()) {
int bytesRead = channel.read(size);
if (bytesRead < 0)
throw new EOFException();
read += bytesRead;
if (!size.hasRemaining()) {
size.rewind();
int receiveSize = size.getInt();
if (receiveSize < 0)
throw new InvalidReceiveException("Invalid receive (size = " + receiveSize + ")");
if (maxSize != UNLIMITED && receiveSize > maxSize) {
throw new ThingsboardKafkaClientError("Invalid receive (size = " + receiveSize + " larger than " + maxSize + ")");
}
requestedBufferSize = receiveSize; //may be 0 for some payloads (SASL)
if (receiveSize == 0) {
buffer = EMPTY_BUFFER;
}
}
}
if (buffer == null && requestedBufferSize != -1) { //we know the size we want but havent been able to allocate it yet
if (requestedBufferSize > TB_LOG_REQUESTED_BUFFER_SIZE) {
String stackTrace = Arrays.stream(Thread.currentThread().getStackTrace()).map(StackTraceElement::toString).collect(Collectors.joining("|"));
log.error("Allocating buffer of size {} for source {}", requestedBufferSize, source);
log.error("Stack Trace: {}", stackTrace);
}
buffer = memoryPool.tryAllocate(requestedBufferSize);
if (buffer == null)
log.trace("Broker low on memory - could not allocate buffer of size {} for source {}", requestedBufferSize, source);
}
if (buffer != null) {
int bytesRead = channel.read(buffer);
if (bytesRead < 0)
throw new EOFException();
read += bytesRead;
}
return read;
}
@Override
public boolean requiredMemoryAmountKnown() {
return requestedBufferSize != -1;
}
@Override
public boolean memoryAllocated() {
return buffer != null;
}
@Override
public void close() throws IOException {
if (buffer != null && buffer != EMPTY_BUFFER) {
memoryPool.release(buffer);
buffer = null;
}
}
public ByteBuffer payload() {
return this.buffer;
}
public int bytesRead() {
if (buffer == null)
return size.position();
return buffer.position() + size.position();
}
/**
* Returns the total size of the receive including payload and size buffer
* for use in metrics. This is consistent with {@link NetworkSend#size()}
*/
public int size() {
return payload().limit() + size.limit();
}
private int getMaxSize(int maxSize) {
return maxSize == UNLIMITED ? TB_MAX_REQUESTED_BUFFER_SIZE : Math.min(maxSize, TB_MAX_REQUESTED_BUFFER_SIZE);
}
}

14
application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java

@ -467,7 +467,7 @@ public class ActorSystemContext {
} }
private void persistEvent(Event event) { private void persistEvent(Event event) {
eventService.save(event); eventService.saveAsync(event);
} }
private String toString(Throwable e) { private String toString(Throwable e) {
@ -552,10 +552,10 @@ public class ActorSystemContext {
} }
event.setBody(node); event.setBody(node);
ListenableFuture<Event> future = eventService.saveAsync(event); ListenableFuture<Void> future = eventService.saveAsync(event);
Futures.addCallback(future, new FutureCallback<Event>() { Futures.addCallback(future, new FutureCallback<Void>() {
@Override @Override
public void onSuccess(@Nullable Event event) { public void onSuccess(@Nullable Void event) {
} }
@ -605,10 +605,10 @@ public class ActorSystemContext {
} }
event.setBody(node); event.setBody(node);
ListenableFuture<Event> future = eventService.saveAsync(event); ListenableFuture<Void> future = eventService.saveAsync(event);
Futures.addCallback(future, new FutureCallback<Event>() { Futures.addCallback(future, new FutureCallback<Void>() {
@Override @Override
public void onSuccess(@Nullable Event event) { public void onSuccess(@Nullable Void event) {
} }

2
application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java

@ -59,7 +59,7 @@ public class StatsActor extends ContextAwareActor {
event.setTenantId(msg.getTenantId()); event.setTenantId(msg.getTenantId());
event.setType(DataConstants.STATS); event.setType(DataConstants.STATS);
event.setBody(toBodyJson(systemContext.getServiceInfoProvider().getServiceId(), msg.getMessagesProcessed(), msg.getErrorsOccurred())); event.setBody(toBodyJson(systemContext.getServiceInfoProvider().getServiceId(), msg.getMessagesProcessed(), msg.getErrorsOccurred()));
systemContext.getEventService().save(event); systemContext.getEventService().saveAsync(event);
} }
private JsonNode toBodyJson(String serviceId, long messagesProcessed, long errorsOccurred) { private JsonNode toBodyJson(String serviceId, long messagesProcessed, long errorsOccurred) {

4
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -123,7 +123,6 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.action.EntityActionService; import org.thingsboard.server.service.action.EntityActionService;
import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.component.ComponentDiscoveryService;
import org.thingsboard.server.service.edge.EdgeLicenseService;
import org.thingsboard.server.service.edge.EdgeNotificationService; import org.thingsboard.server.service.edge.EdgeNotificationService;
import org.thingsboard.server.service.edge.rpc.EdgeRpcService; import org.thingsboard.server.service.edge.rpc.EdgeRpcService;
import org.thingsboard.server.service.ota.OtaPackageStateService; import org.thingsboard.server.service.ota.OtaPackageStateService;
@ -269,9 +268,6 @@ public abstract class BaseController {
@Autowired(required = false) @Autowired(required = false)
protected EdgeRpcService edgeGrpcService; protected EdgeRpcService edgeGrpcService;
@Autowired(required = false)
protected EdgeLicenseService edgeLicenseService;
@Autowired @Autowired
protected EntityActionService entityActionService; protected EntityActionService entityActionService;

70
application/src/main/java/org/thingsboard/server/controller/EdgeController.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam; import io.swagger.annotations.ApiParam;
@ -23,7 +22,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@ -52,7 +50,6 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
@ -118,11 +115,7 @@ public class EdgeController extends BaseController {
checkParameter(EDGE_ID, strEdgeId); checkParameter(EDGE_ID, strEdgeId);
try { try {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
Edge edge = checkEdgeId(edgeId, Operation.READ); return checkEdgeId(edgeId, Operation.READ);
if (Authority.CUSTOMER_USER.equals(getCurrentUser().getAuthority())) {
cleanUpLicenseKey(edge);
}
return edge;
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }
@ -139,11 +132,7 @@ public class EdgeController extends BaseController {
checkParameter(EDGE_ID, strEdgeId); checkParameter(EDGE_ID, strEdgeId);
try { try {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
EdgeInfo edgeInfo = checkEdgeInfoId(edgeId, Operation.READ); return checkEdgeInfoId(edgeId, Operation.READ);
if (Authority.CUSTOMER_USER.equals(getCurrentUser().getAuthority())) {
cleanUpLicenseKey(edgeInfo);
}
return edgeInfo;
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }
@ -525,11 +514,6 @@ public class EdgeController extends BaseController {
} else { } else {
result = edgeService.findEdgesByTenantIdAndCustomerId(tenantId, customerId, pageLink); result = edgeService.findEdgesByTenantIdAndCustomerId(tenantId, customerId, pageLink);
} }
if (Authority.CUSTOMER_USER.equals(user.getAuthority())) {
for (Edge edge : result.getData()) {
cleanUpLicenseKey(edge);
}
}
return checkNotNull(result); return checkNotNull(result);
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
@ -570,11 +554,6 @@ public class EdgeController extends BaseController {
} else { } else {
result = edgeService.findEdgeInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink); result = edgeService.findEdgeInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink);
} }
if (Authority.CUSTOMER_USER.equals(user.getAuthority())) {
for (Edge edge : result.getData()) {
cleanUpLicenseKey(edge);
}
}
return checkNotNull(result); return checkNotNull(result);
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
@ -606,11 +585,6 @@ public class EdgeController extends BaseController {
edgesFuture = edgeService.findEdgesByTenantIdCustomerIdAndIdsAsync(tenantId, customerId, edgeIds); edgesFuture = edgeService.findEdgesByTenantIdCustomerIdAndIdsAsync(tenantId, customerId, edgeIds);
} }
List<Edge> edges = edgesFuture.get(); List<Edge> edges = edgesFuture.get();
if (Authority.CUSTOMER_USER.equals(user.getAuthority())) {
for (Edge edge : edges) {
cleanUpLicenseKey(edge);
}
}
return checkNotNull(edges); return checkNotNull(edges);
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
@ -642,11 +616,6 @@ public class EdgeController extends BaseController {
return false; return false;
} }
}).collect(Collectors.toList()); }).collect(Collectors.toList());
if (Authority.CUSTOMER_USER.equals(user.getAuthority())) {
for (Edge edge : edges) {
cleanUpLicenseKey(edge);
}
}
return edges; return edges;
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
@ -732,39 +701,4 @@ public class EdgeController extends BaseController {
} }
}); });
} }
private void cleanUpLicenseKey(Edge edge) {
edge.setEdgeLicenseKey(null);
}
@ApiOperation(value = "Check edge license (checkInstance)",
notes = "Checks license request from edge service by forwarding request to license portal.",
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/license/checkInstance", method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<JsonNode> checkInstance(@RequestBody JsonNode request) throws ThingsboardException {
log.debug("Checking instance [{}]", request);
try {
return edgeLicenseService.checkInstance(request);
} catch (Exception e) {
log.error("Error occurred: [{}]", e.getMessage(), e);
throw new ThingsboardException(e, ThingsboardErrorCode.SUBSCRIPTION_VIOLATION);
}
}
@ApiOperation(value = "Activate edge instance (activateInstance)",
notes = "Activates edge license on license portal.",
produces = MediaType.APPLICATION_JSON_VALUE)
@RequestMapping(value = "/license/activateInstance", params = {"licenseSecret", "releaseDate"}, method = RequestMethod.POST)
@ResponseBody
public ResponseEntity<JsonNode> activateInstance(@RequestParam String licenseSecret,
@RequestParam String releaseDate) throws ThingsboardException {
log.debug("Activating instance [{}], [{}]", licenseSecret, releaseDate);
try {
return edgeLicenseService.activateInstance(licenseSecret, releaseDate);
} catch (Exception e) {
log.error("Error occurred: [{}]", e.getMessage(), e);
throw new ThingsboardException(e, ThingsboardErrorCode.SUBSCRIPTION_VIOLATION);
}
}
} }

4
application/src/main/java/org/thingsboard/server/controller/RuleChainController.java

@ -683,7 +683,7 @@ public class RuleChainController extends BaseController {
checkParameter(RULE_CHAIN_ID, strRuleChainId); checkParameter(RULE_CHAIN_ID, strRuleChainId);
try { try {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
Edge edge = checkEdgeId(edgeId, Operation.READ); Edge edge = checkEdgeId(edgeId, Operation.WRITE);
RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
checkRuleChain(ruleChainId, Operation.READ); checkRuleChain(ruleChainId, Operation.READ);
@ -723,7 +723,7 @@ public class RuleChainController extends BaseController {
checkParameter(RULE_CHAIN_ID, strRuleChainId); checkParameter(RULE_CHAIN_ID, strRuleChainId);
try { try {
EdgeId edgeId = new EdgeId(toUUID(strEdgeId)); EdgeId edgeId = new EdgeId(toUUID(strEdgeId));
Edge edge = checkEdgeId(edgeId, Operation.READ); Edge edge = checkEdgeId(edgeId, Operation.WRITE);
RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId)); RuleChainId ruleChainId = new RuleChainId(toUUID(strRuleChainId));
RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.READ); RuleChain ruleChain = checkRuleChain(ruleChainId, Operation.READ);

1
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -217,6 +217,7 @@ public class ThingsboardInstallService {
case "3.3.3": case "3.3.3":
log.info("Upgrading ThingsBoard from version 3.3.3 to 3.3.4 ..."); log.info("Upgrading ThingsBoard from version 3.3.3 to 3.3.4 ...");
log.info("Updating system data..."); log.info("Updating system data...");
databaseEntitiesUpgradeService.upgradeDatabase("3.3.3");
systemDataLoaderService.updateSystemWidgets(); systemDataLoaderService.updateSystemWidgets();
break; break;

119
application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java

@ -16,6 +16,8 @@
package org.thingsboard.server.service.apiusage; package org.thingsboard.server.service.apiusage;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@ -23,9 +25,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiFeature;
import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageRecordKey;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
@ -49,8 +51,8 @@ import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfigurat
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.common.msg.tools.SchedulerUtils; import org.thingsboard.server.common.msg.tools.SchedulerUtils;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
@ -58,11 +60,9 @@ import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg;
import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.service.partition.AbstractPartitionBasedService;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.telemetry.InternalTelemetryService; import org.thingsboard.server.service.telemetry.InternalTelemetryService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -79,7 +79,6 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@ -87,7 +86,7 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
@Service @Service
public class DefaultTbApiUsageStateService extends TbApplicationEventListener<PartitionChangeEvent> implements TbApiUsageStateService { public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService<EntityId> implements TbApiUsageStateService {
public static final String HOURLY = "Hourly"; public static final String HOURLY = "Hourly";
public static final FutureCallback<Integer> VOID_CALLBACK = new FutureCallback<Integer>() { public static final FutureCallback<Integer> VOID_CALLBACK = new FutureCallback<Integer>() {
@ -102,23 +101,22 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
private final TbClusterService clusterService; private final TbClusterService clusterService;
private final PartitionService partitionService; private final PartitionService partitionService;
private final TenantService tenantService; private final TenantService tenantService;
private final CustomerService customerService;
private final TimeseriesService tsService; private final TimeseriesService tsService;
private final ApiUsageStateService apiUsageStateService; private final ApiUsageStateService apiUsageStateService;
private final SchedulerComponent scheduler;
private final TbTenantProfileCache tenantProfileCache; private final TbTenantProfileCache tenantProfileCache;
private final MailService mailService; private final MailService mailService;
private final DbCallbackExecutorService dbExecutor;
@Lazy @Lazy
@Autowired @Autowired
private InternalTelemetryService tsWsService; private InternalTelemetryService tsWsService;
// Entities that should be processed on this server // Entities that should be processed on this server
private final Map<EntityId, BaseApiUsageState> myUsageStates = new ConcurrentHashMap<>(); final Map<EntityId, BaseApiUsageState> myUsageStates = new ConcurrentHashMap<>();
// Entities that should be processed on other servers // Entities that should be processed on other servers
private final Map<EntityId, ApiUsageState> otherUsageStates = new ConcurrentHashMap<>(); final Map<EntityId, ApiUsageState> otherUsageStates = new ConcurrentHashMap<>();
private final Set<EntityId> deletedEntities = Collections.newSetFromMap(new ConcurrentHashMap<>()); final Set<EntityId> deletedEntities = Collections.newSetFromMap(new ConcurrentHashMap<>());
@Value("${usage.stats.report.enabled:true}") @Value("${usage.stats.report.enabled:true}")
private boolean enabled; private boolean enabled;
@ -133,33 +131,42 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
public DefaultTbApiUsageStateService(TbClusterService clusterService, public DefaultTbApiUsageStateService(TbClusterService clusterService,
PartitionService partitionService, PartitionService partitionService,
TenantService tenantService, TenantService tenantService,
CustomerService customerService,
TimeseriesService tsService, TimeseriesService tsService,
ApiUsageStateService apiUsageStateService, ApiUsageStateService apiUsageStateService,
SchedulerComponent scheduler,
TbTenantProfileCache tenantProfileCache, TbTenantProfileCache tenantProfileCache,
MailService mailService) { MailService mailService,
DbCallbackExecutorService dbExecutor) {
this.clusterService = clusterService; this.clusterService = clusterService;
this.partitionService = partitionService; this.partitionService = partitionService;
this.tenantService = tenantService; this.tenantService = tenantService;
this.customerService = customerService;
this.tsService = tsService; this.tsService = tsService;
this.apiUsageStateService = apiUsageStateService; this.apiUsageStateService = apiUsageStateService;
this.scheduler = scheduler;
this.tenantProfileCache = tenantProfileCache; this.tenantProfileCache = tenantProfileCache;
this.mailService = mailService; this.mailService = mailService;
this.mailExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("api-usage-svc-mail")); this.mailExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("api-usage-svc-mail"));
this.dbExecutor = dbExecutor;
} }
@PostConstruct @PostConstruct
public void init() { public void init() {
super.init();
if (enabled) { if (enabled) {
log.info("Starting api usage service."); log.info("Starting api usage service.");
scheduler.scheduleAtFixedRate(this::checkStartOfNextCycle, nextCycleCheckInterval, nextCycleCheckInterval, TimeUnit.MILLISECONDS); scheduledExecutor.scheduleAtFixedRate(this::checkStartOfNextCycle, nextCycleCheckInterval, nextCycleCheckInterval, TimeUnit.MILLISECONDS);
log.info("Started api usage service."); log.info("Started api usage service.");
} }
} }
@Override
protected String getServiceName() {
return "API Usage";
}
@Override
protected String getSchedulerExecutorName() {
return "api-usage-scheduled";
}
@Override @Override
public void process(TbProtoQueueMsg<ToUsageStatsServiceMsg> msg, TbCallback callback) { public void process(TbProtoQueueMsg<ToUsageStatsServiceMsg> msg, TbCallback callback) {
ToUsageStatsServiceMsg statsMsg = msg.getValue(); ToUsageStatsServiceMsg statsMsg = msg.getValue();
@ -216,19 +223,6 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
} }
} }
@Override
protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
if (partitionChangeEvent.getServiceType().equals(ServiceType.TB_CORE)) {
myUsageStates.entrySet().removeIf(entry -> {
return !partitionService.resolve(ServiceType.TB_CORE, entry.getValue().getTenantId(), entry.getKey()).isMyPartition();
});
otherUsageStates.entrySet().removeIf(entry -> {
return partitionService.resolve(ServiceType.TB_CORE, entry.getValue().getTenantId(), entry.getKey()).isMyPartition();
});
initStatesFromDataBase();
}
}
@Override @Override
public ApiUsageState getApiUsageState(TenantId tenantId) { public ApiUsageState getApiUsageState(TenantId tenantId) {
TenantApiUsageState tenantState = (TenantApiUsageState) myUsageStates.get(tenantId); TenantApiUsageState tenantState = (TenantApiUsageState) myUsageStates.get(tenantId);
@ -311,6 +305,18 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
oldProfileData.getConfiguration(), profile.getProfileData().getConfiguration()); oldProfileData.getConfiguration(), profile.getProfileData().getConfiguration());
} }
private void addEntityState(TopicPartitionInfo tpi, BaseApiUsageState state) {
EntityId entityId = state.getEntityId();
Set<EntityId> entityIds = partitionedEntities.get(tpi);
if (entityIds != null) {
entityIds.add(entityId);
myUsageStates.put(entityId, state);
} else {
log.debug("[{}] belongs to external partition {}", entityId, tpi.getFullTopicName());
throw new RuntimeException(entityId.getEntityType() + " belongs to external partition " + tpi.getFullTopicName() + "!");
}
}
private void updateProfileThresholds(TenantId tenantId, ApiUsageStateId id, private void updateProfileThresholds(TenantId tenantId, ApiUsageStateId id,
TenantProfileConfiguration oldData, TenantProfileConfiguration newData) { TenantProfileConfiguration oldData, TenantProfileConfiguration newData) {
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
@ -339,6 +345,11 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
myUsageStates.remove(customerId); myUsageStates.remove(customerId);
} }
@Override
protected void cleanupEntityOnPartitionRemoval(EntityId entityId) {
myUsageStates.remove(entityId);
}
private void persistAndNotify(BaseApiUsageState state, Map<ApiFeature, ApiUsageStateValue> result) { private void persistAndNotify(BaseApiUsageState state, Map<ApiFeature, ApiUsageStateValue> result) {
log.info("[{}] Detected update of the API state for {}: {}", state.getEntityId(), state.getEntityType(), result); log.info("[{}] Detected update of the API state for {}: {}", state.getEntityId(), state.getEntityType(), result);
apiUsageStateService.update(state.getApiUsageState()); apiUsageStateService.update(state.getApiUsageState());
@ -420,7 +431,7 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), counts, VOID_CALLBACK); tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), counts, VOID_CALLBACK);
} }
private BaseApiUsageState getOrFetchState(TenantId tenantId, EntityId entityId) { BaseApiUsageState getOrFetchState(TenantId tenantId, EntityId entityId) {
if (entityId == null || entityId.isNullUid()) { if (entityId == null || entityId.isNullUid()) {
entityId = tenantId; entityId = tenantId;
} }
@ -473,7 +484,12 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
} }
} }
log.debug("[{}] Initialized state: {}", entityId, storedState); log.debug("[{}] Initialized state: {}", entityId, storedState);
myUsageStates.put(entityId, state); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId);
if (tpi.isMyPartition()) {
addEntityState(tpi, state);
} else {
otherUsageStates.put(entityId, state.getApiUsageState());
}
saveNewCounts(state, newCounts); saveNewCounts(state, newCounts);
} catch (InterruptedException | ExecutionException e) { } catch (InterruptedException | ExecutionException e) {
log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e); log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e);
@ -482,38 +498,44 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
return state; return state;
} }
private void initStatesFromDataBase() { @Override
protected void onRepartitionEvent() {
otherUsageStates.entrySet().removeIf(entry ->
partitionService.resolve(ServiceType.TB_CORE, entry.getValue().getTenantId(), entry.getKey()).isMyPartition());
}
@Override
protected void onAddedPartitions(Set<TopicPartitionInfo> addedPartitions) {
try { try {
log.info("Initializing tenant states."); log.info("Initializing tenant states.");
updateLock.lock(); updateLock.lock();
try { try {
ExecutorService tmpInitExecutor = ThingsBoardExecutors.newWorkStealingPool(20, "init-tenant-states-from-db"); PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024);
try { List<ListenableFuture<?>> futures = new ArrayList<>();
PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024); for (Tenant tenant : tenantIterator) {
List<Future<?>> futures = new ArrayList<>(); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), tenant.getId());
for (Tenant tenant : tenantIterator) { if (addedPartitions.contains(tpi)) {
if (!myUsageStates.containsKey(tenant.getId()) && partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), tenant.getId()).isMyPartition()) { if (!myUsageStates.containsKey(tenant.getId()) && tpi.isMyPartition()) {
log.debug("[{}] Initializing tenant state.", tenant.getId()); log.debug("[{}] Initializing tenant state.", tenant.getId());
futures.add(tmpInitExecutor.submit(() -> { futures.add(dbExecutor.submit(() -> {
try { try {
updateTenantState((TenantApiUsageState) getOrFetchState(tenant.getId(), tenant.getId()), tenantProfileCache.get(tenant.getTenantProfileId())); updateTenantState((TenantApiUsageState) getOrFetchState(tenant.getId(), tenant.getId()), tenantProfileCache.get(tenant.getTenantProfileId()));
log.debug("[{}] Initialized tenant state.", tenant.getId()); log.debug("[{}] Initialized tenant state.", tenant.getId());
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}] Failed to initialize tenant API state", tenant.getId(), e); log.warn("[{}] Failed to initialize tenant API state", tenant.getId(), e);
} }
return null;
})); }));
} }
} else {
log.debug("[{}][{}] Tenant doesn't belong to current partition. tpi [{}]", tenant.getName(), tenant.getId(), tpi);
} }
for (Future<?> future : futures) {
future.get();
}
} finally {
tmpInitExecutor.shutdownNow();
} }
Futures.whenAllComplete(futures);
} finally { } finally {
updateLock.unlock(); updateLock.unlock();
} }
log.info("Initialized tenant states."); log.info("Initialized {} tenant states.", myUsageStates.size());
} catch (Exception e) { } catch (Exception e) {
log.warn("Unknown failure", e); log.warn("Unknown failure", e);
} }
@ -521,6 +543,7 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
@PreDestroy @PreDestroy
private void destroy() { private void destroy() {
super.stop();
if (mailExecutor != null) { if (mailExecutor != null) {
mailExecutor.shutdownNow(); mailExecutor.shutdownNow();
} }

110
application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeLicenseService.java

@ -1,110 +0,0 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpHost;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.thingsboard.server.queue.util.TbCoreComponent;
import javax.annotation.PostConstruct;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.HashMap;
import java.util.Map;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
@Service
@TbCoreComponent
@Slf4j
public class DefaultEdgeLicenseService implements EdgeLicenseService {
private RestTemplate restTemplate;
private static final String EDGE_LICENSE_SERVER_ENDPOINT = "https://license.thingsboard.io";
@Value("${edges.enabled:false}")
private boolean edgesEnabled;
@PostConstruct
public void init() {
if (edgesEnabled) {
initRestTemplate();
}
}
@Override
public ResponseEntity<JsonNode> checkInstance(JsonNode request) {
return this.restTemplate.postForEntity(EDGE_LICENSE_SERVER_ENDPOINT + "/api/license/checkInstance", request, JsonNode.class);
}
@Override
public ResponseEntity<JsonNode> activateInstance(String edgeLicenseSecret, String releaseDate) {
Map<String, String> params = new HashMap<>();
params.put("licenseSecret", edgeLicenseSecret);
params.put("releaseDate", releaseDate);
return this.restTemplate.postForEntity(EDGE_LICENSE_SERVER_ENDPOINT + "/api/license/activateInstance?licenseSecret={licenseSecret}&releaseDate={releaseDate}", null, JsonNode.class, params);
}
private void initRestTemplate() {
boolean jdkHttpClientEnabled = isNotEmpty(System.getProperty("tb.proxy.jdk")) && System.getProperty("tb.proxy.jdk").equalsIgnoreCase("true");
boolean systemProxyEnabled = isNotEmpty(System.getProperty("tb.proxy.system")) && System.getProperty("tb.proxy.system").equalsIgnoreCase("true");
boolean proxyEnabled = isNotEmpty(System.getProperty("tb.proxy.host")) && isNotEmpty(System.getProperty("tb.proxy.port"));
if (jdkHttpClientEnabled) {
log.warn("Going to use plain JDK Http Client!");
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
if (proxyEnabled) {
log.warn("Going to use Proxy Server: [{}:{}]", System.getProperty("tb.proxy.host"), System.getProperty("tb.proxy.port"));
factory.setProxy(new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(System.getProperty("tb.proxy.host"), Integer.parseInt(System.getProperty("tb.proxy.port")))));
}
this.restTemplate = new RestTemplate(new SimpleClientHttpRequestFactory());
} else {
CloseableHttpClient httpClient;
HttpComponentsClientHttpRequestFactory requestFactory;
if (systemProxyEnabled) {
log.warn("Going to use System Proxy Server!");
httpClient = HttpClients.createSystem();
requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
this.restTemplate = new RestTemplate(requestFactory);
} else if (proxyEnabled) {
log.warn("Going to use Proxy Server: [{}:{}]", System.getProperty("tb.proxy.host"), System.getProperty("tb.proxy.port"));
httpClient = HttpClients.custom().setSSLHostnameVerifier(new DefaultHostnameVerifier()).setProxy(new HttpHost(System.getProperty("tb.proxy.host"), Integer.parseInt(System.getProperty("tb.proxy.port")), "https")).build();
requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
this.restTemplate = new RestTemplate(requestFactory);
} else {
httpClient = HttpClients.custom().setSSLHostnameVerifier(new DefaultHostnameVerifier()).build();
requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setHttpClient(httpClient);
this.restTemplate = new RestTemplate(requestFactory);
}
}
}
}

6
application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java

@ -55,12 +55,6 @@ public class EdgeBulkImportService extends AbstractBulkImportService<Edge> {
case DESCRIPTION: case DESCRIPTION:
additionalInfo.set("description", new TextNode(value)); additionalInfo.set("description", new TextNode(value));
break; break;
case EDGE_LICENSE_KEY:
entity.setEdgeLicenseKey(value);
break;
case CLOUD_ENDPOINT:
entity.setCloudEndpoint(value);
break;
case ROUTING_KEY: case ROUTING_KEY:
entity.setRoutingKey(value); entity.setRoutingKey(value);
break; break;

2
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java

@ -690,8 +690,6 @@ public final class EdgeGrpcSession implements Closeable {
.setType(edge.getType()) .setType(edge.getType())
.setRoutingKey(edge.getRoutingKey()) .setRoutingKey(edge.getRoutingKey())
.setSecret(edge.getSecret()) .setSecret(edge.getSecret())
.setEdgeLicenseKey(edge.getEdgeLicenseKey())
.setCloudEndpoint(edge.getCloudEndpoint())
.setAdditionalInfo(JacksonUtil.toString(edge.getAdditionalInfo())) .setAdditionalInfo(JacksonUtil.toString(edge.getAdditionalInfo()))
.setCloudType("CE"); .setCloudType("CE");
if (edge.getCustomerId() != null) { if (edge.getCustomerId() != null) {

8
application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java

@ -41,17 +41,17 @@ public class EdgeSyncCursor {
int currentIdx = 0; int currentIdx = 0;
public EdgeSyncCursor(EdgeContextComponent ctx, Edge edge) { public EdgeSyncCursor(EdgeContextComponent ctx, Edge edge) {
fetchers.add(new SystemWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService()));
fetchers.add(new TenantWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService()));
fetchers.add(new DeviceProfilesEdgeEventFetcher(ctx.getDeviceProfileService()));
fetchers.add(new RuleChainsEdgeEventFetcher(ctx.getRuleChainService())); fetchers.add(new RuleChainsEdgeEventFetcher(ctx.getRuleChainService()));
fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService(), ctx.getFreemarkerConfig()));
fetchers.add(new DeviceProfilesEdgeEventFetcher(ctx.getDeviceProfileService()));
fetchers.add(new TenantAdminUsersEdgeEventFetcher(ctx.getUserService())); fetchers.add(new TenantAdminUsersEdgeEventFetcher(ctx.getUserService()));
if (edge.getCustomerId() != null && !EntityId.NULL_UUID.equals(edge.getCustomerId().getId())) { if (edge.getCustomerId() != null && !EntityId.NULL_UUID.equals(edge.getCustomerId().getId())) {
fetchers.add(new CustomerEdgeEventFetcher()); fetchers.add(new CustomerEdgeEventFetcher());
fetchers.add(new CustomerUsersEdgeEventFetcher(ctx.getUserService(), edge.getCustomerId())); fetchers.add(new CustomerUsersEdgeEventFetcher(ctx.getUserService(), edge.getCustomerId()));
} }
fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService(), ctx.getFreemarkerConfig()));
fetchers.add(new AssetsEdgeEventFetcher(ctx.getAssetService())); fetchers.add(new AssetsEdgeEventFetcher(ctx.getAssetService()));
fetchers.add(new SystemWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService()));
fetchers.add(new TenantWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService()));
fetchers.add(new DashboardsEdgeEventFetcher(ctx.getDashboardService())); fetchers.add(new DashboardsEdgeEventFetcher(ctx.getDashboardService()));
} }

2
application/src/main/java/org/thingsboard/server/service/importing/BulkImportColumnType.java

@ -45,8 +45,6 @@ public enum BulkImportColumnType {
LWM2M_SERVER_CLIENT_SECRET_KEY("clientSecretKey"), LWM2M_SERVER_CLIENT_SECRET_KEY("clientSecretKey"),
IS_GATEWAY, IS_GATEWAY,
DESCRIPTION, DESCRIPTION,
EDGE_LICENSE_KEY,
CLOUD_ENDPOINT,
ROUTING_KEY, ROUTING_KEY,
SECRET; SECRET;

20
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -512,6 +512,26 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.error("Failed updating schema!!!", e); log.error("Failed updating schema!!!", e);
} }
break; break;
case "3.3.3":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
try {
conn.createStatement().execute("ALTER TABLE edge DROP COLUMN edge_license_key;"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
conn.createStatement().execute("ALTER TABLE edge DROP COLUMN cloud_endpoint;"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
} catch (Exception ignored) {
}
log.info("Updating TTL cleanup procedure for the event table...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.3", "schema_event_ttl_procedure.sql");
loadSql(schemaUpdateFile, conn);
log.info("Updating schema settings...");
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003004;");
log.info("Schema updated.");
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
break;
default: default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
} }

148
application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java

@ -0,0 +1,148 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.partition;
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import java.util.HashSet;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
@Slf4j
public abstract class AbstractPartitionBasedService<T extends EntityId> extends TbApplicationEventListener<PartitionChangeEvent> {
protected final ConcurrentMap<TopicPartitionInfo, Set<T>> partitionedEntities = new ConcurrentHashMap<>();
final Queue<Set<TopicPartitionInfo>> subscribeQueue = new ConcurrentLinkedQueue<>();
protected ListeningScheduledExecutorService scheduledExecutor;
abstract protected String getServiceName();
abstract protected String getSchedulerExecutorName();
abstract protected void onAddedPartitions(Set<TopicPartitionInfo> addedPartitions);
abstract protected void cleanupEntityOnPartitionRemoval(T entityId);
public Set<T> getPartitionedEntities(TopicPartitionInfo tpi) {
return partitionedEntities.get(tpi);
}
protected void init() {
// Should be always single threaded due to absence of locks.
scheduledExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName(getSchedulerExecutorName())));
}
protected ServiceType getServiceType() {
return ServiceType.TB_CORE;
}
protected void stop() {
if (scheduledExecutor != null) {
scheduledExecutor.shutdownNow();
}
}
/**
* DiscoveryService will call this event from the single thread (one-by-one).
* Events order is guaranteed by DiscoveryService.
* The only concurrency is expected from the [main] thread on Application started.
* Async implementation. Locks is not allowed by design.
* Any locks or delays in this module will affect DiscoveryService and entire system
*/
@Override
protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
if (getServiceType().equals(partitionChangeEvent.getServiceType())) {
log.debug("onTbApplicationEvent, processing event: {}", partitionChangeEvent);
subscribeQueue.add(partitionChangeEvent.getPartitions());
scheduledExecutor.submit(this::pollInitStateFromDB);
}
}
protected void pollInitStateFromDB() {
final Set<TopicPartitionInfo> partitions = getLatestPartitions();
if (partitions == null) {
log.debug("Nothing to do. Partitions are empty.");
return;
}
initStateFromDB(partitions);
}
private void initStateFromDB(Set<TopicPartitionInfo> partitions) {
try {
log.info("[{}] CURRENT PARTITIONS: {}", getServiceName(), partitionedEntities.keySet());
log.info("[{}] NEW PARTITIONS: {}", getServiceName(), partitions);
Set<TopicPartitionInfo> addedPartitions = new HashSet<>(partitions);
addedPartitions.removeAll(partitionedEntities.keySet());
log.info("[{}] ADDED PARTITIONS: {}", getServiceName(), addedPartitions);
Set<TopicPartitionInfo> removedPartitions = new HashSet<>(partitionedEntities.keySet());
removedPartitions.removeAll(partitions);
log.info("[{}] REMOVED PARTITIONS: {}", getServiceName(), removedPartitions);
// We no longer manage current partition of entities;
removedPartitions.forEach(partition -> {
Set<T> entities = partitionedEntities.remove(partition);
entities.forEach(this::cleanupEntityOnPartitionRemoval);
});
onRepartitionEvent();
addedPartitions.forEach(tpi -> partitionedEntities.computeIfAbsent(tpi, key -> ConcurrentHashMap.newKeySet()));
if (!addedPartitions.isEmpty()) {
onAddedPartitions(addedPartitions);
}
log.info("[{}] Managing following partitions:", getServiceName());
partitionedEntities.forEach((tpi, entities) -> {
log.info("[{}][{}]: {} entities", getServiceName(), tpi.getFullTopicName(), entities.size());
});
} catch (Throwable t) {
log.warn("[{}] Failed to init entities state from DB", getServiceName(), t);
}
}
protected void onRepartitionEvent() {
}
private Set<TopicPartitionInfo> getLatestPartitions() {
log.debug("getLatestPartitionsFromQueue, queue size {}", subscribeQueue.size());
Set<TopicPartitionInfo> partitions = null;
while (!subscribeQueue.isEmpty()) {
partitions = subscribeQueue.poll();
log.debug("polled from the queue partitions {}", partitions);
}
log.debug("getLatestPartitionsFromQueue, partitions {}", partitions);
return partitions;
}
}

210
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java

@ -20,15 +20,15 @@ import com.google.common.base.Function;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
@ -41,6 +41,7 @@ import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageDataIterable;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgDataType;
@ -52,13 +53,10 @@ import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.TbApplicationEventListener;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.service.partition.AbstractPartitionBasedService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -68,14 +66,11 @@ import javax.annotation.PreDestroy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Queue;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
@ -94,7 +89,7 @@ import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE;
@Service @Service
@TbCoreComponent @TbCoreComponent
@Slf4j @Slf4j
public class DefaultDeviceStateService extends TbApplicationEventListener<PartitionChangeEvent> implements DeviceStateService { public class DefaultDeviceStateService extends AbstractPartitionBasedService<DeviceId> implements DeviceStateService {
public static final String ACTIVITY_STATE = "active"; public static final String ACTIVITY_STATE = "active";
public static final String LAST_CONNECT_TIME = "lastConnectTime"; public static final String LAST_CONNECT_TIME = "lastConnectTime";
@ -131,12 +126,9 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
@Getter @Getter
private int initFetchPackSize; private int initFetchPackSize;
private ListeningScheduledExecutorService scheduledExecutor;
private ExecutorService deviceStateExecutor; private ExecutorService deviceStateExecutor;
private final ConcurrentMap<TopicPartitionInfo, Set<DeviceId>> partitionedDevices = new ConcurrentHashMap<>();
final ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>();
final Queue<Set<TopicPartitionInfo>> subscribeQueue = new ConcurrentLinkedQueue<>(); final ConcurrentMap<DeviceId, DeviceStateData> deviceStates = new ConcurrentHashMap<>();
public DefaultDeviceStateService(TenantService tenantService, DeviceService deviceService, public DefaultDeviceStateService(TenantService tenantService, DeviceService deviceService,
AttributesService attributesService, TimeseriesService tsService, AttributesService attributesService, TimeseriesService tsService,
@ -156,25 +148,36 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
@PostConstruct @PostConstruct
public void init() { public void init() {
super.init();
deviceStateExecutor = Executors.newFixedThreadPool( deviceStateExecutor = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors(), ThingsBoardThreadFactory.forName("device-state")); Runtime.getRuntime().availableProcessors(), ThingsBoardThreadFactory.forName("device-state"));
// Should be always single threaded due to absence of locks.
scheduledExecutor = MoreExecutors.listeningDecorator(Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("device-state-scheduled")));
scheduledExecutor.scheduleAtFixedRate(this::updateInactivityStateIfExpired, new Random().nextInt(defaultStateCheckIntervalInSec), defaultStateCheckIntervalInSec, TimeUnit.SECONDS); scheduledExecutor.scheduleAtFixedRate(this::updateInactivityStateIfExpired, new Random().nextInt(defaultStateCheckIntervalInSec), defaultStateCheckIntervalInSec, TimeUnit.SECONDS);
} }
@PreDestroy @PreDestroy
public void stop() { public void stop() {
super.stop();
if (deviceStateExecutor != null) { if (deviceStateExecutor != null) {
deviceStateExecutor.shutdownNow(); deviceStateExecutor.shutdownNow();
} }
if (scheduledExecutor != null) { }
scheduledExecutor.shutdownNow();
} @Override
protected String getServiceName() {
return "Device State";
}
@Override
protected String getSchedulerExecutorName() {
return "device-state-scheduled";
} }
@Override @Override
public void onDeviceConnect(TenantId tenantId, DeviceId deviceId) { public void onDeviceConnect(TenantId tenantId, DeviceId deviceId) {
if (cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId)) {
return;
}
log.trace("on Device Connect [{}]", deviceId.getId()); log.trace("on Device Connect [{}]", deviceId.getId());
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
@ -182,17 +185,19 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
save(deviceId, LAST_CONNECT_TIME, ts); save(deviceId, LAST_CONNECT_TIME, ts);
pushRuleEngineMessage(stateData, CONNECT_EVENT); pushRuleEngineMessage(stateData, CONNECT_EVENT);
checkAndUpdateState(deviceId, stateData); checkAndUpdateState(deviceId, stateData);
cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId);
} }
@Override @Override
public void onDeviceActivity(TenantId tenantId, DeviceId deviceId, long lastReportedActivity) { public void onDeviceActivity(TenantId tenantId, DeviceId deviceId, long lastReportedActivity) {
if (cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId)) {
return;
}
log.trace("on Device Activity [{}], lastReportedActivity [{}]", deviceId.getId(), lastReportedActivity); log.trace("on Device Activity [{}], lastReportedActivity [{}]", deviceId.getId(), lastReportedActivity);
final DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); final DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
if (lastReportedActivity > 0 && lastReportedActivity > stateData.getState().getLastActivityTime()) { if (lastReportedActivity > 0 && lastReportedActivity > stateData.getState().getLastActivityTime()) {
updateActivityState(deviceId, stateData, lastReportedActivity); updateActivityState(deviceId, stateData, lastReportedActivity);
} }
cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId);
} }
void updateActivityState(DeviceId deviceId, DeviceStateData stateData, long lastReportedActivity) { void updateActivityState(DeviceId deviceId, DeviceStateData stateData, long lastReportedActivity) {
@ -208,18 +213,20 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
} }
} else { } else {
log.debug("updateActivityState - fetched state IN NULL for device {}, lastReportedActivity {}", deviceId, lastReportedActivity); log.debug("updateActivityState - fetched state IN NULL for device {}, lastReportedActivity {}", deviceId, lastReportedActivity);
cleanUpDeviceStateMap(deviceId); cleanupEntity(deviceId);
} }
} }
@Override @Override
public void onDeviceDisconnect(TenantId tenantId, DeviceId deviceId) { public void onDeviceDisconnect(TenantId tenantId, DeviceId deviceId) {
if (cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId)) {
return;
}
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
long ts = System.currentTimeMillis(); long ts = System.currentTimeMillis();
stateData.getState().setLastDisconnectTime(ts); stateData.getState().setLastDisconnectTime(ts);
save(deviceId, LAST_DISCONNECT_TIME, ts); save(deviceId, LAST_DISCONNECT_TIME, ts);
pushRuleEngineMessage(stateData, DISCONNECT_EVENT); pushRuleEngineMessage(stateData, DISCONNECT_EVENT);
cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId);
} }
@Override @Override
@ -227,11 +234,14 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
if (inactivityTimeout <= 0L) { if (inactivityTimeout <= 0L) {
return; return;
} }
if (cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId)) {
return;
}
log.trace("on Device Activity Timeout Update device id {} inactivityTimeout {}", deviceId, inactivityTimeout); log.trace("on Device Activity Timeout Update device id {} inactivityTimeout {}", deviceId, inactivityTimeout);
DeviceStateData stateData = getOrFetchDeviceStateData(deviceId); DeviceStateData stateData = getOrFetchDeviceStateData(deviceId);
stateData.getState().setInactivityTimeout(inactivityTimeout); stateData.getState().setInactivityTimeout(inactivityTimeout);
checkAndUpdateState(deviceId, stateData); checkAndUpdateState(deviceId, stateData);
cleanDeviceStateIfBelongsExternalPartition(tenantId, deviceId);
} }
@Override @Override
@ -246,11 +256,11 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
Device device = deviceService.findDeviceById(TenantId.SYS_TENANT_ID, deviceId); Device device = deviceService.findDeviceById(TenantId.SYS_TENANT_ID, deviceId);
if (device != null) { if (device != null) {
if (proto.getAdded()) { if (proto.getAdded()) {
Futures.addCallback(fetchDeviceState(device), new FutureCallback<DeviceStateData>() { Futures.addCallback(fetchDeviceState(device), new FutureCallback<>() {
@Override @Override
public void onSuccess(@Nullable DeviceStateData state) { public void onSuccess(@Nullable DeviceStateData state) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, device.getId()); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, device.getId());
if (partitionedDevices.containsKey(tpi)) { if (partitionedEntities.containsKey(tpi)) {
addDeviceUsingState(tpi, state); addDeviceUsingState(tpi, state);
save(deviceId, ACTIVITY_STATE, false); save(deviceId, ACTIVITY_STATE, false);
callback.onSuccess(); callback.onSuccess();
@ -286,105 +296,25 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
} }
} }
/**
* DiscoveryService will call this event from the single thread (one-by-one).
* Events order is guaranteed by DiscoveryService.
* The only concurrency is expected from the [main] thread on Application started.
* Async implementation. Locks is not allowed by design.
* Any locks or delays in this module will affect DiscoveryService and entire system
*/
@Override @Override
protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) { protected void onAddedPartitions(Set<TopicPartitionInfo> addedPartitions) {
if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) { PageDataIterable<Tenant> tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024);
log.debug("onTbApplicationEvent ServiceType is TB_CORE, processing queue {}", partitionChangeEvent); for (Tenant tenant : tenantIterator) {
subscribeQueue.add(partitionChangeEvent.getPartitions());
scheduledExecutor.submit(this::pollInitStateFromDB);
}
}
void pollInitStateFromDB() {
final Set<TopicPartitionInfo> partitions = getLatestPartitionsFromQueue();
if (partitions == null) {
log.info("Device state service. Nothing to do. partitions is null");
return;
}
initStateFromDB(partitions);
}
// TODO: move to utils
Set<TopicPartitionInfo> getLatestPartitionsFromQueue() {
log.debug("getLatestPartitionsFromQueue, queue size {}", subscribeQueue.size());
Set<TopicPartitionInfo> partitions = null;
while (!subscribeQueue.isEmpty()) {
partitions = subscribeQueue.poll();
log.debug("polled from the queue partitions {}", partitions);
}
log.debug("getLatestPartitionsFromQueue, partitions {}", partitions);
return partitions;
}
private void initStateFromDB(Set<TopicPartitionInfo> partitions) {
try {
log.info("CURRENT PARTITIONS: {}", partitionedDevices.keySet());
log.info("NEW PARTITIONS: {}", partitions);
Set<TopicPartitionInfo> addedPartitions = new HashSet<>(partitions);
addedPartitions.removeAll(partitionedDevices.keySet());
log.info("ADDED PARTITIONS: {}", addedPartitions);
Set<TopicPartitionInfo> removedPartitions = new HashSet<>(partitionedDevices.keySet());
removedPartitions.removeAll(partitions);
log.info("REMOVED PARTITIONS: {}", removedPartitions);
// We no longer manage current partition of devices;
removedPartitions.forEach(partition -> {
Set<DeviceId> devices = partitionedDevices.remove(partition);
devices.forEach(this::cleanUpDeviceStateMap);
});
addedPartitions.forEach(tpi -> partitionedDevices.computeIfAbsent(tpi, key -> ConcurrentHashMap.newKeySet()));
initPartitions(addedPartitions);
scheduledExecutor.submit(() -> {
log.info("Managing following partitions:");
partitionedDevices.forEach((tpi, devices) -> {
log.info("[{}]: {} devices", tpi.getFullTopicName(), devices.size());
});
});
} catch (Throwable t) {
log.warn("Failed to init device states from DB", t);
}
}
//TODO 3.0: replace this dummy search with new functionality to search by partitions using SQL capabilities.
//Adding only entities that are in new partitions
boolean initPartitions(Set<TopicPartitionInfo> addedPartitions) {
if (addedPartitions.isEmpty()) {
return false;
}
List<Tenant> tenants = tenantService.findTenants(new PageLink(Integer.MAX_VALUE)).getData();
for (Tenant tenant : tenants) {
log.debug("Finding devices for tenant [{}]", tenant.getName()); log.debug("Finding devices for tenant [{}]", tenant.getName());
final PageLink pageLink = new PageLink(initFetchPackSize); final PageLink pageLink = new PageLink(initFetchPackSize);
scheduledExecutor.submit(() -> processPageAndSubmitNextPage(addedPartitions, tenant, pageLink, scheduledExecutor)); processPageAndSubmitNextPage(addedPartitions, tenant, pageLink);
} }
return true;
} }
private void processPageAndSubmitNextPage(final Set<TopicPartitionInfo> addedPartitions, final Tenant tenant, final PageLink pageLink, final ExecutorService executor) { private void processPageAndSubmitNextPage(final Set<TopicPartitionInfo> addedPartitions, final Tenant tenant, final PageLink pageLink) {
log.trace("[{}] Process page {} from {}", tenant, pageLink.getPage(), pageLink.getPageSize()); log.trace("[{}] Process page {} from {}", tenant, pageLink.getPage(), pageLink.getPageSize());
List<ListenableFuture<Void>> fetchFutures = new ArrayList<>(); List<ListenableFuture<Void>> fetchFutures = new ArrayList<>();
PageData<Device> page = deviceService.findDevicesByTenantId(tenant.getId(), pageLink); PageData<Device> page = deviceService.findDevicesByTenantId(tenant.getId(), pageLink);
for (Device device : page.getData()) { for (Device device : page.getData()) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), device.getId()); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), device.getId());
if (addedPartitions.contains(tpi)) { if (addedPartitions.contains(tpi) && !deviceStates.containsKey(device.getId())) {
log.debug("[{}][{}] Device belong to current partition. tpi [{}]. Fetching state from DB", device.getName(), device.getId(), tpi); log.debug("[{}][{}] Device belong to current partition. tpi [{}]. Fetching state from DB", device.getName(), device.getId(), tpi);
ListenableFuture<Void> future = Futures.transform(fetchDeviceState(device), new Function<DeviceStateData, Void>() { ListenableFuture<Void> future = Futures.transform(fetchDeviceState(device), new Function<>() {
@Nullable @Nullable
@Override @Override
public Void apply(@Nullable DeviceStateData state) { public Void apply(@Nullable DeviceStateData state) {
@ -403,7 +333,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
} }
} }
Futures.addCallback(Futures.successfulAsList(fetchFutures), new FutureCallback<List<Void>>() { Futures.addCallback(Futures.successfulAsList(fetchFutures), new FutureCallback<>() {
@Override @Override
public void onSuccess(List<Void> result) { public void onSuccess(List<Void> result) {
log.trace("[{}] Success init device state from DB for batch size {}", tenant.getId(), result.size()); log.trace("[{}] Success init device state from DB for batch size {}", tenant.getId(), result.size());
@ -419,7 +349,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
final PageLink nextPageLink = page.hasNext() ? pageLink.nextPageLink() : null; final PageLink nextPageLink = page.hasNext() ? pageLink.nextPageLink() : null;
if (nextPageLink != null) { if (nextPageLink != null) {
log.trace("[{}] Submit next page {} from {}", tenant, nextPageLink.getPage(), nextPageLink.getPageSize()); log.trace("[{}] Submit next page {} from {}", tenant, nextPageLink.getPage(), nextPageLink.getPageSize());
executor.submit(() -> processPageAndSubmitNextPage(addedPartitions, tenant, nextPageLink, executor)); processPageAndSubmitNextPage(addedPartitions, tenant, nextPageLink);
} }
} }
@ -435,10 +365,10 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
} }
private void addDeviceUsingState(TopicPartitionInfo tpi, DeviceStateData state) { private void addDeviceUsingState(TopicPartitionInfo tpi, DeviceStateData state) {
Set<DeviceId> deviceIds = partitionedDevices.get(tpi); Set<DeviceId> deviceIds = partitionedEntities.get(tpi);
if (deviceIds != null) { if (deviceIds != null) {
deviceIds.add(state.getDeviceId()); deviceIds.add(state.getDeviceId());
deviceStates.put(state.getDeviceId(), state); deviceStates.putIfAbsent(state.getDeviceId(), state);
} else { } else {
log.debug("[{}] Device belongs to external partition {}", state.getDeviceId(), tpi.getFullTopicName()); log.debug("[{}] Device belongs to external partition {}", state.getDeviceId(), tpi.getFullTopicName());
throw new RuntimeException("Device belongs to external partition " + tpi.getFullTopicName() + "!"); throw new RuntimeException("Device belongs to external partition " + tpi.getFullTopicName() + "!");
@ -447,7 +377,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
void updateInactivityStateIfExpired() { void updateInactivityStateIfExpired() {
final long ts = System.currentTimeMillis(); final long ts = System.currentTimeMillis();
partitionedDevices.forEach((tpi, deviceIds) -> { partitionedEntities.forEach((tpi, deviceIds) -> {
log.debug("Calculating state updates. tpi {} for {} devices", tpi.getFullTopicName(), deviceIds.size()); log.debug("Calculating state updates. tpi {} for {} devices", tpi.getFullTopicName(), deviceIds.size());
for (DeviceId deviceId : deviceIds) { for (DeviceId deviceId : deviceIds) {
updateInactivityStateIfExpired(ts, deviceId); updateInactivityStateIfExpired(ts, deviceId);
@ -464,16 +394,22 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
log.trace("Processing state {} for device {}", stateData, deviceId); log.trace("Processing state {} for device {}", stateData, deviceId);
if (stateData != null) { if (stateData != null) {
DeviceState state = stateData.getState(); DeviceState state = stateData.getState();
if (!isActive(ts, state) && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime()) && stateData.getDeviceCreationTime() + state.getInactivityTimeout() < ts) { if (!isActive(ts, state)
state.setActive(false); && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime())
state.setLastInactivityAlarmTime(ts); && stateData.getDeviceCreationTime() + state.getInactivityTimeout() < ts) {
save(deviceId, INACTIVITY_ALARM_TIME, ts); if (partitionService.resolve(ServiceType.TB_CORE, stateData.getTenantId(), deviceId).isMyPartition()) {
save(deviceId, ACTIVITY_STATE, false); state.setActive(false);
pushRuleEngineMessage(stateData, INACTIVITY_EVENT); state.setLastInactivityAlarmTime(ts);
save(deviceId, ACTIVITY_STATE, false);
save(deviceId, INACTIVITY_ALARM_TIME, ts);
pushRuleEngineMessage(stateData, INACTIVITY_EVENT);
} else {
cleanupEntity(deviceId);
}
} }
} else { } else {
log.debug("[{}] Device that belongs to other server is detected and removed.", deviceId); log.debug("[{}] Device that belongs to other server is detected and removed.", deviceId);
cleanUpDeviceStateMap(deviceId); cleanupEntity(deviceId);
} }
} }
@ -506,26 +442,36 @@ public class DefaultDeviceStateService extends TbApplicationEventListener<Partit
} }
} }
private void cleanDeviceStateIfBelongsExternalPartition(TenantId tenantId, final DeviceId deviceId) { private boolean cleanDeviceStateIfBelongsExternalPartition(TenantId tenantId, final DeviceId deviceId) {
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId);
if (!partitionedDevices.containsKey(tpi)) { boolean cleanup = !partitionedEntities.containsKey(tpi);
cleanUpDeviceStateMap(deviceId); if (cleanup) {
cleanupEntity(deviceId);
log.debug("[{}][{}] device belongs to external partition. Probably rebalancing is in progress. Topic: {}" log.debug("[{}][{}] device belongs to external partition. Probably rebalancing is in progress. Topic: {}"
, tenantId, deviceId, tpi.getFullTopicName()); , tenantId, deviceId, tpi.getFullTopicName());
} }
return cleanup;
} }
private void onDeviceDeleted(TenantId tenantId, DeviceId deviceId) { private void onDeviceDeleted(TenantId tenantId, DeviceId deviceId) {
cleanUpDeviceStateMap(deviceId); cleanupEntity(deviceId);
TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId);
Set<DeviceId> deviceIdSet = partitionedDevices.get(tpi); Set<DeviceId> deviceIdSet = partitionedEntities.get(tpi);
deviceIdSet.remove(deviceId); if (deviceIdSet != null) {
deviceIdSet.remove(deviceId);
}
} }
private void cleanUpDeviceStateMap(DeviceId deviceId) { @Override
protected void cleanupEntityOnPartitionRemoval(DeviceId deviceId) {
cleanupEntity(deviceId);
}
private void cleanupEntity(DeviceId deviceId) {
deviceStates.remove(deviceId); deviceStates.remove(deviceId);
} }
private ListenableFuture<DeviceStateData> fetchDeviceState(Device device) { private ListenableFuture<DeviceStateData> fetchDeviceState(Device device) {
ListenableFuture<DeviceStateData> future; ListenableFuture<DeviceStateData> future;
if (persistToTelemetry) { if (persistToTelemetry) {

32
application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java

@ -22,7 +22,8 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.ttl.AbstractCleanUpService;
import java.util.concurrent.TimeUnit;
@TbCoreComponent @TbCoreComponent
@Slf4j @Slf4j
@ -33,10 +34,13 @@ public class EventsCleanUpService extends AbstractCleanUpService {
"#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.events.execution_interval_ms})}"; "#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.events.execution_interval_ms})}";
@Value("${sql.ttl.events.events_ttl}") @Value("${sql.ttl.events.events_ttl}")
private long ttl; private long ttlInSec;
@Value("${sql.ttl.events.debug_events_ttl}") @Value("${sql.ttl.events.debug_events_ttl}")
private long debugTtl; private long debugTtlInSec;
@Value("${sql.ttl.events.execution_interval_ms}")
private long executionIntervalInMs;
@Value("${sql.ttl.events.enabled}") @Value("${sql.ttl.events.enabled}")
private boolean ttlTaskExecutionEnabled; private boolean ttlTaskExecutionEnabled;
@ -51,7 +55,27 @@ public class EventsCleanUpService extends AbstractCleanUpService {
@Scheduled(initialDelayString = RANDOM_DELAY_INTERVAL_MS_EXPRESSION, fixedDelayString = "${sql.ttl.events.execution_interval_ms}") @Scheduled(initialDelayString = RANDOM_DELAY_INTERVAL_MS_EXPRESSION, fixedDelayString = "${sql.ttl.events.execution_interval_ms}")
public void cleanUp() { public void cleanUp() {
if (ttlTaskExecutionEnabled && isSystemTenantPartitionMine()) { if (ttlTaskExecutionEnabled && isSystemTenantPartitionMine()) {
eventService.cleanupEvents(ttl, debugTtl); long ts = System.currentTimeMillis();
long regularEventStartTs;
long regularEventEndTs;
long debugEventStartTs;
long debugEventEndTs;
if (ttlInSec > 0) {
regularEventEndTs = ts - TimeUnit.SECONDS.toMillis(ttlInSec);
regularEventStartTs = regularEventEndTs - 2 * executionIntervalInMs;
} else {
regularEventStartTs = regularEventEndTs = 0;
}
if (debugTtlInSec > 0) {
debugEventEndTs = ts - TimeUnit.SECONDS.toMillis(debugTtlInSec);
debugEventStartTs = debugEventEndTs - 2 * executionIntervalInMs;
} else {
debugEventStartTs = debugEventEndTs = 0;
}
eventService.cleanupEvents(regularEventStartTs, regularEventEndTs, debugEventStartTs, debugEventEndTs);
} }
} }

5
application/src/main/resources/thingsboard.yml

@ -278,6 +278,11 @@ sql:
stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}" stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}"
batch_threads: "${SQL_TS_LATEST_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution batch_threads: "${SQL_TS_LATEST_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution
update_by_latest_ts: "${SQL_TS_UPDATE_BY_LATEST_TIMESTAMP:true}" update_by_latest_ts: "${SQL_TS_UPDATE_BY_LATEST_TIMESTAMP:true}"
events:
batch_size: "${SQL_EVENTS_BATCH_SIZE:10000}"
batch_max_delay: "${SQL_EVENTS_BATCH_MAX_DELAY_MS:100}"
stats_print_interval_ms: "${SQL_EVENTS_BATCH_STATS_PRINT_MS:10000}"
batch_threads: "${SQL_EVENTS_BATCH_THREADS:3}" # batch thread count have to be a prime number like 3 or 5 to gain perfect hash distribution
# Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks # Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks
batch_sort: "${SQL_BATCH_SORT:false}" batch_sort: "${SQL_BATCH_SORT:false}"
# Specify whether to remove null characters from strValue of attributes and timeseries before insert # Specify whether to remove null characters from strValue of attributes and timeseries before insert

6
application/src/test/java/org/thingsboard/server/actors/stats/StatsActorTest.java

@ -59,11 +59,11 @@ class StatsActorTest {
@Test @Test
void givenNonEmptyStatMessage_whenOnStatsPersistMsg_thenNoAction() { void givenNonEmptyStatMessage_whenOnStatsPersistMsg_thenNoAction() {
statsActor.onStatsPersistMsg(new StatsPersistMsg(0, 1, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID)); statsActor.onStatsPersistMsg(new StatsPersistMsg(0, 1, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID));
verify(eventService, times(1)).save(any(Event.class)); verify(eventService, times(1)).saveAsync(any(Event.class));
statsActor.onStatsPersistMsg(new StatsPersistMsg(1, 0, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID)); statsActor.onStatsPersistMsg(new StatsPersistMsg(1, 0, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID));
verify(eventService, times(2)).save(any(Event.class)); verify(eventService, times(2)).saveAsync(any(Event.class));
statsActor.onStatsPersistMsg(new StatsPersistMsg(1, 1, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID)); statsActor.onStatsPersistMsg(new StatsPersistMsg(1, 1, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID));
verify(eventService, times(3)).save(any(Event.class)); verify(eventService, times(3)).saveAsync(any(Event.class));
} }
} }

2
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -606,8 +606,6 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
edge.setType(type); edge.setType(type);
edge.setSecret(RandomStringUtils.randomAlphanumeric(20)); edge.setSecret(RandomStringUtils.randomAlphanumeric(20));
edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20)); edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20));
edge.setEdgeLicenseKey(RandomStringUtils.randomAlphanumeric(20));
edge.setCloudEndpoint("http://localhost:8080");
return edge; return edge;
} }
} }

61
application/src/test/java/org/thingsboard/server/controller/AbstractWebsocketTest.java

@ -15,81 +15,20 @@
*/ */
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hamcrest.Matcher;
import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootContextLoader; import org.springframework.boot.test.context.SpringBootContextLoader;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort; import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.http.MockHttpInputMessage;
import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.config.ThingsboardSecurityConfiguration;
import org.thingsboard.server.service.mail.TestMailService;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest;
import org.thingsboard.server.service.security.auth.rest.LoginRequest;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
@ActiveProfiles("test") @ActiveProfiles("test")
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)

2
application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java

@ -105,8 +105,6 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest {
Assert.assertNotNull(savedEdge.getCustomerId()); Assert.assertNotNull(savedEdge.getCustomerId());
Assert.assertEquals(NULL_UUID, savedEdge.getCustomerId().getId()); Assert.assertEquals(NULL_UUID, savedEdge.getCustomerId().getId());
Assert.assertEquals(edge.getName(), savedEdge.getName()); Assert.assertEquals(edge.getName(), savedEdge.getName());
Assert.assertTrue(StringUtils.isNoneBlank(savedEdge.getEdgeLicenseKey()));
Assert.assertTrue(StringUtils.isNoneBlank(savedEdge.getCloudEndpoint()));
savedEdge.setName("My new edge"); savedEdge.setName("My new edge");
doPost("/api/edge", savedEdge, Edge.class); doPost("/api/edge", savedEdge, Edge.class);

2
application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java

@ -54,7 +54,7 @@ public class TbTestWebSocketClient extends WebSocketClient {
@Override @Override
public void onClose(int i, String s, boolean b) { public void onClose(int i, String s, boolean b) {
log.error("CLOSED:"); log.info("CLOSED.");
} }
@Override @Override

378
application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java

@ -25,7 +25,6 @@ import com.google.gson.JsonObject;
import com.google.protobuf.AbstractMessage; import com.google.protobuf.AbstractMessage;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.MessageLite; import com.google.protobuf.MessageLite;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.awaitility.Awaitility; import org.awaitility.Awaitility;
import org.junit.After; import org.junit.After;
@ -34,6 +33,7 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
@ -116,7 +116,6 @@ import org.thingsboard.server.gen.edge.v1.UserUpdateMsg;
import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg;
import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.cluster.TbClusterService;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -129,7 +128,6 @@ import java.util.concurrent.TimeUnit;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j
abstract public class BaseEdgeTest extends AbstractControllerTest { abstract public class BaseEdgeTest extends AbstractControllerTest {
private static final String CUSTOM_DEVICE_PROFILE_NAME = "Thermostat"; private static final String CUSTOM_DEVICE_PROFILE_NAME = "Thermostat";
@ -165,7 +163,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
tenantAdmin.setLastName("Downs"); tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
// sleep 1 seconds to avoid CREDENTIALS updated message for the user // sleep 0.5 second to avoid CREDENTIALS updated message for the user
// user credentials is going to be stored and updated event pushed to edge notification service // user credentials is going to be stored and updated event pushed to edge notification service
// while service will be processing this event edge could be already added and additional message will be pushed // while service will be processing this event edge could be already added and additional message will be pushed
Thread.sleep(500); Thread.sleep(500);
@ -176,7 +174,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectMessageAmount(13); edgeImitator.expectMessageAmount(13);
edgeImitator.connect(); edgeImitator.connect();
testReceivedInitialData(); verifyEdgeConnectionAndInitialData();
} }
@After @After
@ -187,41 +185,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
loginSysAdmin(); loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getUuidId())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test
public void test() throws Exception {
testDevices();
testAssets();
testRuleChains();
testDashboards();
testRelations();
testAlarms();
testEntityView();
testCustomerAndNewUser();
testWidgetsBundleAndWidgetType();
testTimeseries();
testAttributes();
testRpcCall();
testTimeseriesWithFailures();
testSendMessagesToCloud();
}
private void installation() throws Exception { private void installation() throws Exception {
edge = doPost("/api/edge", constructEdge("Test Edge", "test"), Edge.class); edge = doPost("/api/edge", constructEdge("Test Edge", "test"), Edge.class);
@ -230,12 +197,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
Device savedDevice = saveDevice("Edge Device 1", CUSTOM_DEVICE_PROFILE_NAME); Device savedDevice = saveDevice("Edge Device 1", CUSTOM_DEVICE_PROFILE_NAME);
doPost("/api/edge/" + edge.getId().getId().toString() doPost("/api/edge/" + edge.getUuidId()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class); + "/device/" + savedDevice.getUuidId(), Device.class);
Asset savedAsset = saveAsset("Edge Asset 1"); Asset savedAsset = saveAsset("Edge Asset 1");
doPost("/api/edge/" + edge.getId().getId().toString() doPost("/api/edge/" + edge.getUuidId()
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class); + "/asset/" + savedAsset.getUuidId(), Asset.class);
} }
private void extendDeviceProfileData(DeviceProfile deviceProfile) { private void extendDeviceProfileData(DeviceProfile deviceProfile) {
@ -267,8 +234,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
profileData.setProvisionConfiguration(new AllowCreateNewDevicesDeviceProfileProvisionConfiguration("123")); profileData.setProvisionConfiguration(new AllowCreateNewDevicesDeviceProfileProvisionConfiguration("123"));
} }
private void testReceivedInitialData() throws Exception { private void verifyEdgeConnectionAndInitialData() throws Exception {
log.info("Checking received data");
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
EdgeConfiguration configuration = edgeImitator.getConfiguration(); EdgeConfiguration configuration = edgeImitator.getConfiguration();
@ -283,7 +249,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
UUID deviceUUID = new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB()); UUID deviceUUID = new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB());
Device device = doGet("/api/device/" + deviceUUID.toString(), Device.class); Device device = doGet("/api/device/" + deviceUUID.toString(), Device.class);
Assert.assertNotNull(device); Assert.assertNotNull(device);
List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/devices?", List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?",
new TypeReference<PageData<Device>>() {}, new PageLink(100)).getData(); new TypeReference<PageData<Device>>() {}, new PageLink(100)).getData();
Assert.assertTrue(edgeDevices.contains(device)); Assert.assertTrue(edgeDevices.contains(device));
@ -310,7 +276,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
UUID assetUUID = new UUID(assetUpdateMsg.getIdMSB(), assetUpdateMsg.getIdLSB()); UUID assetUUID = new UUID(assetUpdateMsg.getIdMSB(), assetUpdateMsg.getIdLSB());
Asset asset = doGet("/api/asset/" + assetUUID.toString(), Asset.class); Asset asset = doGet("/api/asset/" + assetUUID.toString(), Asset.class);
Assert.assertNotNull(asset); Assert.assertNotNull(asset);
List<Asset> edgeAssets = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/assets?", List<Asset> edgeAssets = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/assets?",
new TypeReference<PageData<Asset>>() {}, new PageLink(100)).getData(); new TypeReference<PageData<Asset>>() {}, new PageLink(100)).getData();
Assert.assertTrue(edgeAssets.contains(asset)); Assert.assertTrue(edgeAssets.contains(asset));
@ -323,15 +289,13 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
UUID ruleChainUUID = new UUID(ruleChainUpdateMsg.getIdMSB(), ruleChainUpdateMsg.getIdLSB()); UUID ruleChainUUID = new UUID(ruleChainUpdateMsg.getIdMSB(), ruleChainUpdateMsg.getIdLSB());
RuleChain ruleChain = doGet("/api/ruleChain/" + ruleChainUUID.toString(), RuleChain.class); RuleChain ruleChain = doGet("/api/ruleChain/" + ruleChainUUID.toString(), RuleChain.class);
Assert.assertNotNull(ruleChain); Assert.assertNotNull(ruleChain);
List<RuleChain> edgeRuleChains = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/ruleChains?", List<RuleChain> edgeRuleChains = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/ruleChains?",
new TypeReference<PageData<RuleChain>>() {}, new PageLink(100)).getData(); new TypeReference<PageData<RuleChain>>() {}, new PageLink(100)).getData();
Assert.assertTrue(edgeRuleChains.contains(ruleChain)); Assert.assertTrue(edgeRuleChains.contains(ruleChain));
testAutoGeneratedCodeByProtobuf(ruleChainUpdateMsg); testAutoGeneratedCodeByProtobuf(ruleChainUpdateMsg);
validateAdminSettings(); validateAdminSettings();
log.info("Received data checked");
} }
private void validateAdminSettings() throws JsonProcessingException { private void validateAdminSettings() throws JsonProcessingException {
@ -367,39 +331,26 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertNotNull(jsonNode.get("test")); Assert.assertNotNull(jsonNode.get("test"));
} }
private void testDevices() throws Exception { @Test
log.info("Testing devices"); public void testDevices() throws Exception {
// 1 // 1
edgeImitator.expectMessageAmount(1); Device savedDevice = saveDeviceOnCloudAndVerifyDeliveryToEdge();
Device savedDevice = saveDevice("Edge Device 2", "Default");
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class);
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType());
Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getName(), savedDevice.getName());
Assert.assertEquals(deviceUpdateMsg.getType(), savedDevice.getType());
// 2 // 2
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString() doDelete("/api/edge/" + edge.getUuidId()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class); + "/device/" + savedDevice.getUuidId(), Device.class);
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceUpdateMsg.getMsgType()); Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceUpdateMsg.getMsgType());
Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits()); Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits()); Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits());
// 3 // 3
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/device/" + savedDevice.getId().getId().toString()) doDelete("/api/device/" + savedDevice.getUuidId())
.andExpect(status().isOk()); .andExpect(status().isOk());
// we should not get any message because device is not assigned to edge any more // we should not get any message because device is not assigned to edge any more
Assert.assertFalse(edgeImitator.waitForMessages(1)); Assert.assertFalse(edgeImitator.waitForMessages(1));
@ -407,8 +358,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 4 // 4
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
savedDevice = saveDevice("Edge Device 3", "Default"); savedDevice = saveDevice("Edge Device 3", "Default");
doPost("/api/edge/" + edge.getId().getId().toString() doPost("/api/edge/" + edge.getUuidId()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class); + "/device/" + savedDevice.getUuidId(), Device.class);
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
@ -421,7 +372,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 5 // 5
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/device/" + savedDevice.getId().getId().toString()) doDelete("/api/device/" + savedDevice.getUuidId())
.andExpect(status().isOk()); .andExpect(status().isOk());
// in this case we should get messages because device was assigned to edge // in this case we should get messages because device was assigned to edge
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
@ -432,17 +383,15 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits()); Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits()); Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits());
log.info("Devices tested successfully");
} }
private void testAssets() throws Exception { @Test
log.info("Testing assets"); public void testAssets() throws Exception {
// 1 // 1
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
Asset savedAsset = saveAsset("Edge Asset 2"); Asset savedAsset = saveAsset("Edge Asset 2");
doPost("/api/edge/" + edge.getId().getId().toString() doPost("/api/edge/" + edge.getUuidId()
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class); + "/asset/" + savedAsset.getUuidId(), Asset.class);
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); Assert.assertTrue(latestMessage instanceof AssetUpdateMsg);
@ -455,8 +404,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 2 // 2
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString() doDelete("/api/edge/" + edge.getUuidId()
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class); + "/asset/" + savedAsset.getUuidId(), Asset.class);
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); Assert.assertTrue(latestMessage instanceof AssetUpdateMsg);
@ -467,15 +416,15 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 3 // 3
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/asset/" + savedAsset.getId().getId().toString()) doDelete("/api/asset/" + savedAsset.getUuidId())
.andExpect(status().isOk()); .andExpect(status().isOk());
Assert.assertFalse(edgeImitator.waitForMessages(1)); Assert.assertFalse(edgeImitator.waitForMessages(1));
// 4 // 4
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
savedAsset = saveAsset("Edge Asset 3"); savedAsset = saveAsset("Edge Asset 3");
doPost("/api/edge/" + edge.getId().getId().toString() doPost("/api/edge/" + edge.getUuidId()
+ "/asset/" + savedAsset.getId().getId().toString(), Asset.class); + "/asset/" + savedAsset.getUuidId(), Asset.class);
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); Assert.assertTrue(latestMessage instanceof AssetUpdateMsg);
@ -488,7 +437,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 5 // 5
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/asset/" + savedAsset.getId().getId().toString()) doDelete("/api/asset/" + savedAsset.getUuidId())
.andExpect(status().isOk()); .andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
@ -497,21 +446,18 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, assetUpdateMsg.getMsgType()); Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, assetUpdateMsg.getMsgType());
Assert.assertEquals(assetUpdateMsg.getIdMSB(), savedAsset.getUuidId().getMostSignificantBits()); Assert.assertEquals(assetUpdateMsg.getIdMSB(), savedAsset.getUuidId().getMostSignificantBits());
Assert.assertEquals(assetUpdateMsg.getIdLSB(), savedAsset.getUuidId().getLeastSignificantBits()); Assert.assertEquals(assetUpdateMsg.getIdLSB(), savedAsset.getUuidId().getLeastSignificantBits());
log.info("Assets tested successfully");
} }
private void testRuleChains() throws Exception { @Test
log.info("Testing RuleChains"); public void testRuleChains() throws Exception {
// 1 // 1
edgeImitator.expectMessageAmount(2); edgeImitator.expectMessageAmount(2);
RuleChain ruleChain = new RuleChain(); RuleChain ruleChain = new RuleChain();
ruleChain.setName("Edge Test Rule Chain"); ruleChain.setName("Edge Test Rule Chain");
ruleChain.setType(RuleChainType.EDGE); ruleChain.setType(RuleChainType.EDGE);
RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class); RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class);
doPost("/api/edge/" + edge.getId().getId().toString() doPost("/api/edge/" + edge.getUuidId()
+ "/ruleChain/" + savedRuleChain.getId().getId().toString(), RuleChain.class); + "/ruleChain/" + savedRuleChain.getUuidId(), RuleChain.class);
createRuleChainMetadata(savedRuleChain); createRuleChainMetadata(savedRuleChain);
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
Optional<RuleChainUpdateMsg> ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class); Optional<RuleChainUpdateMsg> ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class);
@ -528,8 +474,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 3 // 3
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString() doDelete("/api/edge/" + edge.getUuidId()
+ "/ruleChain/" + savedRuleChain.getId().getId().toString(), RuleChain.class); + "/ruleChain/" + savedRuleChain.getUuidId(), RuleChain.class);
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class); ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class);
Assert.assertTrue(ruleChainUpdateMsgOpt.isPresent()); Assert.assertTrue(ruleChainUpdateMsgOpt.isPresent());
@ -540,11 +486,9 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 4 // 4
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/ruleChain/" + savedRuleChain.getId().getId().toString()) doDelete("/api/ruleChain/" + savedRuleChain.getUuidId())
.andExpect(status().isOk()); .andExpect(status().isOk());
Assert.assertFalse(edgeImitator.waitForMessages(1)); Assert.assertFalse(edgeImitator.waitForMessages(1));
log.info("RuleChains tested successfully");
} }
private void testRuleChainMetadataRequestMsg(RuleChainId ruleChainId) throws Exception { private void testRuleChainMetadataRequestMsg(RuleChainId ruleChainId) throws Exception {
@ -608,16 +552,15 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
doPost("/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class); doPost("/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class);
} }
private void testDashboards() throws Exception { @Test
log.info("Testing Dashboards"); public void testDashboards() throws Exception {
// 1 // 1
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
Dashboard dashboard = new Dashboard(); Dashboard dashboard = new Dashboard();
dashboard.setTitle("Edge Test Dashboard"); dashboard.setTitle("Edge Test Dashboard");
Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
doPost("/api/edge/" + edge.getId().getId().toString() doPost("/api/edge/" + edge.getUuidId()
+ "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); + "/dashboard/" + savedDashboard.getUuidId(), Dashboard.class);
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg);
@ -641,8 +584,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 3 // 3
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString() doDelete("/api/edge/" + edge.getUuidId()
+ "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); + "/dashboard/" + savedDashboard.getUuidId(), Dashboard.class);
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg);
@ -653,16 +596,13 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 4 // 4
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/dashboard/" + savedDashboard.getId().getId().toString()) doDelete("/api/dashboard/" + savedDashboard.getUuidId())
.andExpect(status().isOk()); .andExpect(status().isOk());
Assert.assertFalse(edgeImitator.waitForMessages(1)); Assert.assertFalse(edgeImitator.waitForMessages(1));
log.info("Dashboards tested successfully");
} }
private void testRelations() throws Exception { @Test
log.info("Testing Relations"); public void testRelations() throws Exception {
// 1 // 1
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
Device device = findDeviceByName("Edge Device 1"); Device device = findDeviceByName("Edge Device 1");
@ -710,13 +650,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(relationUpdateMsg.getToIdLSB(), relation.getTo().getId().getLeastSignificantBits()); Assert.assertEquals(relationUpdateMsg.getToIdLSB(), relation.getTo().getId().getLeastSignificantBits());
Assert.assertEquals(relationUpdateMsg.getToEntityType(), relation.getTo().getEntityType().name()); Assert.assertEquals(relationUpdateMsg.getToEntityType(), relation.getTo().getEntityType().name());
Assert.assertEquals(relationUpdateMsg.getTypeGroup(), relation.getTypeGroup().name()); Assert.assertEquals(relationUpdateMsg.getTypeGroup(), relation.getTypeGroup().name());
log.info("Relations tested successfully");
} }
private void testAlarms() throws Exception { @Test
log.info("Testing Alarms"); public void testAlarms() throws Exception {
// 1 // 1
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
Device device = findDeviceByName("Edge Device 1"); Device device = findDeviceByName("Edge Device 1");
@ -739,7 +676,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 2 // 2
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doPost("/api/alarm/" + savedAlarm.getId().getId().toString() + "/ack"); doPost("/api/alarm/" + savedAlarm.getUuidId() + "/ack");
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg);
@ -752,7 +689,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 3 // 3
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doPost("/api/alarm/" + savedAlarm.getId().getId().toString() + "/clear"); doPost("/api/alarm/" + savedAlarm.getUuidId() + "/clear");
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg);
@ -765,7 +702,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 4 // 4
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/alarm/" + savedAlarm.getId().getId().toString()) doDelete("/api/alarm/" + savedAlarm.getUuidId())
.andExpect(status().isOk()); .andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
@ -776,13 +713,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(alarmUpdateMsg.getName(), savedAlarm.getName()); Assert.assertEquals(alarmUpdateMsg.getName(), savedAlarm.getName());
Assert.assertEquals(alarmUpdateMsg.getOriginatorName(), device.getName()); Assert.assertEquals(alarmUpdateMsg.getOriginatorName(), device.getName());
Assert.assertEquals(alarmUpdateMsg.getStatus(), AlarmStatus.CLEARED_ACK.name()); Assert.assertEquals(alarmUpdateMsg.getStatus(), AlarmStatus.CLEARED_ACK.name());
log.info("Alarms tested successfully");
} }
private void testEntityView() throws Exception { @Test
log.info("Testing EntityView"); public void testEntityView() throws Exception {
// 1 // 1
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
Device device = findDeviceByName("Edge Device 1"); Device device = findDeviceByName("Edge Device 1");
@ -791,8 +725,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
entityView.setType("test"); entityView.setType("test");
entityView.setEntityId(device.getId()); entityView.setEntityId(device.getId());
EntityView savedEntityView = doPost("/api/entityView", entityView, EntityView.class); EntityView savedEntityView = doPost("/api/entityView", entityView, EntityView.class);
doPost("/api/edge/" + edge.getId().getId().toString() doPost("/api/edge/" + edge.getUuidId()
+ "/entityView/" + savedEntityView.getId().getId().toString(), EntityView.class); + "/entityView/" + savedEntityView.getUuidId(), EntityView.class);
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
verifyEntityViewUpdateMsg(savedEntityView, device); verifyEntityViewUpdateMsg(savedEntityView, device);
@ -818,8 +752,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 3 // 3
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getId().getId().toString() doDelete("/api/edge/" + edge.getUuidId()
+ "/entityView/" + savedEntityView.getId().getId().toString(), EntityView.class); + "/entityView/" + savedEntityView.getUuidId(), EntityView.class);
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg); Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg);
@ -830,11 +764,9 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/entityView/" + savedEntityView.getId().getId().toString()) doDelete("/api/entityView/" + savedEntityView.getUuidId())
.andExpect(status().isOk()); .andExpect(status().isOk());
Assert.assertFalse(edgeImitator.waitForMessages(1)); Assert.assertFalse(edgeImitator.waitForMessages(1));
log.info("EntityView tested successfully");
} }
private void verifyEntityViewUpdateMsg(EntityView entityView, Device device) { private void verifyEntityViewUpdateMsg(EntityView entityView, Device device) {
@ -851,16 +783,15 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(entityViewUpdateMsg.getEntityType().name(), device.getId().getEntityType().name()); Assert.assertEquals(entityViewUpdateMsg.getEntityType().name(), device.getId().getEntityType().name());
} }
private void testCustomerAndNewUser() throws Exception { @Test
log.info("Testing Customer"); public void testCustomerAndNewUser() throws Exception {
// 1 // 1
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("Edge Customer 1"); customer.setTitle("Edge Customer 1");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
doPost("/api/customer/" + savedCustomer.getId().getId().toString() doPost("/api/customer/" + savedCustomer.getUuidId()
+ "/edge/" + edge.getId().getId().toString(), Edge.class); + "/edge/" + edge.getUuidId(), Edge.class);
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg); Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg);
@ -893,7 +824,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 3 // 3
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/customer/edge/" + edge.getId().getId().toString(), Edge.class); doDelete("/api/customer/edge/" + edge.getUuidId(), Edge.class);
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg); Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg);
@ -903,16 +834,13 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(customerUpdateMsg.getIdLSB(), savedCustomer.getUuidId().getLeastSignificantBits()); Assert.assertEquals(customerUpdateMsg.getIdLSB(), savedCustomer.getUuidId().getLeastSignificantBits());
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/customer/" + savedCustomer.getId().getId().toString()) doDelete("/api/customer/" + savedCustomer.getUuidId())
.andExpect(status().isOk()); .andExpect(status().isOk());
Assert.assertFalse(edgeImitator.waitForMessages(1)); Assert.assertFalse(edgeImitator.waitForMessages(1));
log.info("Customer tested successfully");
} }
private void testWidgetsBundleAndWidgetType() throws Exception { @Test
log.info("Testing WidgetsBundle and WidgetType"); public void testWidgetsBundleAndWidgetType() throws Exception {
// 1 // 1
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
WidgetsBundle widgetsBundle = new WidgetsBundle(); WidgetsBundle widgetsBundle = new WidgetsBundle();
@ -951,7 +879,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 3 // 3
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/widgetType/" + savedWidgetType.getId().getId().toString()) doDelete("/api/widgetType/" + savedWidgetType.getUuidId())
.andExpect(status().isOk()); .andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
@ -963,7 +891,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
// 4 // 4
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
doDelete("/api/widgetsBundle/" + savedWidgetsBundle.getId().getId().toString()) doDelete("/api/widgetsBundle/" + savedWidgetsBundle.getUuidId())
.andExpect(status().isOk()); .andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
@ -972,13 +900,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, widgetsBundleUpdateMsg.getMsgType()); Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, widgetsBundleUpdateMsg.getMsgType());
Assert.assertEquals(widgetsBundleUpdateMsg.getIdMSB(), savedWidgetsBundle.getUuidId().getMostSignificantBits()); Assert.assertEquals(widgetsBundleUpdateMsg.getIdMSB(), savedWidgetsBundle.getUuidId().getMostSignificantBits());
Assert.assertEquals(widgetsBundleUpdateMsg.getIdLSB(), savedWidgetsBundle.getUuidId().getLeastSignificantBits()); Assert.assertEquals(widgetsBundleUpdateMsg.getIdLSB(), savedWidgetsBundle.getUuidId().getLeastSignificantBits());
log.info("WidgetsBundle and WidgetType tested successfully");
} }
private void testTimeseries() throws Exception { @Test
log.info("Testing timeseries"); public void testTimeseries() throws Exception {
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
Device device = findDeviceByName("Edge Device 1"); Device device = findDeviceByName("Edge Device 1");
String timeseriesData = "{\"data\":{\"temperature\":25},\"ts\":" + System.currentTimeMillis() + "}"; String timeseriesData = "{\"data\":{\"temperature\":25},\"ts\":" + System.currentTimeMillis() + "}";
@ -1004,18 +929,15 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
TransportProtos.KeyValueProto keyValueProto = tsKvListProto.getKv(0); TransportProtos.KeyValueProto keyValueProto = tsKvListProto.getKv(0);
Assert.assertEquals("temperature", keyValueProto.getKey()); Assert.assertEquals("temperature", keyValueProto.getKey());
Assert.assertEquals(25, keyValueProto.getLongV()); Assert.assertEquals(25, keyValueProto.getLongV());
log.info("Timeseries tested successfully");
} }
private void testAttributes() throws Exception { @Test
log.info("Testing attributes"); public void testAttributes() throws Exception {
Device device = findDeviceByName("Edge Device 1"); Device device = findDeviceByName("Edge Device 1");
testAttributesUpdatedMsg(device); testAttributesUpdatedMsg(device);
testPostAttributesMsg(device); testPostAttributesMsg(device);
testAttributesDeleteMsg(device); testAttributesDeleteMsg(device);
log.info("Attributes tested successfully");
} }
private void testAttributesUpdatedMsg(Device device) throws JsonProcessingException, InterruptedException { private void testAttributesUpdatedMsg(Device device) throws JsonProcessingException, InterruptedException {
@ -1094,7 +1016,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals("key2", attributeDeleteMsg.getAttributeNames(1)); Assert.assertEquals("key2", attributeDeleteMsg.getAttributeNames(1));
} }
private void testRpcCall() throws Exception { @Test
public void testRpcCall() throws Exception {
Device device = findDeviceByName("Edge Device 1"); Device device = findDeviceByName("Edge Device 1");
ObjectNode body = mapper.createObjectNode(); ObjectNode body = mapper.createObjectNode();
@ -1117,9 +1040,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals("test_method", latestDeviceRpcCallMsg.getRequestMsg().getMethod()); Assert.assertEquals("test_method", latestDeviceRpcCallMsg.getRequestMsg().getMethod());
} }
private void testTimeseriesWithFailures() throws Exception { @Test
log.info("Testing timeseries with failures"); public void testTimeseriesWithFailures() throws Exception {
int numberOfTimeseriesToSend = 1000; int numberOfTimeseriesToSend = 1000;
edgeImitator.setRandomFailuresOnTimeseriesDownlink(true); edgeImitator.setRandomFailuresOnTimeseriesDownlink(true);
@ -1147,7 +1069,6 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
} }
edgeImitator.setRandomFailuresOnTimeseriesDownlink(false); edgeImitator.setRandomFailuresOnTimeseriesDownlink(false);
log.info("Timeseries with failures tested successfully");
} }
private boolean isIdxExistsInTheDownlinkList(int idx, List<EntityDataProto> allTelemetryMsgs) { private boolean isIdxExistsInTheDownlinkList(int idx, List<EntityDataProto> allTelemetryMsgs) {
@ -1165,25 +1086,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
return false; return false;
} }
private void testSendMessagesToCloud() throws Exception { @Test
log.info("Sending messages to cloud"); public void testSendDeviceToCloud() throws Exception {
sendDevice();
sendDeviceWithNameThatAlreadyExistsOnCloud();
sendRelationRequest();
sendAlarm();
sendTelemetry();
sendRelation();
sendDeleteDeviceOnEdge();
sendRuleChainMetadataRequest();
sendUserCredentialsRequest();
sendDeviceCredentialsRequest();
sendDeviceRpcResponse();
sendDeviceCredentialsUpdate();
sendAttributesRequest();
log.info("Messages were sent successfully");
}
private void sendDevice() throws Exception {
UUID uuid = Uuids.timeBased(); UUID uuid = Uuids.timeBased();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
@ -1218,7 +1122,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals("Edge Device 2", device.getName()); Assert.assertEquals("Edge Device 2", device.getName());
} }
private void sendDeviceWithNameThatAlreadyExistsOnCloud() throws Exception { @Test
public void testSendDeviceToCloudWithNameThatAlreadyExistsOnCloud() throws Exception {
String deviceOnCloudName = RandomStringUtils.randomAlphanumeric(15); String deviceOnCloudName = RandomStringUtils.randomAlphanumeric(15);
Device deviceOnCloud = saveDevice(deviceOnCloudName, "Default"); Device deviceOnCloud = saveDevice(deviceOnCloudName, "Default");
@ -1270,7 +1175,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertNotEquals(deviceOnCloudName, device.getName()); Assert.assertNotEquals(deviceOnCloudName, device.getName());
} }
private void sendRelationRequest() throws Exception { @Test
public void testSendRelationRequestToCloud() throws Exception {
Device device = findDeviceByName("Edge Device 1"); Device device = findDeviceByName("Edge Device 1");
Asset asset = findAssetByName("Edge Asset 1"); Asset asset = findAssetByName("Edge Asset 1");
@ -1317,8 +1223,9 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(relation.getTypeGroup().name(), relationUpdateMsg.getTypeGroup()); Assert.assertEquals(relation.getTypeGroup().name(), relationUpdateMsg.getTypeGroup());
} }
private void sendAlarm() throws Exception { @Test
Device device = findDeviceByName("Edge Device 2"); public void testSendAlarmToCloud() throws Exception {
Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
AlarmUpdateMsg.Builder alarmUpdateMgBuilder = AlarmUpdateMsg.newBuilder(); AlarmUpdateMsg.Builder alarmUpdateMgBuilder = AlarmUpdateMsg.newBuilder();
@ -1339,7 +1246,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
List<AlarmInfo> alarms = doGetTypedWithPageLink("/api/alarm/{entityType}/{entityId}?", List<AlarmInfo> alarms = doGetTypedWithPageLink("/api/alarm/{entityType}/{entityId}?",
new TypeReference<PageData<AlarmInfo>>() {}, new TypeReference<PageData<AlarmInfo>>() {},
new PageLink(100), device.getId().getEntityType().name(), device.getId().getId().toString()) new PageLink(100), device.getId().getEntityType().name(), device.getUuidId())
.getData(); .getData();
Optional<AlarmInfo> foundAlarm = alarms.stream().filter(alarm -> alarm.getType().equals("alarm from edge")).findAny(); Optional<AlarmInfo> foundAlarm = alarms.stream().filter(alarm -> alarm.getType().equals("alarm from edge")).findAny();
Assert.assertTrue(foundAlarm.isPresent()); Assert.assertTrue(foundAlarm.isPresent());
@ -1349,12 +1256,9 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(AlarmSeverity.CRITICAL, alarmInfo.getSeverity()); Assert.assertEquals(AlarmSeverity.CRITICAL, alarmInfo.getSeverity());
} }
private void sendTelemetry() throws Exception { @Test
List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/devices?", public void testSendTelemetryToCloud() throws Exception {
new TypeReference<PageData<Device>>() {}, new PageLink(100)).getData(); Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge();
Optional<Device> foundDevice = edgeDevices.stream().filter(device1 -> device1.getName().equals("Edge Device 2")).findAny();
Assert.assertTrue(foundDevice.isPresent());
Device device = foundDevice.get();
edgeImitator.expectResponsesAmount(2); edgeImitator.expectResponsesAmount(2);
@ -1402,7 +1306,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(1, timeseries.get(timeseriesKey).size()); Assert.assertEquals(1, timeseries.get(timeseriesKey).size());
Assert.assertEquals(timeseriesValue, timeseries.get(timeseriesKey).get(0).get("value")); Assert.assertEquals(timeseriesValue, timeseries.get(timeseriesKey).get(0).get("value"));
List<Map<String, String>> attributes = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/attributes/" + DataConstants.SERVER_SCOPE, new TypeReference<>() {}); String attributeValuesUrl = "/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/attributes/" + DataConstants.SERVER_SCOPE;
List<Map<String, String>> attributes = doGetAsyncTyped(attributeValuesUrl, new TypeReference<>() {});
Assert.assertEquals(2, attributes.size()); Assert.assertEquals(2, attributes.size());
var result = attributes.stream().filter(kv -> kv.get("key").equals(attributesKey)).filter(kv -> kv.get("value").equals(attributesValue)).findFirst(); var result = attributes.stream().filter(kv -> kv.get("key").equals(attributesKey)).filter(kv -> kv.get("value").equals(attributesValue)).findFirst();
Assert.assertTrue(result.isPresent()); Assert.assertTrue(result.isPresent());
@ -1413,15 +1318,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
new TypeReference<>() {}); new TypeReference<>() {});
} }
private void sendRelation() throws Exception { @Test
List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/devices?", public void testSendRelationToCloud() throws Exception {
new TypeReference<PageData<Device>>() {}, new PageLink(100)).getData(); Device device1 = saveDeviceOnCloudAndVerifyDeliveryToEdge();
Optional<Device> foundDevice1 = edgeDevices.stream().filter(device1 -> device1.getName().equals("Edge Device 1")).findAny(); Device device2 = saveDeviceOnCloudAndVerifyDeliveryToEdge();
Assert.assertTrue(foundDevice1.isPresent());
Device device1 = foundDevice1.get();
Optional<Device> foundDevice2 = edgeDevices.stream().filter(device2 -> device2.getName().equals("Edge Device 2")).findAny();
Assert.assertTrue(foundDevice2.isPresent());
Device device2 = foundDevice2.get();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
RelationUpdateMsg.Builder relationUpdateMsgBuilder = RelationUpdateMsg.newBuilder(); RelationUpdateMsg.Builder relationUpdateMsgBuilder = RelationUpdateMsg.newBuilder();
@ -1444,17 +1344,18 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertTrue(edgeImitator.waitForResponses()); Assert.assertTrue(edgeImitator.waitForResponses());
EntityRelation relation = doGet("/api/relation?" + EntityRelation relation = doGet("/api/relation?" +
"&fromId=" + device2.getId().getId().toString() + "&fromId=" + device2.getUuidId() +
"&fromType=" + device2.getId().getEntityType().name() + "&fromType=" + device2.getId().getEntityType().name() +
"&relationType=" + "test" + "&relationType=" + "test" +
"&relationTypeGroup=" + RelationTypeGroup.COMMON.name() + "&relationTypeGroup=" + RelationTypeGroup.COMMON.name() +
"&toId=" + device1.getId().getId().toString() + "&toId=" + device1.getUuidId() +
"&toType=" + device1.getId().getEntityType().name(), EntityRelation.class); "&toType=" + device1.getId().getEntityType().name(), EntityRelation.class);
Assert.assertNotNull(relation); Assert.assertNotNull(relation);
} }
private void sendDeleteDeviceOnEdge() throws Exception { @Test
Device device = findDeviceByName("Edge Device 2"); public void testSendDeleteDeviceOnEdgeToCloud() throws Exception {
Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge();
UplinkMsg.Builder upLinkMsgBuilder = UplinkMsg.newBuilder(); UplinkMsg.Builder upLinkMsgBuilder = UplinkMsg.newBuilder();
DeviceUpdateMsg.Builder deviceDeleteMsgBuilder = DeviceUpdateMsg.newBuilder(); DeviceUpdateMsg.Builder deviceDeleteMsgBuilder = DeviceUpdateMsg.newBuilder();
deviceDeleteMsgBuilder.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE); deviceDeleteMsgBuilder.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE);
@ -1468,15 +1369,16 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectResponsesAmount(1); edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(upLinkMsgBuilder.build()); edgeImitator.sendUplinkMsg(upLinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses()); Assert.assertTrue(edgeImitator.waitForResponses());
device = doGet("/api/device/" + device.getId().getId().toString(), Device.class); device = doGet("/api/device/" + device.getUuidId(), Device.class);
Assert.assertNotNull(device); Assert.assertNotNull(device);
List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/devices?", List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?",
new TypeReference<PageData<Device>>() { new TypeReference<PageData<Device>>() {
}, new PageLink(100)).getData(); }, new PageLink(100)).getData();
Assert.assertFalse(edgeDevices.contains(device)); Assert.assertFalse(edgeDevices.contains(device));
} }
private void sendRuleChainMetadataRequest() throws Exception { @Test
public void testSendRuleChainMetadataRequestToCloud() throws Exception {
RuleChainId edgeRootRuleChainId = edge.getRootRuleChainId(); RuleChainId edgeRootRuleChainId = edge.getRootRuleChainId();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
@ -1503,7 +1405,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
testAutoGeneratedCodeByProtobuf(ruleChainMetadataUpdateMsg); testAutoGeneratedCodeByProtobuf(ruleChainMetadataUpdateMsg);
} }
private void sendUserCredentialsRequest() throws Exception { @Test
public void testSendUserCredentialsRequestToCloud() throws Exception {
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
UserCredentialsRequestMsg.Builder userCredentialsRequestMsgBuilder = UserCredentialsRequestMsg.newBuilder(); UserCredentialsRequestMsg.Builder userCredentialsRequestMsgBuilder = UserCredentialsRequestMsg.newBuilder();
userCredentialsRequestMsgBuilder.setUserIdMSB(tenantAdmin.getId().getId().getMostSignificantBits()); userCredentialsRequestMsgBuilder.setUserIdMSB(tenantAdmin.getId().getId().getMostSignificantBits());
@ -1528,10 +1431,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
testAutoGeneratedCodeByProtobuf(userCredentialsUpdateMsg); testAutoGeneratedCodeByProtobuf(userCredentialsUpdateMsg);
} }
private void sendDeviceCredentialsRequest() throws Exception { @Test
public void testSendDeviceCredentialsRequestToCloud() throws Exception {
Device device = findDeviceByName("Edge Device 1"); Device device = findDeviceByName("Edge Device 1");
DeviceCredentials deviceCredentials = doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class); DeviceCredentials deviceCredentials = doGet("/api/device/" + device.getUuidId() + "/credentials", DeviceCredentials.class);
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
DeviceCredentialsRequestMsg.Builder deviceCredentialsRequestMsgBuilder = DeviceCredentialsRequestMsg.newBuilder(); DeviceCredentialsRequestMsg.Builder deviceCredentialsRequestMsgBuilder = DeviceCredentialsRequestMsg.newBuilder();
@ -1557,7 +1461,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(deviceCredentialsUpdateMsg.getCredentialsId(), deviceCredentials.getCredentialsId()); Assert.assertEquals(deviceCredentialsUpdateMsg.getCredentialsId(), deviceCredentials.getCredentialsId());
} }
private void sendDeviceRpcResponse() throws Exception { @Test
public void testSendDeviceRpcResponseToCloud() throws Exception {
Device device = findDeviceByName("Edge Device 1"); Device device = findDeviceByName("Edge Device 1");
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
@ -1582,7 +1487,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertTrue(edgeImitator.waitForResponses()); Assert.assertTrue(edgeImitator.waitForResponses());
} }
private void sendDeviceCredentialsUpdate() throws Exception { @Test
public void testSendDeviceCredentialsUpdateToCloud() throws Exception {
Device device = findDeviceByName("Edge Device 1"); Device device = findDeviceByName("Edge Device 1");
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
@ -1601,24 +1507,30 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertTrue(edgeImitator.waitForResponses()); Assert.assertTrue(edgeImitator.waitForResponses());
} }
private void sendAttributesRequest() throws Exception { @Test
public void testSendAttributesRequestToCloud() throws Exception {
Device device = findDeviceByName("Edge Device 1"); Device device = findDeviceByName("Edge Device 1");
sendAttributesRequest(device, DataConstants.SERVER_SCOPE, "{\"key1\":\"value1\"}", sendAttributesRequestAndVerify(device, DataConstants.SERVER_SCOPE, "{\"key1\":\"value1\"}",
"key1", "value1", 2); "key1", "value1");
sendAttributesRequest(device, DataConstants.SHARED_SCOPE, "{\"key2\":\"value2\"}", sendAttributesRequestAndVerify(device, DataConstants.SHARED_SCOPE, "{\"key2\":\"value2\"}",
"key2", "value2", 1); "key2", "value2");
} }
private void sendAttributesRequest(Device device, String scope, String attributesDataStr, String expectedKey, private void sendAttributesRequestAndVerify(Device device, String scope, String attributesDataStr, String expectedKey,
String expectedValue, int expectedSize) throws Exception { String expectedValue) throws Exception {
JsonNode attributesData = mapper.readTree(attributesDataStr); JsonNode attributesData = mapper.readTree(attributesDataStr);
doPost("/api/plugins/telemetry/DEVICE/" + device.getId().getId().toString() + "/attributes/" + scope, doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/attributes/" + scope,
attributesData); attributesData);
// Wait before device attributes saved to database before requesting them from edge // Wait before device attributes saved to database before requesting them from edge
// queue used to save attributes to database Awaitility.await()
Thread.sleep(500); .atMost(10, TimeUnit.SECONDS)
.until(() -> {
String urlTemplate = "/api/plugins/telemetry/DEVICE/" + device.getId() + "/keys/attributes/" + scope;
List<String> actualKeys = doGetAsyncTyped(urlTemplate, new TypeReference<>() {});
return actualKeys != null && !actualKeys.isEmpty() && actualKeys.contains(expectedKey);
});
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
AttributesRequestMsg.Builder attributesRequestMsgBuilder = AttributesRequestMsg.newBuilder(); AttributesRequestMsg.Builder attributesRequestMsgBuilder = AttributesRequestMsg.newBuilder();
@ -1646,19 +1558,39 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertTrue(latestEntityDataMsg.hasAttributesUpdatedMsg()); Assert.assertTrue(latestEntityDataMsg.hasAttributesUpdatedMsg());
TransportProtos.PostAttributeMsg attributesUpdatedMsg = latestEntityDataMsg.getAttributesUpdatedMsg(); TransportProtos.PostAttributeMsg attributesUpdatedMsg = latestEntityDataMsg.getAttributesUpdatedMsg();
Assert.assertEquals(expectedSize, attributesUpdatedMsg.getKvList().size());
boolean found = false;
for (TransportProtos.KeyValueProto keyValueProto : attributesUpdatedMsg.getKvList()) { for (TransportProtos.KeyValueProto keyValueProto : attributesUpdatedMsg.getKvList()) {
if (keyValueProto.getKey().equals(expectedKey)) { if (keyValueProto.getKey().equals(expectedKey)) {
Assert.assertEquals(expectedKey, keyValueProto.getKey()); Assert.assertEquals(expectedKey, keyValueProto.getKey());
Assert.assertEquals(expectedValue, keyValueProto.getStringV()); Assert.assertEquals(expectedValue, keyValueProto.getStringV());
found = true;
} }
} }
Assert.assertTrue("Expected key and value must be found", found);
} }
// Utility methods // Utility methods
private Device saveDeviceOnCloudAndVerifyDeliveryToEdge() throws Exception {
edgeImitator.expectMessageAmount(1);
Device savedDevice = saveDevice(RandomStringUtils.randomAlphanumeric(15), "Default");
doPost("/api/edge/" + edge.getUuidId()
+ "/device/" + savedDevice.getUuidId(), Device.class);
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg);
DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType());
Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getName(), savedDevice.getName());
Assert.assertEquals(deviceUpdateMsg.getType(), savedDevice.getType());
return savedDevice;
}
private Device findDeviceByName(String deviceName) throws Exception { private Device findDeviceByName(String deviceName) throws Exception {
List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/devices?", List<Device> edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?",
new TypeReference<PageData<Device>>() { new TypeReference<PageData<Device>>() {
}, new PageLink(100)).getData(); }, new PageLink(100)).getData();
Optional<Device> foundDevice = edgeDevices.stream().filter(d -> d.getName().equals(deviceName)).findAny(); Optional<Device> foundDevice = edgeDevices.stream().filter(d -> d.getName().equals(deviceName)).findAny();
@ -1669,7 +1601,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
} }
private Asset findAssetByName(String assetName) throws Exception { private Asset findAssetByName(String assetName) throws Exception {
List<Asset> edgeAssets = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/assets?", List<Asset> edgeAssets = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/assets?",
new TypeReference<PageData<Asset>>() { new TypeReference<PageData<Asset>>() {
}, new PageLink(100)).getData(); }, new PageLink(100)).getData();

43
application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java

@ -15,16 +15,23 @@
*/ */
package org.thingsboard.server.rules.flow; package org.thingsboard.server.rules.flow;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.*; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Event;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
@ -38,12 +45,14 @@ import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
import org.thingsboard.server.common.msg.queue.TbMsgCallback; import org.thingsboard.server.common.msg.queue.TbMsgCallback;
import org.thingsboard.server.controller.AbstractRuleEngineControllerTest; import org.thingsboard.server.controller.AbstractRuleEngineControllerTest;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.event.EventService;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.mockito.Mockito.spy;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/** /**
@ -61,8 +70,26 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
@Autowired @Autowired
protected AttributesService attributesService; protected AttributesService attributesService;
@Autowired
protected EventService eventService;
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
EventService spyEventService = spy(eventService);
Mockito.doAnswer((Answer<ListenableFuture<Void>>) invocation -> {
Object[] args = invocation.getArguments();
Event event = (Event) args[0];
ListenableFuture<Void> future = eventService.saveAsync(event);
try {
future.get();
} catch (Exception e) {}
return future;
}).when(spyEventService).saveAsync(Mockito.any(Event.class));
ReflectionTestUtils.setField(actorSystem, "eventService", spyEventService);
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
@ -136,12 +163,9 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
device = doPost("/api/device", device, Device.class); device = doPost("/api/device", device, Device.class);
attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE,
Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey1", "serverAttributeValue1"), System.currentTimeMillis()))); Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey1", "serverAttributeValue1"), System.currentTimeMillis()))).get();
attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE,
Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey2", "serverAttributeValue2"), System.currentTimeMillis()))); Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey2", "serverAttributeValue2"), System.currentTimeMillis()))).get();
Thread.sleep(1000);
TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class); TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class);
Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true); Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true);
@ -249,12 +273,9 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
device = doPost("/api/device", device, Device.class); device = doPost("/api/device", device, Device.class);
attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE,
Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey1", "serverAttributeValue1"), System.currentTimeMillis()))); Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey1", "serverAttributeValue1"), System.currentTimeMillis()))).get();
attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE, attributesService.save(device.getTenantId(), device.getId(), DataConstants.SERVER_SCOPE,
Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey2", "serverAttributeValue2"), System.currentTimeMillis()))); Collections.singletonList(new BaseAttributeKvEntry(new StringDataEntry("serverAttributeKey2", "serverAttributeValue2"), System.currentTimeMillis()))).get();
Thread.sleep(1000);
TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class); TbMsgCallback tbMsgCallback = Mockito.mock(TbMsgCallback.class);
Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true); Mockito.when(tbMsgCallback.isMsgValid()).thenReturn(true);

23
application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java

@ -15,13 +15,16 @@
*/ */
package org.thingsboard.server.rules.lifecycle; package org.thingsboard.server.rules.lifecycle;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration; import org.thingsboard.rule.engine.metadata.TbGetAttributesNodeConfiguration;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
@ -42,6 +45,7 @@ import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg;
import org.thingsboard.server.common.msg.queue.TbMsgCallback; import org.thingsboard.server.common.msg.queue.TbMsgCallback;
import org.thingsboard.server.controller.AbstractRuleEngineControllerTest; import org.thingsboard.server.controller.AbstractRuleEngineControllerTest;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.queue.memory.InMemoryStorage; import org.thingsboard.server.queue.memory.InMemoryStorage;
import java.util.Collections; import java.util.Collections;
@ -50,6 +54,7 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.awaitility.Awaitility.await; import static org.awaitility.Awaitility.await;
import static org.mockito.Mockito.spy;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/** /**
@ -67,8 +72,26 @@ public abstract class AbstractRuleEngineLifecycleIntegrationTest extends Abstrac
@Autowired @Autowired
protected AttributesService attributesService; protected AttributesService attributesService;
@Autowired
protected EventService eventService;
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
EventService spyEventService = spy(eventService);
Mockito.doAnswer((Answer<ListenableFuture<Void>>) invocation -> {
Object[] args = invocation.getArguments();
Event event = (Event) args[0];
ListenableFuture<Void> future = eventService.saveAsync(event);
try {
future.get();
} catch (Exception e) {}
return future;
}).when(spyEventService).saveAsync(Mockito.any(Event.class));
ReflectionTestUtils.setField(actorSystem, "eventService", spyEventService);
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();

86
application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java

@ -0,0 +1,86 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.apiusage;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.scheduler.SchedulerComponent;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import java.util.UUID;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@RunWith(MockitoJUnitRunner.class)
public class DefaultTbApiUsageStateServiceTest {
@Mock
TenantService tenantService;
@Mock
TimeseriesService tsService;
@Mock
TbClusterService clusterService;
@Mock
PartitionService partitionService;
@Mock
TenantApiUsageState tenantUsageStateMock;
@Mock
ApiUsageStateService apiUsageStateService;
@Mock
TbTenantProfileCache tenantProfileCache;
@Mock
MailService mailService;
@Mock
DbCallbackExecutorService dbExecutor;
TenantId tenantId = TenantId.fromUUID(UUID.fromString("00797a3b-7aeb-4b5b-b57a-c2a810d0f112"));
DefaultTbApiUsageStateService service;
@Before
public void setUp() {
service = spy(new DefaultTbApiUsageStateService(clusterService, partitionService, tenantService, tsService, apiUsageStateService, tenantProfileCache, mailService, dbExecutor));
}
@Test
public void givenTenantIdFromEntityStatesMap_whenGetApiUsageState() {
service.myUsageStates.put(tenantId, tenantUsageStateMock);
ApiUsageState tenantUsageState = service.getApiUsageState(tenantId);
assertThat(tenantUsageState, is(tenantUsageStateMock.getApiUsageState()));
Mockito.verify(service, never()).getOrFetchState(tenantId, tenantId);
}
}

1
application/src/test/java/org/thingsboard/server/service/ttl/EventsCleanUpServiceTest.java

@ -42,7 +42,6 @@ public class EventsCleanUpServiceTest {
public void givenInterval_whenRandomDelay_ThenDelayInInterval() { public void givenInterval_whenRandomDelay_ThenDelayInInterval() {
log.info("randomDelay {}", randomDelayMs); log.info("randomDelay {}", randomDelayMs);
log.info("executionIntervalMs {}", executionIntervalMs); log.info("executionIntervalMs {}", executionIntervalMs);
assertThat(executionIntervalMs, is(2220000L));
assertThat(randomDelayMs, greaterThanOrEqualTo(0L)); assertThat(randomDelayMs, greaterThanOrEqualTo(0L));
assertThat(randomDelayMs, lessThanOrEqualTo(executionIntervalMs)); assertThat(randomDelayMs, lessThanOrEqualTo(executionIntervalMs));
} }

315
application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java

@ -16,12 +16,17 @@
package org.thingsboard.server.transport.lwm2m; package org.thingsboard.server.transport.lwm2m;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.eclipse.californium.elements.config.Configuration; import org.eclipse.californium.elements.config.Configuration;
import org.eclipse.leshan.client.californium.LeshanClient;
import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Security;
import org.junit.After; import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.util.SocketUtils; import org.springframework.util.SocketUtils;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
@ -41,6 +46,11 @@ import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileCon
import org.thingsboard.server.common.data.device.profile.DeviceProfileData; 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.DisabledDeviceProfileProvisionConfiguration;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; 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.AbstractLwM2MBootstrapServerCredential;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.NoSecLwM2MBootstrapServerCredential;
import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityDataPageLink; import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.query.EntityDataQuery;
@ -57,94 +67,135 @@ import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd;
import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler;
import java.io.IOException;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static org.awaitility.Awaitility.await;
import static org.eclipse.californium.core.config.CoapConfig.COAP_PORT;
import static org.eclipse.californium.core.config.CoapConfig.COAP_SECURE_PORT;
import static org.eclipse.leshan.client.object.Security.noSec;
import static org.eclipse.leshan.client.object.Security.noSecBootstap;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_STARTED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_INIT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE;
@Slf4j
@DaoSqlTest @DaoSqlTest
public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest {
protected final String TRANSPORT_CONFIGURATION = "{\n" + @SpyBean
" \"type\": \"LWM2M\",\n" + DefaultLwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest;
" \"observeAttr\": {\n" +
" \"keyName\": {\n" + @Autowired
" \"/3_1.0/0/9\": \"batteryLevel\"\n" + private LwM2mClientContext clientContextTest;
" },\n" +
" \"observe\": [],\n" + // Lwm2m Server
" \"attribute\": [\n" + public static final int port = 5685;
" ],\n" + public static final int securityPort = 5686;
" \"telemetry\": [\n" + public static final int portBs = 5687;
" \"/3_1.0/0/9\"\n" + public static final int securityPortBs = 5688;
" ],\n" + public static final int[] SERVERS_PORT_NUMBERS = {port, securityPort, portBs, securityPortBs};
" \"attributeLwm2m\": {}\n" +
" },\n" + public static final String host = "localhost";
" \"bootstrapServerUpdateEnable\": true,\n" + public static final String hostBs = "localhost";
" \"bootstrap\": [\n" + public static final int shortServerId = 123;
public static final int shortServerIdBs = 111;
public static final String COAP = "coap://";
public static final String COAPS = "coaps://";
public static final String URI = COAP + host + ":" + port;
public static final String SECURE_URI = COAPS + host + ":" + securityPort;
public static final String URI_BS = COAP + hostBs + ":" + portBs;
public static final String SECURE_URI_BS = COAPS + hostBs + ":" + securityPortBs;
public static final Configuration COAP_CONFIG = new Configuration().set(COAP_PORT, port).set(COAP_SECURE_PORT, securityPort);
public static Configuration COAP_CONFIG_BS = new Configuration().set(COAP_PORT, portBs).set(COAP_SECURE_PORT, securityPortBs);
public static final Security SECURITY_NO_SEC = noSec(URI, shortServerId);
public static final Security SECURITY_NO_SEC_BS = noSecBootstap(URI_BS);
protected final String OBSERVE_ATTRIBUTES_WITHOUT_PARAMS =
" {\n" + " {\n" +
" \"host\": \"0.0.0.0\",\n" + " \"keyName\": {},\n" +
" \"port\": 5687,\n" + " \"observe\": [],\n" +
" \"binding\": \"U\",\n" + " \"attribute\": [],\n" +
" \"lifetime\": 300,\n" + " \"telemetry\": [],\n" +
" \"securityMode\": \"NO_SEC\",\n" + " \"attributeLwm2m\": {}\n" +
" \"shortServerId\": 111,\n" + " }";
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" + protected final String OBSERVE_ATTRIBUTES_WITH_PARAMS =
" \"defaultMinPeriod\": 1,\n" +
" \"bootstrapServerIs\": true,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" },\n" +
" {\n" + " {\n" +
" \"host\": \"0.0.0.0\",\n" + " \"keyName\": {\n" +
" \"port\": 5685,\n" + " \"/3_1.0/0/9\": \"batteryLevel\"\n" +
" \"binding\": \"U\",\n" + " },\n" +
" \"lifetime\": 300,\n" + " \"observe\": [],\n" +
" \"securityMode\": \"NO_SEC\",\n" + " \"attribute\": [\n" +
" \"shortServerId\": 123,\n" + " ],\n" +
" \"notifIfDisabled\": true,\n" + " \"telemetry\": [\n" +
" \"serverPublicKey\": \"\",\n" + " \"/3_1.0/0/9\"\n" +
" \"defaultMinPeriod\": 1,\n" + " ],\n" +
" \"bootstrapServerIs\": false,\n" + " \"attributeLwm2m\": {}\n" +
" \"clientHoldOffTime\": 1,\n" + " }";
" \"bootstrapServerAccountTimeout\": 0\n" +
" }\n" + protected final String CLIENT_LWM2M_SETTINGS =
" ],\n" + " {\n" +
" \"clientLwM2mSettings\": {\n" + " \"edrxCycle\": null,\n" +
" \"edrxCycle\": null,\n" + " \"powerMode\": \"DRX\",\n" +
" \"powerMode\": \"DRX\",\n" + " \"fwUpdateResource\": null,\n" +
" \"fwUpdateResource\": null,\n" + " \"fwUpdateStrategy\": 1,\n" +
" \"fwUpdateStrategy\": 1,\n" + " \"psmActivityTimer\": null,\n" +
" \"psmActivityTimer\": null,\n" + " \"swUpdateResource\": null,\n" +
" \"swUpdateResource\": null,\n" + " \"swUpdateStrategy\": 1,\n" +
" \"swUpdateStrategy\": 1,\n" + " \"pagingTransmissionWindow\": null,\n" +
" \"pagingTransmissionWindow\": null,\n" + " \"clientOnlyObserveAfterConnect\": 1\n" +
" \"clientOnlyObserveAfterConnect\": 1\n" + " }";
" }\n" +
"}";
protected final Set<Lwm2mTestHelper.LwM2MClientState> expectedStatusesBsSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_BOOTSTRAP_STARTED, ON_BOOTSTRAP_SUCCESS));
protected final Set<Lwm2mTestHelper.LwM2MClientState> expectedStatusesRegistrationLwm2mSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS));
protected final Set<Lwm2mTestHelper.LwM2MClientState> expectedStatusesRegistrationBsSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_BOOTSTRAP_STARTED, ON_BOOTSTRAP_SUCCESS, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS));
protected DeviceProfile deviceProfile; protected DeviceProfile deviceProfile;
protected ScheduledExecutorService executor; protected ScheduledExecutorService executor;
protected TbTestWebSocketClient wsClient; protected TbTestWebSocketClient wsClient;
protected LwM2MTestClient client; protected LwM2MTestClient lwM2MTestClient;
private final LwM2MBootstrapClientCredentials defaultBootstrapCredentials;
private String[] resources; private String[] resources;
public AbstractLwM2MIntegrationTest() { @Before
this.defaultBootstrapCredentials = new LwM2MBootstrapClientCredentials(); public void startInit() throws Exception {
NoSecBootstrapClientCredential serverCredentials = new NoSecBootstrapClientCredential(); init();
this.defaultBootstrapCredentials.setBootstrapServer(serverCredentials);
this.defaultBootstrapCredentials.setLwm2mServer(serverCredentials);
} }
public void init () throws Exception{ @After
public void after() {
wsClient.close();
clientDestroy();
executor.shutdownNow();
}
@AfterClass
public static void afterClass () {
awaitServersDestroy();
}
private void init () throws Exception {
executor = Executors.newScheduledThreadPool(10, ThingsBoardThreadFactory.forName("test-lwm2m-scheduled")); executor = Executors.newScheduledThreadPool(10, ThingsBoardThreadFactory.forName("test-lwm2m-scheduled"));
loginTenantAdmin(); loginTenantAdmin();
for (String resourceName : this.resources) { for (String resourceName : this.resources) {
TbResource lwModel = new TbResource(); TbResource lwModel = new TbResource();
lwModel.setResourceType(ResourceType.LWM2M_MODEL); lwModel.setResourceType(ResourceType.LWM2M_MODEL);
@ -160,24 +211,13 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
wsClient = buildAndConnectWebSocketClient(); wsClient = buildAndConnectWebSocketClient();
} }
@Before
public void beforeTest() throws Exception {
this.init();
}
@After
public void after() {
wsClient.close();
clientDestroy();
executor.shutdownNow();
}
public void basicTestConnectionObserveTelemetry(Security security, public void basicTestConnectionObserveTelemetry(Security security,
LwM2MClientCredential credentials, LwM2MDeviceCredentials deviceCredentials,
Configuration coapConfig, Configuration coapConfig,
String endpoint) throws Exception { String endpoint) throws Exception {
createDeviceProfile(TRANSPORT_CONFIGURATION); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS, getBootstrapServerCredentialsNoSec(NONE));
Device device = createDevice(credentials); createDeviceProfile(transportConfiguration);
Device device = createDevice(deviceCredentials, endpoint);
SingleEntityFilter sef = new SingleEntityFilter(); SingleEntityFilter sef = new SingleEntityFilter();
sef.setSingleEntity(device.getId()); sef.setSingleEntity(device.getId());
@ -194,7 +234,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
wsClient.waitForReply(); wsClient.waitForReply();
wsClient.registerWaitForUpdate(); wsClient.registerWaitForUpdate();
createNewClient(security, coapConfig, false, endpoint); createNewClient(security, coapConfig, false, endpoint, false, null);
String msg = wsClient.waitForUpdate(); String msg = wsClient.waitForUpdate();
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
@ -205,10 +245,14 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); Assert.assertEquals(device.getId(), eData.get(0).getEntityId());
Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES));
var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel"); var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel");
Assert.assertEquals(42, Long.parseLong(tsValue.getValue())); Assert.assertThat(Long.parseLong(tsValue.getValue()), instanceOf(Long.class));
int expectedMax = 50;
int expectedMin = 5;
Assert.assertTrue(expectedMax >= Long.parseLong(tsValue.getValue()));
Assert.assertTrue(expectedMin <= Long.parseLong(tsValue.getValue()));
} }
protected void createDeviceProfile(String transportConfiguration) throws Exception { protected void createDeviceProfile(Lwm2mDeviceProfileTransportConfiguration transportConfiguration) throws Exception {
deviceProfile = new DeviceProfile(); deviceProfile = new DeviceProfile();
deviceProfile.setName("LwM2M"); deviceProfile.setName("LwM2M");
deviceProfile.setType(DeviceProfileType.DEFAULT); deviceProfile.setType(DeviceProfileType.DEFAULT);
@ -220,16 +264,16 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
DeviceProfileData deviceProfileData = new DeviceProfileData(); DeviceProfileData deviceProfileData = new DeviceProfileData();
deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration());
deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null)); deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null));
deviceProfileData.setTransportConfiguration(JacksonUtil.fromString(transportConfiguration, Lwm2mDeviceProfileTransportConfiguration.class)); deviceProfileData.setTransportConfiguration(transportConfiguration);
deviceProfile.setProfileData(deviceProfileData); deviceProfile.setProfileData(deviceProfileData);
deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
Assert.assertNotNull(deviceProfile); Assert.assertNotNull(deviceProfile);
} }
protected Device createDevice(LwM2MClientCredential clientCredentials) throws Exception { protected Device createDevice(LwM2MDeviceCredentials credentials, String endpoint) throws Exception {
Device device = new Device(); Device device = new Device();
device.setName("Device A"); device.setName(endpoint);
device.setDeviceProfileId(deviceProfile.getId()); device.setDeviceProfileId(deviceProfile.getId());
device.setTenantId(tenantId); device.setTenantId(tenantId);
device = doPost("/api/device", device, Device.class); device = doPost("/api/device", device, Device.class);
@ -239,11 +283,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class); doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class);
Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId()); Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId());
deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS);
LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials();
credentials.setClient(clientCredentials);
credentials.setBootstrap(defaultBootstrapCredentials);
deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials)); deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials));
doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk());
return device; return device;
@ -259,16 +298,98 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
this.resources = resources; this.resources = resources;
} }
public void createNewClient(Security security, Configuration coapConfig, boolean isRpc, String endpoint) throws Exception { public void createNewClient(Security security, Configuration coapConfig, boolean isRpc, String endpoint, boolean isBootstrap, Security securityBs) throws Exception {
clientDestroy(); this.clientDestroy();
client = new LwM2MTestClient(this.executor, endpoint); lwM2MTestClient = new LwM2MTestClient(this.executor, endpoint);
int clientPort = SocketUtils.findAvailableTcpPort(); int clientPort = SocketUtils.findAvailableUdpPort();
client.init(security, coapConfig, clientPort, isRpc); lwM2MTestClient.init(security, coapConfig, clientPort, isRpc, isBootstrap, this.shortServerId, this.shortServerIdBs,
securityBs, this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest);
} }
private void clientDestroy() { private void clientDestroy() {
if (client != null) { try {
client.destroy(); if (lwM2MTestClient != null) {
lwM2MTestClient.destroy();
awaitClientDestroy(lwM2MTestClient.getLeshanClient());
}
} catch (Exception e) {
log.error("Failed client Destroy", e);
} }
} }
protected Lwm2mDeviceProfileTransportConfiguration getTransportConfiguration(String observeAttr, List<LwM2MBootstrapServerCredential> bootstrapServerCredentials) {
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration();
TelemetryMappingConfiguration observeAttrConfiguration = JacksonUtil.fromString(observeAttr, 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 List<LwM2MBootstrapServerCredential> getBootstrapServerCredentialsNoSec(LwM2MProfileBootstrapConfigType bootstrapConfigType) {
List<LwM2MBootstrapServerCredential> bootstrap = new ArrayList<>();
switch (bootstrapConfigType) {
case BOTH:
bootstrap.add(getBootstrapServerCredentialNoSec(false));
bootstrap.add(getBootstrapServerCredentialNoSec(true));
break;
case BOOTSTRAP_ONLY:
bootstrap.add(getBootstrapServerCredentialNoSec(true));
break;
case LWM2M_ONLY:
bootstrap.add(getBootstrapServerCredentialNoSec(false));
break;
case NONE:
}
return bootstrap;
}
private AbstractLwM2MBootstrapServerCredential getBootstrapServerCredentialNoSec(boolean isBootstrap) {
AbstractLwM2MBootstrapServerCredential bootstrapServerCredential = new NoSecLwM2MBootstrapServerCredential();
bootstrapServerCredential.setServerPublicKey("");
bootstrapServerCredential.setShortServerId(isBootstrap ? shortServerIdBs : shortServerId);
bootstrapServerCredential.setBootstrapServerIs(isBootstrap);
bootstrapServerCredential.setHost(isBootstrap ? hostBs : host);
bootstrapServerCredential.setPort(isBootstrap ? portBs : port);
return bootstrapServerCredential;
}
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;
}
private static void awaitServersDestroy() {
await("One of servers ports number is not free")
.atMost(3000, TimeUnit.MILLISECONDS)
.until(() -> isServerPortsAvailable() == null);
}
private static String isServerPortsAvailable() {
for (int port : SERVERS_PORT_NUMBERS) {
try (ServerSocket serverSocket = new ServerSocket(port)) {
serverSocket.close();
Assert.assertEquals(true, serverSocket.isClosed());
} catch (IOException e) {
log.warn(String.format("Port %n still in use", port));
return (String.format("Port %n still in use", port));
}
}
return null;
}
private static void awaitClientDestroy(LeshanClient leshanClient) {
await("Destroy LeshanClient: delete All is registered Servers.")
.atMost(2000, TimeUnit.MILLISECONDS)
.until(() -> leshanClient.getRegisteredServers().size() == 0);
}
} }

106
application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java

@ -15,30 +15,8 @@
*/ */
package org.thingsboard.server.transport.lwm2m; package org.thingsboard.server.transport.lwm2m;
import org.eclipse.californium.elements.config.Configuration;
import org.eclipse.leshan.client.object.Security;
import static org.eclipse.californium.core.config.CoapConfig.COAP_PORT;
import static org.eclipse.californium.core.config.CoapConfig.COAP_SECURE_PORT;
import static org.eclipse.leshan.client.object.Security.noSec;
public class Lwm2mTestHelper { public class Lwm2mTestHelper {
// Server
public static final int SECURE_PORT = 5686;
public static final int SECURE_PORT_BS = 5688;
public static final int PORT = 5685;
public static final int PORT_BS = 5687;
public static final String HOST = "localhost";
public static final String HOST_BS = "localhost";
public static final int SHORT_SERVER_ID = 123;
public static final int SHORT_SERVER_ID_BS = 111;
public static final Configuration SECURE_COAP_CONFIG = new Configuration().set(COAP_SECURE_PORT, SECURE_PORT);
public static final String SECURE_URI = "coaps://" + HOST + ":" + SECURE_PORT;
public static final Security SECURITY = noSec("coap://"+ HOST +":" + PORT, SHORT_SERVER_ID);
public static final Configuration COAP_CONFIG = new Configuration().set(COAP_PORT, PORT);
// Models // Models
public static final String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml", "5.xml", "6.xml", "9.xml", "19.xml", "3303.xml"}; public static final String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml", "5.xml", "6.xml", "9.xml", "19.xml", "3303.xml"};
public static final int BINARY_APP_DATA_CONTAINER = 19; public static final int BINARY_APP_DATA_CONTAINER = 19;
@ -46,6 +24,7 @@ public class Lwm2mTestHelper {
// Ids in Client // Ids in Client
public static final int OBJECT_ID_0 = 0; public static final int OBJECT_ID_0 = 0;
public static final int OBJECT_ID_1 = 1;
public static final int OBJECT_INSTANCE_ID_0 = 0; public static final int OBJECT_INSTANCE_ID_0 = 0;
public static final int OBJECT_INSTANCE_ID_1 = 1; public static final int OBJECT_INSTANCE_ID_1 = 1;
public static final int OBJECT_INSTANCE_ID_2 = 2; public static final int OBJECT_INSTANCE_ID_2 = 2;
@ -67,4 +46,87 @@ public class Lwm2mTestHelper {
public static final String RESOURCE_ID_NAME_3_14 = "UtfOffset"; 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_0 = "dataRead";
public static final String RESOURCE_ID_NAME_19_1_0 = "dataWrite"; public static final String RESOURCE_ID_NAME_19_1_0 = "dataWrite";
public static final String RESOURCE_ID_NAME_19_0_3 = "dataDescription";
public enum LwM2MClientState {
ON_INIT(1, "onInit"),
ON_BOOTSTRAP_STARTED(1, "onBootstrapStarted"),
ON_BOOTSTRAP_SUCCESS(2, "onBootstrapSuccess"),
ON_BOOTSTRAP_FAILURE(3, "onBootstrapFailure"),
ON_BOOTSTRAP_TIMEOUT(4, "onBootstrapTimeout"),
ON_REGISTRATION_STARTED(5, "onRegistrationStarted"),
ON_REGISTRATION_SUCCESS(6, "onRegistrationSuccess"),
ON_REGISTRATION_FAILURE(7, "onRegistrationFailure"),
ON_REGISTRATION_TIMEOUT(7, "onRegistrationTimeout"),
ON_UPDATE_STARTED(8, "onUpdateStarted"),
ON_UPDATE_SUCCESS(9, "onUpdateSuccess"),
ON_UPDATE_FAILURE(10, "onUpdateFailure"),
ON_UPDATE_TIMEOUT(11, "onUpdateTimeout"),
ON_DEREGISTRATION_STARTED(12, "onDeregistrationStarted"),
ON_DEREGISTRATION_SUCCESS(13, "onDeregistrationSuccess"),
ON_DEREGISTRATION_FAILURE(14, "onDeregistrationFailure"),
ON_DEREGISTRATION_TIMEOUT(15, "onDeregistrationTimeout"),
ON_EXPECTED_ERROR(16, "onUnexpectedError");
public int code;
public String type;
LwM2MClientState(int code, String type) {
this.code = code;
this.type = type;
}
public static LwM2MClientState fromLwM2MClientStateByType(String type) {
for (LwM2MClientState to : LwM2MClientState.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported Client State type : %s", type));
}
public static LwM2MClientState fromLwM2MClientStateByCode(int code) {
for (LwM2MClientState to : LwM2MClientState.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported Client State code : %s", code));
}
}
public enum LwM2MProfileBootstrapConfigType {
LWM2M_ONLY(1, "only Lwm2m Server"),
BOOTSTRAP_ONLY(2, "only Bootstrap Server"),
BOTH(3, "Lwm2m Server and Bootstrap Server"),
NONE(4, "Without Lwm2m Server and Bootstrap Server");
public int code;
public String type;
LwM2MProfileBootstrapConfigType(int code, String type) {
this.code = code;
this.type = type;
}
public static LwM2MProfileBootstrapConfigType fromLwM2MBootstrapConfigByType(String type) {
for (LwM2MProfileBootstrapConfigType to : LwM2MProfileBootstrapConfigType.values()) {
if (to.type.equals(type)) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported Profile Bootstrap Config type : %s", type));
}
public static LwM2MProfileBootstrapConfigType fromLwM2MBootstrapConfigByCode(int code) {
for (LwM2MProfileBootstrapConfigType to : LwM2MProfileBootstrapConfigType.values()) {
if (to.code == code) {
return to;
}
}
throw new IllegalArgumentException(String.format("Unsupported Profile Bootstrap Config code : %s", code));
}
}
} }

181
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java

@ -26,6 +26,7 @@ import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.client.object.Server; import org.eclipse.leshan.client.object.Server;
import org.eclipse.leshan.client.observer.LwM2mClientObserver; import org.eclipse.leshan.client.observer.LwM2mClientObserver;
import org.eclipse.leshan.client.resource.DummyInstanceEnabler; import org.eclipse.leshan.client.resource.DummyInstanceEnabler;
import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler;
import org.eclipse.leshan.client.resource.ObjectsInitializer; import org.eclipse.leshan.client.resource.ObjectsInitializer;
import org.eclipse.leshan.client.servers.ServerIdentity; import org.eclipse.leshan.client.servers.ServerIdentity;
import org.eclipse.leshan.core.ResponseCode; import org.eclipse.leshan.core.ResponseCode;
@ -41,12 +42,20 @@ import org.eclipse.leshan.core.request.DeregisterRequest;
import org.eclipse.leshan.core.request.RegisterRequest; import org.eclipse.leshan.core.request.RegisterRequest;
import org.eclipse.leshan.core.request.UpdateRequest; import org.eclipse.leshan.core.request.UpdateRequest;
import org.junit.Assert; import org.junit.Assert;
import org.mockito.Mockito;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY;
import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL; import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL;
@ -57,6 +66,25 @@ import static org.eclipse.leshan.core.LwM2mId.SECURITY;
import static org.eclipse.leshan.core.LwM2mId.SERVER; import static org.eclipse.leshan.core.LwM2mId.SERVER;
import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT; import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_FAILURE;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_STARTED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_TIMEOUT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_FAILURE;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_STARTED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_TIMEOUT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_EXPECTED_ERROR;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_INIT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_FAILURE;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_STARTED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_TIMEOUT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_FAILURE;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_STARTED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_UPDATE_TIMEOUT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_12;
@ -70,28 +98,61 @@ public class LwM2MTestClient {
private final ScheduledExecutorService executor; private final ScheduledExecutorService executor;
private final String endpoint; private final String endpoint;
private LeshanClient client; private LeshanClient leshanClient;
private Server lwm2mServer; private Security lwm2mSecurity;
private Security lwm2mSecurityBs;
private Lwm2mServer lwm2mServer;
private Lwm2mServer lwm2mServerBs;
private SimpleLwM2MDevice lwM2MDevice; private SimpleLwM2MDevice lwM2MDevice;
private FwLwM2MDevice fwLwM2MDevice; private FwLwM2MDevice fwLwM2MDevice;
private SwLwM2MDevice swLwM2MDevice; private SwLwM2MDevice swLwM2MDevice;
private LwM2mBinaryAppDataContainer lwM2MBinaryAppDataContainer; private LwM2mBinaryAppDataContainer lwM2MBinaryAppDataContainer;
private LwM2MLocationParams locationParams; private LwM2MLocationParams locationParams;
private LwM2mTemperatureSensor lwM2MTemperatureSensor; private LwM2mTemperatureSensor lwM2MTemperatureSensor;
private LwM2MClientState clientState;
public void init(Security security, Configuration coapConfig, int port, boolean isRpc) throws InvalidDDFFileException, IOException { private Set<LwM2MClientState> clientStates;
Assert.assertNull("client already initialized", client); private DefaultLwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest;
private LwM2mClientContext clientContext;
public void init(Security security, Configuration coapConfig, int port, boolean isRpc, boolean isBootstrap,
int shortServerId, int shortServerIdBs, Security securityBs,
DefaultLwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler,
LwM2mClientContext clientContext) throws InvalidDDFFileException, IOException {
Assert.assertNull("client already initialized", leshanClient);
this.defaultLwM2mUplinkMsgHandlerTest = defaultLwM2mUplinkMsgHandler;
this.clientContext = clientContext;
List<ObjectModel> models = new ArrayList<>(); List<ObjectModel> models = new ArrayList<>();
for (String resourceName : resources) { for (String resourceName : resources) {
models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName)); models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName));
} }
LwM2mModel model = new StaticModel(models); LwM2mModel model = new StaticModel(models);
ObjectsInitializer initializer = new ObjectsInitializer(model); ObjectsInitializer initializer = new ObjectsInitializer(model);
initializer.setInstancesForObject(SECURITY, security); if (securityBs == null) {
initializer.setInstancesForObject(SERVER, lwm2mServer = new Server(123, 300)); initializer.setInstancesForObject(SECURITY, this.lwm2mSecurity = security);
initializer.setInstancesForObject(DEVICE, lwM2MDevice = new SimpleLwM2MDevice()); } else {
securityBs.setId(0);
security.setId(1);
LwM2mInstanceEnabler[] instances = new LwM2mInstanceEnabler[]{this.lwm2mSecurityBs = securityBs, this.lwm2mSecurity = security};
initializer.setClassForObject(SECURITY, Security.class);
initializer.setInstancesForObject(SECURITY, instances);
}
if (isBootstrap) {
initializer.setInstancesForObject(SERVER, lwm2mServerBs = new Lwm2mServer(shortServerIdBs, 300));
} else {
if (securityBs == null) {
initializer.setInstancesForObject(SERVER, lwm2mServer = new Lwm2mServer(shortServerId, 300));
} else {
lwm2mServerBs = new Lwm2mServer(shortServerIdBs, 300);
lwm2mServerBs.setId(0);
lwm2mServer = new Lwm2mServer(shortServerId, 300);
lwm2mServer.setId(1);
LwM2mInstanceEnabler[] instances = new LwM2mInstanceEnabler[]{lwm2mServerBs, lwm2mServer};
initializer.setClassForObject(SERVER, Server.class);
initializer.setInstancesForObject(SERVER, instances);
}
}
initializer.setInstancesForObject(DEVICE, lwM2MDevice = new SimpleLwM2MDevice(executor));
initializer.setInstancesForObject(FIRMWARE, fwLwM2MDevice = new FwLwM2MDevice()); initializer.setInstancesForObject(FIRMWARE, fwLwM2MDevice = new FwLwM2MDevice());
initializer.setInstancesForObject(SOFTWARE_MANAGEMENT, swLwM2MDevice = new SwLwM2MDevice()); initializer.setInstancesForObject(SOFTWARE_MANAGEMENT, swLwM2MDevice = new SwLwM2MDevice());
initializer.setClassForObject(ACCESS_CONTROL, DummyInstanceEnabler.class); initializer.setClassForObject(ACCESS_CONTROL, DummyInstanceEnabler.class);
@ -119,105 +180,133 @@ public class LwM2MTestClient {
builder.setDecoder(new DefaultLwM2mDecoder(false)); builder.setDecoder(new DefaultLwM2mDecoder(false));
builder.setEncoder(new DefaultLwM2mEncoder(new LwM2mValueConverterImpl(), false)); builder.setEncoder(new DefaultLwM2mEncoder(new LwM2mValueConverterImpl(), false));
client = builder.build(); clientState = ON_INIT;
clientStates = new HashSet<>();
clientStates.add(clientState);
leshanClient = builder.build();
LwM2mClientObserver observer = new LwM2mClientObserver() { LwM2mClientObserver observer = new LwM2mClientObserver() {
@Override @Override
public void onBootstrapStarted(ServerIdentity bsserver, BootstrapRequest request) { public void onBootstrapStarted(ServerIdentity bsserver, BootstrapRequest request) {
log.info("ClientObserver -> onBootstrapStarted..."); clientState = ON_BOOTSTRAP_STARTED;
clientStates.add(clientState);
} }
@Override @Override
public void onBootstrapSuccess(ServerIdentity bsserver, BootstrapRequest request) { public void onBootstrapSuccess(ServerIdentity bsserver, BootstrapRequest request) {
log.info("ClientObserver -> onBootstrapSuccess..."); clientState = ON_BOOTSTRAP_SUCCESS;
clientStates.add(clientState);
} }
@Override @Override
public void onBootstrapFailure(ServerIdentity bsserver, BootstrapRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { public void onBootstrapFailure(ServerIdentity bsserver, BootstrapRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
log.info("ClientObserver -> onBootstrapFailure..."); clientState = ON_BOOTSTRAP_FAILURE;
clientStates.add(clientState);
} }
@Override @Override
public void onBootstrapTimeout(ServerIdentity bsserver, BootstrapRequest request) { public void onBootstrapTimeout(ServerIdentity bsserver, BootstrapRequest request) {
log.info("ClientObserver -> onBootstrapTimeout..."); clientState = ON_BOOTSTRAP_TIMEOUT;
clientStates.add(clientState);
} }
@Override @Override
public void onRegistrationStarted(ServerIdentity server, RegisterRequest request) { public void onRegistrationStarted(ServerIdentity server, RegisterRequest request) {
// log.info("ClientObserver -> onRegistrationStarted... EndpointName [{}]", request.getEndpointName()); clientState = ON_REGISTRATION_STARTED;
clientStates.add(clientState);
} }
@Override @Override
public void onRegistrationSuccess(ServerIdentity server, RegisterRequest request, String registrationID) { public void onRegistrationSuccess(ServerIdentity server, RegisterRequest request, String registrationID) {
log.info("ClientObserver -> onRegistrationSuccess... EndpointName [{}] [{}]", request.getEndpointName(), registrationID); clientState = ON_REGISTRATION_SUCCESS;
clientStates.add(clientState);
} }
@Override @Override
public void onRegistrationFailure(ServerIdentity server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { public void onRegistrationFailure(ServerIdentity server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
log.info("ClientObserver -> onRegistrationFailure... ServerIdentity [{}]", server); clientState = ON_REGISTRATION_FAILURE;
clientStates.add(clientState);
} }
@Override @Override
public void onRegistrationTimeout(ServerIdentity server, RegisterRequest request) { public void onRegistrationTimeout(ServerIdentity server, RegisterRequest request) {
log.info("ClientObserver -> onRegistrationTimeout... RegisterRequest [{}]", request); clientState = ON_REGISTRATION_TIMEOUT;
clientStates.add(clientState);
} }
@Override @Override
public void onUpdateStarted(ServerIdentity server, UpdateRequest request) { public void onUpdateStarted(ServerIdentity server, UpdateRequest request) {
// log.info("ClientObserver -> onUpdateStarted... UpdateRequest [{}]", request); clientState = ON_UPDATE_STARTED;
clientStates.add(clientState);
} }
@Override @Override
public void onUpdateSuccess(ServerIdentity server, UpdateRequest request) { public void onUpdateSuccess(ServerIdentity server, UpdateRequest request) {
// log.info("ClientObserver -> onUpdateSuccess... UpdateRequest [{}]", request); clientState = ON_UPDATE_SUCCESS;
clientStates.add(clientState);
} }
@Override @Override
public void onUpdateFailure(ServerIdentity server, UpdateRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { public void onUpdateFailure(ServerIdentity server, UpdateRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
clientState = ON_UPDATE_FAILURE;
clientStates.add(clientState);
} }
@Override @Override
public void onUpdateTimeout(ServerIdentity server, UpdateRequest request) { public void onUpdateTimeout(ServerIdentity server, UpdateRequest request) {
clientState = ON_UPDATE_TIMEOUT;
clientStates.add(clientState);
} }
@Override @Override
public void onDeregistrationStarted(ServerIdentity server, DeregisterRequest request) { public void onDeregistrationStarted(ServerIdentity server, DeregisterRequest request) {
log.info("ClientObserver ->onDeregistrationStarted... DeregisterRequest [{}]", request.getRegistrationId()); clientState = ON_DEREGISTRATION_STARTED;
clientStates.add(clientState);
} }
@Override @Override
public void onDeregistrationSuccess(ServerIdentity server, DeregisterRequest request) { public void onDeregistrationSuccess(ServerIdentity server, DeregisterRequest request) {
log.info("ClientObserver ->onDeregistrationSuccess... DeregisterRequest [{}]", request.getRegistrationId()); clientState = ON_DEREGISTRATION_SUCCESS;
clientStates.add(clientState);
} }
@Override @Override
public void onDeregistrationFailure(ServerIdentity server, DeregisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { public void onDeregistrationFailure(ServerIdentity server, DeregisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) {
log.info("ClientObserver ->onDeregistrationFailure... DeregisterRequest [{}] [{}]", request.getRegistrationId(), request.getRegistrationId()); clientState = ON_DEREGISTRATION_FAILURE;
clientStates.add(clientState);
} }
@Override @Override
public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) { public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) {
log.info("ClientObserver ->onDeregistrationTimeout... DeregisterRequest [{}] [{}]", request.getRegistrationId(), request.getRegistrationId()); clientState = ON_DEREGISTRATION_TIMEOUT;
clientStates.add(clientState);
} }
@Override @Override
public void onUnexpectedError(Throwable unexpectedError) { public void onUnexpectedError(Throwable unexpectedError) {
clientState = ON_EXPECTED_ERROR;
clientStates.add(clientState);
} }
}; };
this.client.addObserver(observer); this.leshanClient.addObserver(observer);
if (!isRpc) { if (!isRpc) {
client.start(); this.start(true);
} }
} }
public void destroy() { public void destroy() {
if (client != null) { if (leshanClient != null) {
client.destroy(true); leshanClient.destroy(true);
}
if (lwm2mSecurityBs != null) {
lwm2mSecurityBs = null;
}
if (lwm2mSecurity != null) {
lwm2mSecurity = null;
}
if (lwm2mServerBs != null) {
lwm2mServerBs = null;
} }
if (lwm2mServer != null) { if (lwm2mServer != null) {
lwm2mServer = null; lwm2mServer = null;
@ -239,9 +328,29 @@ public class LwM2MTestClient {
} }
} }
public void start() { public void start(boolean isStartLw) {
if (client != null) { if (leshanClient != null) {
client.start(); leshanClient.start();
if (isStartLw) {
this.awaitClientAfterStartConnectLw();
}
}
}
private void awaitClientAfterStartConnectLw() {
LwM2mClient lwM2MClient = this.clientContext.getClientByEndpoint(endpoint);
CountDownLatch latch = new CountDownLatch(1);
Mockito.doAnswer(invocation -> {
latch.countDown();
return null;
}).when(defaultLwM2mUplinkMsgHandlerTest).initAttributes(lwM2MClient, true);
try {
if (!latch.await(1, TimeUnit.SECONDS)) {
throw new RuntimeException("Failed to await TimeOut lwm2m client initialization!");
}
} catch (InterruptedException e) {
throw new RuntimeException("Exception Failed to await lwm2m client initialization! ", e);
} }
} }
} }

7
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java

@ -173,7 +173,8 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements
} }
private String getDescription() { private String getDescription() {
return this.description == null ? "meter reading" : this.description; // return this.description == null ? "meter reading" : this.description;
return this.description;
} }
private void setTimestamp(long time) { private void setTimestamp(long time) {
@ -203,6 +204,10 @@ public class LwM2mBinaryAppDataContainer extends BaseInstanceEnabler implements
} }
private Map<Integer, byte[]> getData() { private Map<Integer, byte[]> getData() {
if (data == null) {
this.data = new HashMap<>();
this.data.put(0, new byte[]{(byte) 0xAC});
}
return data; return data;
} }

235
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/Lwm2mServer.java

@ -0,0 +1,235 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.client;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.client.resource.BaseInstanceEnabler;
import org.eclipse.leshan.client.resource.LwM2mInstanceEnabler;
import org.eclipse.leshan.client.servers.ServerIdentity;
import org.eclipse.leshan.core.model.ObjectModel;
import org.eclipse.leshan.core.model.ResourceModel.Type;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.request.BindingMode;
import org.eclipse.leshan.core.response.ExecuteResponse;
import org.eclipse.leshan.core.response.ReadResponse;
import org.eclipse.leshan.core.response.WriteResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Objects;
/**
* A simple {@link LwM2mInstanceEnabler} for the Server (1) object.
*/
@Slf4j
public class Lwm2mServer extends BaseInstanceEnabler {
private static final Logger LOG = LoggerFactory.getLogger(Lwm2mServer.class);
private final static List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3, 6, 7, 8, 22);
private int shortServerId;
private long lifetime;
private Long defaultMinPeriod;
private Long defaultMaxPeriod;
private EnumSet<BindingMode> binding;
private BindingMode preferredTransport;
private boolean notifyWhenDisable;
public Lwm2mServer() {
// should only be used at bootstrap time
}
public Lwm2mServer(int shortServerId, long lifetime, EnumSet<BindingMode> binding, boolean notifyWhenDisable,
BindingMode preferredTransport) {
this.shortServerId = shortServerId;
this.lifetime = lifetime;
this.binding = binding;
this.notifyWhenDisable = notifyWhenDisable;
this.preferredTransport = preferredTransport;
}
public Lwm2mServer(int shortServerId, long lifetime) {
this(shortServerId, lifetime, EnumSet.of(BindingMode.U), false, BindingMode.U);
}
@Override
public ReadResponse read(ServerIdentity identity, int resourceid) {
if (!identity.isSystem())
LOG.debug("Read on Server resource /{}/{}/{}", getModel().id, getId(), resourceid);
switch (resourceid) {
case 0: // short server ID
return ReadResponse.success(resourceid, shortServerId);
case 1: // lifetime
return ReadResponse.success(resourceid, lifetime);
case 2: // default min period
if (null == defaultMinPeriod)
return ReadResponse.notFound();
return ReadResponse.success(resourceid, defaultMinPeriod);
case 3: // default max period
if (null == defaultMaxPeriod)
return ReadResponse.notFound();
return ReadResponse.success(resourceid, defaultMaxPeriod);
case 6: // notification storing when disable or offline
return ReadResponse.success(resourceid, notifyWhenDisable);
case 7: // binding
return ReadResponse.success(resourceid, BindingMode.toString(binding));
case 22: // preferred transport
if (preferredTransport == null)
return ReadResponse.notFound();
return ReadResponse.success(resourceid, preferredTransport.toString());
default:
return super.read(identity, resourceid);
}
}
@Override
public WriteResponse write(ServerIdentity identity, boolean replace, int resourceid, LwM2mResource value) {
if (!identity.isSystem())
log.debug("Write on Server resource /{}/{}/{}", getModel().id, getId(), resourceid);
switch (resourceid) {
case 0:
if (value.getType() != Type.INTEGER) {
return WriteResponse.badRequest("invalid type");
}
int previousShortServerId = shortServerId;
shortServerId = ((Long) value.getValue()).intValue();
if (previousShortServerId != shortServerId)
fireResourceChange(resourceid);
return WriteResponse.success();
case 1:
if (value.getType() != Type.INTEGER) {
return WriteResponse.badRequest("invalid type");
}
long previousLifetime = lifetime;
lifetime = (Long) value.getValue();
if (previousLifetime != lifetime)
fireResourceChange(resourceid);
return WriteResponse.success();
case 2:
if (value.getType() != Type.INTEGER) {
return WriteResponse.badRequest("invalid type");
}
Long previousDefaultMinPeriod = defaultMinPeriod;
defaultMinPeriod = (Long) value.getValue();
if (!Objects.equals(previousDefaultMinPeriod, defaultMinPeriod))
fireResourceChange(resourceid);
return WriteResponse.success();
case 3:
if (value.getType() != Type.INTEGER) {
return WriteResponse.badRequest("invalid type");
}
Long previousDefaultMaxPeriod = defaultMaxPeriod;
defaultMaxPeriod = (Long) value.getValue();
if (!Objects.equals(previousDefaultMaxPeriod, defaultMaxPeriod))
fireResourceChange(resourceid);
return WriteResponse.success();
case 6: // notification storing when disable or offline
if (value.getType() != Type.BOOLEAN) {
return WriteResponse.badRequest("invalid type");
}
boolean previousNotifyWhenDisable = notifyWhenDisable;
notifyWhenDisable = (boolean) value.getValue();
if (previousNotifyWhenDisable != notifyWhenDisable)
fireResourceChange(resourceid);
return WriteResponse.success();
case 7: // binding
if (value.getType() != Type.STRING) {
return WriteResponse.badRequest("invalid type");
}
try {
EnumSet<BindingMode> previousBinding = binding;
binding = BindingMode.parse((String) value.getValue());
if (!Objects.equals(previousBinding, binding))
fireResourceChange(resourceid);
return WriteResponse.success();
} catch (IllegalArgumentException e) {
return WriteResponse.badRequest("invalid value");
}
case 22: // preferredTransport
if (value.getType() != Type.STRING) {
return WriteResponse.badRequest("invalid type");
}
try {
BindingMode previousPreferedTransport = preferredTransport;
preferredTransport = BindingMode.valueOf((String) value.getValue());
if (!Objects.equals(previousPreferedTransport, preferredTransport))
fireResourceChange(resourceid);
return WriteResponse.success();
} catch (IllegalArgumentException e) {
return WriteResponse.badRequest("invalid value");
}
default:
return super.write(identity, replace, resourceid, value);
}
}
@Override
public ExecuteResponse execute(ServerIdentity identity, int resourceid, String params) {
log.info("Execute on Server resource /{}/{}/{}", getModel().id, getId(), resourceid);
if (resourceid == 8) {
getLwM2mClient().triggerRegistrationUpdate(identity);
return ExecuteResponse.success();
} else if (resourceid == 9) {
boolean success = getLwM2mClient().triggerClientInitiatedBootstrap(true);
if (success) {
return ExecuteResponse.success();
}
else {
return ExecuteResponse.badRequest("probably no bootstrap server configured");
}
} else {
return super.execute(identity, resourceid, params);
}
}
@Override
public void reset(int resourceid) {
switch (resourceid) {
case 2:
defaultMinPeriod = null;
break;
case 3:
defaultMaxPeriod = null;
break;
default:
super.reset(resourceid);
}
}
@Override
public List<Integer> getAvailableResourceIds(ObjectModel model) {
return supportedResources;
}
}

26
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java

@ -32,16 +32,39 @@ import java.util.Calendar;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.PrimitiveIterator;
import java.util.Random; import java.util.Random;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@Slf4j @Slf4j
public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyable { public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyable {
private static final Random RANDOM = new Random(); private static final Random RANDOM = new Random();
private static final int min = 5;
private static final int max = 50;
private static final PrimitiveIterator.OfInt randomIterator = new Random().ints(min,max + 1).iterator();
private static final List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21); private static final List<Integer> supportedResources = Arrays.asList(0, 1, 2, 3, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21);
public SimpleLwM2MDevice() {
}
public SimpleLwM2MDevice(ScheduledExecutorService executorService) {
try {
executorService.scheduleWithFixedDelay(() -> {
fireResourceChange(9);
}
, 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN
} catch (Throwable e) {
log.error("[{}]Throwable", e.toString());
e.printStackTrace();
}
}
@Override @Override
public ReadResponse read(ServerIdentity identity, int resourceId) { public ReadResponse read(ServerIdentity identity, int resourceId) {
if (!identity.isSystem()) if (!identity.isSystem())
@ -135,7 +158,8 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
} }
private int getBatteryLevel() { private int getBatteryLevel() {
return 42; return randomIterator.nextInt();
// return 42;
} }
private long getMemoryFree() { private long getMemoryFree() {

162
application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java

@ -20,7 +20,8 @@ import lombok.extern.slf4j.Slf4j;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus;
@ -46,105 +47,59 @@ import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.QUEU
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING;
import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.COAP_CONFIG; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY;
@Slf4j @Slf4j
public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest {
public static final int TIMEOUT = 30; public static final int TIMEOUT = 30;
private final String OTA_TRANSPORT_CONFIGURATION = "{\n" +
" \"type\": \"LWM2M\",\n" +
" \"observeAttr\": {\n" +
" \"keyName\": {\n" +
" \"/5_1.0/0/3\": \"state\",\n" +
" \"/5_1.0/0/5\": \"updateResult\",\n" +
" \"/5_1.0/0/6\": \"pkgname\",\n" +
" \"/5_1.0/0/7\": \"pkgversion\",\n" +
" \"/5_1.0/0/9\": \"firmwareUpdateDeliveryMethod\",\n" +
" \"/9_1.0/0/0\": \"pkgname\",\n" +
" \"/9_1.0/0/1\": \"pkgversion\",\n" +
" \"/9_1.0/0/7\": \"updateState\",\n" +
" \"/9_1.0/0/9\": \"updateResult\"\n" +
" },\n" +
" \"observe\": [\n" +
" \"/5_1.0/0/3\",\n" +
" \"/5_1.0/0/5\",\n" +
" \"/5_1.0/0/6\",\n" +
" \"/5_1.0/0/7\",\n" +
" \"/5_1.0/0/9\",\n" +
" \"/9_1.0/0/0\",\n" +
" \"/9_1.0/0/1\",\n" +
" \"/9_1.0/0/7\",\n" +
" \"/9_1.0/0/9\"\n" +
" ],\n" +
" \"attribute\": [],\n" +
" \"telemetry\": [\n" +
" \"/5_1.0/0/3\",\n" +
" \"/5_1.0/0/5\",\n" +
" \"/5_1.0/0/6\",\n" +
" \"/5_1.0/0/7\",\n" +
" \"/5_1.0/0/9\",\n" +
" \"/9_1.0/0/0\",\n" +
" \"/9_1.0/0/1\",\n" +
" \"/9_1.0/0/7\",\n" +
" \"/9_1.0/0/9\"\n" +
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" },\n" +
" \"bootstrapServerUpdateEnable\": true,\n" +
" \"bootstrap\": [\n" +
" {\n" +
" \"host\": \"0.0.0.0\",\n" +
" \"port\": 5687,\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"shortServerId\": 111,\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
" \"bootstrapServerIs\": true,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" },\n" +
" {\n" +
" \"host\": \"0.0.0.0\",\n" +
" \"port\": 5685,\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"shortServerId\": 123,\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
" \"bootstrapServerIs\": false,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" }\n" +
" ],\n" +
" \"clientLwM2mSettings\": {\n" +
" \"edrxCycle\": null,\n" +
" \"powerMode\": \"DRX\",\n" +
" \"fwUpdateResource\": null,\n" +
" \"fwUpdateStrategy\": 1,\n" +
" \"psmActivityTimer\": null,\n" +
" \"swUpdateResource\": null,\n" +
" \"swUpdateStrategy\": 1,\n" +
" \"pagingTransmissionWindow\": null,\n" +
" \"clientOnlyObserveAfterConnect\": 1\n" +
" }\n" +
"}";
@Test
public void testFirmwareUpdateWithClientWithoutFirmwareOtaInfoFromProfile() throws Exception {
createDeviceProfile(TRANSPORT_CONFIGURATION);
NoSecClientCredential credentials = createNoSecClientCredentials(this.CLIENT_ENDPOINT_WITHOUT_FW_INFO);
final Device device = createDevice(credentials);
createNewClient(SECURITY, COAP_CONFIG, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO);
Thread.sleep(1000); protected final String OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA =
" {\n" +
" \"keyName\": {\n" +
" \"/5_1.0/0/3\": \"state\",\n" +
" \"/5_1.0/0/5\": \"updateResult\",\n" +
" \"/5_1.0/0/6\": \"pkgname\",\n" +
" \"/5_1.0/0/7\": \"pkgversion\",\n" +
" \"/5_1.0/0/9\": \"firmwareUpdateDeliveryMethod\",\n" +
" \"/9_1.0/0/0\": \"pkgname\",\n" +
" \"/9_1.0/0/1\": \"pkgversion\",\n" +
" \"/9_1.0/0/7\": \"updateState\",\n" +
" \"/9_1.0/0/9\": \"updateResult\"\n" +
" },\n" +
" \"observe\": [\n" +
" \"/5_1.0/0/3\",\n" +
" \"/5_1.0/0/5\",\n" +
" \"/5_1.0/0/6\",\n" +
" \"/5_1.0/0/7\",\n" +
" \"/5_1.0/0/9\",\n" +
" \"/9_1.0/0/0\",\n" +
" \"/9_1.0/0/1\",\n" +
" \"/9_1.0/0/7\",\n" +
" \"/9_1.0/0/9\"\n" +
" ],\n" +
" \"attribute\": [],\n" +
" \"telemetry\": [\n" +
" \"/5_1.0/0/3\",\n" +
" \"/5_1.0/0/5\",\n" +
" \"/5_1.0/0/6\",\n" +
" \"/5_1.0/0/7\",\n" +
" \"/5_1.0/0/9\",\n" +
" \"/9_1.0/0/0\",\n" +
" \"/9_1.0/0/1\",\n" +
" \"/9_1.0/0/7\",\n" +
" \"/9_1.0/0/9\"\n" +
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" }";
@Test
public void testFirmwareUpdateWithClientWithoutFirmwareOtaInfoFromProfile() throws Exception {
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS, getBootstrapServerCredentialsNoSec(NONE));
createDeviceProfile(transportConfiguration);
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_WITHOUT_FW_INFO));
final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO);
createNewClient(SECURITY_NO_SEC, COAP_CONFIG, false, this.CLIENT_ENDPOINT_WITHOUT_FW_INFO, false, null);
device.setFirmwareId(createFirmware().getId()); device.setFirmwareId(createFirmware().getId());
final Device savedDevice = doPost("/api/device", device, Device.class); final Device savedDevice = doPost("/api/device", device, Device.class);
@ -163,13 +118,11 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest {
@Test @Test
public void testFirmwareUpdateByObject5() throws Exception { public void testFirmwareUpdateByObject5() throws Exception {
createDeviceProfile(OTA_TRANSPORT_CONFIGURATION); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA, getBootstrapServerCredentialsNoSec(NONE));
NoSecClientCredential credentials = createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA5); createDeviceProfile(transportConfiguration);
final Device device = createDevice(credentials); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA5));
createNewClient(SECURITY, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA5); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA5);
createNewClient(SECURITY_NO_SEC, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA5, false, null);
Thread.sleep(1000);
device.setFirmwareId(createFirmware().getId()); device.setFirmwareId(createFirmware().getId());
final Device savedDevice = doPost("/api/device", device, Device.class); final Device savedDevice = doPost("/api/device", device, Device.class);
@ -200,10 +153,11 @@ public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest {
* */ * */
@Test @Test
public void testSoftwareUpdateByObject9() throws Exception { public void testSoftwareUpdateByObject9() throws Exception {
createDeviceProfile(OTA_TRANSPORT_CONFIGURATION); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA, getBootstrapServerCredentialsNoSec(NONE));
NoSecClientCredential credentials = createNoSecClientCredentials(CLIENT_ENDPOINT_OTA9); createDeviceProfile(transportConfiguration);
final Device device = createDevice(credentials); LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(this.CLIENT_ENDPOINT_OTA9));
createNewClient(SECURITY, COAP_CONFIG, false, CLIENT_ENDPOINT_OTA9); final Device device = createDevice(deviceCredentials, this.CLIENT_ENDPOINT_OTA9);
createNewClient(SECURITY_NO_SEC, COAP_CONFIG, false, this.CLIENT_ENDPOINT_OTA9, false, null);
Thread.sleep(1000); Thread.sleep(1000);

168
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java

@ -17,9 +17,11 @@ package org.thingsboard.server.transport.lwm2m.rpc;
import org.junit.Before; import org.junit.Before;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -31,8 +33,6 @@ import static org.eclipse.leshan.core.LwM2mId.FIRMWARE;
import static org.eclipse.leshan.core.LwM2mId.SERVER; import static org.eclipse.leshan.core.LwM2mId.SERVER;
import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT; import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.COAP_CONFIG;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.TEMPERATURE_SENSOR;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0;
@ -45,12 +45,12 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.resources;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE;
@DaoSqlTest @DaoSqlTest
public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
protected String RPC_TRANSPORT_CONFIGURATION; protected String OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC;
protected String deviceId; protected String deviceId;
public Set expectedObjects; public Set expectedObjects;
public Set expectedObjectIdVers; public Set expectedObjectIdVers;
@ -60,7 +60,7 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg
protected String objectInstanceIdVer_1; protected String objectInstanceIdVer_1;
protected String objectIdVer_0; protected String objectIdVer_0;
protected String objectIdVer_2; protected String objectIdVer_2;
private static final Predicate PREDICATE_3 = path -> (!((String) path).contains("/" + TEMPERATURE_SENSOR) && ((String) path).contains("/" + DEVICE)); private static final Predicate PREDICATE_3 = path -> (!((String) path).startsWith("/" + TEMPERATURE_SENSOR) && ((String) path).startsWith("/" + DEVICE));
protected String objectIdVer_3; protected String objectIdVer_3;
protected String objectInstanceIdVer_3; protected String objectInstanceIdVer_3;
protected String objectInstanceIdVer_5; protected String objectInstanceIdVer_5;
@ -70,27 +70,29 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg
protected String objectIdVer_3303; protected String objectIdVer_3303;
protected static AtomicInteger endpointSequence = new AtomicInteger(); protected static AtomicInteger endpointSequence = new AtomicInteger();
protected static String DEVICE_ENDPOINT_RPC_PREF = "deviceEndpointRpc"; protected static String DEVICE_ENDPOINT_RPC_PREF = "deviceEndpointRpc";
protected String idVer_3_0_9;
protected String idVer_19_0_0;
public AbstractRpcLwM2MIntegrationTest(){ public AbstractRpcLwM2MIntegrationTest() {
setResources(resources); setResources(resources);
} }
@Before @Before
public void beforeTest() throws Exception { public void startInitRPC() throws Exception {
String endpoint = DEVICE_ENDPOINT_RPC_PREF + endpointSequence.incrementAndGet(); initRpc();
init(); }
createNewClient (SECURITY, COAP_CONFIG, true, endpoint);
private void initRpc () throws Exception {
String endpoint = DEVICE_ENDPOINT_RPC_PREF + endpointSequence.incrementAndGet();
createNewClient(SECURITY_NO_SEC, COAP_CONFIG, true, endpoint, false, null);
expectedObjects = ConcurrentHashMap.newKeySet(); expectedObjects = ConcurrentHashMap.newKeySet();
expectedObjectIdVers = ConcurrentHashMap.newKeySet(); expectedObjectIdVers = ConcurrentHashMap.newKeySet();
expectedInstances = ConcurrentHashMap.newKeySet(); expectedInstances = ConcurrentHashMap.newKeySet();
expectedObjectIdVerInstances = ConcurrentHashMap.newKeySet(); expectedObjectIdVerInstances = ConcurrentHashMap.newKeySet();
client.getClient().getObjectTree().getObjectEnablers().forEach((key, val) -> { lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().forEach((key, val) -> {
if (key > 0) { if (key > 0) {
String objectVerId = "/" + key; String objectVerId = "/" + key;
if (!val.getObjectModel().version.equals("1.0")) { objectVerId += ("_" + val.getObjectModel().version);
objectVerId += ("_" + val.getObjectModel().version);
}
expectedObjects.add("/" + key); expectedObjects.add("/" + key);
expectedObjectIdVers.add(objectVerId); expectedObjectIdVers.add(objectVerId);
String finalObjectVerId = objectVerId; String finalObjectVerId = objectVerId;
@ -100,102 +102,58 @@ public abstract class AbstractRpcLwM2MIntegrationTest extends AbstractLwM2MInteg
}); });
} }
}); });
String ver_Id_0 = client.getClient().getObjectTree().getModel().getObjectModel(OBJECT_ID_0).version; String ver_Id_0 = lwM2MTestClient.getLeshanClient().getObjectTree().getModel().getObjectModel(OBJECT_ID_0).version;
if ("1.0".equals(ver_Id_0)) { objectIdVer_0 = "/" + OBJECT_ID_0 + "_" + ver_Id_0;
objectIdVer_0 = "/" + OBJECT_ID_0; objectIdVer_2 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).startsWith("/" + ACCESS_CONTROL)).findFirst().get();
} objectIdVer_3 = (String) expectedObjectIdVers.stream().filter(PREDICATE_3).findFirst().get();
else { objectIdVer_19 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).startsWith("/" + BINARY_APP_DATA_CONTAINER)).findFirst().get();
objectIdVer_0 = "/" + OBJECT_ID_0 + "_" + ver_Id_0; objectIdVer_3303 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).startsWith("/" + TEMPERATURE_SENSOR)).findFirst().get();
} objectInstanceIdVer_1 = (String) expectedObjectIdVerInstances.stream().filter(path -> (!((String) path).startsWith("/" + BINARY_APP_DATA_CONTAINER) && ((String) path).startsWith("/" + SERVER))).findFirst().get();
objectIdVer_2 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + ACCESS_CONTROL)).findFirst().get();
objectIdVer_3 = (String) expectedObjects.stream().filter(PREDICATE_3).findFirst().get();
objectIdVer_19 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + BINARY_APP_DATA_CONTAINER)).findFirst().get();
objectIdVer_3303 = (String) expectedObjectIdVers.stream().filter(path -> ((String) path).contains("/" + TEMPERATURE_SENSOR)).findFirst().get();
objectInstanceIdVer_1 = (String) expectedObjectIdVerInstances.stream().filter(path -> (!((String) path).contains("/" + BINARY_APP_DATA_CONTAINER) && ((String) path).contains("/" + SERVER))).findFirst().get();
objectInstanceIdVer_3 = (String) expectedObjectIdVerInstances.stream().filter(PREDICATE_3).findFirst().get(); objectInstanceIdVer_3 = (String) expectedObjectIdVerInstances.stream().filter(PREDICATE_3).findFirst().get();
objectInstanceIdVer_5 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).contains("/" + FIRMWARE)).findFirst().get(); objectInstanceIdVer_5 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).startsWith("/" + FIRMWARE)).findFirst().get();
objectInstanceIdVer_9 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).contains("/" + SOFTWARE_MANAGEMENT)).findFirst().get(); objectInstanceIdVer_9 = (String) expectedObjectIdVerInstances.stream().filter(path -> ((String) path).startsWith("/" + SOFTWARE_MANAGEMENT)).findFirst().get();
RPC_TRANSPORT_CONFIGURATION = "{\n" + idVer_3_0_9 = objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_9;
" \"type\": \"LWM2M\",\n" + idVer_19_0_0 = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0;
" \"observeAttr\": {\n" +
" \"keyName\": {\n" + OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC =
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_9 + "\": \"" + RESOURCE_ID_NAME_3_9 + "\",\n" +
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\": \"" + RESOURCE_ID_NAME_3_14 + "\",\n" +
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0 + "\": \"" + RESOURCE_ID_NAME_19_0_0 + "\",\n" +
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\": \"" + RESOURCE_ID_NAME_19_1_0 + "\"\n" +
" },\n" +
" \"observe\": [\n" +
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_9 + "\",\n" +
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0 + "\"\n" +
" ],\n" +
" \"attribute\": [\n" +
" ],\n" +
" \"telemetry\": [\n" +
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_9 + "\",\n" +
" \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\",\n" +
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0 + "\",\n" +
" \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\"\n" +
" ],\n" +
" \"attributeLwm2m\": {}\n" +
" },\n" +
" \"bootstrapServerUpdateEnable\": true,\n" +
" \"bootstrap\": [\n" +
" {\n" +
" \"host\": \"0.0.0.0\",\n" +
" \"port\": 5687,\n" +
" \"binding\": \"U\",\n" +
" \"lifetime\": 300,\n" +
" \"securityMode\": \"NO_SEC\",\n" +
" \"shortServerId\": 111,\n" +
" \"notifIfDisabled\": true,\n" +
" \"serverPublicKey\": \"\",\n" +
" \"defaultMinPeriod\": 1,\n" +
" \"bootstrapServerIs\": true,\n" +
" \"clientHoldOffTime\": 1,\n" +
" \"bootstrapServerAccountTimeout\": 0\n" +
" },\n" +
" {\n" + " {\n" +
" \"host\": \"0.0.0.0\",\n" + " \"keyName\": {\n" +
" \"port\": 5685,\n" + " \"" + idVer_3_0_9 + "\": \"" + RESOURCE_ID_NAME_3_9 + "\",\n" +
" \"binding\": \"U\",\n" + " \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\": \"" + RESOURCE_ID_NAME_3_14 + "\",\n" +
" \"lifetime\": 300,\n" + " \"" + idVer_19_0_0 + "\": \"" + RESOURCE_ID_NAME_19_0_0 + "\",\n" +
" \"securityMode\": \"NO_SEC\",\n" + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\": \"" + RESOURCE_ID_NAME_19_1_0 + "\"\n" +
" \"shortServerId\": 123,\n" + " },\n" +
" \"notifIfDisabled\": true,\n" + " \"observe\": [\n" +
" \"serverPublicKey\": \"\",\n" + " \"" + idVer_3_0_9 + "\",\n" +
" \"defaultMinPeriod\": 1,\n" + " \"" + idVer_19_0_0 + "\"\n" +
" \"bootstrapServerIs\": false,\n" + " ],\n" +
" \"clientHoldOffTime\": 1,\n" + " \"attribute\": [\n" +
" \"bootstrapServerAccountTimeout\": 0\n" + " \"" + objectIdVer_3 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_14 + "\"\n" +
" }\n" + " ],\n" +
" ],\n" + " \"telemetry\": [\n" +
" \"clientLwM2mSettings\": {\n" + " \"" + idVer_3_0_9 + "\",\n" +
" \"edrxCycle\": null,\n" + " \"" + idVer_19_0_0 + "\",\n" +
" \"powerMode\": \"DRX\",\n" + " \"" + objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "\"\n" +
" \"fwUpdateResource\": null,\n" + " ],\n" +
" \"fwUpdateStrategy\": 1,\n" + " \"attributeLwm2m\": {}\n" +
" \"psmActivityTimer\": null,\n" + " }";
" \"swUpdateResource\": null,\n" +
" \"swUpdateStrategy\": 1,\n" + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITH_PARAMS_RPC, getBootstrapServerCredentialsNoSec(NONE));
" \"pagingTransmissionWindow\": null,\n" + createDeviceProfile(transportConfiguration);
" \"clientOnlyObserveAfterConnect\": 1\n" +
" }\n" + LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(endpoint));
"}"; final Device device = createDevice(deviceCredentials, endpoint);
createDeviceProfile(RPC_TRANSPORT_CONFIGURATION);
NoSecClientCredential credentials = createNoSecClientCredentials(endpoint);
final Device device = createDevice(credentials);
deviceId = device.getId().getId().toString(); deviceId = device.getId().getId().toString();
client.start(); lwM2MTestClient.start(true);
} }
protected String pathIdVerToObjectId(String pathIdVer) { protected String pathIdVerToObjectId(String pathIdVer) {
if (pathIdVer.contains("_")){ if (pathIdVer.contains("_")) {
String [] objVer = pathIdVer.split("/"); String[] objVer = pathIdVer.split("/");
objVer[1] = objVer[1].split("_")[0]; objVer[1] = objVer[1].split("_")[0];
return String.join("/", objVer); return String.join("/", objVer);
} }
return pathIdVer; return pathIdVer;
} }

2
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java

@ -124,7 +124,7 @@ public class RpcLwm2mIntegrationDiscoverTest extends AbstractRpcLwM2MIntegration
String expectedInstance = (String) expectedInstances.stream().findFirst().get(); String expectedInstance = (String) expectedInstances.stream().findFirst().get();
String expectedObjectInstanceId = pathIdVerToObjectId(expectedInstance); String expectedObjectInstanceId = pathIdVerToObjectId(expectedInstance);
LwM2mPath expectedPath = new LwM2mPath(expectedObjectInstanceId); LwM2mPath expectedPath = new LwM2mPath(expectedObjectInstanceId);
int expectedResource = client.getClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey(); int expectedResource = lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey();
String expected = expectedInstance + "/" + expectedResource; String expected = expectedInstance + "/" + expectedResource;
String actualResult = sendDiscover(expected); String actualResult = sendDiscover(expected);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);

7
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java

@ -121,8 +121,8 @@ public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationT
/** /**
* bad: resource operation not "E" * bad: resource operation not "E"
* Execute {"id":"5/0/3"} * Execute {"id":"5_1.0/0/3"}
* {"result":"BAD_REQUEST","error":"Resource with /5/0/3 is not executable."} * {"result":"BAD_REQUEST","error":"Resource with /5_1.0/0/3 is not executable."}
*/ */
@Test @Test
public void testExecuteResourceWithOperationNotExecuteById_Result_METHOD_NOT_ALLOWED() throws Exception { public void testExecuteResourceWithOperationNotExecuteById_Result_METHOD_NOT_ALLOWED() throws Exception {
@ -130,8 +130,7 @@ public class RpcLwm2mIntegrationExecuteTest extends AbstractRpcLwM2MIntegrationT
String actualResult = sendRPCExecuteById(expectedPath); String actualResult = sendRPCExecuteById(expectedPath);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expectedObjectId = pathIdVerToObjectId((String) expectedPath); String expected = "Resource with " + expectedPath + " is not executable.";
String expected = "Resource with " + expectedObjectId + " is not executable.";
String actual = rpcActualResult.get("error").asText(); String actual = rpcActualResult.get("error").asText();
assertTrue(actual.equals(expected)); assertTrue(actual.equals(expected));
} }

78
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java

@ -26,11 +26,11 @@ import static org.eclipse.leshan.core.LwM2mId.ACCESS_CONTROL;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_DATA_CONTAINER;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_0; 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_3; 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;
public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationTest { public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationTest {
@ -40,23 +40,42 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
*/ */
@Test @Test
public void testObserveReadAllNothingObservation_Result_CONTENT_Value_Count_0() throws Exception { public void testObserveReadAllNothingObservation_Result_CONTENT_Value_Count_0() throws Exception {
String actualResultBefore = sendObserve("ObserveReadAll", null);
ObjectNode rpcActualResultBefore = JacksonUtil.fromString(actualResultBefore, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultBefore.get("result").asText());
assertTrue(rpcActualResultBefore.get("value").asText().contains(fromVersionedIdToObjectId(idVer_3_0_9)));
assertTrue(rpcActualResultBefore.get("value").asText().contains(fromVersionedIdToObjectId(idVer_19_0_0)));
String actualResult = sendObserve("ObserveCancelAll", null); String actualResult = sendObserve("ObserveCancelAll", null);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
actualResult = sendObserve("ObserveReadAll", null); assertEquals("2", rpcActualResult.get("value").asText());
rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); String actualResultAfter = sendObserve("ObserveReadAll", null);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); ObjectNode rpcActualResultAfter = JacksonUtil.fromString(actualResultAfter, ObjectNode.class);
assertEquals("[]", rpcActualResult.get("value").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultAfter.get("result").asText());
String expectResultAfter = "[]";
assertEquals( expectResultAfter, rpcActualResultAfter.get("value").asText());
} }
/** /**
* Observe {"id":"/3/0/9"} * Observe {"id":"/3/0/0"}
* @throws Exception * @throws Exception
*/ */
@Test @Test
public void testObserveSingleResource_Result_CONTENT_Value_SingleResource() throws Exception { public void testObserveSingleResourceWithout_IdVer_1_0_Result_CONTENT_Value_SingleResource() throws Exception {
String expectedIdVer = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; String expectedId = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0;
String actualResult = sendObserve("Observe", expectedIdVer); String actualResult = sendObserve("Observe", fromVersionedIdToObjectId(expectedId));
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource"));
}
/**
* Observe {"id":"/3_1.0/0/14"}
* @throws Exception
*/
@Test
public void testObserveSingleResourceWith_IdVer_1_0_Result_CONTENT_Value_SingleResource() throws Exception {
String expectedId = objectInstanceIdVer_3 + "/" + RESOURCE_ID_14;
String actualResult = sendObserve("Observe", expectedId);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource")); assertTrue(rpcActualResult.get("value").asText().contains("LwM2mSingleResource"));
@ -70,7 +89,7 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
public void testObserveWithBadVersion_Result_BadRequest_ErrorMsg_BadVersionMustBe1_0() throws Exception { public void testObserveWithBadVersion_Result_BadRequest_ErrorMsg_BadVersionMustBe1_0() 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); LwM2mPath expectedPath = new LwM2mPath(expectedInstance);
int expectedResource = client.getClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey(); int expectedResource = lwM2MTestClient.getLeshanClient().getObjectTree().getObjectEnablers().get(expectedPath.getObjectId()).getObjectModel().resources.entrySet().stream().findAny().get().getKey();
String expectedId = "/" + expectedPath.getObjectId() + "_1.2" + "/" + expectedPath.getObjectInstanceId() + "/" + expectedResource; String expectedId = "/" + expectedPath.getObjectId() + "_1.2" + "/" + expectedPath.getObjectInstanceId() + "/" + expectedResource;
String actualResult = sendObserve("Observe", expectedId); String actualResult = sendObserve("Observe", expectedId);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
@ -100,11 +119,10 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
*/ */
@Test @Test
public void testObserveNoImplementedResourceOnDeviceValueNull_Result_BadRequest() throws Exception { public void testObserveNoImplementedResourceOnDeviceValueNull_Result_BadRequest() throws Exception {
String objectIdVer = (String) expectedObjectIdVers.stream().filter(path -> ((String)path).contains("/" + BINARY_APP_DATA_CONTAINER)).findFirst().get(); String expected = objectIdVer_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_3;
String expected = objectIdVer + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0;
String actualResult = sendObserve("Observe", expected); String actualResult = sendObserve("Observe", expected);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
String expectedValue = "values MUST NOT be null"; String expectedValue = "value MUST NOT be null";
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
assertEquals(expectedValue, rpcActualResult.get("error").asText()); assertEquals(expectedValue, rpcActualResult.get("error").asText());
} }
@ -125,14 +143,12 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
/** /**
* Repeated request on Observe * Repeated request on Observe
* Observe {"id":"/3/0/0"} * Observe {"id":"/3/0/9"}
* @throws Exception * @throws Exception
*/ */
@Test @Test
public void testObserveRepeatedRequestObserveOnDevice_Result_BAD_REQUEST_ErrorMsg_AlreadyRegistered() throws Exception { public void testObserveRepeatedRequestObserveOnDevice_Result_BAD_REQUEST_ErrorMsg_AlreadyRegistered() throws Exception {
String expectedId = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; String actualResult = sendObserve("Observe", idVer_3_0_9);
sendObserve("Observe", expectedId);
String actualResult = sendObserve("Observe", expectedId);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String expected = "Observation is already registered!"; String expected = "Observation is already registered!";
@ -144,18 +160,18 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
* @throws Exception * @throws Exception
*/ */
@Test @Test
public void testObserveReadAll_Result_CONTENT_Value_Contains_Paths_Count_ObserveAll() throws Exception { public void testObserveReadAll_Result_CONTENT_Value_Contains_Paths_Count_ObserveReadAll() throws Exception {
sendObserve("ObserveCancelAll", null); String actualResultCancel = sendObserve("ObserveCancelAll", null);
String expectedId_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; ObjectNode rpcActualResultCancel = JacksonUtil.fromString(actualResultCancel, ObjectNode.class);
String expectedId_9 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_9; assertEquals(ResponseCode.CONTENT.getName(), rpcActualResultCancel.get("result").asText());
sendObserve("Observe", expectedId_0); sendObserve("Observe",idVer_19_0_0);
sendObserve("Observe", expectedId_9); sendObserve("Observe", idVer_3_0_9);
String actualResult = sendObserve("ObserveReadAll", null); String actualResult = sendObserve("ObserveReadAll", null);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
String actualValues = rpcActualResult.get("value").asText(); String actualValues = rpcActualResult.get("value").asText();
assertTrue(actualValues.contains(expectedId_0)); assertTrue(actualValues.contains(fromVersionedIdToObjectId(idVer_19_0_0)));
assertTrue(actualValues.contains(expectedId_9)); assertTrue(actualValues.contains(fromVersionedIdToObjectId(idVer_3_0_9)));
assertEquals(2, actualValues.split(",").length); assertEquals(2, actualValues.split(",").length);
} }
@ -167,11 +183,11 @@ public class RpcLwm2mIntegrationObserveTest extends AbstractRpcLwM2MIntegrationT
@Test @Test
public void testObserveCancelOneResource_Result_CONTENT_Value_Count_1() throws Exception { public void testObserveCancelOneResource_Result_CONTENT_Value_Count_1() throws Exception {
sendObserve("ObserveCancelAll", null); sendObserve("ObserveCancelAll", null);
String expectedId_0 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_0; String expectedId_3_0_3 = objectInstanceIdVer_3 + "/" + RESOURCE_ID_3;
String expectedId_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3; String expectedId_5_0_3 = objectInstanceIdVer_5 + "/" + RESOURCE_ID_3;
sendObserve("Observe", expectedId_0); sendObserve("Observe", expectedId_3_0_3);
sendObserve("Observe", expectedId_3); sendObserve("Observe", expectedId_5_0_3);
String actualResult = sendObserve("ObserveCancel", expectedId_0); String actualResult = sendObserve("ObserveCancel", expectedId_3_0_3);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText()); assertEquals(ResponseCode.CONTENT.getName(), rpcActualResult.get("result").asText());
assertEquals("1", rpcActualResult.get("value").asText()); assertEquals("1", rpcActualResult.get("value").asText());

25
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java

@ -30,6 +30,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.BINARY_APP_
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_INSTANCE_ID_1;
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_0;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_0_3;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_19_1_0; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_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_14;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_NAME_3_9;
@ -181,11 +182,12 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest
* ReadComposite {"keys":["batteryLevel", "UtfOffset", "dataRead", "dataWrite"]} * ReadComposite {"keys":["batteryLevel", "UtfOffset", "dataRead", "dataWrite"]}
*/ */
@Test @Test
public void testReadCompositeSingleResourceByKeys_Result_CONTENT_Value_3_0_IsLwM2mSingleResource_19_0_0_AND_19_0_1_Null() throws Exception { public void testReadCompositeSingleResourceByKeys_Result_CONTENT_Value_3_0_IsLwM2mSingleResource_19_0_0_AND_19_0_1__IsLwM2mMultipleResource() throws Exception {
String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9; String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9;
String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14; String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14;
String expectedKey19_0_0 = RESOURCE_ID_NAME_19_0_0; String expectedKey19_0_0 = RESOURCE_ID_NAME_19_0_0;
String expectedKey19_1_0 = RESOURCE_ID_NAME_19_1_0; String expectedKey19_1_0 = RESOURCE_ID_NAME_19_1_0;
String expectedKey19_X_0 = "=LwM2mMultipleResource [id=0, values={0=LwM2mResourceInstance [id=0, value=1Bytes, type=OPAQUE]";
String expectedKeys = "[\"" + expectedKey3_0_9 + "\", \"" + expectedKey3_0_14 + "\", \"" + expectedKey19_0_0 + "\", \"" + expectedKey19_1_0 + "\"]"; String expectedKeys = "[\"" + expectedKey3_0_9 + "\", \"" + expectedKey3_0_14 + "\", \"" + expectedKey19_0_0 + "\", \"" + expectedKey19_1_0 + "\"]";
String actualResult = sendCompositeRPCByKeys(expectedKeys); String actualResult = sendCompositeRPCByKeys(expectedKeys);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class); ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
@ -194,8 +196,8 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest
String objectId_19 = pathIdVerToObjectId(objectIdVer_19); String objectId_19 = pathIdVerToObjectId(objectIdVer_19);
String expected3_0_9 = objectInstanceId_3 + "/" + RESOURCE_ID_9 + "=LwM2mSingleResource [id=" + RESOURCE_ID_9 + ", value="; String expected3_0_9 = objectInstanceId_3 + "/" + RESOURCE_ID_9 + "=LwM2mSingleResource [id=" + RESOURCE_ID_9 + ", value=";
String expected3_0_14 = objectInstanceId_3 + "/" + RESOURCE_ID_14 + "=LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value="; String expected3_0_14 = objectInstanceId_3 + "/" + RESOURCE_ID_14 + "=LwM2mSingleResource [id=" + RESOURCE_ID_14 + ", value=";
String expected19_0_0 = objectId_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0 + "=null"; String expected19_0_0 = objectId_19 + "/" + OBJECT_INSTANCE_ID_0 + "/" + RESOURCE_ID_0 + expectedKey19_X_0;
String expected19_1_0 = objectId_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + "=null"; String expected19_1_0 = objectId_19 + "/" + OBJECT_INSTANCE_ID_1 + "/" + RESOURCE_ID_0 + expectedKey19_X_0;
String actualValues = rpcActualResult.get("value").asText(); String actualValues = rpcActualResult.get("value").asText();
assertTrue(actualValues.contains(expected3_0_9)); assertTrue(actualValues.contains(expected3_0_9));
assertTrue(actualValues.contains(expected3_0_14)); assertTrue(actualValues.contains(expected3_0_14));
@ -203,6 +205,23 @@ public class RpcLwm2mIntegrationReadTest extends AbstractRpcLwM2MIntegrationTest
assertTrue(actualValues.contains(expected19_1_0)); assertTrue(actualValues.contains(expected19_1_0));
} }
/**
* ReadComposite {"keys":["batteryLevel", "UtfOffset", "dataDescription"]}
*/
@Test
public void testReadCompositeSingleResourceByKeys_Result_CONTENT_Value_3_0_IsLwM2mSingleResource_19_0_3_IsNotConfiguredInTheDeviceProfile() throws Exception {
String expectedKey3_0_9 = RESOURCE_ID_NAME_3_9;
String expectedKey3_0_14 = RESOURCE_ID_NAME_3_14;
String expectedKey19_0_3 = RESOURCE_ID_NAME_19_0_3;
String expectedKeys = "[\"" + expectedKey3_0_9 + "\", \"" + expectedKey3_0_14 + "\", \"" + expectedKey19_0_3 + "\"]";
String actualResult = sendCompositeRPCByKeys(expectedKeys);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.BAD_REQUEST.getName(), rpcActualResult.get("result").asText());
String actualValue = rpcActualResult.get("error").asText();
String expectedValue = expectedKey19_0_3 + " is not configured in the device profile!";
assertEquals(actualValue, expectedValue);
}
private String sendRPCById(String path) throws Exception { private String sendRPCById(String path) throws Exception {
String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}"; String setRpcRequest = "{\"method\": \"Read\", \"params\": {\"id\": \"" + path + "\"}}";

324
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java

@ -15,11 +15,38 @@
*/ */
package org.thingsboard.server.transport.lwm2m.security; package org.thingsboard.server.transport.lwm2m.security;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.eclipse.californium.elements.config.Configuration;
import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.core.ResponseCode;
import org.eclipse.leshan.core.util.Hex;
import org.junit.Assert;
import org.springframework.test.web.servlet.MvcResult;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MBootstrapClientCredentials; 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.LwM2MSecurityMode;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecBootstrapClientCredential;
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.credentials.lwm2m.RPKBootstrapClientCredential;
import org.thingsboard.server.common.data.device.credentials.lwm2m.X509BootstrapClientCredential;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.PSKLwM2MBootstrapServerCredential;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.RPKLwM2MBootstrapServerCredential;
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.X509LwM2MBootstrapServerCredential;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest;
import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; import org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState;
import org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -27,28 +54,50 @@ import java.security.GeneralSecurityException;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import static org.awaitility.Awaitility.await;
import static org.eclipse.leshan.client.object.Security.noSecBootstap;
import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_STARTED;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_DEREGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.OBJECT_ID_1;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.RESOURCE_ID_9;
@DaoSqlTest @DaoSqlTest
@Slf4j
public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { public abstract class AbstractSecurityLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest {
protected final String CREDENTIALS_PATH = "lwm2m/credentials/"; // client public key or id used for PSK protected final String CREDENTIALS_PATH = "lwm2m/credentials/"; // client public key or id used for PSK
// Get keys PSK // Get keys PSK
protected final String CLIENT_PSK_IDENTITY = "SOME_PSK_ID"; // client public key or id used for PSK protected final String CLIENT_PSK_IDENTITY = "SOME_PSK_ID"; // client public key or id used for PSK
protected final String CLIENT_PSK_IDENTITY_BS = "SOME_PSK_ID_BS"; // client public key or id used for PSK
protected final String CLIENT_PSK_KEY = "73656372657450534b73656372657450"; // client private/secret key used for PSK protected final String CLIENT_PSK_KEY = "73656372657450534b73656372657450"; // client private/secret key used for PSK
// Server // Server
protected static final String SERVER_JKS_FOR_TEST = "lwm2mserver"; protected static final String SERVER_JKS_FOR_TEST = "lwm2mserver";
protected static final String SERVER_STORE_PWD = "server_ks_password"; protected static final String SERVER_STORE_PWD = "server_ks_password";
protected static final String SERVER_CERT_ALIAS = "server"; protected static final String SERVER_CERT_ALIAS = "server";
protected final X509Certificate serverX509Cert; // server certificate signed by rootCA protected static final String SERVER_CERT_ALIAS_BS = "bootstrap";
protected final PublicKey serverPublicKeyFromCert; // server public key used for RPK protected final X509Certificate serverX509Cert; // server certificate signed by rootCA
protected final X509Certificate serverX509CertBs; // serverBs certificate signed by rootCA
protected final PublicKey serverPublicKeyFromCert; // server public key used for RPK
protected final PublicKey serverPublicKeyFromCertBs; // serverBs public key used for RPK
// Client // Client
protected LwM2MTestClient client;
protected static final String CLIENT_ENDPOINT_NO_SEC = "LwNoSec00000000"; protected static final String CLIENT_ENDPOINT_NO_SEC = "LwNoSec00000000";
protected static final String CLIENT_ENDPOINT_NO_SEC_BS = "LwNoSecBs00000000";
protected static final String CLIENT_ENDPOINT_PSK = "LwPsk00000000"; protected static final String CLIENT_ENDPOINT_PSK = "LwPsk00000000";
protected static final String CLIENT_ENDPOINT_PSK_BS = "LwPskBs00000000";
protected static final String CLIENT_ENDPOINT_RPK = "LwRpk00000000"; protected static final String CLIENT_ENDPOINT_RPK = "LwRpk00000000";
protected static final String CLIENT_ENDPOINT_RPK_BS = "LwRpkBs00000000";
protected static final String CLIENT_ENDPOINT_X509_TRUST = "LwX50900000000"; protected static final String CLIENT_ENDPOINT_X509_TRUST = "LwX50900000000";
protected static final String CLIENT_ENDPOINT_X509_TRUST_NO = "LwX509TrustNo"; protected static final String CLIENT_ENDPOINT_X509_TRUST_NO = "LwX509TrustNo";
protected static final String CLIENT_JKS_FOR_TEST = "lwm2mclient"; protected static final String CLIENT_JKS_FOR_TEST = "lwm2mclient";
@ -56,19 +105,16 @@ protected final X509Certificate serverX509Cert;
protected static final String CLIENT_ALIAS_CERT_TRUST = "client_alias_00000000"; protected static final String CLIENT_ALIAS_CERT_TRUST = "client_alias_00000000";
protected static final String CLIENT_ALIAS_CERT_TRUST_NO = "client_alias_trust_no"; protected static final String CLIENT_ALIAS_CERT_TRUST_NO = "client_alias_trust_no";
protected final X509Certificate clientX509CertTrust; // client certificate signed by intermediate, rootCA with a good CN ("host name") protected final X509Certificate clientX509CertTrust; // client certificate signed by intermediate, rootCA with a good CN ("host name")
protected final PrivateKey clientPrivateKeyFromCertTrust; // client private key used for X509 and RPK protected final PrivateKey clientPrivateKeyFromCertTrust; // client private key used for X509 and RPK
protected final PublicKey clientPublicKeyFromCertTrust; // client public key used for RPK protected final X509Certificate clientX509CertTrustNo; // client certificate signed by intermediate, rootCA with a good CN ("host name")
protected final X509Certificate clientX509CertTrustNo; // client certificate signed by intermediate, rootCA with a good CN ("host name") protected final PrivateKey clientPrivateKeyFromCertTrustNo; // client private key used for X509 and RPK
protected final PrivateKey clientPrivateKeyFromCertTrustNo; // client private key used for X509 and RPK private final String[] RESOURCES_SECURITY = new String[]{"1.xml", "2.xml", "3.xml", "5.xml", "9.xml"};
protected final PublicKey clientPublicKeyFromCertTrustNo; // client public key used for RPK
private final String[] RESOURCES_SECURITY = new String[]{"1.xml", "2.xml", "3.xml", "5.xml", "9.xml"};
private final LwM2MBootstrapClientCredentials defaultBootstrapCredentials; private final LwM2MBootstrapClientCredentials defaultBootstrapCredentials;
public AbstractSecurityLwM2MIntegrationTest() { public AbstractSecurityLwM2MIntegrationTest() {
// create client credentials // create client credentials
setResources(this.RESOURCES_SECURITY); setResources(this.RESOURCES_SECURITY);
@ -82,12 +128,9 @@ protected final X509Certificate serverX509Cert;
// Trust // Trust
clientPrivateKeyFromCertTrust = (PrivateKey) clientKeyStore.getKey(CLIENT_ALIAS_CERT_TRUST, clientKeyStorePwd); clientPrivateKeyFromCertTrust = (PrivateKey) clientKeyStore.getKey(CLIENT_ALIAS_CERT_TRUST, clientKeyStorePwd);
clientX509CertTrust = (X509Certificate) clientKeyStore.getCertificate(CLIENT_ALIAS_CERT_TRUST); clientX509CertTrust = (X509Certificate) clientKeyStore.getCertificate(CLIENT_ALIAS_CERT_TRUST);
clientPublicKeyFromCertTrust = clientX509CertTrust != null ? clientX509CertTrust.getPublicKey() : null;
// No trust // No trust
clientPrivateKeyFromCertTrustNo = (PrivateKey) clientKeyStore.getKey(CLIENT_ALIAS_CERT_TRUST_NO, clientKeyStorePwd); clientPrivateKeyFromCertTrustNo = (PrivateKey) clientKeyStore.getKey(CLIENT_ALIAS_CERT_TRUST_NO, clientKeyStorePwd);
clientX509CertTrustNo = (X509Certificate) clientKeyStore.getCertificate(CLIENT_ALIAS_CERT_TRUST_NO); clientX509CertTrustNo = (X509Certificate) clientKeyStore.getCertificate(CLIENT_ALIAS_CERT_TRUST_NO);
clientPublicKeyFromCertTrustNo = clientX509CertTrustNo != null ? clientX509CertTrustNo.getPublicKey() : null;
} catch (GeneralSecurityException | IOException e) { } catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -103,6 +146,9 @@ protected final X509Certificate serverX509Cert;
serverX509Cert = (X509Certificate) serverKeyStore.getCertificate(SERVER_CERT_ALIAS); serverX509Cert = (X509Certificate) serverKeyStore.getCertificate(SERVER_CERT_ALIAS);
serverPublicKeyFromCert = serverX509Cert.getPublicKey(); serverPublicKeyFromCert = serverX509Cert.getPublicKey();
serverX509CertBs = (X509Certificate) serverKeyStore.getCertificate(SERVER_CERT_ALIAS_BS);
serverPublicKeyFromCertBs = serverX509CertBs.getPublicKey();
} catch (GeneralSecurityException | IOException e) { } catch (GeneralSecurityException | IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -114,4 +160,248 @@ protected final X509Certificate serverX509Cert;
defaultBootstrapCredentials.setBootstrapServer(serverCredentials); defaultBootstrapCredentials.setBootstrapServer(serverCredentials);
defaultBootstrapCredentials.setLwm2mServer(serverCredentials); defaultBootstrapCredentials.setLwm2mServer(serverCredentials);
} }
public void basicTestConnectionBefore(String clientEndpoint,
String awaitAlias,
LwM2MProfileBootstrapConfigType type,
Set<LwM2MClientState> expectedStatuses,
LwM2MClientState finishState) throws Exception {
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsNoSec(type));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint));
this.basicTestConnection(noSecBootstap(URI_BS),
deviceCredentials,
COAP_CONFIG_BS,
clientEndpoint,
transportConfiguration,
awaitAlias,
expectedStatuses,
true,
finishState,
false);
}
protected void basicTestConnection(Security security,
LwM2MDeviceCredentials deviceCredentials,
Configuration coapConfig,
String endpoint,
Lwm2mDeviceProfileTransportConfiguration transportConfiguration,
String awaitAlias,
Set<LwM2MClientState> expectedStatuses,
boolean isBootstrap,
LwM2MClientState finishState,
boolean isStartLw) throws Exception {
createNewClient(security, coapConfig, true, endpoint, isBootstrap, null);
createDeviceProfile(transportConfiguration);
final Device device = createDevice(deviceCredentials, endpoint);
device.getId().getId().toString();
lwM2MTestClient.start(isStartLw);
await(awaitAlias)
.atMost(1000, TimeUnit.MILLISECONDS)
.until(() -> finishState.equals(lwM2MTestClient.getClientState()));
Assert.assertEquals(expectedStatuses, lwM2MTestClient.getClientStates());
}
public void basicTestConnectionBootstrapRequestTriggerBefore(String clientEndpoint, String awaitAlias, LwM2MProfileBootstrapConfigType type) throws Exception {
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsNoSec(type));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint));
this.basicTestConnectionBootstrapRequestTrigger(
SECURITY_NO_SEC,
deviceCredentials,
COAP_CONFIG,
clientEndpoint,
transportConfiguration,
awaitAlias,
expectedStatusesRegistrationLwm2mSuccess,
expectedStatusesRegistrationBsSuccess,
false,
SECURITY_NO_SEC_BS);
}
private void basicTestConnectionBootstrapRequestTrigger(Security security,
LwM2MDeviceCredentials deviceCredentials,
Configuration coapConfig,
String endpoint,
Lwm2mDeviceProfileTransportConfiguration transportConfiguration,
String awaitAlias,
Set<LwM2MClientState> expectedStatusesLwm2m,
Set<LwM2MClientState> expectedStatusesBs,
boolean isBootstrap,
Security securityBs) throws Exception {
createNewClient(security, coapConfig, true, endpoint, isBootstrap, securityBs);
createDeviceProfile(transportConfiguration);
final Device device = createDevice(deviceCredentials, endpoint);
String deviceId = device.getId().getId().toString();
lwM2MTestClient.start(true);
await(awaitAlias)
.atMost(1000, TimeUnit.MILLISECONDS)
.until(() -> ON_REGISTRATION_SUCCESS.equals(lwM2MTestClient.getClientState()));
Assert.assertEquals(expectedStatusesLwm2m, lwM2MTestClient.getClientStates());
String executedPath = "/" + OBJECT_ID_1 + "_" + lwM2MTestClient.getLeshanClient().getObjectTree().getModel().getObjectModel(OBJECT_ID_1).version
+ "/0/" + RESOURCE_ID_9;
String actualResult = sendRPCSecurityExecuteById(executedPath, deviceId, endpoint);
ObjectNode rpcActualResult = JacksonUtil.fromString(actualResult, ObjectNode.class);
assertEquals(ResponseCode.CHANGED.getName(), rpcActualResult.get("result").asText());
expectedStatusesBs.add(ON_DEREGISTRATION_STARTED);
expectedStatusesBs.add(ON_DEREGISTRATION_SUCCESS);
await(awaitAlias)
.atMost(1000, TimeUnit.MILLISECONDS)
.until(() -> ON_REGISTRATION_SUCCESS.equals(lwM2MTestClient.getClientState()));
Assert.assertEquals(expectedStatusesBs, lwM2MTestClient.getClientStates());
}
protected List<LwM2MBootstrapServerCredential> getBootstrapServerCredentialsSecure(LwM2MSecurityMode mode, LwM2MProfileBootstrapConfigType bootstrapConfigType) {
List<LwM2MBootstrapServerCredential> bootstrap = new ArrayList<>();
switch (bootstrapConfigType) {
case BOTH:
bootstrap.add(getBootstrapServerCredential(mode, false));
bootstrap.add(getBootstrapServerCredential(mode, true));
break;
case BOOTSTRAP_ONLY:
bootstrap.add(getBootstrapServerCredential(mode, true));
break;
case LWM2M_ONLY:
bootstrap.add(getBootstrapServerCredential(mode, false));
break;
case NONE:
}
return bootstrap;
}
private AbstractLwM2MBootstrapServerCredential getBootstrapServerCredential(LwM2MSecurityMode mode, boolean isBootstrap) {
AbstractLwM2MBootstrapServerCredential bootstrapServerCredential;
switch (mode) {
case PSK:
bootstrapServerCredential = new PSKLwM2MBootstrapServerCredential();
bootstrapServerCredential.setServerPublicKey("");
break;
case RPK:
bootstrapServerCredential = new RPKLwM2MBootstrapServerCredential();
if (isBootstrap) {
bootstrapServerCredential.setServerPublicKey(Base64.encodeBase64String(serverPublicKeyFromCertBs.getEncoded()));
} else {
bootstrapServerCredential.setServerPublicKey(Base64.encodeBase64String(serverPublicKeyFromCert.getEncoded()));
}
break;
case X509:
bootstrapServerCredential = new X509LwM2MBootstrapServerCredential();
try {
if (isBootstrap) {
bootstrapServerCredential.setServerPublicKey(Base64.encodeBase64String(serverX509CertBs.getEncoded()));
} else {
bootstrapServerCredential.setServerPublicKey(Base64.encodeBase64String(serverX509Cert.getEncoded()));
}
} catch (CertificateEncodingException e) {
e.printStackTrace();
}
break;
default:
throw new IllegalStateException("Unexpected value: " + mode);
}
bootstrapServerCredential.setShortServerId(isBootstrap ? shortServerIdBs : shortServerId);
bootstrapServerCredential.setBootstrapServerIs(isBootstrap);
bootstrapServerCredential.setHost(isBootstrap ? hostBs : host);
bootstrapServerCredential.setPort(isBootstrap ? securityPortBs : securityPort);
return bootstrapServerCredential;
}
protected LwM2MDeviceCredentials getDeviceCredentialsSecure(LwM2MClientCredential clientCredentials,
PrivateKey privateKey,
X509Certificate certificate,
LwM2MSecurityMode mode,
boolean privateKeyIsBad) {
LwM2MDeviceCredentials credentials = new LwM2MDeviceCredentials();
credentials.setClient(clientCredentials);
LwM2MBootstrapClientCredentials bootstrapCredentials;
switch (mode) {
case PSK:
bootstrapCredentials = getBootstrapClientCredentialsPsk(clientCredentials);
break;
case RPK:
bootstrapCredentials = getBootstrapClientCredentialsRpk(certificate, privateKey, privateKeyIsBad);
break;
case X509:
bootstrapCredentials = getBootstrapClientCredentialsX509(certificate, privateKey, privateKeyIsBad);
break;
default:
throw new IllegalStateException("Unexpected value: " + mode);
}
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;
}
private LwM2MBootstrapClientCredentials getBootstrapClientCredentialsRpk(X509Certificate certificate, PrivateKey privateKey, boolean privateKeyIsBad) {
LwM2MBootstrapClientCredentials bootstrapCredentials = new LwM2MBootstrapClientCredentials();
RPKBootstrapClientCredential serverCredentials = new RPKBootstrapClientCredential();
if (certificate != null && certificate.getPublicKey() != null && privateKey != null) {
serverCredentials.setClientPublicKeyOrId(Base64.encodeBase64String(certificate.getPublicKey().getEncoded()));
if (privateKeyIsBad) {
serverCredentials.setClientSecretKey(Hex.encodeHexString(privateKey.getEncoded()));
} else {
serverCredentials.setClientSecretKey(Base64.encodeBase64String(privateKey.getEncoded()));
}
}
bootstrapCredentials.setBootstrapServer(serverCredentials);
bootstrapCredentials.setLwm2mServer(serverCredentials);
return bootstrapCredentials;
}
private LwM2MBootstrapClientCredentials getBootstrapClientCredentialsX509(X509Certificate certificate, PrivateKey privateKey, boolean privateKeyIsBad) {
LwM2MBootstrapClientCredentials bootstrapCredentials = new LwM2MBootstrapClientCredentials();
X509BootstrapClientCredential serverCredentials = new X509BootstrapClientCredential();
if (certificate != null) {
try {
serverCredentials.setClientPublicKeyOrId(Base64.encodeBase64String(certificate.getEncoded()));
if (privateKeyIsBad) {
serverCredentials.setClientSecretKey(Hex.encodeHexString(privateKey.getEncoded()));
} else {
serverCredentials.setClientSecretKey(Base64.encodeBase64String(privateKey.getEncoded()));
}
} catch (CertificateEncodingException e) {
log.error("Client`s certificate [{}] is bad. [{}]", certificate, e.getMessage());
}
}
bootstrapCredentials.setBootstrapServer(serverCredentials);
bootstrapCredentials.setLwm2mServer(serverCredentials);
return bootstrapCredentials;
}
protected MvcResult createDeviceWithMvcResult(LwM2MDeviceCredentials credentials, String endpoint) throws Exception {
Device device = new Device();
device.setName(endpoint);
device.setDeviceProfileId(deviceProfile.getId());
device.setTenantId(tenantId);
device = doPost("/api/device", device, Device.class);
Assert.assertNotNull(device);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class);
Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId());
deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS);
deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials));
return doPost("/api/device/credentials", deviceCredentials).andReturn();
}
private String sendRPCSecurityExecuteById(String path, String deviceId, String endpoint) throws Exception {
log.info("endpoint1: [{}]", endpoint);
String setRpcRequest = "{\"method\": \"Execute\", \"params\": {\"id\": \"" + path + "\"}}";
return doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setRpcRequest, String.class, status().isOk());
}
} }

76
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java

@ -15,21 +15,81 @@
*/ */
package org.thingsboard.server.transport.lwm2m.security.sql; package org.thingsboard.server.transport.lwm2m.security.sql;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.COAP_CONFIG; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_BOOTSTRAP_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURITY; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOOTSTRAP_ONLY;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.LWM2M_ONLY;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE;
@Slf4j
public class NoSecLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest { public class NoSecLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest {
//Lwm2m only
@Test @Test
public void testConnectAndObserveTelemetry() throws Exception { public void testWithNoSecConnectLwm2mSuccessAndObserveTelemetry() throws Exception {
NoSecClientCredential clientCredentials = createNoSecClientCredentials(CLIENT_ENDPOINT_NO_SEC); String clientEndpoint = CLIENT_ENDPOINT_NO_SEC;
super.basicTestConnectionObserveTelemetry(SECURITY, clientCredentials, COAP_CONFIG, CLIENT_ENDPOINT_NO_SEC); LwM2MDeviceCredentials clientCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint));
super.basicTestConnectionObserveTelemetry(SECURITY_NO_SEC, clientCredentials, COAP_CONFIG, clientEndpoint);
} }
// Bootstrap + Lwm2m
@Test
public void testWithNoSecConnectBsSuccess_UpdateTwoSectionsBootstrapAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS;
String awaitAlias = "await on client state (NoSecBS two section)";
basicTestConnectionBefore(clientEndpoint, awaitAlias, BOTH, expectedStatusesRegistrationBsSuccess, ON_REGISTRATION_SUCCESS);
}
@Test
public void testWithNoSecConnectBsSuccess_UpdateLwm2mSectionAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + LWM2M_ONLY.name();
String awaitAlias = "await on client state (NoSecBS Lwm2m section)";
basicTestConnectionBefore(clientEndpoint, awaitAlias, LWM2M_ONLY, expectedStatusesRegistrationBsSuccess, ON_REGISTRATION_SUCCESS);
}
@Test
public void testWithNoSecConnectBsSuccess_UpdateBootstrapSectionAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_NO_SEC + BOOTSTRAP_ONLY.name();
String awaitAlias = "await on client state (NoSecBS Bootstrap section)";
basicTestConnectionBefore(clientEndpoint, awaitAlias, BOOTSTRAP_ONLY, expectedStatusesBsSuccess, ON_BOOTSTRAP_SUCCESS);
}
@Test
public void testWithNoSecConnectBsSuccess_UpdateNoneSectionAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_NO_SEC + NONE.name();
String awaitAlias = "await on client state (NoSecBS None section)";
basicTestConnectionBefore(clientEndpoint, awaitAlias, NONE, expectedStatusesBsSuccess, ON_BOOTSTRAP_SUCCESS);
}
@Test
public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateTwoSectionAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + BOTH.name();
String awaitAlias = "await on client state (NoSecBS Trigger Two section)";
basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, BOTH);
}
@Test
public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateBootstrapSectionAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + BOOTSTRAP_ONLY.name();
String awaitAlias = "await on client state (NoSecBS Trigger Bootstrap section)";
basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, BOOTSTRAP_ONLY);
}
@Test
public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateLwm2mSectionAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + LWM2M_ONLY.name();
String awaitAlias = "await on client state (NoSecBS Trigger Lwm2m section)";
basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, LWM2M_ONLY);
}
@Test
public void testWithNoSecConnectLwm2mSuccessBootstrapRequestTriggerConnectBsSuccess_UpdateNoneSectionAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_NO_SEC_BS + "Trigger" + NONE.name();
String awaitAlias = "await on client state (NoSecBS Trigger None section)";
basicTestConnectionBootstrapRequestTriggerBefore(clientEndpoint, awaitAlias, NONE);
}
} }

91
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java

@ -18,28 +18,97 @@ package org.thingsboard.server.transport.lwm2m.security.sql;
import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.Hex;
import org.junit.Test; import org.junit.Test;
import org.springframework.test.web.servlet.MvcResult;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredential;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import static org.eclipse.leshan.client.object.Security.psk; import static org.eclipse.leshan.client.object.Security.psk;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP_CONFIG; import static org.eclipse.leshan.client.object.Security.pskBootstrap;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_URI; import static org.junit.Assert.assertEquals;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID; import static org.junit.Assert.assertTrue;
import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.PSK;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE;
public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTest { public class PskLwm2mIntegrationTest extends AbstractSecurityLwM2MIntegrationTest {
//Lwm2m only
@Test @Test
public void testConnectWithPSKAndObserveTelemetry() throws Exception { public void testWithPskConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_PSK;
String identity = CLIENT_PSK_IDENTITY;
String keyPsk = CLIENT_PSK_KEY;
PSKClientCredential clientCredentials = new PSKClientCredential(); PSKClientCredential clientCredentials = new PSKClientCredential();
clientCredentials.setEndpoint(CLIENT_ENDPOINT_PSK); clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setKey(CLIENT_PSK_KEY); clientCredentials.setIdentity(identity);
clientCredentials.setIdentity(CLIENT_PSK_IDENTITY); clientCredentials.setKey(keyPsk);
Security security = psk(SECURE_URI, Security security = psk(SECURE_URI,
SHORT_SERVER_ID, shortServerId,
CLIENT_PSK_IDENTITY.getBytes(StandardCharsets.UTF_8), identity.getBytes(StandardCharsets.UTF_8),
Hex.decodeHex(CLIENT_PSK_KEY.toCharArray())); Hex.decodeHex(keyPsk.toCharArray()));
super.basicTestConnectionObserveTelemetry(security, clientCredentials, SECURE_COAP_CONFIG, CLIENT_ENDPOINT_PSK); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false);
this.basicTestConnection(security,
deviceCredentials,
COAP_CONFIG,
clientEndpoint,
transportConfiguration,
"await on client state (Psk_Lwm2m)",
expectedStatusesRegistrationLwm2mSuccess,
false,
ON_REGISTRATION_SUCCESS,
true);
}
@Test
public void testWithPskConnectLwm2mBadPskKeyByLength_BAD_REQUEST() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_PSK;
String identity = CLIENT_PSK_IDENTITY + "_BadLength";
String keyPsk = CLIENT_PSK_KEY + "05AC";
PSKClientCredential clientCredentials = new PSKClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setIdentity(identity);
clientCredentials.setKey(keyPsk);
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, NONE));
createDeviceProfile(transportConfiguration);
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false);
MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint);
assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus());
String msgExpected = "Key must be HexDec format: 32, 64, 128 characters!";
assertTrue(result.getResponse().getContentAsString().contains(msgExpected));
}
// Bootstrap + Lwm2m
@Test
public void testWithPskConnectBsSuccess_UpdateTwoSectionsBootstrapAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_PSK_BS;
String identity = CLIENT_PSK_IDENTITY_BS;
String keyPsk = CLIENT_PSK_KEY;
PSKClientCredential clientCredentials = new PSKClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setIdentity(identity);
clientCredentials.setKey(keyPsk);
Security securityBs = pskBootstrap(SECURE_URI_BS,
identity.getBytes(StandardCharsets.UTF_8),
Hex.decodeHex(keyPsk.toCharArray()));
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(PSK, BOTH));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, null, null, PSK, false);
this.basicTestConnection(securityBs,
deviceCredentials,
COAP_CONFIG_BS,
clientEndpoint,
transportConfiguration,
"await on client state (PskBS two section)",
expectedStatusesRegistrationBsSuccess,
true,
ON_REGISTRATION_SUCCESS,
true);
} }
} }

115
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java

@ -15,30 +15,117 @@
*/ */
package org.thingsboard.server.transport.lwm2m.security.sql; package org.thingsboard.server.transport.lwm2m.security.sql;
import org.apache.commons.codec.binary.Base64;
import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.core.util.Hex;
import org.junit.Test; import org.junit.Test;
import org.springframework.test.web.servlet.MvcResult;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredential;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest;
import org.apache.commons.codec.binary.Base64;; import javax.servlet.http.HttpServletResponse;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import static org.eclipse.leshan.client.object.Security.rpk; import static org.eclipse.leshan.client.object.Security.rpk;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP_CONFIG; import static org.eclipse.leshan.client.object.Security.rpkBootstrap;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_URI; import static org.junit.Assert.assertEquals;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID; import static org.junit.Assert.assertTrue;
import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.RPK;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE;
public class RpkLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest { public class RpkLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest {
//Lwm2m only
@Test @Test
public void testConnectWithRPKAndObserveTelemetry() throws Exception { public void testWithRpkConnectLwm2mSuccess() throws Exception {
RPKClientCredential rpkClientCredentials = new RPKClientCredential(); String clientEndpoint = CLIENT_ENDPOINT_RPK;
rpkClientCredentials.setEndpoint(CLIENT_ENDPOINT_RPK); X509Certificate certificate = clientX509CertTrust;
rpkClientCredentials.setKey(new String(Base64.encodeBase64(clientPublicKeyFromCertTrust.getEncoded()))); PrivateKey privateKey = clientPrivateKeyFromCertTrust;
Security security = rpk(SECURE_URI, RPKClientCredential clientCredentials = new RPKClientCredential();
SHORT_SERVER_ID, clientCredentials.setEndpoint(clientEndpoint);
clientPublicKeyFromCertTrust.getEncoded(), clientCredentials.setKey(Base64.encodeBase64String(certificate.getPublicKey().getEncoded()));
clientPrivateKeyFromCertTrust.getEncoded(), Security securityBs = rpk(SECURE_URI,
serverPublicKeyFromCert.getEncoded()); shortServerId,
super.basicTestConnectionObserveTelemetry(security, rpkClientCredentials, SECURE_COAP_CONFIG, CLIENT_ENDPOINT_RPK); certificate.getPublicKey().getEncoded(),
privateKey.getEncoded(),
serverX509Cert.getPublicKey().getEncoded());
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(RPK, NONE));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, RPK, false);
this.basicTestConnection(securityBs,
deviceCredentials,
COAP_CONFIG,
clientEndpoint,
transportConfiguration,
"await on client state (Rpk_Lwm2m)",
expectedStatusesRegistrationLwm2mSuccess,
false,
ON_REGISTRATION_SUCCESS,
true);
}
@Test
public void testWithRpkValidationPublicKeyBase64format_BAD_REQUEST() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_RPK + "BadPublicKey";
X509Certificate certificate = clientX509CertTrust;
PrivateKey privateKey = clientPrivateKeyFromCertTrust;
RPKClientCredential clientCredentials = new RPKClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setKey(Hex.encodeHexString(certificate.getPublicKey().getEncoded()));
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(RPK, NONE));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, RPK, false);
createDeviceProfile(transportConfiguration);
MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint);
assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus());
String msgExpected = "LwM2M client RPK key must be in standard [RFC7250] and support only EC algorithm and then encoded to Base64 format!";
assertTrue(result.getResponse().getContentAsString().contains(msgExpected));
}
@Test
public void testWithRpkValidationPrivateKeyBase64format_BAD_REQUEST() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_RPK + "BadPrivateKey";
X509Certificate certificate = clientX509CertTrust;
PrivateKey privateKey = clientPrivateKeyFromCertTrust;
RPKClientCredential clientCredentials = new RPKClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setKey(Base64.encodeBase64String(certificate.getPublicKey().getEncoded()));
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(RPK, NONE));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, RPK, true);
createDeviceProfile(transportConfiguration);
MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint);
assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus());
String msgExpected = "Bootstrap server client RPK secret key must be in PKCS#8 format (DER encoding, standard [RFC5958]) and then encoded to Base64 format!";
assertTrue(result.getResponse().getContentAsString().contains(msgExpected));
}
// Bootstrap + Lwm2m
@Test
public void testWithRpkConnectBsSuccess_UpdateTwoSectionsBootstrapAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_RPK_BS;
X509Certificate certificate = clientX509CertTrust;
PrivateKey privateKey = clientPrivateKeyFromCertTrust;
RPKClientCredential clientCredentials = new RPKClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setKey(Base64.encodeBase64String(certificate.getPublicKey().getEncoded()));
Security securityBs = rpkBootstrap(SECURE_URI_BS,
certificate.getPublicKey().getEncoded(),
privateKey.getEncoded(),
serverX509CertBs.getPublicKey().getEncoded());
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(RPK, BOTH));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, clientPrivateKeyFromCertTrust, certificate, RPK, false);
this.basicTestConnection(securityBs,
deviceCredentials,
COAP_CONFIG_BS,
clientEndpoint,
transportConfiguration,
"await on client state (RpkBS two section)",
expectedStatusesRegistrationBsSuccess,
true,
ON_REGISTRATION_SUCCESS,
true);
} }
} }

112
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java

@ -16,28 +16,116 @@
package org.thingsboard.server.transport.lwm2m.security.sql; package org.thingsboard.server.transport.lwm2m.security.sql;
import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.core.util.Hex;
import org.junit.Test; import org.junit.Test;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.util.Base64Utils;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential;
import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest;
import javax.servlet.http.HttpServletResponse;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import static org.eclipse.leshan.client.object.Security.x509; import static org.eclipse.leshan.client.object.Security.x509;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP_CONFIG; import static org.eclipse.leshan.client.object.Security.x509Bootstrap;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_URI; import static org.junit.Assert.assertEquals;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID; import static org.junit.Assert.assertTrue;
import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.X509;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE;
public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest { public class X509_NoTrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest {
//Lwm2m only
@Test @Test
public void testConnectWithCertAndObserveTelemetry() throws Exception { public void testWithX509NoTrustConnectLwm2mSuccess() throws Exception {
X509ClientCredential credentials = new X509ClientCredential(); String clientEndpoint = CLIENT_ENDPOINT_X509_TRUST_NO;
credentials.setEndpoint(CLIENT_ENDPOINT_X509_TRUST_NO); X509Certificate certificate = clientX509CertTrustNo;
credentials.setCert(SslUtil.getCertificateString(clientX509CertTrustNo)); PrivateKey privateKey = clientPrivateKeyFromCertTrustNo;
X509ClientCredential clientCredentials = new X509ClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setCert(Base64Utils.encodeToString(certificate.getEncoded()));
Security security = x509(SECURE_URI, Security security = x509(SECURE_URI,
SHORT_SERVER_ID, shortServerId,
clientX509CertTrustNo.getEncoded(), certificate.getEncoded(),
clientPrivateKeyFromCertTrustNo.getEncoded(), privateKey.getEncoded(),
serverX509Cert.getEncoded()); serverX509Cert.getEncoded());
super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, CLIENT_ENDPOINT_X509_TRUST_NO); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, NONE));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false);
this.basicTestConnection(security,
deviceCredentials,
COAP_CONFIG,
clientEndpoint,
transportConfiguration,
"await on client state (X509_Trust_Lwm2m)",
expectedStatusesRegistrationLwm2mSuccess,
false,
ON_REGISTRATION_SUCCESS,
true);
}
@Test
public void testWithX509NoTrustValidationPublicKeyBase64format_BAD_REQUEST() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_X509_TRUST_NO + "BadPublicKey";
X509Certificate certificate = clientX509CertTrustNo;
PrivateKey privateKey = clientPrivateKeyFromCertTrustNo;
X509ClientCredential clientCredentials = new X509ClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setCert(Hex.encodeHexString(certificate.getEncoded()));
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, NONE));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false);
createDeviceProfile(transportConfiguration);
MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint);
assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus());
String msgExpected = "LwM2M client X509 certificate must be in DER-encoded X509v3 format and support only EC algorithm and then encoded to Base64 format!";
assertTrue(result.getResponse().getContentAsString().contains(msgExpected));
}
@Test
public void testWithX509NoTrustValidationPrivateKeyBase64format_BAD_REQUEST() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_X509_TRUST_NO + "BadPrivateKey";
X509Certificate certificate = clientX509CertTrustNo;
PrivateKey privateKey = clientPrivateKeyFromCertTrustNo;
X509ClientCredential clientCredentials = new X509ClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setCert(Base64Utils.encodeToString(certificate.getEncoded()));
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, NONE));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, true);
createDeviceProfile(transportConfiguration);
MvcResult result = createDeviceWithMvcResult(deviceCredentials, clientEndpoint);
assertEquals(HttpServletResponse.SC_BAD_REQUEST, result.getResponse().getStatus());
String msgExpected = "Bootstrap server client X509 secret key must be in PKCS#8 format (DER encoding, standard [RFC5958]) and then encoded to Base64 format!";
assertTrue(result.getResponse().getContentAsString().contains(msgExpected));
}
// Bootstrap + Lwm2m
@Test
public void testWithX509NoTrustConnectBsSuccess_UpdateTwoSectionsBootstrapAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_X509_TRUST_NO;
X509Certificate certificate = clientX509CertTrustNo;
PrivateKey privateKey = clientPrivateKeyFromCertTrustNo;
X509ClientCredential clientCredentials = new X509ClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setCert(Base64Utils.encodeToString(certificate.getEncoded()));
Security security = x509Bootstrap(SECURE_URI_BS,
certificate.getEncoded(),
privateKey.getEncoded(),
serverX509CertBs.getEncoded());
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, BOTH));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false);
this.basicTestConnection(security,
deviceCredentials,
COAP_CONFIG_BS,
clientEndpoint,
transportConfiguration,
"await on client state (X509NoTrust two section)",
expectedStatusesRegistrationBsSuccess,
true,
ON_REGISTRATION_SUCCESS,
true);
} }
} }

69
application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java

@ -17,26 +17,75 @@ package org.thingsboard.server.transport.lwm2m.security.sql;
import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.client.object.Security;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential; import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredential;
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest; import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import static org.eclipse.leshan.client.object.Security.x509; import static org.eclipse.leshan.client.object.Security.x509;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_COAP_CONFIG; import static org.eclipse.leshan.client.object.Security.x509Bootstrap;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SECURE_URI; import static org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode.X509;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.SHORT_SERVER_ID; import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MClientState.ON_REGISTRATION_SUCCESS;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.BOTH;
import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfileBootstrapConfigType.NONE;
public class X509_TrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest { public class X509_TrustLwM2MIntegrationTest extends AbstractSecurityLwM2MIntegrationTest {
//Lwm2m only
@Test @Test
public void testConnectAndObserveTelemetry() throws Exception { public void testWithX509TrustConnectLwm2mSuccess() throws Exception {
X509ClientCredential credentials = new X509ClientCredential(); String clientEndpoint = CLIENT_ENDPOINT_X509_TRUST;
credentials.setEndpoint(CLIENT_ENDPOINT_X509_TRUST); X509Certificate certificate = clientX509CertTrust;
PrivateKey privateKey = clientPrivateKeyFromCertTrust;
X509ClientCredential clientCredentials = new X509ClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setCert("");
Security security = x509(SECURE_URI, Security security = x509(SECURE_URI,
SHORT_SERVER_ID, shortServerId,
clientX509CertTrust.getEncoded(), certificate.getEncoded(),
clientPrivateKeyFromCertTrust.getEncoded(), privateKey.getEncoded(),
serverX509Cert.getEncoded()); serverX509Cert.getEncoded());
super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, CLIENT_ENDPOINT_X509_TRUST); Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, NONE));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false);
this.basicTestConnection(security,
deviceCredentials,
COAP_CONFIG,
clientEndpoint,
transportConfiguration,
"await on client state (X509_Trust_Lwm2m)",
expectedStatusesRegistrationLwm2mSuccess,
false,
ON_REGISTRATION_SUCCESS,
true);
} }
// Bootstrap + Lwm2m
@Test
public void testWithX509TrustConnectBsSuccess_UpdateTwoSectionsBootstrapAndLm2m_ConnectLwm2mSuccess() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_X509_TRUST;
X509Certificate certificate = clientX509CertTrust;
PrivateKey privateKey = clientPrivateKeyFromCertTrust;
X509ClientCredential clientCredentials = new X509ClientCredential();
clientCredentials.setEndpoint(clientEndpoint);
clientCredentials.setCert("");
Security security = x509Bootstrap(SECURE_URI_BS,
certificate.getEncoded(),
privateKey.getEncoded(),
serverX509CertBs.getEncoded());
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(OBSERVE_ATTRIBUTES_WITHOUT_PARAMS, getBootstrapServerCredentialsSecure(X509, BOTH));
LwM2MDeviceCredentials deviceCredentials = getDeviceCredentialsSecure(clientCredentials, privateKey, certificate, X509, false);
this.basicTestConnection(security,
deviceCredentials,
COAP_CONFIG_BS,
clientEndpoint,
transportConfiguration,
"await on client state (X509Trust two section)",
expectedStatusesRegistrationBsSuccess,
true,
ON_REGISTRATION_SUCCESS,
true);
}
} }

8
application/src/test/resources/application-test.properties

@ -1,20 +1,12 @@
transport.lwm2m.server.security.credentials.enabled=true transport.lwm2m.server.security.credentials.enabled=true
transport.lwm2m.server.security.credentials.type=KEYSTORE transport.lwm2m.server.security.credentials.type=KEYSTORE
transport.lwm2m.server.security.credentials.keystore.store_file=lwm2m/credentials/lwm2mserver.jks transport.lwm2m.server.security.credentials.keystore.store_file=lwm2m/credentials/lwm2mserver.jks
#transport.lwm2m.server.security.credentials.keystore.store_password=server
#transport.lwm2m.server.security.credentials.keystore.key_alias=server
#transport.lwm2m.server.security.credentials.keystore.key_password=server
transport.lwm2m.bootstrap.enabled=false
transport.lwm2m.bootstrap.security.credentials.enabled=true transport.lwm2m.bootstrap.security.credentials.enabled=true
transport.lwm2m.bootstrap.security.credentials.type=KEYSTORE transport.lwm2m.bootstrap.security.credentials.type=KEYSTORE
transport.lwm2m.bootstrap.security.credentials.keystore.store_file=lwm2m/credentials/lwm2mserver.jks transport.lwm2m.bootstrap.security.credentials.keystore.store_file=lwm2m/credentials/lwm2mserver.jks
#transport.lwm2m.bootstrap.security.credentials.keystore.store_password=server
#transport.lwm2m.bootstrap.security.credentials.keystore.key_alias=server
#transport.lwm2m.bootstrap.security.credentials.keystore.key_password=server
transport.lwm2m.security.trust-credentials.enabled=true transport.lwm2m.security.trust-credentials.enabled=true
transport.lwm2m.security.trust-credentials.type=KEYSTORE transport.lwm2m.security.trust-credentials.type=KEYSTORE
transport.lwm2m.security.trust-credentials.keystore.store_file=lwm2m/credentials/lwm2mtruststorechain.jks transport.lwm2m.security.trust-credentials.keystore.store_file=lwm2m/credentials/lwm2mtruststorechain.jks
#transport.lwm2m.security.trust-credentials.keystore.store_password=server
edges.enabled=true edges.enabled=true
edges.storage.no_read_records_sleep=500 edges.storage.no_read_records_sleep=500

0
application/src/test/resources/logback.xml → application/src/test/resources/logback-test.xml

299
application/src/test/resources/lwm2m/credentials/shell/lwM2M_cfssl_chain_trusts_and_clients_for_test.sh

@ -1,299 +0,0 @@
#!/usr/bin/env bash
# Change working directory
cd -- "$(
dirname "${0}"
)" || exit 1
readonly TRUST_PATH="Trust"
readonly CA_ROOT_CERT_KEY="ca-root"
readonly CA_ROOT_ALIAS="root"
readonly CA_INTERMEDIATE_CERT_KEY_PREF="intermediate_ca"
CA_INTERMEDIATE_START=0
CA_INTERMEDIATE_FINISH=2
CA_INTERMEDIATE_NUMBER=${CA_INTERMEDIATE_START}
CA_INTERMEDIATE_CERT_SIGN=${CA_ROOT_CERT_KEY}
CA_LIST_CERT_FOR_CAT=""
readonly CA_TRUST_STORE_ALL_CHAIN="lwm2mtruststorechain"
readonly CA_TRUST_STORE_PWD="server_ks_password"
readonly CA_TRUST_CERT_ALIAS="root"
readonly CA_TRUST_CERT_CHAIN_JKS="lwm2mtruststorechain"
readonly CA_TRUST_STORE_CHAIN_ALIAS="trust_cert_chain_alias"
readonly CLIENT_PATH="Client"
readonly CLIENT_JKS_FOR_TEST="lwm2mclient"
readonly CLIENT_CERT_KEY_PREF="LwX509"
readonly CLIENT_CERT_ALIAS_PREF="client_alias_"
readonly CLIENT_STORE_PWD="client_ks_password"
readonly CLIENT_HOST_NAME="thingsboard_test.io"
CLIENT_START=0
CLIENT_FINISH=1
CLIENT_NUMBER=${CLIENT_START}
SERVER_HOST_NAME="localhost.localdomain"
SERVER_LOCAL_HOST_NAME="localhost"
SERVER_PUBLIC_HOST_NAMES="-"
readonly CF_COMMANDS="
cfssl
cfssljson
"
if [ ! -z "$1" ]; then
CA_INTERMEDIATE_START=$1
CA_INTERMEDIATE_NUMBER=${CA_INTERMEDIATE_START}
fi
if [ ! -z "$2" ]; then
CA_INTERMEDIATE_FINISH=$2
fi
if [ ! -z "$3" ]; then
CLIENT_START=$1
CLIENT_NUMBER=${CLIENT_START}
fi
if [ ! -z "$4" ]; then
CLIENT_FINISH=$4
fi
# Change working directory
rm -rf ${TRUST_PATH}
mkdir -p ${TRUST_PATH}
rm -rf ${CLIENT_PATH}
mkdir -p ${CLIENT_PATH}
cd -- "$(
dirname "${0}"
)" || exit 1
rm *.csr
rm *.p12
rm *.json
rm *.pem
rm *.jks
intermediate_common_name() {
echo "${CA_INTERMEDIATE_CERT_KEY_PREF}${CA_INTERMEDIATE_NUMBER}"
}
set_list_sert_for_cat() {
local first="$1"
echo "$first ${CA_LIST_CERT_FOR_CAT}"
}
client_common_name() {
echo "${CLIENT_CERT_KEY_PREF}$(printf "%08d" ${CLIENT_NUMBER})"
}
client_alias_name() {
echo "${CLIENT_CERT_ALIAS_PREF}$(printf "%08d" ${CLIENT_NUMBER})"
}
for COMMAND in ${CF_COMMANDS}; do
if ! command -v ${COMMAND} &> /dev/null; then
echo "ERROR: Missing command ${COMMAND}" >&2
echo "Install the package from: https://pkg.cfssl.org/" >&2
exit 1
fi
done
tee ./${TRUST_PATH}/ca-config.json 1> /dev/null <<-CONFIG
{
"signing": {
"default": {
"expiry": "8760h",
"names": [
{
"C": "UK",
"ST": "Kyiv city",
"L": "Kyiv",
"O": "Thingsboard",
"OU": "DEVELOPER_TEST"
}
]
},
"profiles": {
"server": {
"expiry": "43800h",
"key": {
"algo": "ecdsa",
"size": 256
},
"usages": [
"signing",
"key encipherment",
"server auth"
]
},
"client": {
"expiry": "43800h",
"key": {
"algo": "ecdsa",
"size": 256
},
"usages": [
"signing",
"key encipherment",
"client auth"
]
},
"client-server": {
"expiry": "43800h",
"key": {
"algo": "ecdsa",
"size": 256
},
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
]
}
}
}
}
CONFIG
tee ./${TRUST_PATH}/ca-root-to-intermediate-config.json 1> /dev/null <<-CONFIG
{
"signing": {
"default": {
"expiry": "43800h",
"ca_constraint": {
"is_ca": true,
"max_path_len": 0,
"max_path_len_zero": true
},
"key": {
"algo": "ecdsa",
"size": 256
},
"usages": [
"digital signature",
"cert sign",
"crl sign",
"signing"
]
}
}
}
CONFIG
echo "===================================================="
echo -e "Generate the root of certificates: \n-${CA_ROOT_KEY}-key.pem (certificate key)\n-${CA_ROOT_KEY}.pem (certificate)\n-${CA_ROOT_KEY}.csr (sign request)"
echo "===================================================="
cfssl genkey \
-initca \
- \
<<-CONFIG | cfssljson -bare ./${TRUST_PATH}/${CA_ROOT_CERT_KEY}
{
"CN": "ROOT CA",
"key": {
"algo": "ecdsa",
"size": 256
},
"names": [
{
"C": "UK",
"ST": "Kyiv city",
"L": "Kyiv",
"O": "Thingsboard",
"OU": "DEVELOPER_TEST"
}
],
"ca": {
"expiry": "131400h"
}
}
CONFIG
CA_LIST_CERT_FOR_CAT=$(set_list_sert_for_cat ./${TRUST_PATH}/${CA_ROOT_CERT_KEY}.pem)
echo "===================================================="
echo -e "Generate and Signed the intermediates of our certificates: \n-${CA_INTERMEDIATE_CERT_KEY_PREF}?-key.pem (certificate key)\n-${CA_INTERMEDIATE_CERT_KEY_PREF}?.pem (certificate)\n-${CA_INTERMEDIATE_CERT_KEY_PREF}?.csr (sign request)"
echo "===================================================="
while [[ ${CA_INTERMEDIATE_NUMBER} -lt ${CA_INTERMEDIATE_FINISH} ]];
do
CA_INTERMEDIATE_CERT_KEY=$(intermediate_common_name)
CA_INTERMEDIATE_NUMBER=$((${CA_INTERMEDIATE_NUMBER} + 1))
cfssl gencert \
-ca ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_SIGN}.pem \
-ca-key ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_SIGN}-key.pem \
-config ./${TRUST_PATH}/ca-root-to-intermediate-config.json \
-hostname "${SERVER_HOST_NAME},${SERVER_LOCAL_HOST_NAME}${SERVER_PUBLIC_HOST_NAMES:+, }${SERVER_PUBLIC_HOST_NAMES}" \
- \
<<-CONFIG | cfssljson -bare ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY}
{
"CN": "${CA_INTERMEDIATE_CERT_KEY}",
"names": [
{
"C": "UK",
"ST": "Kyiv city",
"L": "Kyiv",
"O": "Thingsboard",
"OU": "DEVELOPER_TEST"
}
]
}
CONFIG
#openssl x509 -in ${CA_INTERMEDIATE_CERT_KEY}.pem -text -noout
CA_LIST_CERT_FOR_CAT=$(set_list_sert_for_cat ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem)
CA_INTERMEDIATE_CERT_SIGN=${CA_INTERMEDIATE_CERT_KEY}
done
echo "===================================================="
echo -e "Add the CA_certificate to keystore: ${CA_TRUST_CERT_CHAIN_JKS}.jks"
echo "===================================================="
cat ${CA_LIST_CERT_FOR_CAT} > ./${TRUST_PATH}/${CA_TRUST_STORE_ALL_CHAIN}.pem
openssl pkcs12 -export -in ./${TRUST_PATH}/${CA_TRUST_STORE_ALL_CHAIN}.pem -inkey ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY}-key.pem -out ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY}.p12 -name ${CA_TRUST_STORE_CHAIN_ALIAS} -CAfile ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem -caname ${CA_ROOT_ALIAS} -passin pass:${CA_TRUST_STORE_PWD} -passout pass:${CA_TRUST_STORE_PWD}
keytool -importkeystore -deststorepass ${CA_TRUST_STORE_PWD} -destkeypass ${CA_TRUST_STORE_PWD} -destkeystore ./${TRUST_PATH}/${CA_TRUST_CERT_CHAIN_JKS}.jks -srckeystore ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY}.p12 -srcstoretype PKCS12 -srcstorepass ${CA_TRUST_STORE_PWD} -alias ${CA_TRUST_STORE_CHAIN_ALIAS}
keytool -list -v -keystore ./${TRUST_PATH}/lwm2mtruststorechain.jks -storepass server_ks_password -storetype PKCS12
echo "===================================================="
echo -e "Generate and Signed the clients of our certificates: \n-${CLIENT_CERT_KEY_PREF}?-key.pem (certificate key)\n-${CLIENT_CERT_KEY_PREF}?.pem (certificate)\n-${CCLIENT_CERT_KEY_PREF}?.csr (sign request)"
echo "===================================================="
while [[ ${CLIENT_NUMBER} -lt ${CLIENT_FINISH} ]];
do
CLIENT_CERT_KEY=$(client_common_name)
CLIENT_CERT_ALIAS=$(client_alias_name)
CLIENT_NUMBER=$((${CLIENT_NUMBER} + 1))
cfssl gencert \
-ca ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem \
-ca-key ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY}-key.pem \
-config ./${TRUST_PATH}/ca-config.json \
-profile client \
-hostname "${CLIENT_HOST_NAME}" \
- \
<<-CONFIG | cfssljson -bare ./${CLIENT_PATH}/${CLIENT_CERT_KEY}
{
"CN": "${CLIENT_CERT_KEY}"
}
CONFIG
echo "===================================================="
echo -e "Add the client certificate (${CLIENT_CERT_KEY}.pem) to keystore: ${CLIENT_JKS_FOR_TEST}.jks"
echo "===================================================="
cat ./${CLIENT_PATH}/${CLIENT_CERT_KEY}.pem ${CA_LIST_CERT_FOR_CAT} > ./${CLIENT_PATH}/${CLIENT_CERT_KEY}_chain.pem
openssl pkcs12 -export -in ./${CLIENT_PATH}/${CLIENT_CERT_KEY}_chain.pem -inkey ./${CLIENT_PATH}/${CLIENT_CERT_KEY}-key.pem -out ./${CLIENT_PATH}/${CLIENT_CERT_KEY}.p12 -name ${CLIENT_CERT_ALIAS} -CAfile ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem -caname ${CA_ROOT_ALIAS} -passin pass:${CLIENT_STORE_PWD} -passout pass:${CLIENT_STORE_PWD}
keytool -importkeystore -deststorepass ${CLIENT_STORE_PWD} -destkeypass ${CLIENT_STORE_PWD} -destkeystore ./${CLIENT_PATH}/${CLIENT_JKS_FOR_TEST}.jks -srckeystore ./${CLIENT_PATH}/${CLIENT_CERT_KEY}.p12 -srcstoretype PKCS12 -srcstorepass ${CLIENT_STORE_PWD} -alias ${CLIENT_CERT_ALIAS}
done
keytool -list -v -keystore ./${CLIENT_PATH}/lwm2mclient.jks -storepass client_ks_password -storetype PKCS12
rm ./${TRUST_PATH}/*.p12
rm ./${TRUST_PATH}/*.csr
rm ./${TRUST_PATH}/*.json
rm ./${TRUST_PATH}/${CA_ROOT_CERT_KEY}*
rm ./${TRUST_PATH}/${CA_INTERMEDIATE_CERT_KEY_PREF}*
rm ./${CLIENT_PATH}/*.p12 2> /dev/null
rm ./${CLIENT_PATH}/*.csr 2> /dev/null

65
application/src/test/resources/lwm2m/credentials/shell/lwm2m_cfssl_chain_for_test_All.sh

@ -1,65 +0,0 @@
#!/usr/bin/env bash
readonly INTERMEDIATE_START=0
readonly INTERMEDIATE_FINISH=2
readonly CLIENT_START=0
readonly CLIENT_FINISH=5
IS_IHFO=false
IS_SERVER_CREATED_KEY=true
IS_TRUST_CLIENT_CREATED_KEY=true
cd -- "$(
dirname "${0}"
)" || exit 1
Help()
{
# Display Help
echo "Description of the script functions."
echo
echo "Syntax: scriptTemplate [-g|h|v|V]"
echo "options:"
echo "h Print this Help."
echo "v Verbose mode."
echo "V Print software version and exit."
echo
}
if [ "$1" == "-h" ] ; then
echo -e "Usage 2: ./`basename $0` \"Information is not displayed\" : \"Keys for the server are generated\" : \"Keys for the clients and trusts are generated\""
echo -e "Usage 1: ./`basename $0` true \"Information is displayed\" : \"Keys for the server are generated\" : \"Keys for the clients and trusts are generated\""
echo -e "Usage 3: ./`basename $0` true false \"Information is displayed\" : \"Keys for the server are not generated\" : \"Keys for the clients and trusts are generated\""
echo -e "Usage 4: ./`basename $0` true false false \"Information is displayed\" : \"Keys for the server are not generated\" : \"Keys for the clients and trusts are not generated\""
echo -e "Usage 4: ./`basename $0` true true false \"Information is displayed\" : \"Keys for the server are generated\" : \"Keys for the clients and trusts are not generated\""
echo "This Help File: ./`basename $0` -h"
exit 0
fi
if [ -n "$1" ]; then
IS_IHFO=$1
fi
if [ -n "$2" ]; then
IS_SERVER_CREATED_KEY=$2
fi
if [ -n "$3" ]; then
IS_TRUST_CLIENT_CREATED_KEY=$3
fi
if [ "$IS_IHFO" = false ] ; then
if [ "$IS_SERVER_CREATED_KEY" = true ] ; then
./lwm2m_cfssl_chain_server_for_test.sh > /dev/null 2>&1 &
fi
if [ "$IS_TRUST_CLIENT_CREATED_KEY" = true ] ; then
./lwM2M_cfssl_chain_trusts_and_clients_for_test.sh ${INTERMEDIATE_START} ${INTERMEDIATE_FINISH} ${CLIENT_START} ${CLIENT_FINISH} > /dev/null 2>&1 &
fi
else
if [ "$IS_SERVER_CREATED_KEY" = true ] ; then
./lwm2m_cfssl_chain_server_for_test.sh
fi
if [ "$IS_TRUST_CLIENT_CREATED_KEY" = true ] ; then
./lwM2M_cfssl_chain_trusts_and_clients_for_test.sh ${INTERMEDIATE_START} ${INTERMEDIATE_FINISH} ${CLIENT_START} ${CLIENT_FINISH}
fi
fi

298
application/src/test/resources/lwm2m/credentials/shell/lwm2m_cfssl_chain_server_for_test.sh

@ -1,298 +0,0 @@
#!/usr/bin/env bash
# REF: https://github.com/cloudflare/cfssl
# Change working directory
cd -- "$(
dirname "${0}"
)" || exit 1
readonly CA_ROOT_CERT_KEY="ca-root"
readonly CA_ROOT_ALIAS="root"
readonly CA_INTERMEDIATE_CERT_KEY_PREF="intermediate_ca"
CA_INTERMEDIATE_NUMBER=0
CA_LIST_CERT_FOR_CAT=""
readonly CF_COMMANDS="
cfssl
cfssljson
"
readonly SERVER_JKS_FOR_TEST="lwm2mserver"
readonly STORE_PASS_PWD="server_ks_password"
readonly SERVER_PATH="Server"
readonly SERVER_CERT_KEY="lwm2mserver"
readonly SERVER_CERT_CHAIN="lwm2mserver_chain"
readonly SERVER_CERT_ALIAS="server"
readonly BS_SERVER_CERT_KEY="lwm2mserverbs"
readonly BS_SERVER_CERT_CHAIN="lwm2mserverbs_chain"
readonly BS_SERVER_CERT_ALIAS="bootstrap"
SERVER_HOST_NAME="localhost.localdomain"
SERVER_LOCAL_HOST_NAME="localhost"
SERVER_PUBLIC_HOST_NAMES="-"
intermediate_common_name() {
echo "${CA_INTERMEDIATE_CERT_KEY_PREF}${CA_INTERMEDIATE_NUMBER}"
}
set_list_sert_for_cat() {
local first="$1"
echo "$first ${CA_LIST_CERT_FOR_CAT}"
}
# Change working directory
rm -rf ${SERVER_PATH}
mkdir -p ${SERVER_PATH}
cd -- "$(
dirname ./${SERVER_PATH}
)" || exit 1
rm *.csr
rm *.p12
rm *.json
rm *.pem
rm *.jks
CA_INTERMEDIATE_CERT_SIGN=${CA_ROOT_CERT_KEY}
CA_INTERMEDIATE_CERT_KEY=$(intermediate_common_name)
CA_INTERMEDIATE_NUMBER=$((${CA_INTERMEDIATE_NUMBER} + 1))
CA_LIST_CERT_FOR_CAT=""
for COMMAND in ${CF_COMMANDS}; do
if ! command -v ${COMMAND} &> /dev/null; then
echo "ERROR: Missing command ${COMMAND}" >&2
echo "Install the package from: https://pkg.cfssl.org/" >&2
exit 1
fi
done
tee ./${SERVER_PATH}/ca-config.json 1> /dev/null <<-CONFIG
{
"signing": {
"default": {
"expiry": "8760h",
"names": [
{
"C": "UK",
"ST": "Kyiv city",
"L": "Kyiv",
"O": "Thingsboard",
"OU": "DEVELOPER_TEST"
}
]
},
"profiles": {
"server": {
"expiry": "43800h",
"key": {
"algo": "ecdsa",
"size": 256
},
"usages": [
"signing",
"key encipherment",
"server auth"
]
},
"client": {
"expiry": "43800h",
"key": {
"algo": "ecdsa",
"size": 256
},
"usages": [
"signing",
"key encipherment",
"client auth"
]
},
"client-server": {
"expiry": "43800h",
"key": {
"algo": "ecdsa",
"size": 256
},
"usages": [
"signing",
"key encipherment",
"server auth",
"client auth"
]
}
}
}
}
CONFIG
tee ./${SERVER_PATH}/ca-root-to-intermediate-config.json 1> /dev/null <<-CONFIG
{
"signing": {
"default": {
"expiry": "43800h",
"ca_constraint": {
"is_ca": true,
"max_path_len": 0,
"max_path_len_zero": true
},
"key": {
"algo": "ecdsa",
"size": 256
},
"usages": [
"digital signature",
"cert sign",
"crl sign",
"signing"
]
}
}
}
CONFIG
echo "===================================================="
echo -e "Generate the root of certificates: \n-${CA_ROOT_KEY}-key.pem (certificate key)\n-${CA_ROOT_KEY}.pem (certificate)\n-${CA_ROOT_KEY}.csr (sign request)"
echo "===================================================="
cfssl genkey \
-initca \
- \
<<-CONFIG | cfssljson -bare ./${SERVER_PATH}/${CA_ROOT_CERT_KEY}
{
"CN": "ROOT CA for servers",
"key": {
"algo": "ecdsa",
"size": 256
},
"names": [
{
"C": "UK",
"ST": "Kyiv city",
"L": "Kyiv",
"O": "Thingsboard",
"OU": "DEVELOPER_TEST"
}
],
"ca": {
"expiry": "131400h"
}
}
CONFIG
CA_LIST_CERT_FOR_CAT=$(set_list_sert_for_cat ./${SERVER_PATH}/${CA_ROOT_CERT_KEY}.pem)
echo "===================================================="
echo -e "Generate and Signed the first intermediates of our certificates: \n-${CA_INTERMEDIATE_CERT_KEY}-key.pem (certificate key)\n-${CA_INTERMEDIATE_CERT_KEY}.pem (certificate)\n-${CA_INTERMEDIATE_CERT_KEY}.csr (sign request)"
echo "===================================================="
cfssl gencert \
-ca ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_SIGN}.pem \
-ca-key ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_SIGN}-key.pem \
-config ./${SERVER_PATH}/ca-root-to-intermediate-config.json \
-hostname "${SERVER_HOST_NAME},${SERVER_LOCAL_HOST_NAME}${SERVER_PUBLIC_HOST_NAMES:+, }${SERVER_PUBLIC_HOST_NAMES}" \
- \
<<-CONFIG | cfssljson -bare ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}
{
"CN": "${CA_INTERMEDIATE_CERT_KEY}",
"names": [
{
"C": "UK",
"ST": "Kyiv city",
"L": "Kyiv",
"O": "Thingsboard",
"OU": "DEVELOPER_TEST"
}
]
}
CONFIG
CA_LIST_CERT_FOR_CAT=$(set_list_sert_for_cat ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem)
## Lwm2m Server certificate
echo "===================================================="
echo -e "Generate and Signed the server certificate: \n-${SERVER_CERT_KEY}-key.pem (certificate key)\n-${SERVER_CERT_KEY}.pem (certificate)\n-${SERVER_CERT_KEY}.csr (sign request)"
echo "===================================================="
cfssl gencert \
-ca ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem \
-ca-key ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}-key.pem \
-config ./${SERVER_PATH}/ca-config.json \
-profile server \
-hostname "${SERVER_HOST_NAME},${SERVER_LOCAL_HOST_NAME}${SERVER_PUBLIC_HOST_NAMES:+, }${SERVER_PUBLIC_HOST_NAMES}" \
- \
<<-CONFIG | cfssljson -bare ./${SERVER_PATH}/${SERVER_CERT_KEY}
{
"CN": "${SERVER_LOCAL_HOST_NAME}"
}
CONFIG
echo "===================================================="
echo -e "Add the server certificate (${SERVER_CERT_KEY}.pem) to keystore: ${SERVER_JKS_FOR_TEST}.jks"
echo "===================================================="
cat ./${SERVER_PATH}/${SERVER_CERT_KEY}.pem ${CA_LIST_CERT_FOR_CAT} > ./${SERVER_PATH}/${SERVER_CERT_CHAIN}.pem
openssl pkcs12 -export -in ./${SERVER_PATH}/${SERVER_CERT_CHAIN}.pem -inkey ./${SERVER_PATH}/${SERVER_CERT_KEY}-key.pem -out ./${SERVER_PATH}/${SERVER_CERT_KEY}.p12 -name ${SERVER_CERT_ALIAS} -CAfile ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem -caname ${CA_ROOT_ALIAS} -passin pass:${STORE_PASS_PWD} -passout pass:${STORE_PASS_PWD}
keytool -importkeystore -deststorepass ${STORE_PASS_PWD} -destkeypass ${STORE_PASS_PWD} -destkeystore ./${SERVER_PATH}/${SERVER_JKS_FOR_TEST}.jks -srckeystore ./${SERVER_PATH}/${SERVER_CERT_KEY}.p12 -srcstoretype PKCS12 -srcstorepass ${STORE_PASS_PWD} -alias ${SERVER_CERT_ALIAS}
CA_INTERMEDIATE_CERT_SIGN=${CA_INTERMEDIATE_CERT_KEY}
CA_INTERMEDIATE_CERT_KEY=$(intermediate_common_name)
CA_INTERMEDIATE_NUMBER=$((${CA_INTERMEDIATE_NUMBER} + 1))
echo "===================================================="
echo -e "Generate and Signed the second intermediates of our certificates: \n-${CA_INTERMEDIATE_CERT_KEY}-key.pem (certificate key)\n-${CA_INTERMEDIATE_CERT_KEY}.pem (certificate)\n-${CA_INTERMEDIATE_CERT_KEY}.csr (sign request)"
echo "===================================================="
cfssl gencert \
-ca ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_SIGN}.pem \
-ca-key ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_SIGN}-key.pem \
-config ./${SERVER_PATH}/ca-root-to-intermediate-config.json \
-hostname "${SERVER_HOST_NAME},${SERVER_LOCAL_HOST_NAME}${SERVER_PUBLIC_HOST_NAMES:+, }${SERVER_PUBLIC_HOST_NAMES}" \
- \
<<-CONFIG | cfssljson -bare ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}
{
"CN": "${CA_INTERMEDIATE_CERT_KEY}",
"names": [
{
"C": "UK",
"ST": "Kyiv city",
"L": "Kyiv",
"O": "Thingsboard",
"OU": "DEVELOPER_TEST"
}
]
}
CONFIG
CA_LIST_CERT_FOR_CAT=$(set_list_sert_for_cat ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem)
## Bootstrap server certificate
echo "===================================================="
echo -e "Generate and Signed the server certificate: \n-${BS_SERVER_CERT_KEY}-key.pem (certificate key)\n-${BS_SERVER_CERT_KEY}.pem (certificate)\n-${BS_SERVER_CERT_KEY}.csr (sign request)"
echo "===================================================="
cfssl gencert \
-ca ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem \
-ca-key ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}-key.pem \
-config ./${SERVER_PATH}/ca-config.json \
-profile server \
-hostname "${SERVER_HOST_NAME},${SERVER_LOCAL_HOST_NAME}${SERVER_PUBLIC_HOST_NAMES:+, }${SERVER_PUBLIC_HOST_NAMES}" \
- \
<<-CONFIG | cfssljson -bare ./${SERVER_PATH}/${BS_SERVER_CERT_KEY}
{
"CN": "${SERVER_LOCAL_HOST_NAME}"
}
CONFIG
echo "===================================================="
echo -e "Add the Bootstrap server certificate (${BS_SERVER_CERT_KEY}.pem) to keystore: ${SERVER_JKS_FOR_TEST}.jks"
echo "===================================================="
cat ./${SERVER_PATH}/${BS_SERVER_CERT_KEY}.pem ${CA_LIST_CERT_FOR_CAT} > ./${SERVER_PATH}/${BS_SERVER_CERT_CHAIN}.pem
openssl pkcs12 -export -in ./${SERVER_PATH}/${BS_SERVER_CERT_CHAIN}.pem -inkey ./${SERVER_PATH}/${BS_SERVER_CERT_KEY}-key.pem -out ./${SERVER_PATH}/${BS_SERVER_CERT_KEY}.p12 -name ${BS_SERVER_CERT_ALIAS} -CAfile ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY}.pem -caname ${CA_ROOT_ALIAS} -passin pass:${STORE_PASS_PWD} -passout pass:${STORE_PASS_PWD}
keytool -importkeystore -deststorepass ${STORE_PASS_PWD} -destkeypass ${STORE_PASS_PWD} -destkeystore ./${SERVER_PATH}/${SERVER_JKS_FOR_TEST}.jks -srckeystore ./${SERVER_PATH}/${BS_SERVER_CERT_KEY}.p12 -srcstoretype PKCS12 -srcstorepass ${STORE_PASS_PWD} -alias ${BS_SERVER_CERT_ALIAS}
keytool -list -v -keystore ./${SERVER_PATH}/lwm2mserver.jks -storepass server_ks_password -storetype PKCS12
rm ./${SERVER_PATH}/*.p12 2> /dev/null
rm ./${SERVER_PATH}/*.csr 2> /dev/null
rm ./${SERVER_PATH}/*.json 2> /dev/null
rm ./${SERVER_PATH}/${CA_INTERMEDIATE_CERT_KEY_PREF}* 2> /dev/null
rm ./${SERVER_PATH}/${CA_ROOT_CERT_KEY}* 2> /dev/null
mv ./${SERVER_PATH}/${SERVER_CERT_KEY}-key.pem ./${SERVER_PATH}/${SERVER_CERT_KEY}_key.pem
mv ./${SERVER_PATH}/${BS_SERVER_CERT_KEY}-key.pem ./${SERVER_PATH}/${BS_SERVER_CERT_KEY}_key.pem

8
common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java

@ -28,11 +28,7 @@ import java.util.Optional;
public interface EventService { public interface EventService {
Event save(Event event); ListenableFuture<Void> saveAsync(Event event);
ListenableFuture<Event> saveAsync(Event event);
Optional<Event> saveIfNotExists(Event event);
Optional<Event> findEvent(TenantId tenantId, EntityId entityId, String eventType, String eventUid); Optional<Event> findEvent(TenantId tenantId, EntityId entityId, String eventType, String eventUid);
@ -48,6 +44,6 @@ public interface EventService {
void removeEvents(TenantId tenantId, EntityId entityId, EventFilter eventFilter, Long startTime, Long endTime); void removeEvents(TenantId tenantId, EntityId entityId, EventFilter eventFilter, Long startTime, Long endTime);
void cleanupEvents(long ttl, long debugTtl); void cleanupEvents(long regularEventStartTs, long regularEventEndTs, long debugEventStartTs, long debugEventEndTs);
} }

20
common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java

@ -57,12 +57,6 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H
@NoXss @NoXss
@Length(fieldName = "secret") @Length(fieldName = "secret")
private String secret; private String secret;
@NoXss
@Length(fieldName = "edgeLicenseKey", max = 30)
private String edgeLicenseKey;
@NoXss
@Length(fieldName = "cloudEndpoint")
private String cloudEndpoint;
public Edge() { public Edge() {
super(); super();
@ -82,8 +76,6 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H
this.name = edge.getName(); this.name = edge.getName();
this.routingKey = edge.getRoutingKey(); this.routingKey = edge.getRoutingKey();
this.secret = edge.getSecret(); this.secret = edge.getSecret();
this.edgeLicenseKey = edge.getEdgeLicenseKey();
this.cloudEndpoint = edge.getCloudEndpoint();
} }
public void update(Edge edge) { public void update(Edge edge) {
@ -95,8 +87,6 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H
this.name = edge.getName(); this.name = edge.getName();
this.routingKey = edge.getRoutingKey(); this.routingKey = edge.getRoutingKey();
this.secret = edge.getSecret(); this.secret = edge.getSecret();
this.edgeLicenseKey = edge.getEdgeLicenseKey();
this.cloudEndpoint = edge.getCloudEndpoint();
} }
@ApiModelProperty(position = 1, value = "JSON object with the Edge Id. " + @ApiModelProperty(position = 1, value = "JSON object with the Edge Id. " +
@ -162,14 +152,4 @@ public class Edge extends SearchTextBasedWithAdditionalInfo<EdgeId> implements H
return this.secret; return this.secret;
} }
@ApiModelProperty(position = 11, required = true, value = "Edge license key obtained from license portal", example = "AgcnI24Z06XC&m6Sxsdgf")
public String getEdgeLicenseKey() {
return this.edgeLicenseKey;
}
@ApiModelProperty(position = 12, required = true, value = "Edge uses this cloud URL to activate and periodically check it's license", example = "https://thingsboard.cloud")
public String getCloudEndpoint() {
return this.cloudEndpoint;
}
} }

13
application/src/main/java/org/thingsboard/server/service/edge/EdgeLicenseService.java → common/data/src/main/java/org/thingsboard/server/common/data/exception/ThingsboardKafkaClientError.java

@ -13,14 +13,11 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.service.edge; package org.thingsboard.server.common.data.exception;
import com.fasterxml.jackson.databind.JsonNode; public class ThingsboardKafkaClientError extends Error {
import org.springframework.http.ResponseEntity;
public interface EdgeLicenseService { public ThingsboardKafkaClientError(String message) {
super(message);
ResponseEntity<JsonNode> checkInstance(JsonNode request); }
ResponseEntity<JsonNode> activateInstance(String licenseSecret, String releaseDate);
} }

4
common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java

@ -162,7 +162,9 @@ public class EdgeGrpcClient implements EdgeRpcClient {
public void disconnect(boolean onError) throws InterruptedException { public void disconnect(boolean onError) throws InterruptedException {
if (!onError) { if (!onError) {
try { try {
inputStream.onCompleted(); if (inputStream != null) {
inputStream.onCompleted();
}
} catch (Exception e) { } catch (Exception e) {
log.error("Exception during onCompleted", e); log.error("Exception during onCompleted", e);
} }

6
common/edge-api/src/main/proto/edge.proto

@ -99,10 +99,8 @@ message EdgeConfiguration {
string type = 8; string type = 8;
string routingKey = 9; string routingKey = 9;
string secret = 10; string secret = 10;
string edgeLicenseKey = 11; string additionalInfo = 11;
string cloudEndpoint = 12; string cloudType = 12;
string additionalInfo = 13;
string cloudType = 14;
} }
enum UpdateMsgType { enum UpdateMsgType {

11
common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java

@ -38,7 +38,8 @@ public final class TbMsgMetaData implements Serializable {
} }
public TbMsgMetaData(Map<String, String> data) { public TbMsgMetaData(Map<String, String> data) {
this.data = new ConcurrentHashMap<>(data); this.data = new ConcurrentHashMap<>();
data.forEach(this::putValue);
} }
/** /**
@ -49,20 +50,20 @@ public final class TbMsgMetaData implements Serializable {
} }
public String getValue(String key) { public String getValue(String key) {
return data.get(key); return this.data.get(key);
} }
public void putValue(String key, String value) { public void putValue(String key, String value) {
if (key != null && value != null) { if (key != null && value != null) {
data.put(key, value); this.data.put(key, value);
} }
} }
public Map<String, String> values() { public Map<String, String> values() {
return new HashMap<>(data); return new HashMap<>(this.data);
} }
public TbMsgMetaData copy() { public TbMsgMetaData copy() {
return new TbMsgMetaData(data); return new TbMsgMetaData(this.data);
} }
} }

56
common/message/src/test/java/org/thingsboard/server/common/msg/TbMsgMetaDataTest.java

@ -0,0 +1,56 @@
/**
* Copyright © 2016-2022 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.common.msg;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
import java.util.Map;
import static org.junit.Assert.assertEquals;
public class TbMsgMetaDataTest {
private static final ObjectMapper objectMapper = new ObjectMapper();
private final String metadataJsonStr = "{\"deviceName\":\"Test Device\",\"deviceType\":\"default\",\"ts\":\"1645112691407\"}";
private JsonNode metadataJson;
private Map<String, String> metadataExpected;
@Before
public void startInit() throws Exception {
metadataJson = objectMapper.readValue(metadataJsonStr, JsonNode.class);
metadataExpected = objectMapper.convertValue(metadataJson, new TypeReference<>() {
});
}
@Test
public void testScript_whenMetadataWithoutPropertiesValueNull_returnMetadataWithAllValue() {
TbMsgMetaData tbMsgMetaData = new TbMsgMetaData(metadataExpected);
Map<String, String> dataActual = tbMsgMetaData.values();
assertEquals(metadataExpected.size(), dataActual.size());
}
@Test
public void testScript_whenMetadataWithPropertiesValueNull_returnMetadataWithoutPropertiesValueEqualsNull() {
metadataExpected.put("deviceName", null);
TbMsgMetaData tbMsgMetaData = new TbMsgMetaData(metadataExpected);
Map<String, String> dataActual = tbMsgMetaData.copy().getData();
assertEquals(metadataExpected.size() - 1, dataActual.size());
}
}

3
common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java

@ -158,6 +158,8 @@ public class HashPartitionService implements PartitionService {
} }
}); });
tpiCache.clear();
oldPartitions.forEach((serviceQueueKey, partitions) -> { oldPartitions.forEach((serviceQueueKey, partitions) -> {
if (!myPartitions.containsKey(serviceQueueKey)) { if (!myPartitions.containsKey(serviceQueueKey)) {
log.info("[{}] NO MORE PARTITIONS FOR CURRENT KEY", serviceQueueKey); log.info("[{}] NO MORE PARTITIONS FOR CURRENT KEY", serviceQueueKey);
@ -174,7 +176,6 @@ public class HashPartitionService implements PartitionService {
applicationEventPublisher.publishEvent(new PartitionChangeEvent(this, serviceQueueKey, tpiList)); applicationEventPublisher.publishEvent(new PartitionChangeEvent(this, serviceQueueKey, tpiList));
} }
}); });
tpiCache.clear();
if (currentOtherServices == null) { if (currentOtherServices == null) {
currentOtherServices = new ArrayList<>(otherServices); currentOtherServices = new ArrayList<>(otherServices);

12
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java

@ -67,9 +67,13 @@ public class LwM2MTransportBootstrapService {
@PreDestroy @PreDestroy
public void shutdown() { public void shutdown() {
log.info("Stopping LwM2M transport bootstrap server!"); try {
server.destroy(); log.info("Stopping LwM2M transport bootstrap server!");
log.info("LwM2M transport bootstrap server stopped!"); server.destroy();
log.info("LwM2M transport bootstrap server stopped!");
} catch (Exception e) {
log.error("Failed to gracefully stop the LwM2M transport bootstrap server!", e);
}
} }
public LeshanBootstrapServer getLhBootstrapServer() { public LeshanBootstrapServer getLhBootstrapServer() {
@ -118,7 +122,7 @@ public class LwM2MTransportBootstrapService {
} else { } else {
/* by default trust all */ /* by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]); builder.setTrustedCertificates(new X509Certificate[0]);
log.info("Unable to load X509 files for BootStrapServer"); log.info("Unable to load X509 files for BootStrap Server");
dtlsConfig.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, PSK_CIPHER_SUITES); dtlsConfig.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, PSK_CIPHER_SUITES);
} }
} }

21
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java

@ -33,6 +33,7 @@ import org.eclipse.leshan.server.bootstrap.BootstrapSession;
import org.eclipse.leshan.server.bootstrap.BootstrapTaskProvider; import org.eclipse.leshan.server.bootstrap.BootstrapTaskProvider;
import org.eclipse.leshan.server.bootstrap.BootstrapUtil; import org.eclipse.leshan.server.bootstrap.BootstrapUtil;
import java.math.BigInteger;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
@ -43,6 +44,7 @@ import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE;
import static org.eclipse.leshan.server.bootstrap.BootstrapUtil.toWriteRequest; import static org.eclipse.leshan.server.bootstrap.BootstrapUtil.toWriteRequest;
@Slf4j @Slf4j
@ -164,9 +166,20 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
protected void findServerInstanceId(BootstrapReadResponse readResponse) { protected void findServerInstanceId(BootstrapReadResponse readResponse) {
this.serverInstances = new HashMap<>(); this.serverInstances = new HashMap<>();
((LwM2mObject) readResponse.getContent()).getInstances().values().forEach(instance -> { try {
serverInstances.put(((Long) instance.getResource(0).getValue()).intValue(), instance.getId()); ((LwM2mObject) readResponse.getContent()).getInstances().values().forEach(instance -> {
}); var shId = OPAQUE.equals(instance.getResource(0).getType()) ? new BigInteger((byte[]) instance.getResource(0).getValue()).intValue() : instance.getResource(0).getValue();
int shortId;
if (shId instanceof Long) {
shortId = ((Long) shId).intValue();
} else {
shortId = (int) shId;
}
serverInstances.put(shortId, instance.getId());
});
} catch (Exception e) {
log.error("Failed find Server Instance Id. ", e);
}
if (this.securityInstances != null && this.securityInstances.size() > 0 && this.serverInstances != null && this.serverInstances.size() > 0) { if (this.securityInstances != null && this.securityInstances.size() > 0 && this.serverInstances != null && this.serverInstances.size() > 0) {
this.findBootstrapServerId(); this.findBootstrapServerId();
} }
@ -251,7 +264,7 @@ public class LwM2MBootstrapConfigStoreTaskProvider implements BootstrapTaskProvi
if (this.bootstrapServerIdNew != null && server.getValue().shortId == this.bootstrapServerIdNew && if (this.bootstrapServerIdNew != null && server.getValue().shortId == this.bootstrapServerIdNew &&
(this.bootstrapServerIdNew != this.bootstrapServerIdOld || securityInstanceId != this.serverInstances.get(this.bootstrapServerIdOld))) { (this.bootstrapServerIdNew != this.bootstrapServerIdOld || securityInstanceId != this.serverInstances.get(this.bootstrapServerIdOld))) {
pathsDelete.add("/1/" + this.serverInstances.get(this.bootstrapServerIdOld)); pathsDelete.add("/1/" + this.serverInstances.get(this.bootstrapServerIdOld));
/** Delete instance if serverIdNew is present in serverInstances and securityInstanceIdOld by serverIdNew not equals serverInstanceIdOld */ /** Delete instance if serverIdNew is present in serverInstances and securityInstanceIdOld by serverIdNew not equals serverInstanceIdOld */
} else if (this.serverInstances.containsKey(server.getValue().shortId) && securityInstanceId != this.serverInstances.get(server.getValue().shortId)) { } else if (this.serverInstances.containsKey(server.getValue().shortId) && securityInstanceId != this.serverInstances.get(server.getValue().shortId)) {
pathsDelete.add("/1/" + this.serverInstances.get(server.getValue().shortId)); pathsDelete.add("/1/" + this.serverInstances.get(server.getValue().shortId));
} }

10
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java

@ -99,9 +99,13 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService {
@PreDestroy @PreDestroy
public void shutdown() { public void shutdown() {
log.info("Stopping LwM2M transport server!"); try {
server.destroy(); log.info("Stopping LwM2M transport server!");
log.info("LwM2M transport server stopped!"); server.destroy();
log.info("LwM2M transport server stopped!");
} catch (Exception e) {
log.error("Failed to gracefully stop the LwM2M transport server!", e);
}
} }
private LeshanServer getLhServer() { private LeshanServer getLhServer() {

10
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MNetworkConfig.java

@ -31,16 +31,6 @@ public class LwM2MNetworkConfig {
Configuration coapConfig = new Configuration(); Configuration coapConfig = new Configuration();
coapConfig.set(CoapConfig.COAP_PORT, serverPortNoSec); coapConfig.set(CoapConfig.COAP_PORT, serverPortNoSec);
coapConfig.set(CoapConfig.COAP_SECURE_PORT, serverSecurePort); coapConfig.set(CoapConfig.COAP_SECURE_PORT, serverSecurePort);
/**
Example:Property for large packet:
#NetworkConfig config = new NetworkConfig();
#config.setInt(CoapConfig.MAX_MESSAGE_SIZE,32);
#config.setInt(CoapConfig.PREFERRED_BLOCK_SIZE,32);
#config.setInt(CoapConfig.MAX_RESOURCE_BODY_SIZE,2048);
#config.setInt(CoapConfig.MAX_RETRANSMIT,3);
#config.setInt(CoapConfig.MAX_TRANSMIT_WAIT,120000);
*/
/** /**
Property to indicate if the response should always include the Block2 option \ Property to indicate if the response should always include the Block2 option \
when client request early blockwise negociation but the response can be sent on one packet. when client request early blockwise negociation but the response can be sent on one packet.

8
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java

@ -85,7 +85,9 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider {
this.registration = registration; this.registration = registration;
this.tenantId = lwM2mClientContext.getClientByEndpoint(registration.getEndpoint()).getTenantId(); this.tenantId = lwM2mClientContext.getClientByEndpoint(registration.getEndpoint()).getTenantId();
this.modelsLock = new ReentrantLock(); this.modelsLock = new ReentrantLock();
models.computeIfAbsent(tenantId, t -> new ConcurrentHashMap<>()); if (tenantId != null) {
models.computeIfAbsent(tenantId, t -> new ConcurrentHashMap<>());
}
} }
@Override @Override
@ -127,8 +129,8 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider {
private ObjectModel getObjectModelDynamic(Integer objectId, String version) { private ObjectModel getObjectModelDynamic(Integer objectId, String version) {
String key = getKeyIdVer(objectId, version); String key = getKeyIdVer(objectId, version);
ObjectModel objectModel = models.get(tenantId).get(key); ObjectModel objectModel = tenantId != null ? models.get(tenantId).get(key) : null;
if (objectModel == null) { if (tenantId != null && objectModel == null) {
modelsLock.lock(); modelsLock.lock();
try { try {
objectModel = models.get(tenantId).get(key); objectModel = models.get(tenantId).get(key);

21
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java

@ -287,11 +287,22 @@ public class LwM2mClient {
} }
public ObjectModel getObjectModel(String pathIdVer, LwM2mModelProvider modelProvider) { public ObjectModel getObjectModel(String pathIdVer, LwM2mModelProvider modelProvider) {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); try {
String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer));
String verRez = getVerFromPathIdVerOrId(pathIdVer); String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId());
return verRez != null && verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration) String verRez = getVerFromPathIdVerOrId(pathIdVer);
.getObjectModel(pathIds.getObjectId()) : null; return verRez != null && verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration)
.getObjectModel(pathIds.getObjectId()) : null;
} catch (Exception e) {
if (registration == null) {
log.error("[{}] Failed Registration is null, GetObjectModelRegistration. ", this.endpoint, e);
} else if (registration.getSupportedObject() == null) {
log.error("[{}] Failed SupportedObject in Registration, GetObjectModelRegistration.", this.endpoint, e);
} else {
log.error("[{}] Failed ModelProvider.getObjectModel [{}] in Registration. ", this.endpoint, registration.getSupportedObject(), e);
}
return null;
}
} }

3
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java

@ -369,6 +369,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
} }
private Lwm2mDeviceProfileTransportConfiguration doGetAndCache(UUID profileId) { private Lwm2mDeviceProfileTransportConfiguration doGetAndCache(UUID profileId) {
Lwm2mDeviceProfileTransportConfiguration result = profiles.get(profileId); Lwm2mDeviceProfileTransportConfiguration result = profiles.get(profileId);
if (result == null) { if (result == null) {
log.debug("Fetching profile [{}]", profileId); log.debug("Fetching profile [{}]", profileId);
@ -376,7 +377,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
if (deviceProfile != null) { if (deviceProfile != null) {
result = profileUpdate(deviceProfile); result = profileUpdate(deviceProfile);
} else { } else {
log.info("Device profile was not found! Most probably device profile [{}] has been removed from the database.", profileId); log.warn("Device profile was not found! Most probably device profile [{}] has been removed from the database.", profileId);
} }
} }
return result; return result;

4
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java

@ -486,7 +486,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
new TbLwM2MLatchCallback<>(latch, new TbLwM2MReadCallback(this, logService, lwM2MClient, versionedId)))); new TbLwM2MLatchCallback<>(latch, new TbLwM2MReadCallback(this, logService, lwM2MClient, versionedId))));
latch.await(); latch.await();
} catch (InterruptedException e) { } catch (InterruptedException e) {
log.error("[{}] Failed to await Read requests!", lwM2MClient.getEndpoint()); log.error("[{}] Failed to await Read requests!", lwM2MClient.getEndpoint(), e);
} catch (Exception e) { } catch (Exception e) {
log.error("[{}] Failed to process read requests!", lwM2MClient.getEndpoint(), e); log.error("[{}] Failed to process read requests!", lwM2MClient.getEndpoint(), e);
logService.log(lwM2MClient, "Failed to process read requests. Possible profile misconfiguration."); logService.log(lwM2MClient, "Failed to process read requests. Possible profile misconfiguration.");
@ -504,7 +504,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
latch.await(); latch.await();
} catch (InterruptedException e) { } catch (InterruptedException e) {
log.error("[{}] Failed to await Observe requests!", lwM2MClient.getEndpoint()); log.error("[{}] Failed to await Observe requests!", lwM2MClient.getEndpoint(), e);
} catch (Exception e) { } catch (Exception e) {
log.error("[{}] Failed to process observe requests!", lwM2MClient.getEndpoint(), e); log.error("[{}] Failed to process observe requests!", lwM2MClient.getEndpoint(), e);
logService.log(lwM2MClient, "Failed to process observe requests. Possible profile misconfiguration."); logService.log(lwM2MClient, "Failed to process observe requests. Possible profile misconfiguration.");

6
dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java

@ -404,12 +404,6 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic
if (StringUtils.isEmpty(edge.getRoutingKey())) { if (StringUtils.isEmpty(edge.getRoutingKey())) {
throw new DataValidationException("Edge routing key should be specified!"); throw new DataValidationException("Edge routing key should be specified!");
} }
if (StringUtils.isEmpty(edge.getEdgeLicenseKey())) {
throw new DataValidationException("Edge license key should be specified!");
}
if (StringUtils.isEmpty(edge.getCloudEndpoint())) {
throw new DataValidationException("Cloud endpoint should be specified!");
}
if (edge.getTenantId() == null) { if (edge.getTenantId() == null) {
throw new DataValidationException("Edge should be assigned to tenant!"); throw new DataValidationException("Edge should be assigned to tenant!");
} else { } else {

22
dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java

@ -47,28 +47,12 @@ public class BaseEventService implements EventService {
public EventDao eventDao; public EventDao eventDao;
@Override @Override
public Event save(Event event) { public ListenableFuture<Void> saveAsync(Event event) {
eventValidator.validate(event, Event::getTenantId);
return eventDao.save(event.getTenantId(), event);
}
@Override
public ListenableFuture<Event> saveAsync(Event event) {
eventValidator.validate(event, Event::getTenantId); eventValidator.validate(event, Event::getTenantId);
checkAndTruncateDebugEvent(event); checkAndTruncateDebugEvent(event);
return eventDao.saveAsync(event); return eventDao.saveAsync(event);
} }
@Override
public Optional<Event> saveIfNotExists(Event event) {
eventValidator.validate(event, Event::getTenantId);
if (StringUtils.isEmpty(event.getUid())) {
throw new DataValidationException("Event uid should be specified!.");
}
checkAndTruncateDebugEvent(event);
return eventDao.saveIfNotExists(event);
}
private void checkAndTruncateDebugEvent(Event event) { private void checkAndTruncateDebugEvent(Event event) {
if (event.getType().startsWith("DEBUG") && event.getBody() != null && event.getBody().has("data")) { if (event.getType().startsWith("DEBUG") && event.getBody() != null && event.getBody().has("data")) {
String dataStr = event.getBody().get("data").asText(); String dataStr = event.getBody().get("data").asText();
@ -141,8 +125,8 @@ public class BaseEventService implements EventService {
} }
@Override @Override
public void cleanupEvents(long ttl, long debugTtl) { public void cleanupEvents(long regularEventStartTs, long regularEventEndTs, long debugEventStartTs, long debugEventEndTs) {
eventDao.cleanupEvents(ttl, debugTtl); eventDao.cleanupEvents(regularEventStartTs, regularEventEndTs, debugEventStartTs, debugEventEndTs);
} }
private DataValidator<Event> eventValidator = private DataValidator<Event> eventValidator =

26
dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java

@ -33,29 +33,13 @@ import java.util.UUID;
*/ */
public interface EventDao extends Dao<Event> { public interface EventDao extends Dao<Event> {
/**
* Save or update event object
*
* @param event the event object
* @return saved event object
*/
Event save(TenantId tenantId, Event event);
/** /**
* Save or update event object async * Save or update event object async
* *
* @param event the event object * @param event the event object
* @return saved event object future * @return saved event object future
*/ */
ListenableFuture<Event> saveAsync(Event event); ListenableFuture<Void> saveAsync(Event event);
/**
* Save event object if it is not yet saved
*
* @param event the event object
* @return saved event object
*/
Optional<Event> saveIfNotExists(Event event);
/** /**
* Find event by tenantId, entityId and eventUid. * Find event by tenantId, entityId and eventUid.
@ -104,8 +88,10 @@ public interface EventDao extends Dao<Event> {
/** /**
* Executes stored procedure to cleanup old events. Uses separate ttl for debug and other events. * Executes stored procedure to cleanup old events. Uses separate ttl for debug and other events.
* @param otherEventsTtl the ttl for events in seconds * @param regularEventStartTs the start time of the interval to use to delete non debug events
* @param debugEventsTtl the ttl for debug events in seconds * @param regularEventEndTs the end time of the interval to use to delete non debug events
* @param debugEventStartTs the start time of the interval to use to delete debug events
* @param debugEventEndTs the end time of the interval to use to delete debug events
*/ */
void cleanupEvents(long otherEventsTtl, long debugEventsTtl); void cleanupEvents(long regularEventStartTs, long regularEventEndTs, long debugEventStartTs, long debugEventEndTs);
} }

2
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -544,8 +544,6 @@ public class ModelConstants {
public static final String EDGE_ROUTING_KEY_PROPERTY = "routing_key"; public static final String EDGE_ROUTING_KEY_PROPERTY = "routing_key";
public static final String EDGE_SECRET_PROPERTY = "secret"; public static final String EDGE_SECRET_PROPERTY = "secret";
public static final String EDGE_LICENSE_KEY_PROPERTY = "edge_license_key";
public static final String EDGE_CLOUD_ENDPOINT_KEY_PROPERTY = "cloud_endpoint";
/** /**
* Edge queue constants. * Edge queue constants.

14
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractEdgeEntity.java

@ -35,10 +35,8 @@ import javax.persistence.Column;
import javax.persistence.MappedSuperclass; import javax.persistence.MappedSuperclass;
import java.util.UUID; import java.util.UUID;
import static org.thingsboard.server.dao.model.ModelConstants.EDGE_CLOUD_ENDPOINT_KEY_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.EDGE_CUSTOMER_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_CUSTOMER_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.EDGE_LABEL_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_LABEL_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.EDGE_LICENSE_KEY_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.EDGE_NAME_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_NAME_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.EDGE_ROOT_RULE_CHAIN_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_ROOT_RULE_CHAIN_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.EDGE_ROUTING_KEY_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.EDGE_ROUTING_KEY_PROPERTY;
@ -80,12 +78,6 @@ public abstract class AbstractEdgeEntity<T extends Edge> extends BaseSqlEntity<T
@Column(name = EDGE_SECRET_PROPERTY) @Column(name = EDGE_SECRET_PROPERTY)
private String secret; private String secret;
@Column(name = EDGE_LICENSE_KEY_PROPERTY)
private String edgeLicenseKey;
@Column(name = EDGE_CLOUD_ENDPOINT_KEY_PROPERTY)
private String cloudEndpoint;
@Type(type = "json") @Type(type = "json")
@Column(name = ModelConstants.EDGE_ADDITIONAL_INFO_PROPERTY) @Column(name = ModelConstants.EDGE_ADDITIONAL_INFO_PROPERTY)
private JsonNode additionalInfo; private JsonNode additionalInfo;
@ -113,8 +105,6 @@ public abstract class AbstractEdgeEntity<T extends Edge> extends BaseSqlEntity<T
this.label = edge.getLabel(); this.label = edge.getLabel();
this.routingKey = edge.getRoutingKey(); this.routingKey = edge.getRoutingKey();
this.secret = edge.getSecret(); this.secret = edge.getSecret();
this.edgeLicenseKey = edge.getEdgeLicenseKey();
this.cloudEndpoint = edge.getCloudEndpoint();
this.additionalInfo = edge.getAdditionalInfo(); this.additionalInfo = edge.getAdditionalInfo();
} }
@ -130,8 +120,6 @@ public abstract class AbstractEdgeEntity<T extends Edge> extends BaseSqlEntity<T
this.searchText = edgeEntity.getSearchText(); this.searchText = edgeEntity.getSearchText();
this.routingKey = edgeEntity.getRoutingKey(); this.routingKey = edgeEntity.getRoutingKey();
this.secret = edgeEntity.getSecret(); this.secret = edgeEntity.getSecret();
this.edgeLicenseKey = edgeEntity.getEdgeLicenseKey();
this.cloudEndpoint = edgeEntity.getCloudEndpoint();
this.additionalInfo = edgeEntity.getAdditionalInfo(); this.additionalInfo = edgeEntity.getAdditionalInfo();
} }
@ -166,8 +154,6 @@ public abstract class AbstractEdgeEntity<T extends Edge> extends BaseSqlEntity<T
edge.setLabel(label); edge.setLabel(label);
edge.setRoutingKey(routingKey); edge.setRoutingKey(routingKey);
edge.setSecret(secret); edge.setSecret(secret);
edge.setEdgeLicenseKey(edgeLicenseKey);
edge.setCloudEndpoint(cloudEndpoint);
edge.setAdditionalInfo(additionalInfo); edge.setAdditionalInfo(additionalInfo);
return edge; return edge;
} }

91
dao/src/main/java/org/thingsboard/server/dao/sql/event/AbstractEventInsertRepository.java

@ -1,91 +0,0 @@
/**
* Copyright © 2016-2022 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.dao.sql.event;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.thingsboard.server.dao.model.sql.EventEntity;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
@Slf4j
public abstract class AbstractEventInsertRepository implements EventInsertRepository {
@PersistenceContext
protected EntityManager entityManager;
@Autowired
protected PlatformTransactionManager transactionManager;
protected EventEntity saveAndGet(EventEntity entity, String insertOrUpdateOnPrimaryKeyConflict, String insertOrUpdateOnUniqueKeyConflict) {
EventEntity eventEntity = null;
TransactionStatus insertTransaction = getTransactionStatus(TransactionDefinition.PROPAGATION_REQUIRED);
try {
eventEntity = processSaveOrUpdate(entity, insertOrUpdateOnPrimaryKeyConflict);
transactionManager.commit(insertTransaction);
} catch (Throwable throwable) {
transactionManager.rollback(insertTransaction);
if (throwable.getCause() instanceof ConstraintViolationException) {
log.trace("Insert request leaded in a violation of a defined integrity constraint {} for Entity with entityId {} and entityType {}", throwable.getMessage(), entity.getEventUid(), entity.getEventType());
TransactionStatus transaction = getTransactionStatus(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
try {
eventEntity = processSaveOrUpdate(entity, insertOrUpdateOnUniqueKeyConflict);
transactionManager.commit(transaction);
} catch (Throwable th) {
log.trace("Could not execute the update statement for Entity with entityId {} and entityType {}", entity.getEventUid(), entity.getEventType());
transactionManager.rollback(transaction);
}
} else {
log.trace("Could not execute the insert statement for Entity with entityId {} and entityType {}", entity.getEventUid(), entity.getEventType());
}
}
return eventEntity;
}
@Modifying
protected abstract EventEntity doProcessSaveOrUpdate(EventEntity entity, String query);
protected Query getQuery(EventEntity entity, String query) {
return entityManager.createNativeQuery(query, EventEntity.class)
.setParameter("id", entity.getUuid())
.setParameter("created_time", entity.getCreatedTime())
.setParameter("body", entity.getBody().toString())
.setParameter("entity_id", entity.getEntityId())
.setParameter("entity_type", entity.getEntityType().name())
.setParameter("event_type", entity.getEventType())
.setParameter("event_uid", entity.getEventUid())
.setParameter("tenant_id", entity.getTenantId())
.setParameter("ts", entity.getTs());
}
private EventEntity processSaveOrUpdate(EventEntity entity, String query) {
return doProcessSaveOrUpdate(entity, query);
}
private TransactionStatus getTransactionStatus(int propagationRequired) {
DefaultTransactionDefinition insertDefinition = new DefaultTransactionDefinition();
insertDefinition.setPropagationBehavior(propagationRequired);
return transactionManager.getTransaction(insertDefinition);
}
}

2
dao/src/main/java/org/thingsboard/server/dao/sql/event/EventCleanupRepository.java

@ -17,6 +17,6 @@ package org.thingsboard.server.dao.sql.event;
public interface EventCleanupRepository { public interface EventCleanupRepository {
void cleanupEvents(long otherEventsTtl, long debugEventsTtl); void cleanupEvents(long regularEventStartTs, long regularEventEndTs, long debugEventStartTs, long debugEventEndTs);
} }

72
dao/src/main/java/org/thingsboard/server/dao/sql/event/EventInsertRepository.java

@ -15,10 +15,78 @@
*/ */
package org.thingsboard.server.dao.sql.event; package org.thingsboard.server.dao.sql.event;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.jdbc.core.BatchPreparedStatementSetter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import org.thingsboard.server.dao.model.sql.EventEntity; import org.thingsboard.server.dao.model.sql.EventEntity;
import org.thingsboard.server.dao.util.PsqlDao;
public interface EventInsertRepository { import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import java.util.regex.Pattern;
EventEntity saveOrUpdate(EventEntity entity); @PsqlDao
@Repository
@Transactional
public class EventInsertRepository {
private static final ThreadLocal<Pattern> PATTERN_THREAD_LOCAL = ThreadLocal.withInitial(() -> Pattern.compile(String.valueOf(Character.MIN_VALUE)));
private static final String EMPTY_STR = "";
private static final String INSERT =
"INSERT INTO event (id, created_time, body, entity_id, entity_type, event_type, event_uid, tenant_id, ts) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) " +
"ON CONFLICT DO NOTHING;";
@Autowired
protected JdbcTemplate jdbcTemplate;
@Autowired
private TransactionTemplate transactionTemplate;
@Value("${sql.remove_null_chars:true}")
private boolean removeNullChars;
protected void save(List<EventEntity> entities) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
jdbcTemplate.batchUpdate(INSERT, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
EventEntity event = entities.get(i);
ps.setObject(1, event.getId());
ps.setLong(2, event.getCreatedTime());
ps.setString(3, replaceNullChars(event.getBody().toString()));
ps.setObject(4, event.getEntityId());
ps.setString(5, event.getEntityType().name());
ps.setString(6, event.getEventType());
ps.setString(7, event.getEventUid());
ps.setObject(8, event.getTenantId());
ps.setLong(9, event.getTs());
}
@Override
public int getBatchSize() {
return entities.size();
}
});
}
});
}
private String replaceNullChars(String strValue) {
if (removeNullChars && strValue != null) {
return PATTERN_THREAD_LOCAL.get().matcher(strValue).replaceAll(EMPTY_STR);
}
return strValue;
}
} }

54
dao/src/main/java/org/thingsboard/server/dao/sql/event/HsqlEventCleanupRepository.java

@ -1,54 +0,0 @@
/**
* Copyright © 2016-2022 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.dao.sql.event;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import org.thingsboard.server.dao.sql.JpaAbstractDaoListeningExecutorService;
import org.thingsboard.server.dao.util.HsqlDao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.concurrent.TimeUnit;
@Slf4j
@HsqlDao
@Repository
public class HsqlEventCleanupRepository extends JpaAbstractDaoListeningExecutorService implements EventCleanupRepository {
@Override
public void cleanupEvents(long otherEventsTtl, long debugEventsTtl) {
long otherExpirationTime = System.currentTimeMillis() - otherEventsTtl * 1000;
try (Connection connection = dataSource.getConnection();
PreparedStatement stmt = connection.prepareStatement("DELETE FROM event WHERE ts < ? AND event_type != 'DEBUG_RULE_NODE' AND event_type != 'DEBUG_RULE_CHAIN'")) {
stmt.setLong(1, otherExpirationTime);
stmt.setQueryTimeout((int) TimeUnit.HOURS.toSeconds(1));
stmt.execute();
} catch (SQLException e) {
log.error("SQLException occurred during events TTL task execution ", e);
}
long debugExpirationTime = System.currentTimeMillis() - debugEventsTtl * 1000;
try (Connection connection = dataSource.getConnection();
PreparedStatement stmt = connection.prepareStatement("DELETE FROM event WHERE ts < ? AND (event_type = 'DEBUG_RULE_NODE' OR event_type = 'DEBUG_RULE_CHAIN')")) {
stmt.setLong(1, debugExpirationTime);
stmt.setQueryTimeout((int) TimeUnit.HOURS.toSeconds(1));
stmt.execute();
} catch (SQLException e) {
log.error("SQLException occurred during events TTL task execution ", e);
}
}
}

63
dao/src/main/java/org/thingsboard/server/dao/sql/event/HsqlEventInsertRepository.java

@ -1,63 +0,0 @@
/**
* Copyright © 2016-2022 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.dao.sql.event;
import org.springframework.stereotype.Repository;
import org.thingsboard.server.dao.model.sql.EventEntity;
import org.thingsboard.server.dao.util.HsqlDao;
import javax.persistence.Query;
@HsqlDao
@Repository
public class HsqlEventInsertRepository extends AbstractEventInsertRepository {
private static final String P_KEY_CONFLICT_STATEMENT = "(event.id=I.id)";
private static final String UNQ_KEY_CONFLICT_STATEMENT = "(event.tenant_id=I.tenant_id AND event.entity_type=I.entity_type AND event.entity_id=I.entity_id AND event.event_type=I.event_type AND event.event_uid=I.event_uid)";
private static final String INSERT_OR_UPDATE_ON_P_KEY_CONFLICT = getInsertString(P_KEY_CONFLICT_STATEMENT);
private static final String INSERT_OR_UPDATE_ON_UNQ_KEY_CONFLICT = getInsertString(UNQ_KEY_CONFLICT_STATEMENT);
@Override
public EventEntity saveOrUpdate(EventEntity entity) {
return saveAndGet(entity, INSERT_OR_UPDATE_ON_P_KEY_CONFLICT, INSERT_OR_UPDATE_ON_UNQ_KEY_CONFLICT);
}
@Override
protected EventEntity doProcessSaveOrUpdate(EventEntity entity, String query) {
getQuery(entity, query).executeUpdate();
return entityManager.find(EventEntity.class, entity.getUuid());
}
protected Query getQuery(EventEntity entity, String query) {
return entityManager.createNativeQuery(query, EventEntity.class)
.setParameter("id", entity.getUuid().toString())
.setParameter("created_time", entity.getCreatedTime())
.setParameter("body", entity.getBody().toString())
.setParameter("entity_id", entity.getEntityId().toString())
.setParameter("entity_type", entity.getEntityType().name())
.setParameter("event_type", entity.getEventType())
.setParameter("event_uid", entity.getEventUid())
.setParameter("tenant_id", entity.getTenantId().toString())
.setParameter("ts", entity.getTs());
}
private static String getInsertString(String conflictStatement) {
return "MERGE INTO event USING (VALUES UUID(:id), :created_time, :body, UUID(:entity_id), :entity_type, :event_type, :event_uid, UUID(:tenant_id), :ts) I (id, created_time, body, entity_id, entity_type, event_type, event_uid, tenant_id, ts) ON " + conflictStatement
+ " WHEN MATCHED THEN UPDATE SET event.id = I.id, event.created_time = I.created_time, event.body = I.body, event.entity_id = I.entity_id, event.entity_type = I.entity_type, event.event_type = I.event_type, event.event_uid = I.event_uid, event.tenant_id = I.tenant_id, event.ts = I.ts" +
" WHEN NOT MATCHED THEN INSERT (id, created_time, body, entity_id, entity_type, event_type, event_uid, tenant_id, ts) VALUES (I.id, I.created_time, I.body, I.entity_id, I.entity_type, I.event_type, I.event_uid, I.tenant_id, I.ts)";
}
}

121
dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java

@ -20,6 +20,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -31,18 +32,24 @@ import org.thingsboard.server.common.data.event.LifeCycleEventFilter;
import org.thingsboard.server.common.data.event.StatisticsEventFilter; import org.thingsboard.server.common.data.event.StatisticsEventFilter;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EventId; import org.thingsboard.server.common.data.id.EventId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.event.EventDao; import org.thingsboard.server.dao.event.EventDao;
import org.thingsboard.server.dao.model.sql.EventEntity; import org.thingsboard.server.dao.model.sql.EventEntity;
import org.thingsboard.server.dao.sql.JpaAbstractDao; import org.thingsboard.server.dao.sql.JpaAbstractDao;
import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent;
import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams;
import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.function.Function;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@ -74,8 +81,55 @@ public class JpaBaseEventDao extends JpaAbstractDao<EventEntity, Event> implemen
return eventRepository; return eventRepository;
} }
@Autowired
ScheduledLogExecutorComponent logExecutor;
@Autowired
private StatsFactory statsFactory;
@Value("${sql.events.batch_size:10000}")
private int batchSize;
@Value("${sql.events.batch_max_delay:100}")
private long maxDelay;
@Value("${sql.events.stats_print_interval_ms:10000}")
private long statsPrintIntervalMs;
@Value("${sql.events.batch_threads:3}")
private int batchThreads;
@Value("${sql.batch_sort:false}")
private boolean batchSortEnabled;
private TbSqlBlockingQueueWrapper<EventEntity> queue;
@PostConstruct
private void init() {
TbSqlBlockingQueueParams params = TbSqlBlockingQueueParams.builder()
.logName("Events")
.batchSize(batchSize)
.maxDelay(maxDelay)
.statsPrintIntervalMs(statsPrintIntervalMs)
.statsNamePrefix("events")
.batchSortEnabled(batchSortEnabled)
.build();
Function<EventEntity, Integer> hashcodeFunction = entity -> entity.getEntityId().hashCode();
queue = new TbSqlBlockingQueueWrapper<>(params, hashcodeFunction, batchThreads, statsFactory);
queue.init(logExecutor, v -> eventInsertRepository.save(v),
Comparator.comparing((EventEntity eventEntity) -> eventEntity.getTs())
);
}
@PreDestroy
private void destroy() {
if (queue != null) {
queue.destroy();
}
}
@Override @Override
public Event save(TenantId tenantId, Event event) { public ListenableFuture<Void> saveAsync(Event event) {
log.debug("Save event [{}] ", event); log.debug("Save event [{}] ", event);
if (event.getId() == null) { if (event.getId() == null) {
UUID timeBased = Uuids.timeBased(); UUID timeBased = Uuids.timeBased();
@ -92,33 +146,27 @@ public class JpaBaseEventDao extends JpaAbstractDao<EventEntity, Event> implemen
if (StringUtils.isEmpty(event.getUid())) { if (StringUtils.isEmpty(event.getUid())) {
event.setUid(event.getId().toString()); event.setUid(event.getId().toString());
} }
return save(new EventEntity(event), false).orElse(null);
return save(new EventEntity(event));
} }
@Override private ListenableFuture<Void> save(EventEntity entity) {
public ListenableFuture<Event> saveAsync(Event event) { log.debug("Save event [{}] ", entity);
log.debug("Save event [{}] ", event); if (entity.getTenantId() == null) {
if (event.getId() == null) { log.trace("Save system event with predefined id {}", systemTenantId);
UUID timeBased = Uuids.timeBased(); entity.setTenantId(systemTenantId);
event.setId(new EventId(timeBased));
event.setCreatedTime(Uuids.unixTimestamp(timeBased));
} else if (event.getCreatedTime() == 0L) {
UUID eventId = event.getId().getId();
if (eventId.version() == 1) {
event.setCreatedTime(Uuids.unixTimestamp(eventId));
} else {
event.setCreatedTime(System.currentTimeMillis());
}
} }
if (StringUtils.isEmpty(event.getUid())) { if (entity.getUuid() == null) {
event.setUid(event.getId().toString()); entity.setUuid(Uuids.timeBased());
} }
return service.submit(() -> save(new EventEntity(event), false).orElse(null)); if (StringUtils.isEmpty(entity.getEventUid())) {
entity.setEventUid(entity.getUuid().toString());
}
return addToQueue(entity);
} }
@Override private ListenableFuture<Void> addToQueue(EventEntity entity) {
public Optional<Event> saveIfNotExists(Event event) { return queue.add(entity);
return save(new EventEntity(event), true);
} }
@Override @Override
@ -259,28 +307,9 @@ public class JpaBaseEventDao extends JpaAbstractDao<EventEntity, Event> implemen
} }
@Override @Override
public void cleanupEvents(long otherEventsTtl, long debugEventsTtl) { public void cleanupEvents(long regularEventStartTs, long regularEventEndTs, long debugEventStartTs, long debugEventEndTs) {
log.info("Going to cleanup old events using debug events ttl: {}s and other events ttl: {}s", debugEventsTtl, otherEventsTtl); log.info("Going to cleanup old events. Interval for regular events: [{}:{}], for debug events: [{}:{}]", regularEventStartTs, regularEventEndTs, debugEventStartTs, debugEventEndTs);
eventCleanupRepository.cleanupEvents(otherEventsTtl, debugEventsTtl); eventCleanupRepository.cleanupEvents(regularEventStartTs, regularEventEndTs, debugEventStartTs, debugEventEndTs);
}
public Optional<Event> save(EventEntity entity, boolean ifNotExists) {
log.debug("Save event [{}] ", entity);
if (entity.getTenantId() == null) {
log.trace("Save system event with predefined id {}", systemTenantId);
entity.setTenantId(systemTenantId);
}
if (entity.getUuid() == null) {
entity.setUuid(Uuids.timeBased());
}
if (StringUtils.isEmpty(entity.getEventUid())) {
entity.setEventUid(entity.getUuid().toString());
}
if (ifNotExists &&
eventRepository.findByTenantIdAndEntityTypeAndEntityId(entity.getTenantId(), entity.getEntityType(), entity.getEntityId()) != null) {
return Optional.empty();
}
return Optional.of(DaoUtil.getData(eventInsertRepository.saveOrUpdate(entity)));
} }
private long notNull(Long value) { private long notNull(Long value) {

12
dao/src/main/java/org/thingsboard/server/dao/sql/event/PsqlEventCleanupRepository.java

@ -32,12 +32,14 @@ import java.util.concurrent.TimeUnit;
public class PsqlEventCleanupRepository extends JpaAbstractDaoListeningExecutorService implements EventCleanupRepository { public class PsqlEventCleanupRepository extends JpaAbstractDaoListeningExecutorService implements EventCleanupRepository {
@Override @Override
public void cleanupEvents(long otherEventsTtl, long debugEventsTtl) { public void cleanupEvents(long regularEventStartTs, long regularEventEndTs, long debugEventStartTs, long debugEventEndTs) {
try (Connection connection = dataSource.getConnection(); try (Connection connection = dataSource.getConnection();
PreparedStatement stmt = connection.prepareStatement("call cleanup_events_by_ttl(?,?,?)")) { PreparedStatement stmt = connection.prepareStatement("call cleanup_events_by_ttl(?,?,?,?,?)")) {
stmt.setLong(1, otherEventsTtl); stmt.setLong(1, regularEventStartTs);
stmt.setLong(2, debugEventsTtl); stmt.setLong(2, regularEventEndTs);
stmt.setLong(3, 0); stmt.setLong(3, debugEventStartTs);
stmt.setLong(4, debugEventEndTs);
stmt.setLong(5, 0);
stmt.setQueryTimeout((int) TimeUnit.HOURS.toSeconds(1)); stmt.setQueryTimeout((int) TimeUnit.HOURS.toSeconds(1));
stmt.execute(); stmt.execute();
printWarnings(stmt); printWarnings(stmt);

53
dao/src/main/java/org/thingsboard/server/dao/sql/event/PsqlEventInsertRepository.java

@ -1,53 +0,0 @@
/**
* Copyright © 2016-2022 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.dao.sql.event;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import org.thingsboard.server.dao.model.sql.EventEntity;
import org.thingsboard.server.dao.util.PsqlDao;
@Slf4j
@PsqlDao
@Repository
public class PsqlEventInsertRepository extends AbstractEventInsertRepository {
private static final String P_KEY_CONFLICT_STATEMENT = "(id)";
private static final String UNQ_KEY_CONFLICT_STATEMENT = "(tenant_id, created_time, entity_type, entity_id, event_type, event_uid)";
private static final String UPDATE_P_KEY_STATEMENT = "id = :id";
private static final String UPDATE_UNQ_KEY_STATEMENT = "created_time = :created_time, tenant_id = :tenant_id, entity_type = :entity_type, entity_id = :entity_id, event_type = :event_type, event_uid = :event_uid";
private static final String INSERT_OR_UPDATE_ON_P_KEY_CONFLICT = getInsertOrUpdateString(P_KEY_CONFLICT_STATEMENT, UPDATE_UNQ_KEY_STATEMENT);
private static final String INSERT_OR_UPDATE_ON_UNQ_KEY_CONFLICT = getInsertOrUpdateString(UNQ_KEY_CONFLICT_STATEMENT, UPDATE_P_KEY_STATEMENT);
@Override
public EventEntity saveOrUpdate(EventEntity entity) {
return saveAndGet(entity, INSERT_OR_UPDATE_ON_P_KEY_CONFLICT, INSERT_OR_UPDATE_ON_UNQ_KEY_CONFLICT);
}
@Override
protected EventEntity doProcessSaveOrUpdate(EventEntity entity, String query) {
return (EventEntity) getQuery(entity, query).getSingleResult();
}
private static String getInsertOrUpdateString(String eventKeyStatement, String updateKeyStatement) {
return "INSERT INTO event (id, created_time, body, entity_id, entity_type, event_type, event_uid, tenant_id, ts) " +
"VALUES (:id, :created_time, :body, :entity_id, :entity_type, :event_type, :event_uid, :tenant_id, :ts) " +
"ON CONFLICT " + eventKeyStatement + " DO UPDATE SET body = :body, ts = :ts," + updateKeyStatement + " returning *";
}
}

10
dao/src/main/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDao.java

@ -58,19 +58,17 @@ public class JpaTenantDao extends JpaAbstractSearchTextDao<TenantEntity, Tenant>
} }
@Override @Override
public PageData<Tenant> findTenantsByRegion(TenantId tenantId, String region, PageLink pageLink) { public PageData<Tenant> findTenants(TenantId tenantId, PageLink pageLink) {
return DaoUtil.toPageData(tenantRepository return DaoUtil.toPageData(tenantRepository
.findByRegionNextPage( .findTenantsNextPage(
region,
Objects.toString(pageLink.getTextSearch(), ""), Objects.toString(pageLink.getTextSearch(), ""),
DaoUtil.toPageable(pageLink))); DaoUtil.toPageable(pageLink)));
} }
@Override @Override
public PageData<TenantInfo> findTenantInfosByRegion(TenantId tenantId, String region, PageLink pageLink) { public PageData<TenantInfo> findTenantInfos(TenantId tenantId, PageLink pageLink) {
return DaoUtil.toPageData(tenantRepository return DaoUtil.toPageData(tenantRepository
.findTenantInfoByRegionNextPage( .findTenantInfosNextPage(
region,
Objects.toString(pageLink.getTextSearch(), ""), Objects.toString(pageLink.getTextSearch(), ""),
DaoUtil.toPageable(pageLink, TenantInfoEntity.tenantInfoColumnMap))); DaoUtil.toPageable(pageLink, TenantInfoEntity.tenantInfoColumnMap)));
} }

14
dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantRepository.java

@ -36,19 +36,15 @@ public interface TenantRepository extends JpaRepository<TenantEntity, UUID> {
"WHERE t.id = :tenantId") "WHERE t.id = :tenantId")
TenantInfoEntity findTenantInfoById(@Param("tenantId") UUID tenantId); TenantInfoEntity findTenantInfoById(@Param("tenantId") UUID tenantId);
@Query("SELECT t FROM TenantEntity t WHERE t.region = :region " + @Query("SELECT t FROM TenantEntity t WHERE LOWER(t.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))")
"AND LOWER(t.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") Page<TenantEntity> findTenantsNextPage(@Param("textSearch") String textSearch,
Page<TenantEntity> findByRegionNextPage(@Param("region") String region, Pageable pageable);
@Param("textSearch") String textSearch,
Pageable pageable);
@Query("SELECT new org.thingsboard.server.dao.model.sql.TenantInfoEntity(t, p.name) " + @Query("SELECT new org.thingsboard.server.dao.model.sql.TenantInfoEntity(t, p.name) " +
"FROM TenantEntity t " + "FROM TenantEntity t " +
"LEFT JOIN TenantProfileEntity p on p.id = t.tenantProfileId " + "LEFT JOIN TenantProfileEntity p on p.id = t.tenantProfileId " +
"WHERE t.region = :region " + "WHERE LOWER(t.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))")
"AND LOWER(t.searchText) LIKE LOWER(CONCAT('%', :textSearch, '%'))") Page<TenantInfoEntity> findTenantInfosNextPage(@Param("textSearch") String textSearch,
Page<TenantInfoEntity> findTenantInfoByRegionNextPage(@Param("region") String region,
@Param("textSearch") String textSearch,
Pageable pageable); Pageable pageable);
@Query("SELECT t.id FROM TenantEntity t") @Query("SELECT t.id FROM TenantEntity t")

7
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantDao.java

@ -37,15 +37,14 @@ public interface TenantDao extends Dao<Tenant> {
Tenant save(TenantId tenantId, Tenant tenant); Tenant save(TenantId tenantId, Tenant tenant);
/** /**
* Find tenants by region and page link. * Find tenants by page link.
* *
* @param region the region
* @param pageLink the page link * @param pageLink the page link
* @return the list of tenant objects * @return the list of tenant objects
*/ */
PageData<Tenant> findTenantsByRegion(TenantId tenantId, String region, PageLink pageLink); PageData<Tenant> findTenants(TenantId tenantId, PageLink pageLink);
PageData<TenantInfo> findTenantInfosByRegion(TenantId tenantId, String region, PageLink pageLink); PageData<TenantInfo> findTenantInfos(TenantId tenantId, PageLink pageLink);
PageData<TenantId> findTenantsIds(PageLink pageLink); PageData<TenantId> findTenantsIds(PageLink pageLink);

15
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java

@ -24,7 +24,6 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo; import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
@ -168,20 +167,20 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
public PageData<Tenant> findTenants(PageLink pageLink) { public PageData<Tenant> findTenants(PageLink pageLink) {
log.trace("Executing findTenants pageLink [{}]", pageLink); log.trace("Executing findTenants pageLink [{}]", pageLink);
Validator.validatePageLink(pageLink); Validator.validatePageLink(pageLink);
return tenantDao.findTenantsByRegion(TenantId.SYS_TENANT_ID, DEFAULT_TENANT_REGION, pageLink); return tenantDao.findTenants(TenantId.SYS_TENANT_ID, pageLink);
} }
@Override @Override
public PageData<TenantInfo> findTenantInfos(PageLink pageLink) { public PageData<TenantInfo> findTenantInfos(PageLink pageLink) {
log.trace("Executing findTenantInfos pageLink [{}]", pageLink); log.trace("Executing findTenantInfos pageLink [{}]", pageLink);
Validator.validatePageLink(pageLink); Validator.validatePageLink(pageLink);
return tenantDao.findTenantInfosByRegion(TenantId.SYS_TENANT_ID, DEFAULT_TENANT_REGION, pageLink); return tenantDao.findTenantInfos(TenantId.SYS_TENANT_ID, pageLink);
} }
@Override @Override
public void deleteTenants() { public void deleteTenants() {
log.trace("Executing deleteTenants"); log.trace("Executing deleteTenants");
tenantsRemover.removeEntities(TenantId.SYS_TENANT_ID, DEFAULT_TENANT_REGION); tenantsRemover.removeEntities(TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID);
} }
private DataValidator<Tenant> tenantValidator = private DataValidator<Tenant> tenantValidator =
@ -214,12 +213,12 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe
} }
}; };
private PaginatedRemover<String, Tenant> tenantsRemover = private PaginatedRemover<TenantId, Tenant> tenantsRemover =
new PaginatedRemover<String, Tenant>() { new PaginatedRemover<>() {
@Override @Override
protected PageData<Tenant> findEntities(TenantId tenantId, String region, PageLink pageLink) { protected PageData<Tenant> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {
return tenantDao.findTenantsByRegion(tenantId, region, pageLink); return tenantDao.findTenants(tenantId, pageLink);
} }
@Override @Override

27
dao/src/main/resources/sql/schema-entities.sql

@ -601,8 +601,6 @@ CREATE TABLE IF NOT EXISTS edge (
label varchar(255), label varchar(255),
routing_key varchar(255), routing_key varchar(255),
secret varchar(255), secret varchar(255),
edge_license_key varchar(30),
cloud_endpoint varchar(255),
search_text varchar(255), search_text varchar(255),
tenant_id uuid, tenant_id uuid,
CONSTRAINT edge_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT edge_name_unq_key UNIQUE (tenant_id, name),
@ -634,24 +632,31 @@ CREATE TABLE IF NOT EXISTS rpc (
status varchar(255) NOT NULL status varchar(255) NOT NULL
); );
CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint) CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(
IN regular_events_start_ts bigint,
IN regular_events_end_ts bigint,
IN debug_events_start_ts bigint,
IN debug_events_end_ts bigint,
INOUT deleted bigint)
LANGUAGE plpgsql AS LANGUAGE plpgsql AS
$$ $$
DECLARE DECLARE
ttl_ts bigint;
debug_ttl_ts bigint;
ttl_deleted_count bigint DEFAULT 0; ttl_deleted_count bigint DEFAULT 0;
debug_ttl_deleted_count bigint DEFAULT 0; debug_ttl_deleted_count bigint DEFAULT 0;
BEGIN BEGIN
IF ttl > 0 THEN IF regular_events_start_ts > 0 AND regular_events_end_ts > 0 THEN
ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - ttl::bigint * 1000)::bigint;
EXECUTE format( EXECUTE format(
'WITH deleted AS (DELETE FROM event WHERE ts < %L::bigint AND (event_type != %L::varchar AND event_type != %L::varchar) RETURNING *) SELECT count(*) FROM deleted', ttl_ts, 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into ttl_deleted_count; 'WITH deleted AS (DELETE FROM event WHERE id in (SELECT id from event WHERE ts > %L::bigint AND ts < %L::bigint AND ' ||
'(event_type != %L::varchar AND event_type != %L::varchar)) RETURNING *) ' ||
'SELECT count(*) FROM deleted', regular_events_start_ts, regular_events_end_ts,
'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into ttl_deleted_count;
END IF; END IF;
IF debug_ttl > 0 THEN IF debug_events_start_ts > 0 AND debug_events_end_ts > 0 THEN
debug_ttl_ts := (EXTRACT(EPOCH FROM current_timestamp) * 1000 - debug_ttl::bigint * 1000)::bigint;
EXECUTE format( EXECUTE format(
'WITH deleted AS (DELETE FROM event WHERE ts < %L::bigint AND (event_type = %L::varchar OR event_type = %L::varchar) RETURNING *) SELECT count(*) FROM deleted', debug_ttl_ts, 'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into debug_ttl_deleted_count; 'WITH deleted AS (DELETE FROM event WHERE id in (SELECT id from event WHERE ts > %L::bigint AND ts < %L::bigint AND ' ||
'(event_type = %L::varchar OR event_type = %L::varchar)) RETURNING *) ' ||
'SELECT count(*) FROM deleted', debug_events_start_ts, debug_events_end_ts,
'DEBUG_RULE_NODE', 'DEBUG_RULE_CHAIN') into debug_ttl_deleted_count;
END IF; END IF;
RAISE NOTICE 'Events removed by ttl: %', ttl_deleted_count; RAISE NOTICE 'Events removed by ttl: %', ttl_deleted_count;
RAISE NOTICE 'Debug Events removed by ttl: %', debug_ttl_deleted_count; RAISE NOTICE 'Debug Events removed by ttl: %', debug_ttl_deleted_count;

3
dao/src/test/java/org/thingsboard/server/dao/AbstractJpaDaoTest.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao; package org.thingsboard.server.dao;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Answers;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.TestExecutionListeners;
@ -36,7 +37,7 @@ import org.thingsboard.server.dao.service.DaoSqlTest;
DirtiesContextTestExecutionListener.class}) DirtiesContextTestExecutionListener.class})
public abstract class AbstractJpaDaoTest { public abstract class AbstractJpaDaoTest {
@MockBean @MockBean(answer = Answers.RETURNS_MOCKS)
StatsFactory statsFactory; StatsFactory statsFactory;
} }

2
dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java

@ -249,8 +249,6 @@ public abstract class AbstractServiceTest {
edge.setType(type); edge.setType(type);
edge.setSecret(RandomStringUtils.randomAlphanumeric(20)); edge.setSecret(RandomStringUtils.randomAlphanumeric(20));
edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20)); edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20));
edge.setEdgeLicenseKey(RandomStringUtils.randomAlphanumeric(20));
edge.setCloudEndpoint("http://localhost:8080");
return edge; return edge;
} }
} }

4
dao/src/test/java/org/thingsboard/server/dao/service/BaseDashboardServiceTest.java

@ -32,13 +32,11 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.SortOrder;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
@ -420,8 +418,6 @@ public abstract class BaseDashboardServiceTest extends AbstractServiceTest {
edge.setType("default"); edge.setType("default");
edge.setSecret(RandomStringUtils.randomAlphanumeric(20)); edge.setSecret(RandomStringUtils.randomAlphanumeric(20));
edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20)); edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20));
edge.setEdgeLicenseKey(RandomStringUtils.randomAlphanumeric(20));
edge.setCloudEndpoint("http://localhost:8080");
edge = edgeService.saveEdge(edge, true); edge = edgeService.saveEdge(edge, true);
try { try {
dashboardService.assignDashboardToEdge(tenantId, dashboard.getId(), edge.getId()); dashboardService.assignDashboardToEdge(tenantId, dashboard.getId(), edge.getId());

2
dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java

@ -314,8 +314,6 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
edge.setLabel("EdgeLabel" + i); edge.setLabel("EdgeLabel" + i);
edge.setSecret(RandomStringUtils.randomAlphanumeric(20)); edge.setSecret(RandomStringUtils.randomAlphanumeric(20));
edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20)); edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20));
edge.setEdgeLicenseKey(RandomStringUtils.randomAlphanumeric(20));
edge.setCloudEndpoint("http://localhost:8080");
return edge; return edge;
} }

33
dao/src/test/java/org/thingsboard/server/dao/service/event/BaseEventServiceTest.java

@ -17,6 +17,7 @@ package org.thingsboard.server.dao.service.event;
import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.api.core.uuid.Uuids;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.Event;
@ -35,6 +36,7 @@ import java.time.LocalDateTime;
import java.time.Month; import java.time.Month;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutionException;
public abstract class BaseEventServiceTest extends AbstractServiceTest { public abstract class BaseEventServiceTest extends AbstractServiceTest {
@ -42,21 +44,13 @@ public abstract class BaseEventServiceTest extends AbstractServiceTest {
public void saveEvent() throws Exception { public void saveEvent() throws Exception {
DeviceId devId = new DeviceId(Uuids.timeBased()); DeviceId devId = new DeviceId(Uuids.timeBased());
Event event = generateEvent(null, devId, "ALARM", Uuids.timeBased().toString()); Event event = generateEvent(null, devId, "ALARM", Uuids.timeBased().toString());
Event saved = eventService.save(event); eventService.saveAsync(event).get();
Optional<Event> loaded = eventService.findEvent(event.getTenantId(), event.getEntityId(), event.getType(), event.getUid()); Optional<Event> loaded = eventService.findEvent(event.getTenantId(), event.getEntityId(), event.getType(), event.getUid());
Assert.assertTrue(loaded.isPresent()); Assert.assertTrue(loaded.isPresent());
Assert.assertNotNull(loaded.get()); Assert.assertNotNull(loaded.get());
Assert.assertEquals(saved, loaded.get()); Assert.assertEquals(event.getEntityId(), loaded.get().getEntityId());
} Assert.assertEquals(event.getType(), loaded.get().getType());
Assert.assertEquals(event.getBody(), loaded.get().getBody());
@Test
public void saveEventIfNotExists() throws Exception {
DeviceId devId = new DeviceId(Uuids.timeBased());
Event event = generateEvent(null, devId, "ALARM", Uuids.timeBased().toString());
Optional<Event> saved = eventService.saveIfNotExists(event);
Assert.assertTrue(saved.isPresent());
saved = eventService.saveIfNotExists(event);
Assert.assertFalse(saved.isPresent());
} }
@Test @Test
@ -96,11 +90,11 @@ public abstract class BaseEventServiceTest extends AbstractServiceTest {
@Test @Test
public void findEventsByTypeAndTimeDescOrder() throws Exception { public void findEventsByTypeAndTimeDescOrder() throws Exception {
long timeBeforeStartTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 11, 30).toEpochSecond(ZoneOffset.UTC); long timeBeforeStartTime = LocalDateTime.of(2017, Month.NOVEMBER, 1, 11, 30).toEpochSecond(ZoneOffset.UTC);
long startTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 12, 0).toEpochSecond(ZoneOffset.UTC); long startTime = LocalDateTime.of(2017, Month.NOVEMBER, 1, 12, 0).toEpochSecond(ZoneOffset.UTC);
long eventTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 12, 30).toEpochSecond(ZoneOffset.UTC); long eventTime = LocalDateTime.of(2017, Month.NOVEMBER, 1, 12, 30).toEpochSecond(ZoneOffset.UTC);
long endTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 13, 0).toEpochSecond(ZoneOffset.UTC); long endTime = LocalDateTime.of(2017, Month.NOVEMBER, 1, 13, 0).toEpochSecond(ZoneOffset.UTC);
long timeAfterEndTime = LocalDateTime.of(2016, Month.NOVEMBER, 1, 13, 30).toEpochSecond(ZoneOffset.UTC); long timeAfterEndTime = LocalDateTime.of(2017, Month.NOVEMBER, 1, 13, 30).toEpochSecond(ZoneOffset.UTC);
CustomerId customerId = new CustomerId(Uuids.timeBased()); CustomerId customerId = new CustomerId(Uuids.timeBased());
TenantId tenantId = TenantId.fromUUID(Uuids.timeBased()); TenantId tenantId = TenantId.fromUUID(Uuids.timeBased());
@ -129,9 +123,10 @@ public abstract class BaseEventServiceTest extends AbstractServiceTest {
Assert.assertFalse(events.hasNext()); Assert.assertFalse(events.hasNext());
} }
private Event saveEventWithProvidedTime(long time, EntityId entityId, TenantId tenantId) throws IOException { private Event saveEventWithProvidedTime(long time, EntityId entityId, TenantId tenantId) throws Exception {
Event event = generateEvent(tenantId, entityId, DataConstants.STATS, null); Event event = generateEvent(tenantId, entityId, DataConstants.STATS, null);
event.setId(new EventId(Uuids.startOf(time))); event.setId(new EventId(Uuids.startOf(time)));
return eventService.save(event); eventService.saveAsync(event).get();
return event;
} }
} }

29
dao/src/test/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDaoTest.java

@ -37,6 +37,7 @@ import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ExecutionException;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@ -63,18 +64,6 @@ public class JpaBaseEventDaoTest extends AbstractJpaDaoTest {
} }
} }
@Test
public void testSaveIfNotExists() {
UUID eventId = Uuids.timeBased();
UUID entityId = Uuids.timeBased();
Event event = getEvent(eventId, tenantId, entityId);
Optional<Event> optEvent1 = eventDao.saveIfNotExists(event);
assertTrue("Optional is expected to be non-empty", optEvent1.isPresent());
assertEquals(event, optEvent1.get());
Optional<Event> optEvent2 = eventDao.saveIfNotExists(event);
assertFalse("Optional is expected to be empty", optEvent2.isPresent());
}
@Test @Test
public void findEvent() { public void findEvent() {
UUID entityId = Uuids.timeBased(); UUID entityId = Uuids.timeBased();
@ -85,7 +74,7 @@ public class JpaBaseEventDaoTest extends AbstractJpaDaoTest {
} }
@Test @Test
public void findEventsByEntityIdAndPageLink() { public void findEventsByEntityIdAndPageLink() throws Exception {
UUID entityId1 = Uuids.timeBased(); UUID entityId1 = Uuids.timeBased();
UUID entityId2 = Uuids.timeBased(); UUID entityId2 = Uuids.timeBased();
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
@ -118,7 +107,7 @@ public class JpaBaseEventDaoTest extends AbstractJpaDaoTest {
} }
@Test @Test
public void findEventsByEntityIdAndEventTypeAndPageLink() { public void findEventsByEntityIdAndEventTypeAndPageLink() throws Exception {
UUID entityId1 = Uuids.timeBased(); UUID entityId1 = Uuids.timeBased();
UUID entityId2 = Uuids.timeBased(); UUID entityId2 = Uuids.timeBased();
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
@ -145,27 +134,27 @@ public class JpaBaseEventDaoTest extends AbstractJpaDaoTest {
assertEquals(1, events5.getData().size()); assertEquals(1, events5.getData().size());
} }
private long createEventsTwoEntitiesTwoTypes(UUID tenantId, UUID entityId1, UUID entityId2, int count) { private long createEventsTwoEntitiesTwoTypes(UUID tenantId, UUID entityId1, UUID entityId2, int count) throws Exception {
for (int i = 0; i < count / 2; i++) { for (int i = 0; i < count / 2; i++) {
String type = i % 2 == 0 ? STATS : ALARM; String type = i % 2 == 0 ? STATS : ALARM;
UUID eventId1 = Uuids.timeBased(); UUID eventId1 = Uuids.timeBased();
Event event1 = getEvent(eventId1, tenantId, entityId1, type); Event event1 = getEvent(eventId1, tenantId, entityId1, type);
eventDao.save(TenantId.fromUUID(tenantId), event1); eventDao.saveAsync(event1).get();
UUID eventId2 = Uuids.timeBased(); UUID eventId2 = Uuids.timeBased();
Event event2 = getEvent(eventId2, tenantId, entityId2, type); Event event2 = getEvent(eventId2, tenantId, entityId2, type);
eventDao.save(TenantId.fromUUID(tenantId), event2); eventDao.saveAsync(event2).get();
} }
return System.currentTimeMillis(); return System.currentTimeMillis();
} }
private long createEventsTwoEntities(UUID tenantId, UUID entityId1, UUID entityId2, int count) { private long createEventsTwoEntities(UUID tenantId, UUID entityId1, UUID entityId2, int count) throws Exception {
for (int i = 0; i < count / 2; i++) { for (int i = 0; i < count / 2; i++) {
UUID eventId1 = Uuids.timeBased(); UUID eventId1 = Uuids.timeBased();
Event event1 = getEvent(eventId1, tenantId, entityId1); Event event1 = getEvent(eventId1, tenantId, entityId1);
eventDao.save(TenantId.fromUUID(tenantId), event1); eventDao.saveAsync(event1).get();
UUID eventId2 = Uuids.timeBased(); UUID eventId2 = Uuids.timeBased();
Event event2 = getEvent(eventId2, tenantId, entityId2); Event event2 = getEvent(eventId2, tenantId, entityId2);
eventDao.save(TenantId.fromUUID(tenantId), event2); eventDao.saveAsync(event2).get();
} }
return System.currentTimeMillis(); return System.currentTimeMillis();
} }

16
dao/src/test/java/org/thingsboard/server/dao/sql/tenant/JpaTenantDaoTest.java

@ -66,36 +66,34 @@ public class JpaTenantDaoTest extends AbstractJpaDaoTest {
@Test @Test
//@DatabaseSetup("classpath:dbunit/empty_dataset.xml") //@DatabaseSetup("classpath:dbunit/empty_dataset.xml")
public void testFindTenantsByRegion() { public void testFindTenants() {
createTenants(); createTenants();
assertEquals(60, tenantDao.find(AbstractServiceTest.SYSTEM_TENANT_ID).size()); assertEquals(30, tenantDao.find(AbstractServiceTest.SYSTEM_TENANT_ID).size());
PageLink pageLink = new PageLink(20, 0, "title"); PageLink pageLink = new PageLink(20, 0, "title");
PageData<Tenant> tenants1 = tenantDao.findTenantsByRegion(AbstractServiceTest.SYSTEM_TENANT_ID, "REGION_1", pageLink); PageData<Tenant> tenants1 = tenantDao.findTenants(AbstractServiceTest.SYSTEM_TENANT_ID, pageLink);
assertEquals(20, tenants1.getData().size()); assertEquals(20, tenants1.getData().size());
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
PageData<Tenant> tenants2 = tenantDao.findTenantsByRegion(AbstractServiceTest.SYSTEM_TENANT_ID, "REGION_1", PageData<Tenant> tenants2 = tenantDao.findTenants(AbstractServiceTest.SYSTEM_TENANT_ID,
pageLink); pageLink);
assertEquals(10, tenants2.getData().size()); assertEquals(10, tenants2.getData().size());
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
PageData<Tenant> tenants3 = tenantDao.findTenantsByRegion(AbstractServiceTest.SYSTEM_TENANT_ID, "REGION_1", PageData<Tenant> tenants3 = tenantDao.findTenants(AbstractServiceTest.SYSTEM_TENANT_ID,
pageLink); pageLink);
assertEquals(0, tenants3.getData().size()); assertEquals(0, tenants3.getData().size());
} }
private void createTenants() { private void createTenants() {
for (int i = 0; i < 30; i++) { for (int i = 0; i < 30; i++) {
createTenant("REGION_1", "TITLE", i); createTenant("TITLE", i);
createTenant("REGION_2", "TITLE", i);
} }
} }
void createTenant(String region, String title, int index) { void createTenant(String title, int index) {
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setId(TenantId.fromUUID(Uuids.timeBased())); tenant.setId(TenantId.fromUUID(Uuids.timeBased()));
tenant.setRegion(region);
tenant.setTitle(title + "_" + index); tenant.setTitle(title + "_" + index);
tenant.setTenantProfileId(tenantProfile.getId()); tenant.setTenantProfileId(tenantProfile.getId());
createdTenants.add(tenantDao.save(TenantId.SYS_TENANT_ID, tenant)); createdTenants.add(tenantDao.save(TenantId.SYS_TENANT_ID, tenant));

3
pom.xml

@ -97,6 +97,8 @@
</sonar.exclusions> </sonar.exclusions>
<elasticsearch.version>5.0.2</elasticsearch.version> <elasticsearch.version>5.0.2</elasticsearch.version>
<delight-nashorn-sandbox.version>0.2.1</delight-nashorn-sandbox.version> <delight-nashorn-sandbox.version>0.2.1</delight-nashorn-sandbox.version>
<!-- IMPORTANT: If you change the version of the kafka client, make sure to synchronize our overwritten implementation of the
org.apache.kafka.common.network.NetworkReceive class in the application module. It addresses the issue https://issues.apache.org/jira/browse/KAFKA-4090. -->
<kafka.version>2.8.0</kafka.version> <kafka.version>2.8.0</kafka.version>
<bucket4j.version>4.1.1</bucket4j.version> <bucket4j.version>4.1.1</bucket4j.version>
<fst.version>2.57</fst.version> <fst.version>2.57</fst.version>
@ -807,6 +809,7 @@
<exclude>**/*.patch</exclude> <exclude>**/*.patch</exclude>
<exclude>**/apache/cassandra/io/**</exclude> <exclude>**/apache/cassandra/io/**</exclude>
<exclude>.run/**</exclude> <exclude>.run/**</exclude>
<exclude>**/NetworkReceive.java</exclude>
</excludes> </excludes>
<mapping> <mapping>
<proto>JAVADOC_STYLE</proto> <proto>JAVADOC_STYLE</proto>

30
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java

@ -23,10 +23,12 @@ import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata; import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.common.errors.TimeoutException;
import org.apache.kafka.common.serialization.StringSerializer; import org.apache.kafka.common.serialization.StringSerializer;
import org.apache.kafka.common.header.Headers; import org.apache.kafka.common.header.Headers;
import org.apache.kafka.common.header.internals.RecordHeader; import org.apache.kafka.common.header.internals.RecordHeader;
import org.apache.kafka.common.header.internals.RecordHeaders; import org.apache.kafka.common.header.internals.RecordHeaders;
import org.springframework.util.ReflectionUtils;
import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNode;
@ -34,10 +36,12 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.TbRelationTypes; import org.thingsboard.rule.engine.api.TbRelationTypes;
import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.exception.ThingsboardKafkaClientError;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import java.lang.reflect.Field;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Properties; import java.util.Properties;
@ -62,16 +66,23 @@ public class TbKafkaNode implements TbNode {
private static final String TOPIC = "topic"; private static final String TOPIC = "topic";
private static final String ERROR = "error"; private static final String ERROR = "error";
public static final String TB_MSG_MD_PREFIX = "tb_msg_md_"; public static final String TB_MSG_MD_PREFIX = "tb_msg_md_";
private static final Field IO_THREAD_FIELD = ReflectionUtils.findField(KafkaProducer.class, "ioThread");
static {
IO_THREAD_FIELD.setAccessible(true);
}
private TbKafkaNodeConfiguration config; private TbKafkaNodeConfiguration config;
private boolean addMetadataKeyValuesAsKafkaHeaders; private boolean addMetadataKeyValuesAsKafkaHeaders;
private Charset toBytesCharset; private Charset toBytesCharset;
private Producer<?, String> producer; private Producer<?, String> producer;
private Throwable initError;
@Override @Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbKafkaNodeConfiguration.class); this.config = TbNodeUtils.convert(configuration, TbKafkaNodeConfiguration.class);
this.initError = null;
Properties properties = new Properties(); Properties properties = new Properties();
properties.put(ProducerConfig.CLIENT_ID_CONFIG, "producer-tb-kafka-node-" + ctx.getSelfId().getId().toString() + "-" + ctx.getServiceId()); properties.put(ProducerConfig.CLIENT_ID_CONFIG, "producer-tb-kafka-node-" + ctx.getSelfId().getId().toString() + "-" + ctx.getServiceId());
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, config.getBootstrapServers()); properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, config.getBootstrapServers());
@ -89,6 +100,13 @@ public class TbKafkaNode implements TbNode {
toBytesCharset = config.getKafkaHeadersCharset() != null ? Charset.forName(config.getKafkaHeadersCharset()) : StandardCharsets.UTF_8; toBytesCharset = config.getKafkaHeadersCharset() != null ? Charset.forName(config.getKafkaHeadersCharset()) : StandardCharsets.UTF_8;
try { try {
this.producer = new KafkaProducer<>(properties); this.producer = new KafkaProducer<>(properties);
Thread ioThread = (Thread) ReflectionUtils.getField(IO_THREAD_FIELD, producer);
ioThread.setUncaughtExceptionHandler((thread, throwable) -> {
if (throwable instanceof ThingsboardKafkaClientError) {
initError = throwable;
destroy();
}
});
} catch (Exception e) { } catch (Exception e) {
throw new TbNodeException(e); throw new TbNodeException(e);
} }
@ -98,10 +116,14 @@ public class TbKafkaNode implements TbNode {
public void onMsg(TbContext ctx, TbMsg msg) { public void onMsg(TbContext ctx, TbMsg msg) {
String topic = TbNodeUtils.processPattern(config.getTopicPattern(), msg); String topic = TbNodeUtils.processPattern(config.getTopicPattern(), msg);
try { try {
ctx.getExternalCallExecutor().executeAsync(() -> { if (initError != null) {
publish(ctx, msg, topic); ctx.tellFailure(msg, new RuntimeException("Failed to initialize Kafka rule node producer: " + initError.getMessage()));
return null; } else {
}); ctx.getExternalCallExecutor().executeAsync(() -> {
publish(ctx, msg, topic);
return null;
});
}
} catch (Exception e) { } catch (Exception e) {
ctx.tellFailure(msg, e); ctx.tellFailure(msg, e);
} }

70
tools/src/main/shell/lwm2m/lwm2m_cfssl_chain_all_for_test.sh

@ -29,26 +29,41 @@ cd -- "$(
dirname "${0}" dirname "${0}"
)" || exit 1 )" || exit 1
Help() ResultInfo()
{ {
# Display Help # # Display Help
echo "Description of the script functions." # echo "Description of the script functions."
echo # echo
echo "Syntax: scriptTemplate [-g|h|v|V]" # echo "Syntax: scriptTemplate [-g|h|v|V]"
echo "options:" # echo "options:"
echo "h Print this Help." # echo "h Print this Help."
echo "v Verbose mode." # echo "v Verbose mode."
echo "V Print software version and exit." # echo "V Print software version and exit."
echo # echo
if [ "$IS_IHFO" = false ] ; then
if [ "$IS_SERVER_CREATED_KEY" = true ] ; then
./lwm2m_cfssl_chain_server_for_test.sh > /dev/null 2>&1 &
fi
if [ "$IS_TRUST_CLIENT_CREATED_KEY" = true ] ; then
./lwM2M_cfssl_chain_clients_for_test.sh ${INTERMEDIATE_START} ${INTERMEDIATE_FINISH} ${CLIENT_START} ${CLIENT_FINISH} > /dev/null 2>&1 &
fi
else
if [ "$IS_SERVER_CREATED_KEY" = true ] ; then
./lwm2m_cfssl_chain_server_for_test.sh
fi
if [ "$IS_TRUST_CLIENT_CREATED_KEY" = true ] ; then
./lwM2M_cfssl_chain_clients_for_test.sh ${INTERMEDIATE_START} ${INTERMEDIATE_FINISH} ${CLIENT_START} ${CLIENT_FINISH}
fi
fi
} }
if [ "$1" == "-h" ] ; then if [ "$1" == "-h" ] ||[ "$1" == "-?" ] || [ "$1" == "-help" ] ; then
echo -e "Usage 1: ./`basename $0` \"Information is not displayed\" : \"Keys for the server are generated\" : \"Keys for the clients and trusts are generated\"" echo -e "Usage 1: \"Information is not displayed\" : \"Keys for the server are generated\" : \"Keys for the clients and trusts are generated\"\n./`basename $0`"
echo -e "Usage 2: ./`basename $0` true \"Information is displayed\" : \"Keys for the server are generated\" : \"Keys for the clients and trusts are generated\"" echo -e "Usage 2: \"Information is displayed\" : \"Keys for the server are generated\" : \"Keys for the clients and trusts are generated\"\n./`basename $0` true \n./`basename $0` true true true "
echo -e "Usage 3: ./`basename $0` true false \"Information is displayed\" : \"Keys for the server are not generated\" : \"Keys for the clients and trusts are generated\"" echo -e "Usage 3: \"Information is displayed\" : \"Keys for the server are not generated\" : \"Keys for the clients and trusts are generated\"\n./`basename $0` true false \n./`basename $0` true false true"
echo -e "Usage 4: ./`basename $0` true false false \"Information is displayed\" : \"Keys for the server are not generated\" : \"Keys for the clients and trusts are not generated\"" echo -e "Usage 4: \"Information is displayed\" : \"Keys for the server are not generated\" : \"Keys for the clients and trusts are not generated\"\n./`basename $0` true false false"
echo -e "Usage 5: ./`basename $0` true true false \"Information is displayed\" : \"Keys for the server are generated\" : \"Keys for the clients and trusts are not generated\"" echo -e "Usage 5: \"Information is displayed\" : \"Keys for the server are generated\" : \"Keys for the clients and trusts are not generated\"\n./`basename $0` true true false"
echo "This Help File: ./`basename $0` -h" echo -e "This Help File: \n./`basename $0` -h | -? | -help"
exit 0 exit 0
fi fi
@ -64,6 +79,14 @@ if [ -n "$3" ]; then
IS_TRUST_CLIENT_CREATED_KEY=$3 IS_TRUST_CLIENT_CREATED_KEY=$3
fi fi
if [ "$IS_SERVER_CREATED_KEY" = false ] && [ "$IS_TRUST_CLIENT_CREATED_KEY" = false ] ; then
echo -e "Result is null"
echo -e "This Help File: \n./`basename $0` -h | -? | -help"
exit 0
fi
if [ "$IS_IHFO" = false ] ; then if [ "$IS_IHFO" = false ] ; then
if [ "$IS_SERVER_CREATED_KEY" = true ] ; then if [ "$IS_SERVER_CREATED_KEY" = true ] ; then
./lwm2m_cfssl_chain_server_for_test.sh > /dev/null 2>&1 & ./lwm2m_cfssl_chain_server_for_test.sh > /dev/null 2>&1 &
@ -78,4 +101,15 @@ else
if [ "$IS_TRUST_CLIENT_CREATED_KEY" = true ] ; then if [ "$IS_TRUST_CLIENT_CREATED_KEY" = true ] ; then
./lwM2M_cfssl_chain_clients_for_test.sh ${INTERMEDIATE_START} ${INTERMEDIATE_FINISH} ${CLIENT_START} ${CLIENT_FINISH} ./lwM2M_cfssl_chain_clients_for_test.sh ${INTERMEDIATE_START} ${INTERMEDIATE_FINISH} ${CLIENT_START} ${CLIENT_FINISH}
fi fi
fi fi
echo -e "Result into:"
if [ "$IS_SERVER_CREATED_KEY" = true ] ; then
echo -e "./Server"
fi
if [ "$IS_TRUST_CLIENT_CREATED_KEY" = true ] ; then
echo -e "./Client"
echo -e "./Trust"
fi
echo -e "Finish"

10
ui-ngx/src/app/core/http/entity.service.ts

@ -1050,8 +1050,6 @@ export class EntityService {
additionalInfo: { additionalInfo: {
description: edgeEntityData.description description: edgeEntityData.description
}, },
edgeLicenseKey: edgeEntityData.edgeLicenseKey,
cloudEndpoint: edgeEntityData.cloudEndpoint !== '' ? edgeEntityData.cloudEndpoint : window.location.origin,
routingKey: edgeEntityData.routingKey !== '' ? edgeEntityData.routingKey : guid(), routingKey: edgeEntityData.routingKey !== '' ? edgeEntityData.routingKey : guid(),
secret: edgeEntityData.secret !== '' ? edgeEntityData.secret : generateSecret(20) secret: edgeEntityData.secret !== '' ? edgeEntityData.secret : generateSecret(20)
}; };
@ -1099,8 +1097,6 @@ export class EntityService {
const edgeEntityData: EdgeImportEntityData = entityData as EdgeImportEntityData; const edgeEntityData: EdgeImportEntityData = entityData as EdgeImportEntityData;
if (result.label !== edgeEntityData.label || if (result.label !== edgeEntityData.label ||
result.type !== edgeEntityData.type || result.type !== edgeEntityData.type ||
(edgeEntityData.cloudEndpoint !== '' && result.cloudEndpoint !== edgeEntityData.cloudEndpoint) ||
(edgeEntityData.edgeLicenseKey !== '' && result.edgeLicenseKey !== edgeEntityData.edgeLicenseKey) ||
(edgeEntityData.routingKey !== '' && result.routingKey !== edgeEntityData.routingKey) || (edgeEntityData.routingKey !== '' && result.routingKey !== edgeEntityData.routingKey) ||
(edgeEntityData.secret !== '' && result.secret !== edgeEntityData.secret) || (edgeEntityData.secret !== '' && result.secret !== edgeEntityData.secret) ||
additionalInfo.description !== edgeEntityData.description) { additionalInfo.description !== edgeEntityData.description) {
@ -1108,12 +1104,6 @@ export class EntityService {
result.type = edgeEntityData.type; result.type = edgeEntityData.type;
result.additionalInfo = additionalInfo; result.additionalInfo = additionalInfo;
result.additionalInfo.description = edgeEntityData.description; result.additionalInfo.description = edgeEntityData.description;
if (edgeEntityData.cloudEndpoint !== '') {
result.cloudEndpoint = edgeEntityData.cloudEndpoint;
}
if (edgeEntityData.edgeLicenseKey !== '') {
result.edgeLicenseKey = edgeEntityData.edgeLicenseKey;
}
if (edgeEntityData.routingKey !== '') { if (edgeEntityData.routingKey !== '') {
result.routingKey = edgeEntityData.routingKey; result.routingKey = edgeEntityData.routingKey;
} }

6
ui-ngx/src/app/core/utils.ts

@ -185,6 +185,12 @@ export function objToBase64(obj: any): string {
})); }));
} }
export function base64toString(b64Encoded: string): string {
return decodeURIComponent(atob(b64Encoded).split('').map((c) => {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
}
export function objToBase64URI(obj: any): string { export function objToBase64URI(obj: any): string {
return encodeURIComponent(objToBase64(obj)); return encodeURIComponent(objToBase64(obj));
} }

19
ui-ngx/src/app/modules/home/components/event/event-content-dialog.component.ts

@ -27,6 +27,7 @@ import { getAce } from '@shared/models/ace/ace.models';
import { Observable } from 'rxjs/internal/Observable'; import { Observable } from 'rxjs/internal/Observable';
import { beautifyJs } from '@shared/models/beautify.models'; import { beautifyJs } from '@shared/models/beautify.models';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { base64toString, isLiteralObject } from '@core/utils';
export interface EventContentDialogData { export interface EventContentDialogData {
content: string; content: string;
@ -64,6 +65,14 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
this.createEditor(this.eventContentEditorElmRef, this.content); this.createEditor(this.eventContentEditorElmRef, this.content);
} }
isJson(str) {
try {
return isLiteralObject(JSON.parse(str));
} catch (e) {
return false;
}
}
createEditor(editorElementRef: ElementRef, content: string) { createEditor(editorElementRef: ElementRef, content: string) {
const editorElement = editorElementRef.nativeElement; const editorElement = editorElementRef.nativeElement;
let mode = 'java'; let mode = 'java';
@ -72,6 +81,16 @@ export class EventContentDialogComponent extends DialogComponent<EventContentDia
mode = contentTypesMap.get(this.contentType).code; mode = contentTypesMap.get(this.contentType).code;
if (this.contentType === ContentType.JSON && content) { if (this.contentType === ContentType.JSON && content) {
content$ = beautifyJs(content, {indent_size: 4}); content$ = beautifyJs(content, {indent_size: 4});
} else if (this.contentType === ContentType.BINARY && content) {
try {
const decodedData = base64toString(content);
if (this.isJson(decodedData)) {
mode = 'json';
content$ = beautifyJs(decodedData, {indent_size: 4});
} else {
content$ = of(decodedData);
}
} catch (e) {}
} }
} }
if (!content$) { if (!content$) {

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save