Browse Source

Merge branch 'master' into fix/ota-device-profile

# Conflicts:
#	application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
pull/5990/head
Viacheslav Klimov 4 years ago
parent
commit
5bf9107deb
  1. 20
      README.md
  2. 18
      application/src/main/data/json/system/widget_bundles/maps.json
  3. 50
      application/src/main/data/upgrade/3.3.3/schema_event_ttl_procedure.sql
  4. 184
      application/src/main/java/org/apache/kafka/common/network/NetworkReceive.java
  5. 14
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  6. 2
      application/src/main/java/org/thingsboard/server/actors/stats/StatsActor.java
  7. 38
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  8. 4
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  9. 72
      application/src/main/java/org/thingsboard/server/controller/EdgeController.java
  10. 4
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  11. 1
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  12. 134
      application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java
  13. 110
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeLicenseService.java
  14. 11
      application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java
  15. 8
      application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java
  16. 6
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java
  17. 8
      application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java
  18. 7
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java
  19. 86
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java
  20. 43
      application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityEdgeProcessor.java
  21. 2
      application/src/main/java/org/thingsboard/server/service/importing/BulkImportColumnType.java
  22. 18
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  23. 4
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java
  24. 148
      application/src/main/java/org/thingsboard/server/service/partition/AbstractPartitionBasedService.java
  25. 210
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  26. 2
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java
  27. 2
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
  28. 32
      application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java
  29. 11
      application/src/main/resources/thingsboard.yml
  30. 6
      application/src/test/java/org/thingsboard/server/actors/stats/StatsActorTest.java
  31. 2
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  32. 61
      application/src/test/java/org/thingsboard/server/controller/AbstractWebsocketTest.java
  33. 2
      application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java
  34. 2
      application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
  35. 435
      application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java
  36. 4
      application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java
  37. 62
      application/src/test/java/org/thingsboard/server/rules/flow/AbstractRuleEngineFlowIntegrationTest.java
  38. 23
      application/src/test/java/org/thingsboard/server/rules/lifecycle/AbstractRuleEngineLifecycleIntegrationTest.java
  39. 86
      application/src/test/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateServiceTest.java
  40. 1
      application/src/test/java/org/thingsboard/server/service/ttl/EventsCleanUpServiceTest.java
  41. 315
      application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
  42. 106
      application/src/test/java/org/thingsboard/server/transport/lwm2m/Lwm2mTestHelper.java
  43. 181
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java
  44. 7
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2mBinaryAppDataContainer.java
  45. 235
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/Lwm2mServer.java
  46. 26
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java
  47. 162
      application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java
  48. 168
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/AbstractRpcLwM2MIntegrationTest.java
  49. 2
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDiscoverTest.java
  50. 7
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationExecuteTest.java
  51. 78
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationObserveTest.java
  52. 25
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationReadTest.java
  53. 324
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/AbstractSecurityLwM2MIntegrationTest.java
  54. 76
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/NoSecLwM2MIntegrationTest.java
  55. 91
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/PskLwm2mIntegrationTest.java
  56. 115
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/RpkLwM2MIntegrationTest.java
  57. 112
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_NoTrustLwM2MIntegrationTest.java
  58. 69
      application/src/test/java/org/thingsboard/server/transport/lwm2m/security/sql/X509_TrustLwM2MIntegrationTest.java
  59. 8
      application/src/test/resources/application-test.properties
  60. 0
      application/src/test/resources/logback-test.xml
  61. 299
      application/src/test/resources/lwm2m/credentials/shell/lwM2M_cfssl_chain_trusts_and_clients_for_test.sh
  62. 65
      application/src/test/resources/lwm2m/credentials/shell/lwm2m_cfssl_chain_for_test_All.sh
  63. 298
      application/src/test/resources/lwm2m/credentials/shell/lwm2m_cfssl_chain_server_for_test.sh
  64. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java
  65. 8
      common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java
  66. 20
      common/data/src/main/java/org/thingsboard/server/common/data/edge/Edge.java
  67. 8
      common/data/src/main/java/org/thingsboard/server/common/data/exception/ThingsboardKafkaClientError.java
  68. 11
      common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java
  69. 4
      common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java
  70. 6
      common/edge-api/src/main/proto/edge.proto
  71. 11
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgMetaData.java
  72. 56
      common/message/src/test/java/org/thingsboard/server/common/msg/TbMsgMetaDataTest.java
  73. 3
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java
  74. 2
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java
  75. 12
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java
  76. 21
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapConfigStoreTaskProvider.java
  77. 10
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java
  78. 10
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MNetworkConfig.java
  79. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java
  80. 28
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java
  81. 63
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java
  82. 12
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java
  83. 40
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java
  84. 30
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2MResourceInstance.java
  85. 30
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2MSingleResource.java
  86. 30
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2mMultipleResource.java
  87. 29
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbResourceModel.java
  88. 34
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java
  89. 12
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbRedisLwM2MClientStore.java
  90. 349
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java
  91. 4
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java
  92. 38
      common/transport/lwm2m/src/test/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientTest.java
  93. 101
      common/transport/lwm2m/src/test/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java
  94. 14
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  95. 16
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java
  96. 14
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java
  97. 11
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/limits/ProxyIpFilter.java
  98. 2
      common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java
  99. 37
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  100. 85
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

20
README.md

@ -12,20 +12,20 @@ ThingsBoard documentation is hosted on [thingsboard.io](https://thingsboard.io/d
## IoT use cases ## IoT use cases
[**Smart metering**](https://thingsboard.io/smart-metering/)
[![Smart metering](https://user-images.githubusercontent.com/8308069/31455788-6888a948-aec1-11e7-9819-410e0ba785e0.gif "Smart metering")](https://thingsboard.io/smart-metering/)
[**IoT Rule Engine**](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/)
[![IoT Rule Engine](https://thingsboard.io/images/demo/send-email-rule-chain.gif "IoT Rule Engine")](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/)
[**Smart energy**](https://thingsboard.io/smart-energy/) [**Smart energy**](https://thingsboard.io/smart-energy/)
[![Smart energy](https://cloud.githubusercontent.com/assets/8308069/24495682/aebd45d0-153e-11e7-8de4-7360ed5b41ae.gif "Smart energy")](https://thingsboard.io/smart-energy/) [![Smart energy](https://user-images.githubusercontent.com/8308069/152984256-eb48564a-645c-468d-912b-f554b63104a5.gif "Smart energy")](https://thingsboard.io/smart-energy/)
[**Fleet tracking**](https://thingsboard.io/fleet-tracking/)
[![Fleet tracking](https://user-images.githubusercontent.com/8308069/152984528-0054ed55-8b8b-4cda-ba45-02fe95a81222.gif "Fleet tracking")](https://thingsboard.io/fleet-tracking/)
[**Smart farming**](https://thingsboard.io/smart-farming/) [**Smart farming**](https://thingsboard.io/smart-farming/)
[![Smart farming](https://cloud.githubusercontent.com/assets/8308069/24496824/10dc1144-1542-11e7-8aa1-5d3a281d5a1a.gif "Smart farming")](https://thingsboard.io/smart-farming/) [![Smart farming](https://user-images.githubusercontent.com/8308069/152984443-a98b7d3d-ff7a-4037-9011-e71e1e6f755f.gif "Smart farming")](https://thingsboard.io/smart-farming/)
[**Fleet tracking**](https://thingsboard.io/fleet-tracking/) [**IoT Rule Engine**](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/)
[![Fleet tracking](https://cloud.githubusercontent.com/assets/8308069/24497169/3a1a61e0-1543-11e7-8d55-3c8a13f35634.gif "Fleet tracking")](https://thingsboard.io/fleet-tracking/) [![IoT Rule Engine](https://thingsboard.io/images/demo/send-email-rule-chain.gif "IoT Rule Engine")](https://thingsboard.io/docs/user-guide/rule-engine-2-0/re-getting-started/)
[**Smart metering**](https://thingsboard.io/smart-metering/)
[![Smart metering](https://user-images.githubusercontent.com/8308069/31455788-6888a948-aec1-11e7-9819-410e0ba785e0.gif "Smart metering")](https://thingsboard.io/smart-metering/)
## Getting Started ## Getting Started

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) {

38
application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java

@ -26,10 +26,7 @@ import org.thingsboard.server.actors.TbActorRef;
import org.thingsboard.server.actors.TbEntityActorId; import org.thingsboard.server.actors.TbEntityActorId;
import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate; import org.thingsboard.server.actors.TbEntityTypeActorIdPredicate;
import org.thingsboard.server.actors.device.DeviceActorCreator; import org.thingsboard.server.actors.device.DeviceActorCreator;
import org.thingsboard.server.actors.device.SessionTimeoutCheckMsg;
import org.thingsboard.server.actors.ruleChain.RuleChainInputMsg;
import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor; import org.thingsboard.server.actors.ruleChain.RuleChainManagerActor;
import org.thingsboard.server.actors.ruleChain.RuleChainOutputMsg;
import org.thingsboard.server.actors.service.ContextBasedCreator; import org.thingsboard.server.actors.service.ContextBasedCreator;
import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageState;
@ -65,7 +62,7 @@ import java.util.Optional;
@Slf4j @Slf4j
public class TenantActor extends RuleChainManagerActor { public class TenantActor extends RuleChainManagerActor {
private boolean isRuleEngineForCurrentTenant; private boolean isRuleEngine;
private boolean isCore; private boolean isCore;
private ApiUsageState apiUsageState; private ApiUsageState apiUsageState;
@ -78,39 +75,37 @@ public class TenantActor extends RuleChainManagerActor {
@Override @Override
public void init(TbActorCtx ctx) throws TbActorException { public void init(TbActorCtx ctx) throws TbActorException {
super.init(ctx); super.init(ctx);
log.info("[{}] Starting tenant actor.", tenantId); log.debug("[{}] Starting tenant actor.", tenantId);
try { try {
Tenant tenant = systemContext.getTenantService().findTenantById(tenantId); Tenant tenant = systemContext.getTenantService().findTenantById(tenantId);
if (tenant == null) { if (tenant == null) {
cantFindTenant = true; cantFindTenant = true;
log.info("[{}] Started tenant actor for missing tenant.", tenantId); log.info("[{}] Started tenant actor for missing tenant.", tenantId);
} else { } else {
apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenant.getId()));
// This Service may be started for specific tenant only. // This Service may be started for specific tenant only.
Optional<TenantId> isolatedTenantId = systemContext.getServiceInfoProvider().getIsolatedTenant(); Optional<TenantId> isolatedTenantId = systemContext.getServiceInfoProvider().getIsolatedTenant();
TenantProfile tenantProfile = systemContext.getTenantProfileCache().get(tenant.getTenantProfileId()); TenantProfile tenantProfile = systemContext.getTenantProfileCache().get(tenant.getTenantProfileId());
isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE); isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE);
isRuleEngineForCurrentTenant = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE); isRuleEngine = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE);
if (isRuleEngineForCurrentTenant) { if (isRuleEngine) {
try { try {
if (isolatedTenantId.map(id -> id.equals(tenantId)).orElseGet(() -> !tenantProfile.isIsolatedTbRuleEngine())) { if (isolatedTenantId.map(id -> id.equals(tenantId)).orElseGet(() -> !tenantProfile.isIsolatedTbRuleEngine())) {
if (apiUsageState.isReExecEnabled()) { if (getApiUsageState().isReExecEnabled()) {
log.info("[{}] Going to init rule chains", tenantId); log.debug("[{}] Going to init rule chains", tenantId);
initRuleChains(); initRuleChains();
} else { } else {
log.info("[{}] Skip init of the rule chains due to API limits", tenantId); log.info("[{}] Skip init of the rule chains due to API limits", tenantId);
} }
} else { } else {
isRuleEngineForCurrentTenant = false; isRuleEngine = false;
} }
} catch (Exception e) { } catch (Exception e) {
cantFindTenant = true; cantFindTenant = true;
} }
} }
log.info("[{}] Tenant actor started.", tenantId); log.debug("[{}] Tenant actor started.", tenantId);
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}] Unknown failure", tenantId, e); log.warn("[{}] Unknown failure", tenantId, e);
@ -193,12 +188,12 @@ public class TenantActor extends RuleChainManagerActor {
} }
private void onQueueToRuleEngineMsg(QueueToRuleEngineMsg msg) { private void onQueueToRuleEngineMsg(QueueToRuleEngineMsg msg) {
if (!isRuleEngineForCurrentTenant) { if (!isRuleEngine) {
log.warn("RECEIVED INVALID MESSAGE: {}", msg); log.warn("RECEIVED INVALID MESSAGE: {}", msg);
return; return;
} }
TbMsg tbMsg = msg.getMsg(); TbMsg tbMsg = msg.getMsg();
if (apiUsageState.isReExecEnabled()) { if (getApiUsageState().isReExecEnabled()) {
if (tbMsg.getRuleChainId() == null) { if (tbMsg.getRuleChainId() == null) {
if (getRootChainActor() != null) { if (getRootChainActor() != null) {
getRootChainActor().tell(msg); getRootChainActor().tell(msg);
@ -222,7 +217,7 @@ public class TenantActor extends RuleChainManagerActor {
} }
private void onRuleChainMsg(RuleChainAwareMsg msg) { private void onRuleChainMsg(RuleChainAwareMsg msg) {
if (apiUsageState.isReExecEnabled()) { if (getApiUsageState().isReExecEnabled()) {
getOrCreateActor(msg.getRuleChainId()).tell(msg); getOrCreateActor(msg.getRuleChainId()).tell(msg);
} }
} }
@ -241,7 +236,7 @@ public class TenantActor extends RuleChainManagerActor {
private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) {
if (msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE)) { if (msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE)) {
ApiUsageState old = apiUsageState; ApiUsageState old = getApiUsageState();
apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenantId)); apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenantId));
if (old.isReExecEnabled() && !apiUsageState.isReExecEnabled()) { if (old.isReExecEnabled() && !apiUsageState.isReExecEnabled()) {
log.info("[{}] Received API state update. Going to DISABLE Rule Engine execution.", tenantId); log.info("[{}] Received API state update. Going to DISABLE Rule Engine execution.", tenantId);
@ -261,7 +256,7 @@ public class TenantActor extends RuleChainManagerActor {
edgeRpcService.updateEdge(tenantId, edge); edgeRpcService.updateEdge(tenantId, edge);
} }
} }
} else if (isRuleEngineForCurrentTenant) { } else if (isRuleEngine) {
TbActorRef target = getEntityActorRef(msg.getEntityId()); TbActorRef target = getEntityActorRef(msg.getEntityId());
if (target != null) { if (target != null) {
if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) { if (msg.getEntityId().getEntityType() == EntityType.RULE_CHAIN) {
@ -289,6 +284,13 @@ public class TenantActor extends RuleChainManagerActor {
systemContext.getEdgeRpcService().onEdgeEvent(tenantId, msg.getEdgeId()); systemContext.getEdgeRpcService().onEdgeEvent(tenantId, msg.getEdgeId());
} }
private ApiUsageState getApiUsageState() {
if (apiUsageState == null) {
apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenantId));
}
return apiUsageState;
}
public static class ActorCreator extends ContextBasedCreator { public static class ActorCreator extends ContextBasedCreator {
private final TenantId tenantId; private final TenantId tenantId;

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;

72
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);
} }
@ -180,7 +169,7 @@ public class EdgeController extends BaseController {
accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation, accessControlService.checkPermission(getCurrentUser(), Resource.EDGE, operation,
edge.getId(), edge); edge.getId(), edge);
Edge savedEdge = checkNotNull(edgeService.saveEdge(edge, true)); Edge savedEdge = checkNotNull(edgeService.saveEdge(edge));
onEdgeCreatedOrUpdated(tenantId, savedEdge, edgeTemplateRootRuleChain, !created, getCurrentUser()); onEdgeCreatedOrUpdated(tenantId, savedEdge, edgeTemplateRootRuleChain, !created, getCurrentUser());
return savedEdge; return savedEdge;
@ -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

@ -218,6 +218,7 @@ public class ThingsboardInstallService {
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 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.3.3"); databaseEntitiesUpgradeService.upgradeDatabase("3.3.3");
log.info("Updating system data..."); log.info("Updating system data...");
databaseEntitiesUpgradeService.upgradeDatabase("3.3.3");
systemDataLoaderService.updateSystemWidgets(); systemDataLoaderService.updateSystemWidgets();
break; break;

134
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);
@ -242,17 +236,12 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener<Pa
if (partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) { if (partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) {
return getOrFetchState(tenantId, tenantId).getApiUsageState(); return getOrFetchState(tenantId, tenantId).getApiUsageState();
} else { } else {
updateLock.lock(); state = otherUsageStates.get(tenantId);
try { if (state == null) {
state = otherUsageStates.get(tenantId); state = apiUsageStateService.findTenantApiUsageState(tenantId);
if (state == null) { if (state != null) {
state = apiUsageStateService.findTenantApiUsageState(tenantId); otherUsageStates.put(tenantId, state);
if (state != null) {
otherUsageStates.put(tenantId, state);
}
} }
} finally {
updateLock.unlock();
} }
return state; return state;
} }
@ -311,6 +300,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 +340,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 +426,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 +479,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 +493,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 +538,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);
}
}
}
}

11
application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java

@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -38,7 +39,6 @@ import org.thingsboard.server.service.edge.rpc.processor.CustomerEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.EntityEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.EntityEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor;
import org.thingsboard.server.cluster.TbClusterService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
@ -93,7 +93,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
@Override @Override
public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws IOException { public Edge setEdgeRootRuleChain(TenantId tenantId, Edge edge, RuleChainId ruleChainId) throws IOException {
edge.setRootRuleChainId(ruleChainId); edge.setRootRuleChainId(ruleChainId);
Edge savedEdge = edgeService.saveEdge(edge, true); Edge savedEdge = edgeService.saveEdge(edge);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, EdgeEventActionType.UPDATED, ruleChainId, null); saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.RULE_CHAIN, EdgeEventActionType.UPDATED, ruleChainId, null);
return savedEdge; return savedEdge;
} }
@ -122,7 +122,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
@Override @Override
public void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) { public void pushNotificationToEdge(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback) {
log.trace("Pushing notification to edge {}", edgeNotificationMsg); log.debug("Pushing notification to edge {}", edgeNotificationMsg);
try { try {
TenantId tenantId = TenantId.fromUUID(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB())); TenantId tenantId = TenantId.fromUUID(new UUID(edgeNotificationMsg.getTenantIdMSB(), edgeNotificationMsg.getTenantIdLSB()));
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
@ -153,11 +153,12 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
relationProcessor.processRelationNotification(tenantId, edgeNotificationMsg); relationProcessor.processRelationNotification(tenantId, edgeNotificationMsg);
break; break;
default: default:
log.debug("Edge event type [{}] is not designed to be pushed to edge", type); log.warn("Edge event type [{}] is not designed to be pushed to edge", type);
} }
} catch (Exception e) { } catch (Exception e) {
callback.onFailure(e); callback.onFailure(e);
log.error("Can't push to edge updates, edgeNotificationMsg [{}]", edgeNotificationMsg, e); String errMsg = String.format("Can't push to edge updates, edgeNotificationMsg [%s]", edgeNotificationMsg);
log.error(errMsg, e);
} finally { } finally {
callback.onSuccess(); callback.onSuccess();
} }

8
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;
@ -74,7 +68,7 @@ public class EdgeBulkImportService extends AbstractBulkImportService<Edge> {
@Override @Override
protected Edge saveEntity(Edge entity, Map<BulkImportColumnType, String> fields) { protected Edge saveEntity(Edge entity, Map<BulkImportColumnType, String> fields) {
return edgeService.saveEdge(entity, true); return edgeService.saveEdge(entity);
} }
@Override @Override

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

@ -643,7 +643,9 @@ public final class EdgeGrpcSession implements Closeable {
} }
} }
} catch (Exception e) { } catch (Exception e) {
log.error("[{}] Can't process uplink msg [{}]", this.sessionId, uplinkMsg, e); String errMsg = String.format("[%s] Can't process uplink msg [%s]", this.sessionId, uplinkMsg);
log.error(errMsg, e);
return Futures.immediateFailedFuture(e);
} }
return Futures.allAsList(result); return Futures.allAsList(result);
} }
@ -690,8 +692,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()));
} }

7
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java

@ -19,6 +19,8 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
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.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEvent;
@ -43,6 +45,7 @@ import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetTypeService;
import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.dao.widget.WidgetsBundleService;
@ -62,7 +65,6 @@ import org.thingsboard.server.service.edge.rpc.constructor.WidgetTypeMsgConstruc
import org.thingsboard.server.service.edge.rpc.constructor.WidgetsBundleMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.WidgetsBundleMsgConstructor;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.profile.TbDeviceProfileCache;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
@Slf4j @Slf4j
@ -129,6 +131,9 @@ public abstract class BaseEdgeProcessor {
@Autowired @Autowired
protected WidgetTypeService widgetTypeService; protected WidgetTypeService widgetTypeService;
@Autowired
protected DataValidator<Device> deviceValidator;
@Autowired @Autowired
protected EntityDataMsgConstructor entityDataMsgConstructor; protected EntityDataMsgConstructor entityDataMsgConstructor;

86
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java

@ -26,7 +26,6 @@ import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
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;
@ -45,11 +44,14 @@ 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.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.rpc.RpcError;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
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;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg;
@ -59,7 +61,6 @@ import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgMetadata; import org.thingsboard.server.queue.TbQueueMsgMetadata;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg; import org.thingsboard.server.service.rpc.FromDeviceRpcResponseActorMsg;
import java.util.UUID; import java.util.UUID;
@ -79,38 +80,37 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
String deviceName = deviceUpdateMsg.getName(); String deviceName = deviceUpdateMsg.getName();
Device device = deviceService.findDeviceByTenantIdAndName(tenantId, deviceName); Device device = deviceService.findDeviceByTenantIdAndName(tenantId, deviceName);
if (device != null) { if (device != null) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); boolean deviceAlreadyExistsForThisEdge = isDeviceAlreadyExistsOnCloudForThisEdge(tenantId, edge, device);
PageData<EdgeId> pageData; if (deviceAlreadyExistsForThisEdge) {
do { log.info("[{}] Device with name '{}' already exists on the cloud, and related to this edge [{}]. " +
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, device.getId(), pageLink); "deviceUpdateMsg [{}], Updating device", tenantId, deviceName, edge.getId(), deviceUpdateMsg);
boolean update = false; updateDevice(tenantId, edge, deviceUpdateMsg);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { } else {
if (pageData.getData().contains(edge.getId())) { log.info("[{}] Device with name '{}' already exists on the cloud, but not related to this edge [{}]. deviceUpdateMsg [{}]." +
update = true; "Creating a new device with random prefix and relate to this edge", tenantId, deviceName, edge.getId(), deviceUpdateMsg);
} String newDeviceName = deviceUpdateMsg.getName() + "_" + RandomStringUtils.randomAlphabetic(15);
if (pageData.hasNext()) { Device newDevice;
pageLink = pageLink.nextPageLink(); try {
} newDevice = createDevice(tenantId, edge, deviceUpdateMsg, newDeviceName);
} catch (DataValidationException e) {
String errMsg = String.format("[%s] Device update msg can't be processed due to data validation [%s]", tenantId, deviceUpdateMsg);
log.error(errMsg, e);
return Futures.immediateFuture(null);
} }
ObjectNode body = mapper.createObjectNode();
if (update) { body.put("conflictName", deviceName);
log.info("[{}] Device with name '{}' already exists on the cloud, and related to this edge [{}]. " + saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, newDevice.getId(), body);
"deviceUpdateMsg [{}], Updating device", tenantId, deviceName, edge.getId(), deviceUpdateMsg); saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, newDevice.getId(), null);
updateDevice(tenantId, edge, deviceUpdateMsg); }
} else {
log.info("[{}] Device with name '{}' already exists on the cloud, but not related to this edge [{}]. deviceUpdateMsg [{}]." +
"Creating a new device with random prefix and relate to this edge", tenantId, deviceName, edge.getId(), deviceUpdateMsg);
String newDeviceName = deviceUpdateMsg.getName() + "_" + RandomStringUtils.randomAlphabetic(15);
Device newDevice = createDevice(tenantId, edge, deviceUpdateMsg, newDeviceName);
ObjectNode body = mapper.createObjectNode();
body.put("conflictName", deviceName);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, newDevice.getId(), body);
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, newDevice.getId(), null);
}
} while (pageData != null && pageData.hasNext());
} else { } else {
log.info("[{}] Creating new device and replacing device entity on the edge [{}]", tenantId, deviceUpdateMsg); log.info("[{}] Creating new device and replacing device entity on the edge [{}]", tenantId, deviceUpdateMsg);
device = createDevice(tenantId, edge, deviceUpdateMsg, deviceUpdateMsg.getName()); try {
device = createDevice(tenantId, edge, deviceUpdateMsg, deviceUpdateMsg.getName());
} catch (DataValidationException e) {
String errMsg = String.format("[%s] Device update msg can't be processed due to data validation [%s]", tenantId, deviceUpdateMsg);
log.error(errMsg, e);
return Futures.immediateFuture(null);
}
saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, device.getId(), null); saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, device.getId(), null);
} }
break; break;
@ -131,6 +131,23 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
return Futures.immediateFuture(null); return Futures.immediateFuture(null);
} }
private boolean isDeviceAlreadyExistsOnCloudForThisEdge(TenantId tenantId, Edge edge, Device device) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> pageData;
do {
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, device.getId(), pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
if (pageData.getData().contains(edge.getId())) {
return true;
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
return false;
}
public ListenableFuture<Void> processDeviceCredentialsFromEdge(TenantId tenantId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg) { public ListenableFuture<Void> processDeviceCredentialsFromEdge(TenantId tenantId, DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg) {
log.debug("Executing onDeviceCredentialsUpdate, deviceCredentialsUpdateMsg [{}]", deviceCredentialsUpdateMsg); log.debug("Executing onDeviceCredentialsUpdate, deviceCredentialsUpdateMsg [{}]", deviceCredentialsUpdateMsg);
DeviceId deviceId = new DeviceId(new UUID(deviceCredentialsUpdateMsg.getDeviceIdMSB(), deviceCredentialsUpdateMsg.getDeviceIdLSB())); DeviceId deviceId = new DeviceId(new UUID(deviceCredentialsUpdateMsg.getDeviceIdMSB(), deviceCredentialsUpdateMsg.getDeviceIdLSB()));
@ -194,7 +211,6 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
if (device == null) { if (device == null) {
device = new Device(); device = new Device();
device.setTenantId(tenantId); device.setTenantId(tenantId);
device.setId(deviceId);
device.setCreatedTime(Uuids.unixTimestamp(deviceId.getId())); device.setCreatedTime(Uuids.unixTimestamp(deviceId.getId()));
created = true; created = true;
} }
@ -214,6 +230,12 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor {
deviceUpdateMsg.getDeviceProfileIdLSB())); deviceUpdateMsg.getDeviceProfileIdLSB()));
device.setDeviceProfileId(deviceProfileId); device.setDeviceProfileId(deviceProfileId);
} }
if (created) {
deviceValidator.validate(device, Device::getTenantId);
device.setId(deviceId);
} else {
deviceValidator.validate(device, Device::getTenantId);
}
Device savedDevice = deviceService.saveDevice(device, false); Device savedDevice = deviceService.saveDevice(device, false);
tbClusterService.onDeviceUpdated(savedDevice, created ? null : device, false); tbClusterService.onDeviceUpdated(savedDevice, created ? null : device, false);
if (created) { if (created) {

43
application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityEdgeProcessor.java

@ -94,27 +94,20 @@ public class EntityEdgeProcessor extends BaseEdgeProcessor {
EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType());
EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type,
new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB()));
EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB())); EdgeId edgeId = null;
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); if (edgeNotificationMsg.getEdgeIdMSB() != 0 && edgeNotificationMsg.getEdgeIdLSB() != 0) {
PageData<EdgeId> pageData; edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB()));
}
switch (actionType) { switch (actionType) {
case ADDED: // used only for USER entity case ADDED: // used only for USER entity
case UPDATED: case UPDATED:
case CREDENTIALS_UPDATED: case CREDENTIALS_UPDATED:
do { pushNotificationToAllRelatedEdges(tenantId, entityId, type, actionType);
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
for (EdgeId relatedEdgeId : pageData.getData()) {
saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null);
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
break; break;
case ASSIGNED_TO_CUSTOMER: case ASSIGNED_TO_CUSTOMER:
case UNASSIGNED_FROM_CUSTOMER: case UNASSIGNED_FROM_CUSTOMER:
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> pageData;
do { do {
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink); pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
@ -147,7 +140,11 @@ public class EntityEdgeProcessor extends BaseEdgeProcessor {
} while (pageData != null && pageData.hasNext()); } while (pageData != null && pageData.hasNext());
break; break;
case DELETED: case DELETED:
saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null); if (edgeId != null) {
saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null);
} else {
pushNotificationToAllRelatedEdges(tenantId, entityId, type, actionType);
}
break; break;
case ASSIGNED_TO_EDGE: case ASSIGNED_TO_EDGE:
case UNASSIGNED_FROM_EDGE: case UNASSIGNED_FROM_EDGE:
@ -159,6 +156,22 @@ public class EntityEdgeProcessor extends BaseEdgeProcessor {
} }
} }
private void pushNotificationToAllRelatedEdges(TenantId tenantId, EntityId entityId, EdgeEventType type, EdgeEventActionType actionType) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<EdgeId> pageData;
do {
pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink);
if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) {
for (EdgeId relatedEdgeId : pageData.getData()) {
saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null);
}
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
}
} while (pageData != null && pageData.hasNext());
}
private void updateDependentRuleChains(TenantId tenantId, RuleChainId processingRuleChainId, EdgeId edgeId) { private void updateDependentRuleChains(TenantId tenantId, RuleChainId processingRuleChainId, EdgeId edgeId) {
PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE);
PageData<RuleChain> pageData; PageData<RuleChain> pageData;

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;

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

@ -514,14 +514,24 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
break; break;
case "3.3.3": case "3.3.3":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema..."); 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);
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.3", SCHEMA_UPDATE_SQL); schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.3", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn); loadSql(schemaUpdateFile, conn);
log.info("Updating schema settings..."); log.info("Updating schema settings...");
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3004000;"); conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003004;");
log.info("Schema updated"); log.info("Schema updated.");
} catch (Exception e) { } catch (Exception e) {
log.error("Failed to update schema", e); log.error("Failed updating schema!!!", e);
} }
break; break;
default: default:

4
application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java

@ -64,6 +64,10 @@ public class DefaultCacheCleanupService implements CacheCleanupService {
log.info("Clear cache to upgrade from version 3.3.2 to 3.3.3 ..."); log.info("Clear cache to upgrade from version 3.3.2 to 3.3.3 ...");
clearAll(); clearAll();
break; break;
case "3.3.3":
log.info("Clear cache to upgrade from version 3.3.3 to 3.3.4 ...");
clearAll();
break;
default: default:
//Do nothing, since cache cleanup is optional. //Do nothing, since cache cleanup is optional.
} }

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) {

2
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java

@ -128,7 +128,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
private int maxEntitiesPerDataSubscription; private int maxEntitiesPerDataSubscription;
@Value("${server.ws.max_entities_per_alarm_subscription:1000}") @Value("${server.ws.max_entities_per_alarm_subscription:1000}")
private int maxEntitiesPerAlarmSubscription; private int maxEntitiesPerAlarmSubscription;
@Value("${server.ws.max_alarm_queries_per_refresh_interval:3}") @Value("${server.ws.dynamic_page_link.max_alarm_queries_per_refresh_interval:10}")
private int maxAlarmQueriesPerRefreshInterval; private int maxAlarmQueriesPerRefreshInterval;
private ExecutorService wsCallBackExecutor; private ExecutorService wsCallBackExecutor;

2
application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java

@ -99,6 +99,8 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
log.trace("[{}] Fetching alarms: {}", cmdId, alarmInvocationAttempts); log.trace("[{}] Fetching alarms: {}", cmdId, alarmInvocationAttempts);
if (alarmInvocationAttempts <= maxAlarmQueriesPerRefreshInterval) { if (alarmInvocationAttempts <= maxAlarmQueriesPerRefreshInterval) {
doFetchAlarms(); doFetchAlarms();
} else {
log.trace("[{}] Ignore alarm fetch due to rate limit: [{}] of maximum [{}]", cmdId, alarmInvocationAttempts, maxAlarmQueriesPerRefreshInterval);
} }
} }

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);
} }
} }

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

@ -71,7 +71,7 @@ server:
dynamic_page_link: dynamic_page_link:
refresh_interval: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_INTERVAL_SEC:60}" refresh_interval: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_INTERVAL_SEC:60}"
refresh_pool_size: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_POOL_SIZE:1}" refresh_pool_size: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_POOL_SIZE:1}"
max_alarm_queries_per_refresh_interval: "${TB_SERVER_WS_MAX_ALARM_QUERIES_PER_REFRESH_INTERVAL:3}" max_alarm_queries_per_refresh_interval: "${TB_SERVER_WS_MAX_ALARM_QUERIES_PER_REFRESH_INTERVAL:10}"
max_per_user: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_MAX_PER_USER:10}" max_per_user: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_MAX_PER_USER:10}"
max_entities_per_data_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_DATA_SUBSCRIPTION:10000}" max_entities_per_data_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_DATA_SUBSCRIPTION:10000}"
max_entities_per_alarm_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_ALARM_SUBSCRIPTION:10000}" max_entities_per_alarm_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_ALARM_SUBSCRIPTION:10000}"
@ -165,7 +165,7 @@ ui:
# Help parameters # Help parameters
help: help:
# Base url for UI help assets # Base url for UI help assets
base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.3.3}" base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.3.4}"
database: database:
ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records
@ -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
@ -831,8 +836,6 @@ transport:
log_max_length: "${LWM2M_LOG_MAX_LENGTH:1024}" log_max_length: "${LWM2M_LOG_MAX_LENGTH:1024}"
psm_activity_timer: "${LWM2M_PSM_ACTIVITY_TIMER:10000}" psm_activity_timer: "${LWM2M_PSM_ACTIVITY_TIMER:10000}"
paging_transmission_window: "${LWM2M_PAGING_TRANSMISSION_WINDOW:10000}" paging_transmission_window: "${LWM2M_PAGING_TRANSMISSION_WINDOW:10000}"
# Use redis for Security and Registration stores
redis.enabled: "${LWM2M_REDIS_ENABLED:false}"
network_config: # In this section you can specify custom parameters for LwM2M network configuration and expose the env variables to configure outside network_config: # In this section you can specify custom parameters for LwM2M network configuration and expose the env variables to configure outside
# - key: "PROTOCOL_STAGE_THREAD_COUNT" # - key: "PROTOCOL_STAGE_THREAD_COUNT"
# value: "${LWM2M_PROTOCOL_STAGE_THREAD_COUNT:4}" # value: "${LWM2M_PROTOCOL_STAGE_THREAD_COUNT:4}"

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

435
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;
@ -42,6 +42,7 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
@ -80,6 +81,7 @@ import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.common.data.widget.WidgetsBundle;
import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.adaptor.JsonConverter;
@ -110,13 +112,13 @@ import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.gen.edge.v1.UplinkMsg; import org.thingsboard.server.gen.edge.v1.UplinkMsg;
import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg;
import org.thingsboard.server.gen.edge.v1.UserCredentialsRequestMsg; import org.thingsboard.server.gen.edge.v1.UserCredentialsRequestMsg;
import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; 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 +131,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 +166,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 +177,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
edgeImitator.expectMessageAmount(13); edgeImitator.expectMessageAmount(13);
edgeImitator.connect(); edgeImitator.connect();
testReceivedInitialData(); verifyEdgeConnectionAndInitialData();
} }
@After @After
@ -187,41 +188,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 +200,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 +237,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 +252,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 +279,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 +292,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 +334,54 @@ 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 testDeviceProfiles() throws Exception {
// 1 // 1
DeviceProfile deviceProfile = this.createDeviceProfile("ONE_MORE_DEVICE_PROFILE", null);
extendDeviceProfileData(deviceProfile);
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(1);
Device savedDevice = saveDevice("Edge Device 2", "Default"); deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
doPost("/api/edge/" + edge.getId().getId().toString()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class);
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg);
DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType()); Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType());
Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits()); Assert.assertEquals(deviceProfileUpdateMsg.getIdMSB(), deviceProfile.getUuidId().getMostSignificantBits());
Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits()); Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.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/deviceProfile/" + deviceProfile.getUuidId())
+ "/device/" + savedDevice.getId().getId().toString(), Device.class); .andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage(); latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg);
deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType());
Assert.assertEquals(deviceProfileUpdateMsg.getIdMSB(), deviceProfile.getUuidId().getMostSignificantBits());
Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits());
}
@Test
public void testDevices() throws Exception {
// 1
Device savedDevice = saveDeviceOnCloudAndVerifyDeliveryToEdge();
// 2
edgeImitator.expectMessageAmount(1);
doDelete("/api/edge/" + edge.getUuidId()
+ "/device/" + savedDevice.getUuidId(), Device.class);
Assert.assertTrue(edgeImitator.waitForMessages());
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 +389,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 +403,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 +414,49 @@ 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 testDeviceReachedMaximumAllowedOnCloud() throws Exception {
// update tenant profile configuration
loginSysAdmin();
TenantProfile tenantProfile = doGet("/api/tenantProfile/" + savedTenant.getTenantProfileId().getId(), TenantProfile.class);
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration();
profileConfiguration.setMaxDevices(1);
tenantProfile.getProfileData().setConfiguration(profileConfiguration);
doPost("/api/tenantProfile/", tenantProfile, TenantProfile.class);
loginTenantAdmin();
UUID uuid = Uuids.timeBased();
UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder();
DeviceUpdateMsg.Builder deviceUpdateMsgBuilder = DeviceUpdateMsg.newBuilder();
deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits());
deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits());
deviceUpdateMsgBuilder.setName("Edge Device");
deviceUpdateMsgBuilder.setType("default");
deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE);
uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build());
edgeImitator.expectResponsesAmount(1);
edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build());
Assert.assertTrue(edgeImitator.waitForResponses());
UplinkResponseMsg latestResponseMsg = edgeImitator.getLatestResponseMsg();
Assert.assertTrue(latestResponseMsg.getSuccess());
}
@Test
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 +469,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 +481,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 +502,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 +511,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 +539,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 +551,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 {
@ -603,21 +612,18 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
ruleChainMetaData.addConnectionInfo(0, 2, "fail"); ruleChainMetaData.addConnectionInfo(0, 2, "fail");
ruleChainMetaData.addConnectionInfo(1, 2, "success"); ruleChainMetaData.addConnectionInfo(1, 2, "success");
ruleChainMetaData.addRuleChainConnectionInfo(2, edge.getRootRuleChainId(), "success", mapper.createObjectNode());
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 +647,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 +659,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 +713,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 +739,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 +752,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 +765,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 +776,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 +788,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 +815,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 +827,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 +846,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 +887,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 +897,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 +942,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 +954,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 +963,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 +992,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 +1079,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 +1103,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 +1132,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 +1149,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 +1185,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 +1238,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 +1286,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 +1309,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 +1319,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 +1369,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 +1381,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 +1407,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 +1432,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 +1468,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 +1494,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 +1524,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 +1550,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 +1570,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 +1621,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 +1664,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();

4
application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java

@ -88,6 +88,9 @@ public class EdgeImitator {
@Getter @Getter
private List<AbstractMessage> downlinkMsgs; private List<AbstractMessage> downlinkMsgs;
@Getter
private UplinkResponseMsg latestResponseMsg;
private boolean connected = false; private boolean connected = false;
public EdgeImitator(String host, int port, String routingKey, String routingSecret) throws NoSuchFieldException, IllegalAccessException { public EdgeImitator(String host, int port, String routingKey, String routingSecret) throws NoSuchFieldException, IllegalAccessException {
@ -132,6 +135,7 @@ public class EdgeImitator {
private void onUplinkResponse(UplinkResponseMsg msg) { private void onUplinkResponse(UplinkResponseMsg msg) {
log.info("onUplinkResponse: {}", msg); log.info("onUplinkResponse: {}", msg);
latestResponseMsg = msg;
responsesLatch.countDown(); responsesLatch.countDown();
} }

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

@ -15,19 +15,28 @@
*/ */
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.flow.TbRuleChainInputNodeConfiguration;
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;
import org.thingsboard.server.common.data.rule.NodeConnectionInfo;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
@ -38,12 +47,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 +72,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 +165,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);
@ -216,16 +242,27 @@ public abstract class AbstractRuleEngineFlowIntegrationTest extends AbstractRule
configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1")); configuration1.setServerAttributeNames(Collections.singletonList("serverAttributeKey1"));
ruleNode1.setConfiguration(mapper.valueToTree(configuration1)); ruleNode1.setConfiguration(mapper.valueToTree(configuration1));
rootMetaData.setNodes(Collections.singletonList(ruleNode1)); RuleNode ruleNode12 = new RuleNode();
ruleNode12.setName("Simple Rule Node 1");
ruleNode12.setType(org.thingsboard.rule.engine.flow.TbRuleChainInputNode.class.getName());
ruleNode12.setDebugMode(true);
TbRuleChainInputNodeConfiguration configuration12 = new TbRuleChainInputNodeConfiguration();
configuration12.setRuleChainId(secondaryRuleChain.getId().getId().toString());
ruleNode12.setConfiguration(mapper.valueToTree(configuration12));
rootMetaData.setNodes(Arrays.asList(ruleNode1, ruleNode12));
rootMetaData.setFirstNodeIndex(0); rootMetaData.setFirstNodeIndex(0);
rootMetaData.addRuleChainConnectionInfo(0, secondaryRuleChain.getId(), "Success", mapper.createObjectNode()); NodeConnectionInfo connection = new NodeConnectionInfo();
connection.setFromIndex(0);
connection.setToIndex(1);
connection.setType("Success");
rootMetaData.setConnections(Collections.singletonList(connection));
rootMetaData = saveRuleChainMetaData(rootMetaData); rootMetaData = saveRuleChainMetaData(rootMetaData);
Assert.assertNotNull(rootMetaData); Assert.assertNotNull(rootMetaData);
rootRuleChain = getRuleChain(rootRuleChain.getId()); rootRuleChain = getRuleChain(rootRuleChain.getId());
Assert.assertNotNull(rootRuleChain.getFirstRuleNodeId()); Assert.assertNotNull(rootRuleChain.getFirstRuleNodeId());
RuleChainMetaData secondaryMetaData = new RuleChainMetaData(); RuleChainMetaData secondaryMetaData = new RuleChainMetaData();
secondaryMetaData.setRuleChainId(secondaryRuleChain.getId()); secondaryMetaData.setRuleChainId(secondaryRuleChain.getId());
@ -249,12 +286,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

2
common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java

@ -42,7 +42,7 @@ public interface EdgeService {
Optional<Edge> findEdgeByRoutingKey(TenantId tenantId, String routingKey); Optional<Edge> findEdgeByRoutingKey(TenantId tenantId, String routingKey);
Edge saveEdge(Edge edge, boolean doValidate); Edge saveEdge(Edge edge);
Edge assignEdgeToCustomer(TenantId tenantId, EdgeId edgeId, CustomerId customerId); Edge assignEdgeToCustomer(TenantId tenantId, EdgeId edgeId, CustomerId customerId);

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;
}
} }

8
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2MResource.java → common/data/src/main/java/org/thingsboard/server/common/data/exception/ThingsboardKafkaClientError.java

@ -13,9 +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.transport.lwm2m.server.client; package org.thingsboard.server.common.data.exception;
import org.eclipse.leshan.core.node.LwM2mResource; public class ThingsboardKafkaClientError extends Error {
public interface TbLwM2MResource extends LwM2mResource { public ThingsboardKafkaClientError(String message) {
super(message);
}
} }

11
common/data/src/main/java/org/thingsboard/server/common/data/rule/RuleChainMetaData.java

@ -59,15 +59,4 @@ public class RuleChainMetaData {
} }
connections.add(connectionInfo); connections.add(connectionInfo);
} }
public void addRuleChainConnectionInfo(int fromIndex, RuleChainId targetRuleChainId, String type, JsonNode additionalInfo) {
RuleChainConnectionInfo connectionInfo = new RuleChainConnectionInfo();
connectionInfo.setFromIndex(fromIndex);
connectionInfo.setTargetRuleChainId(targetRuleChainId);
connectionInfo.setType(type);
connectionInfo.setAdditionalInfo(additionalInfo);
if (ruleChainConnections == null) {
ruleChainConnections = new ArrayList<>();
}
ruleChainConnections.add(connectionInfo);
}
} }

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);

2
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java

@ -153,7 +153,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor {
private String validatePayload(UUID sessionId, Request inbound, boolean isEmptyPayloadAllowed) throws AdaptorException { private String validatePayload(UUID sessionId, Request inbound, boolean isEmptyPayloadAllowed) throws AdaptorException {
String payload = inbound.getPayloadString(); String payload = inbound.getPayloadString();
if (payload == null) { if (payload == null) {
log.warn("[{}] Payload is empty!", sessionId); log.debug("[{}] Payload is empty!", sessionId);
if (!isEmptyPayloadAllowed) { if (!isEmptyPayloadAllowed) {
throw new AdaptorException(new IllegalArgumentException("Payload is empty!")); throw new AdaptorException(new IllegalArgumentException("Payload is empty!"));
} }

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);

28
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java

@ -24,6 +24,8 @@ import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mResourceInstance; import org.eclipse.leshan.core.node.LwM2mResourceInstance;
import org.eclipse.leshan.core.request.WriteRequest;
import org.eclipse.leshan.core.response.WriteResponse;
import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.model.LwM2mModelProvider;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
@ -199,11 +201,9 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
// #1.1 // #1.1
if (lwM2MClient.getSharedAttributes().containsKey(pathIdVer)) { if (lwM2MClient.getSharedAttributes().containsKey(pathIdVer)) {
if (tsKvProto.getTs() > lwM2MClient.getSharedAttributes().get(pathIdVer).getTs()) { if (tsKvProto.getTs() > lwM2MClient.getSharedAttributes().get(pathIdVer).getTs()) {
lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto);
attributesUpdate.put(pathIdVer, tsKvProto); attributesUpdate.put(pathIdVer, tsKvProto);
} }
} else { } else {
lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto);
attributesUpdate.put(pathIdVer, tsKvProto); attributesUpdate.put(pathIdVer, tsKvProto);
} }
} }
@ -221,11 +221,11 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
Object newValProto = getValueFromKvProto(tsKvProto.getKv()); Object newValProto = getValueFromKvProto(tsKvProto.getKv());
Object oldResourceValue = this.getResourceValueFormatKv(lwM2MClient, pathIdVer); Object oldResourceValue = this.getResourceValueFormatKv(lwM2MClient, pathIdVer);
if (!resourceModel.multiple || !(newValProto instanceof JsonElement)) { if (!resourceModel.multiple || !(newValProto instanceof JsonElement)) {
this.pushUpdateToClientIfNeeded(lwM2MClient, oldResourceValue, newValProto, pathIdVer, logFailedUpdateOfNonChangedValue); this.pushUpdateToClientIfNeeded(lwM2MClient, oldResourceValue, newValProto, pathIdVer, tsKvProto, logFailedUpdateOfNonChangedValue);
} else { } else {
try { try {
pushUpdateMultiToClientIfNeeded(lwM2MClient, resourceModel, (JsonElement) newValProto, pushUpdateMultiToClientIfNeeded(lwM2MClient, resourceModel, (JsonElement) newValProto,
(Map<Integer, LwM2mResourceInstance>) oldResourceValue, pathIdVer, logFailedUpdateOfNonChangedValue); (Map<Integer, LwM2mResourceInstance>) oldResourceValue, pathIdVer, tsKvProto, logFailedUpdateOfNonChangedValue);
} catch (Exception e) { } catch (Exception e) {
log.error("Failed update resource [" + lwM2MClient.getEndpoint() + "] onAttributesUpdate:", e); log.error("Failed update resource [" + lwM2MClient.getEndpoint() + "] onAttributesUpdate:", e);
String logMsg = String.format("%s: Failed update resource onAttributesUpdate %s.", String logMsg = String.format("%s: Failed update resource onAttributesUpdate %s.",
@ -237,7 +237,7 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
} }
private void pushUpdateToClientIfNeeded(LwM2mClient lwM2MClient, Object oldValue, Object newValue, private void pushUpdateToClientIfNeeded(LwM2mClient lwM2MClient, Object oldValue, Object newValue,
String versionedId, boolean logFailedUpdateOfNonChangedValue) { String versionedId, TransportProtos.TsKvProto tsKvProto, boolean logFailedUpdateOfNonChangedValue) {
if (newValue == null) { if (newValue == null) {
String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. New value is bad", String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. New value is bad",
LOG_LWM2M_ERROR, versionedId, "null"); LOG_LWM2M_ERROR, versionedId, "null");
@ -245,7 +245,13 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
log.error("Failed update resource [{}] [{}]", versionedId, "null"); log.error("Failed update resource [{}] [{}]", versionedId, "null");
} else if ((oldValue == null) || !valueEquals(newValue, oldValue)) { } else if ((oldValue == null) || !valueEquals(newValue, oldValue)) {
TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValue).timeout(clientContext.getRequestTimeout(lwM2MClient)).build(); TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValue).timeout(clientContext.getRequestTimeout(lwM2MClient)).build();
downlinkHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, lwM2MClient, versionedId)); downlinkHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, lwM2MClient, versionedId) {
@Override
public void onSuccess(WriteRequest request, WriteResponse response) {
client.getSharedAttributes().put(versionedId, tsKvProto);
super.onSuccess(request, response);
}
});
} else if (logFailedUpdateOfNonChangedValue) { } else if (logFailedUpdateOfNonChangedValue) {
String logMsg = String.format("%s: Didn't update the versionedId resource - %s value - %s. Value is not changed", String logMsg = String.format("%s: Didn't update the versionedId resource - %s value - %s. Value is not changed",
LOG_LWM2M_WARN, versionedId, newValue); LOG_LWM2M_WARN, versionedId, newValue);
@ -256,7 +262,7 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
private void pushUpdateMultiToClientIfNeeded(LwM2mClient client, ResourceModel resourceModel, JsonElement newValProto, private void pushUpdateMultiToClientIfNeeded(LwM2mClient client, ResourceModel resourceModel, JsonElement newValProto,
Map<Integer, LwM2mResourceInstance> valueOld, String versionedId, Map<Integer, LwM2mResourceInstance> valueOld, String versionedId,
boolean logFailedUpdateOfNonChangedValue) { TransportProtos.TsKvProto tsKvProto, boolean logFailedUpdateOfNonChangedValue) {
Map<Integer, Object> newValues = convertMultiResourceValuesFromJson(newValProto, resourceModel.type, versionedId); Map<Integer, Object> newValues = convertMultiResourceValuesFromJson(newValProto, resourceModel.type, versionedId);
if (newValues.size() > 0 && valueOld != null && valueOld.size() > 0) { if (newValues.size() > 0 && valueOld != null && valueOld.size() > 0) {
valueOld.values().forEach((v) -> { valueOld.values().forEach((v) -> {
@ -270,7 +276,13 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService {
if (newValues.size() > 0) { if (newValues.size() > 0) {
TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValues).timeout(this.config.getTimeout()).build(); TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValues).timeout(this.config.getTimeout()).build();
downlinkHandler.sendWriteReplaceRequest(client, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, client, versionedId)); downlinkHandler.sendWriteReplaceRequest(client, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, client, versionedId) {
@Override
public void onSuccess(WriteRequest request, WriteResponse response) {
client.getSharedAttributes().put(versionedId, tsKvProto);
super.onSuccess(request, response);
}
});
} else if (logFailedUpdateOfNonChangedValue) { } else if (logFailedUpdateOfNonChangedValue) {
log.warn("Didn't update resource [{}] [{}]", versionedId, newValProto); log.warn("Didn't update resource [{}] [{}]", versionedId, newValProto);
String logMsg = String.format("%s: Didn't update resource versionedId - %s value - %s. Value is not changed", String logMsg = String.format("%s: Didn't update resource versionedId - %s value - %s. Value is not changed",

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

@ -42,9 +42,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto;
import org.thingsboard.server.transport.lwm2m.config.TbLwM2mVersion; import org.thingsboard.server.transport.lwm2m.config.TbLwM2mVersion;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
@ -72,15 +69,14 @@ import static org.thingsboard.server.transport.lwm2m.utils.LwM2MTransportUtil.ge
@Slf4j @Slf4j
@EqualsAndHashCode(of = {"endpoint"}) @EqualsAndHashCode(of = {"endpoint"})
@ToString(of = "endpoint") @ToString(of = "endpoint")
public class LwM2mClient implements Serializable { public class LwM2mClient {
private static final long serialVersionUID = 8793482946289222623L;
@Getter
private final String nodeId; private final String nodeId;
@Getter @Getter
private final String endpoint; private final String endpoint;
private transient Lock lock; private final Lock lock;
@Getter @Getter
private final Map<String, ResourceValue> resources; private final Map<String, ResourceValue> resources;
@ -109,7 +105,7 @@ public class LwM2mClient implements Serializable {
@Getter @Getter
private Long edrxCycle; private Long edrxCycle;
@Getter @Getter
private transient Registration registration; private Registration registration;
@Getter @Getter
@Setter @Setter
private boolean asleep; private boolean asleep;
@ -117,14 +113,14 @@ public class LwM2mClient implements Serializable {
private long lastUplinkTime; private long lastUplinkTime;
@Getter @Getter
@Setter @Setter
private transient Future<Void> sleepTask; private Future<Void> sleepTask;
private boolean firstEdrxDownlink = true; private boolean firstEdrxDownlink = true;
@Getter @Getter
private transient Set<ContentFormat> clientSupportContentFormats; private Set<ContentFormat> clientSupportContentFormats;
@Getter @Getter
private transient ContentFormat defaultContentFormat; private ContentFormat defaultContentFormat;
@Getter @Getter
private final AtomicInteger retryAttempts; private final AtomicInteger retryAttempts;
@ -228,14 +224,13 @@ public class LwM2mClient implements Serializable {
} }
public boolean saveResourceValue(String pathRezIdVer, LwM2mResource resource, LwM2mModelProvider modelProvider, Mode mode) { public boolean saveResourceValue(String pathRezIdVer, LwM2mResource resource, LwM2mModelProvider modelProvider, Mode mode) {
if (this.resources.get(pathRezIdVer) != null && this.resources.get(pathRezIdVer).getResourceModel() != null && if (this.resources.get(pathRezIdVer) != null && this.resources.get(pathRezIdVer).getResourceModel() != null) {
resourceEqualsModel(resource, this.resources.get(pathRezIdVer).getResourceModel())) {
this.resources.get(pathRezIdVer).updateLwM2mResource(resource, mode); this.resources.get(pathRezIdVer).updateLwM2mResource(resource, mode);
return true; return true;
} else { } else {
LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer));
ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId());
if (resourceModel != null && resourceEqualsModel(resource, resourceModel)) { if (resourceModel != null) {
this.resources.put(pathRezIdVer, new ResourceValue(resource, resourceModel)); this.resources.put(pathRezIdVer, new ResourceValue(resource, resourceModel));
return true; return true;
} else { } else {
@ -244,11 +239,6 @@ public class LwM2mClient implements Serializable {
} }
} }
private boolean resourceEqualsModel(LwM2mResource resource, ResourceModel resourceModel) {
return ((!resourceModel.multiple && resource instanceof LwM2mSingleResource) ||
(resourceModel.multiple && resource instanceof LwM2mMultipleResource));
}
public Object getResourceValue(String pathRezIdVer, String pathRezId) { public Object getResourceValue(String pathRezIdVer, String pathRezId) {
String pathRez = pathRezIdVer == null ? convertObjectIdToVersionedId(pathRezId, this.registration) : pathRezIdVer; String pathRez = pathRezIdVer == null ? convertObjectIdToVersionedId(pathRezId, this.registration) : pathRezIdVer;
if (this.resources.get(pathRez) != null) { if (this.resources.get(pathRez) != null) {
@ -297,11 +287,22 @@ public class LwM2mClient implements Serializable {
} }
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;
}
} }
@ -423,15 +424,18 @@ public class LwM2mClient implements Serializable {
private ContentFormat calculateDefaultContentFormat(Registration registration) { private ContentFormat calculateDefaultContentFormat(Registration registration) {
if (registration == null) { if (registration == null) {
return ContentFormat.DEFAULT; return ContentFormat.DEFAULT;
} else{ } else {
return TbLwM2mVersion.fromVersion(registration.getLwM2mVersion()).getContentFormat(); return TbLwM2mVersion.fromVersion(registration.getLwM2mVersion()).getContentFormat();
} }
} }
static private Set<ContentFormat> clientSupportContentFormat(Registration registration) { private static Set<ContentFormat> clientSupportContentFormat(Registration registration) {
Set<ContentFormat> contentFormats = new HashSet<>(); Set<ContentFormat> contentFormats = new HashSet<>();
contentFormats.add(ContentFormat.DEFAULT); contentFormats.add(ContentFormat.DEFAULT);
LinkParamValue ct = Arrays.stream(registration.getObjectLinks()).filter(link -> link.getUriReference().equals("/")).findFirst().get().getLinkParams().get("ct"); LinkParamValue ct = Arrays.stream(registration.getObjectLinks())
.filter(link -> link.getUriReference().equals("/"))
.findFirst()
.map(link -> link.getLinkParams().get("ct")).orElse(null);
if (ct != null) { if (ct != null) {
Set<ContentFormat> codes = Stream.of(ct.getUnquoted().replaceAll("\"", "").split(" ", -1)) Set<ContentFormat> codes = Stream.of(ct.getUnquoted().replaceAll("\"", "").split(" ", -1))
.map(String::trim) .map(String::trim)
@ -443,11 +447,6 @@ public class LwM2mClient implements Serializable {
return contentFormats; return contentFormats;
} }
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
this.lock = new ReentrantLock();
}
public long updateLastUplinkTime() { public long updateLastUplinkTime() {
this.lastUplinkTime = System.currentTimeMillis(); this.lastUplinkTime = System.currentTimeMillis();
this.firstEdrxDownlink = true; this.firstEdrxDownlink = true;

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

@ -20,7 +20,6 @@ import lombok.extern.slf4j.Slf4j;
import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.SecurityMode;
import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mPath;
import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.Registration;
import org.eclipse.leshan.server.registration.RegistrationStore;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -73,7 +72,6 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
private final LwM2MSessionManager sessionManager; private final LwM2MSessionManager sessionManager;
private final TransportDeviceProfileCache deviceProfileCache; private final TransportDeviceProfileCache deviceProfileCache;
private final LwM2MModelConfigService modelConfigService; private final LwM2MModelConfigService modelConfigService;
private final RegistrationStore registrationStore;
@Autowired @Autowired
@Lazy @Lazy
@ -120,11 +118,8 @@ public class LwM2mClientContextImpl implements LwM2mClientContext {
private void updateFetchedClient(String nodeId, LwM2mClient client) { private void updateFetchedClient(String nodeId, LwM2mClient client) {
boolean updated = false; boolean updated = false;
Registration registration = registrationStore.getRegistrationByEndpoint(client.getEndpoint()); if (client.getRegistration() != null) {
lwM2mClientsByRegistrationId.put(client.getRegistration().getId(), client);
if (registration != null) {
client.setRegistration(registration);
lwM2mClientsByRegistrationId.put(registration.getId(), client);
} }
if (client.getSession() != null) { if (client.getSession() != null) {
client.refreshSessionId(nodeId); client.refreshSessionId(nodeId);
@ -374,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);
@ -381,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;

40
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java

@ -24,55 +24,39 @@ import org.eclipse.leshan.core.node.LwM2mResourceInstance;
import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.request.WriteRequest.Mode; import org.eclipse.leshan.core.request.WriteRequest.Mode;
import java.io.Serializable; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
@Slf4j @Slf4j
@Data @Data
public class ResourceValue implements Serializable { public class ResourceValue {
private static final long serialVersionUID = -228268906779089402L; private LwM2mResource lwM2mResource;
private ResourceModel resourceModel;
private TbLwM2MResource lwM2mResource;
private TbResourceModel resourceModel;
public ResourceValue(LwM2mResource lwM2mResource, ResourceModel resourceModel) { public ResourceValue(LwM2mResource lwM2mResource, ResourceModel resourceModel) {
this.resourceModel = toTbResourceModel(resourceModel); this.resourceModel = resourceModel;
updateLwM2mResource(lwM2mResource, Mode.UPDATE); updateLwM2mResource(lwM2mResource, Mode.UPDATE);
} }
public void updateLwM2mResource(LwM2mResource lwM2mResource, Mode mode) { public void updateLwM2mResource(LwM2mResource lwM2mResource, Mode mode) {
if (lwM2mResource instanceof LwM2mSingleResource) { if (lwM2mResource instanceof LwM2mSingleResource) {
this.lwM2mResource = new TbLwM2MSingleResource(lwM2mResource.getId(), lwM2mResource.getValue(), lwM2mResource.getType()); this.lwM2mResource = LwM2mSingleResource.newResource(lwM2mResource.getId(), lwM2mResource.getValue(), lwM2mResource.getType());
} else if (lwM2mResource instanceof LwM2mMultipleResource) { } else if (lwM2mResource instanceof LwM2mMultipleResource) {
if (lwM2mResource.getInstances().values().size() > 0) { if (lwM2mResource.getInstances().values().size() > 0) {
Set <TbLwM2MResourceInstance> instancesSet = lwM2mResource.getInstances().values().stream().map(ResourceValue::toTbLwM2MResourceInstance).collect(Collectors.toSet()); Set<LwM2mResourceInstance> instancesSet = new HashSet<>(lwM2mResource.getInstances().values());
if (Mode.REPLACE.equals(mode) && this.lwM2mResource != null) { if (Mode.REPLACE.equals(mode) && this.lwM2mResource != null) {
Map<Integer, LwM2mResourceInstance> oldInstances = this.lwM2mResource.getInstances(); Map<Integer, LwM2mResourceInstance> oldInstances = this.lwM2mResource.getInstances();
oldInstances.values().forEach(v -> { oldInstances.values().forEach(v -> {
if (instancesSet.stream().noneMatch(vIns -> v.getId() == vIns.getId())){ if (instancesSet.stream().noneMatch(vIns -> v.getId() == vIns.getId())) {
instancesSet.add(toTbLwM2MResourceInstance(v)); instancesSet.add(v);
} }
}); });
} }
TbLwM2MResourceInstance[] instances = instancesSet.toArray(new TbLwM2MResourceInstance[0]); LwM2mResourceInstance[] instances = instancesSet.toArray(new LwM2mResourceInstance[0]);
this.lwM2mResource = new TbLwM2mMultipleResource(lwM2mResource.getId(), lwM2mResource.getType(), instances); this.lwM2mResource = new LwM2mMultipleResource(lwM2mResource.getId(), lwM2mResource.getType(), instances);
} }
} }
} }
public void setResourceModel(ResourceModel resourceModel) {
this.resourceModel = toTbResourceModel(resourceModel);
}
private static TbLwM2MResourceInstance toTbLwM2MResourceInstance(LwM2mResourceInstance instance) {
return new TbLwM2MResourceInstance(instance.getId(), instance.getValue(), instance.getType());
}
private static TbResourceModel toTbResourceModel(ResourceModel resourceModel) {
return new TbResourceModel(resourceModel.id, resourceModel.name, resourceModel.operations, resourceModel.multiple,
resourceModel.mandatory, resourceModel.type, resourceModel.rangeEnumeration, resourceModel.units, resourceModel.description);
}
} }

30
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2MResourceInstance.java

@ -1,30 +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.transport.lwm2m.server.client;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mResourceInstance;
import java.io.Serializable;
public class TbLwM2MResourceInstance extends LwM2mResourceInstance implements Serializable {
private static final long serialVersionUID = -8322290426892538345L;
protected TbLwM2MResourceInstance(int id, Object value, ResourceModel.Type type) {
super(id, value, type);
}
}

30
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2MSingleResource.java

@ -1,30 +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.transport.lwm2m.server.client;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mSingleResource;
import java.io.Serializable;
public class TbLwM2MSingleResource extends LwM2mSingleResource implements TbLwM2MResource, Serializable {
private static final long serialVersionUID = -878078368245340809L;
public TbLwM2MSingleResource(int id, Object value, ResourceModel.Type type) {
super(id, value, type);
}
}

30
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbLwM2mMultipleResource.java

@ -1,30 +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.transport.lwm2m.server.client;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import java.io.Serializable;
public class TbLwM2mMultipleResource extends LwM2mMultipleResource implements TbLwM2MResource, Serializable {
private static final long serialVersionUID = 4658477128628087186L;
public TbLwM2mMultipleResource(int id, ResourceModel.Type type, TbLwM2MResourceInstance... instances) {
super(id, type, instances);
}
}

29
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/TbResourceModel.java

@ -1,29 +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.transport.lwm2m.server.client;
import org.eclipse.leshan.core.model.ResourceModel;
import java.io.Serializable;
public class TbResourceModel extends ResourceModel implements Serializable {
private static final long serialVersionUID = -2082846558899793932L;
public TbResourceModel(Integer id, String name, Operations operations, Boolean multiple, Boolean mandatory, Type type, String rangeEnumeration, String units, String description) {
super(id, name, operations, multiple, mandatory, type, rangeEnumeration, units, description);
}
}

34
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java

@ -15,10 +15,9 @@
*/ */
package org.thingsboard.server.transport.lwm2m.server.store; package org.thingsboard.server.transport.lwm2m.server.store;
import lombok.RequiredArgsConstructor;
import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore;
import org.eclipse.leshan.server.californium.registration.InMemoryRegistrationStore; import org.eclipse.leshan.server.californium.registration.InMemoryRegistrationStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -31,58 +30,47 @@ import java.util.Optional;
@Component @Component
@TbLwM2mTransportComponent @TbLwM2mTransportComponent
@RequiredArgsConstructor
public class TbLwM2mStoreFactory { public class TbLwM2mStoreFactory {
@Autowired(required = false) private final Optional<TBRedisCacheConfiguration> redisConfiguration;
private Optional<TBRedisCacheConfiguration> redisConfiguration; private final LwM2MTransportServerConfig config;
private final LwM2mCredentialsSecurityInfoValidator validator;
@Autowired
private LwM2MTransportServerConfig config;
@Autowired
private LwM2mCredentialsSecurityInfoValidator validator;
@Value("${transport.lwm2m.redis.enabled:false}")
private boolean useRedis;
@Bean @Bean
private CaliforniumRegistrationStore registrationStore() { private CaliforniumRegistrationStore registrationStore() {
return isRedis() ? return redisConfiguration.isPresent() ?
new TbLwM2mRedisRegistrationStore(getConnectionFactory()) : new InMemoryRegistrationStore(config.getCleanPeriodInSec()); new TbLwM2mRedisRegistrationStore(getConnectionFactory()) : new InMemoryRegistrationStore(config.getCleanPeriodInSec());
} }
@Bean @Bean
private TbMainSecurityStore securityStore() { private TbMainSecurityStore securityStore() {
return new TbLwM2mSecurityStore(isRedis() ? return new TbLwM2mSecurityStore(redisConfiguration.isPresent() ?
new TbLwM2mRedisSecurityStore(getConnectionFactory()) : new TbInMemorySecurityStore(), validator); new TbLwM2mRedisSecurityStore(getConnectionFactory()) : new TbInMemorySecurityStore(), validator);
} }
@Bean @Bean
private TbLwM2MClientStore clientStore() { private TbLwM2MClientStore clientStore() {
return isRedis() ? new TbRedisLwM2MClientStore(getConnectionFactory()) : new TbDummyLwM2MClientStore(); return redisConfiguration.isPresent() ? new TbRedisLwM2MClientStore(getConnectionFactory()) : new TbDummyLwM2MClientStore();
} }
@Bean @Bean
private TbLwM2MModelConfigStore modelConfigStore() { private TbLwM2MModelConfigStore modelConfigStore() {
return isRedis() ? new TbRedisLwM2MModelConfigStore(getConnectionFactory()) : new TbDummyLwM2MModelConfigStore(); return redisConfiguration.isPresent() ? new TbRedisLwM2MModelConfigStore(getConnectionFactory()) : new TbDummyLwM2MModelConfigStore();
} }
@Bean @Bean
private TbLwM2MClientOtaInfoStore otaStore() { private TbLwM2MClientOtaInfoStore otaStore() {
return isRedis() ? new TbLwM2mRedisClientOtaInfoStore(getConnectionFactory()) : new TbDummyLwM2MClientOtaInfoStore(); return redisConfiguration.isPresent() ? new TbLwM2mRedisClientOtaInfoStore(getConnectionFactory()) : new TbDummyLwM2MClientOtaInfoStore();
} }
@Bean @Bean
private TbLwM2MDtlsSessionStore sessionStore() { private TbLwM2MDtlsSessionStore sessionStore() {
return isRedis() ? new TbLwM2MDtlsSessionRedisStore(getConnectionFactory()) : new TbL2M2MDtlsSessionInMemoryStore(); return redisConfiguration.isPresent() ? new TbLwM2MDtlsSessionRedisStore(getConnectionFactory()) : new TbL2M2MDtlsSessionInMemoryStore();
} }
private RedisConnectionFactory getConnectionFactory() { private RedisConnectionFactory getConnectionFactory() {
return redisConfiguration.get().redisConnectionFactory(); return redisConfiguration.get().redisConnectionFactory();
} }
private boolean isRedis() {
return redisConfiguration.isPresent() && useRedis;
}
} }

12
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbRedisLwM2MClientStore.java

@ -16,7 +16,6 @@
package org.thingsboard.server.transport.lwm2m.server.store; package org.thingsboard.server.transport.lwm2m.server.store;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.nustaq.serialization.FSTConfiguration;
import org.springframework.data.redis.connection.RedisClusterConnection; import org.springframework.data.redis.connection.RedisClusterConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.Cursor; import org.springframework.data.redis.core.Cursor;
@ -29,16 +28,17 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import static org.thingsboard.server.transport.lwm2m.server.store.util.LwM2MClientSerDes.deserialize;
import static org.thingsboard.server.transport.lwm2m.server.store.util.LwM2MClientSerDes.serialize;
@Slf4j @Slf4j
public class TbRedisLwM2MClientStore implements TbLwM2MClientStore { public class TbRedisLwM2MClientStore implements TbLwM2MClientStore {
private static final String CLIENT_EP = "CLIENT#EP#"; private static final String CLIENT_EP = "CLIENT#EP#";
private final RedisConnectionFactory connectionFactory; private final RedisConnectionFactory connectionFactory;
private final FSTConfiguration serializer;
public TbRedisLwM2MClientStore(RedisConnectionFactory redisConnectionFactory) { public TbRedisLwM2MClientStore(RedisConnectionFactory redisConnectionFactory) {
this.connectionFactory = redisConnectionFactory; this.connectionFactory = redisConnectionFactory;
this.serializer = FSTConfiguration.createDefaultConfiguration();
} }
@Override @Override
@ -48,7 +48,7 @@ public class TbRedisLwM2MClientStore implements TbLwM2MClientStore {
if (data == null) { if (data == null) {
return null; return null;
} else { } else {
return (LwM2mClient) serializer.asObject(data); return deserialize(data);
} }
} }
} }
@ -70,7 +70,7 @@ public class TbRedisLwM2MClientStore implements TbLwM2MClientStore {
scans.forEach(scan -> { scans.forEach(scan -> {
scan.forEachRemaining(key -> { scan.forEachRemaining(key -> {
byte[] element = connection.get(key); byte[] element = connection.get(key);
clients.add((LwM2mClient) serializer.asObject(element)); clients.add(deserialize(element));
}); });
}); });
return clients; return clients;
@ -82,7 +82,7 @@ public class TbRedisLwM2MClientStore implements TbLwM2MClientStore {
if (client.getState().equals(LwM2MClientState.UNREGISTERED)) { if (client.getState().equals(LwM2MClientState.UNREGISTERED)) {
log.error("[{}] Client is in invalid state: {}!", client.getEndpoint(), client.getState(), new Exception()); log.error("[{}] Client is in invalid state: {}!", client.getEndpoint(), client.getState(), new Exception());
} else { } else {
byte[] clientSerialized = serializer.asByteArray(client); byte[] clientSerialized = serialize(client);
try (var connection = connectionFactory.getConnection()) { try (var connection = connectionFactory.getConnection()) {
connection.getSet(getKey(client.getEndpoint()), clientSerialized); connection.getSet(getKey(client.getEndpoint()), clientSerialized);
} }

349
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDes.java

@ -0,0 +1,349 @@
/**
* 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.server.store.util;
import com.eclipsesource.json.Json;
import com.eclipsesource.json.JsonObject;
import com.eclipsesource.json.JsonValue;
import com.google.protobuf.util.JsonFormat;
import lombok.SneakyThrows;
import org.eclipse.leshan.core.model.ResourceModel;
import org.eclipse.leshan.core.node.LwM2mMultipleResource;
import org.eclipse.leshan.core.node.LwM2mNodeException;
import org.eclipse.leshan.core.node.LwM2mResource;
import org.eclipse.leshan.core.node.LwM2mSingleResource;
import org.eclipse.leshan.core.node.ObjectLink;
import org.eclipse.leshan.core.util.datatype.ULong;
import org.eclipse.leshan.server.redis.serialization.RegistrationSerDes;
import org.thingsboard.server.common.data.device.data.PowerMode;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientState;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
public class LwM2MClientSerDes {
public static final String VALUE = "value";
@SneakyThrows
public static byte[] serialize(LwM2mClient client) {
JsonObject o = Json.object();
o.add("nodeId", client.getNodeId());
o.add("endpoint", client.getEndpoint());
JsonObject resources = Json.object();
client.getResources().forEach((k, v) -> {
JsonObject resourceValue = Json.object();
resourceValue.add("lwM2mResource", serialize(v.getLwM2mResource()));
resourceValue.add("resourceModel", serialize(v.getResourceModel()));
resources.add(k, resourceValue);
});
o.add("resources", resources);
JsonObject sharedAttributes = Json.object();
for (Map.Entry<String, TransportProtos.TsKvProto> entry : client.getSharedAttributes().entrySet()) {
sharedAttributes.add(entry.getKey(), JsonFormat.printer().print(entry.getValue()));
}
o.add("sharedAttributes", sharedAttributes);
JsonObject keyTsLatestMap = Json.object();
client.getKeyTsLatestMap().forEach((k, v) -> {
keyTsLatestMap.add(k, v.get());
});
o.add("keyTsLatestMap", keyTsLatestMap);
o.add("state", client.getState().toString());
if (client.getSession() != null) {
o.add("session", JsonFormat.printer().print(client.getSession()));
}
if (client.getTenantId() != null) {
o.add("tenantId", client.getTenantId().toString());
}
if (client.getDeviceId() != null) {
o.add("deviceId", client.getDeviceId().toString());
}
if (client.getProfileId() != null) {
o.add("profileId", client.getProfileId().toString());
}
if (client.getPowerMode() != null) {
o.add("powerMode", client.getPowerMode().toString());
}
if (client.getEdrxCycle() != null) {
o.add("edrxCycle", client.getEdrxCycle());
}
if (client.getPsmActivityTimer() != null) {
o.add("psmActivityTimer", client.getPsmActivityTimer());
}
if (client.getPagingTransmissionWindow() != null) {
o.add("pagingTransmissionWindow", client.getPagingTransmissionWindow());
}
if (client.getRegistration() != null) {
o.add("registration", RegistrationSerDes.jSerialize(client.getRegistration()));
}
o.add("asleep", client.isAsleep());
o.add("lastUplinkTime", client.getLastUplinkTime());
Field firstEdrxDownlink = LwM2mClient.class.getDeclaredField("firstEdrxDownlink");
firstEdrxDownlink.setAccessible(true);
o.add("firstEdrxDownlink", (boolean) firstEdrxDownlink.get(client));
o.add("retryAttempts", client.getRetryAttempts().get());
if (client.getLastSentRpcId() != null) {
o.add("lastSentRpcId", client.getLastSentRpcId().toString());
}
return o.toString().getBytes();
}
private static JsonObject serialize(LwM2mResource resource) {
JsonObject o = Json.object();
o.add("id", resource.getId());
o.add("type", resource.getType().toString());
if (resource.isMultiInstances()) {
o.add("multiInstances", true);
JsonObject instances = Json.object();
resource.getInstances().forEach((id, in) -> {
JsonObject instance = Json.object();
instance.add("id", in.getId());
addValue(instance, in.getType(), in.getValue());
instances.add(id.toString(), instance);
});
o.add("instances", instances);
} else {
o.add("multiInstances", false);
addValue(o, resource.getType(), resource.getValue());
}
return o;
}
private static LwM2mResource parseLwM2mResource(JsonObject o) {
boolean multiInstances = o.get("multiInstances").asBoolean();
int id = o.get("id").asInt();
ResourceModel.Type type = ResourceModel.Type.valueOf(o.get("type").asString());
if (multiInstances) {
Map<Integer, Object> instances = new HashMap<>();
o.get("instances").asObject().forEach(entry -> {
instances.put(Integer.valueOf(entry.getName()), parseValue(type, entry.getValue()));
});
return LwM2mMultipleResource.newResource(id, instances, type);
} else {
return LwM2mSingleResource.newResource(id, parseValue(type, o.get(VALUE)));
}
}
private static Object parseValue(ResourceModel.Type type, JsonValue value) {
switch (type) {
case INTEGER:
return value.asLong();
case FLOAT:
return value.asDouble();
case BOOLEAN:
return value.asBoolean();
case OPAQUE:
return Base64.getDecoder().decode(value.asString());
case STRING:
return value.asString();
case TIME:
return new Date(value.asLong());
case OBJLNK:
return ObjectLink.decodeFromString(value.asString());
case UNSIGNED_INTEGER:
return ULong.valueOf(value.asString());
default:
throw new LwM2mNodeException(String.format("Type %s is not supported", type.name()));
}
}
private static JsonObject serialize(ResourceModel resourceModel) {
JsonObject o = Json.object();
o.add("id", resourceModel.id);
o.add("name", resourceModel.name);
o.add("operations", resourceModel.operations.toString());
o.add("multiple", resourceModel.multiple);
o.add("mandatory", resourceModel.mandatory);
o.add("type", resourceModel.type.toString());
o.add("rangeEnumeration", resourceModel.rangeEnumeration);
o.add("units", resourceModel.units);
o.add("description", resourceModel.description);
return o;
}
private static ResourceModel parseResourceModel(JsonObject o) {
Integer id = o.get("id").asInt();
String name = o.get("name").asString();
ResourceModel.Operations operations = ResourceModel.Operations.valueOf(o.get("operations").asString());
Boolean multiple = o.get("multiple").asBoolean();
Boolean mandatory = o.get("mandatory").asBoolean();
ResourceModel.Type type = ResourceModel.Type.valueOf(o.get("type").asString());
String rangeEnumeration = o.get("rangeEnumeration").asString();
String units = o.get("units").asString();
String description = o.get("description").asString();
return new ResourceModel(id, name, operations, multiple, mandatory, type, rangeEnumeration, units, description);
}
private static void addValue(JsonObject o, ResourceModel.Type type, Object value) {
switch (type) {
case INTEGER:
o.add(VALUE, (Long) value);
break;
case FLOAT:
o.add(VALUE, (Double) value);
break;
case BOOLEAN:
o.add(VALUE, (Boolean) value);
break;
case OPAQUE:
o.add(VALUE, Base64.getEncoder().encodeToString((byte[]) value));
break;
case STRING:
o.add(VALUE, (String) value);
break;
case TIME:
o.add(VALUE, ((Date) value).getTime());
break;
case OBJLNK:
o.add(VALUE, ((ObjectLink) value).encodeToString());
break;
case UNSIGNED_INTEGER:
o.add(VALUE, value.toString());
break;
default:
throw new LwM2mNodeException(String.format("Type %s is not supported", type.name()));
}
}
@SneakyThrows
public static LwM2mClient deserialize(byte[] data) {
JsonObject o = Json.parse(new String(data)).asObject();
LwM2mClient lwM2mClient = new LwM2mClient(o.get("nodeId").asString(), o.get("endpoint").asString());
o.get("resources").asObject().forEach(entry -> {
JsonObject resource = entry.getValue().asObject();
LwM2mResource lwM2mResource = parseLwM2mResource(resource.get("lwM2mResource").asObject());
ResourceModel resourceModel = parseResourceModel(resource.get("resourceModel").asObject());
ResourceValue resourceValue = new ResourceValue(lwM2mResource, resourceModel);
lwM2mClient.getResources().put(entry.getName(), resourceValue);
});
for (JsonObject.Member entry : o.get("sharedAttributes").asObject()) {
TransportProtos.TsKvProto.Builder builder = TransportProtos.TsKvProto.newBuilder();
JsonFormat.parser().merge(entry.getValue().asString(), builder);
lwM2mClient.getSharedAttributes().put(entry.getName(), builder.build());
}
o.get("keyTsLatestMap").asObject().forEach(entry -> {
lwM2mClient.getKeyTsLatestMap().put(entry.getName(), new AtomicLong(entry.getValue().asLong()));
});
lwM2mClient.setState(LwM2MClientState.valueOf(o.get("state").asString()));
Class<LwM2mClient> lwM2mClientClass = LwM2mClient.class;
JsonValue session = o.get("session");
if (session != null) {
TransportProtos.SessionInfoProto.Builder builder = TransportProtos.SessionInfoProto.newBuilder();
JsonFormat.parser().merge(session.asString(), builder);
Field sessionField = lwM2mClientClass.getDeclaredField("session");
sessionField.setAccessible(true);
sessionField.set(lwM2mClient, builder.build());
}
JsonValue tenantId = o.get("tenantId");
if (tenantId != null) {
Field tenantIdField = lwM2mClientClass.getDeclaredField("tenantId");
tenantIdField.setAccessible(true);
tenantIdField.set(lwM2mClient, new TenantId(UUID.fromString(tenantId.asString())));
}
JsonValue deviceId = o.get("deviceId");
if (tenantId != null) {
Field deviceIdField = lwM2mClientClass.getDeclaredField("deviceId");
deviceIdField.setAccessible(true);
deviceIdField.set(lwM2mClient, UUID.fromString(deviceId.asString()));
}
JsonValue profileId = o.get("profileId");
if (tenantId != null) {
Field profileIdField = lwM2mClientClass.getDeclaredField("profileId");
profileIdField.setAccessible(true);
profileIdField.set(lwM2mClient, UUID.fromString(profileId.asString()));
}
JsonValue powerMode = o.get("powerMode");
if (powerMode != null) {
Field powerModeField = lwM2mClientClass.getDeclaredField("powerMode");
powerModeField.setAccessible(true);
powerModeField.set(lwM2mClient, PowerMode.valueOf(powerMode.asString()));
}
JsonValue edrxCycle = o.get("edrxCycle");
if (edrxCycle != null) {
Field edrxCycleField = lwM2mClientClass.getDeclaredField("edrxCycle");
edrxCycleField.setAccessible(true);
edrxCycleField.set(lwM2mClient, edrxCycle.asLong());
}
JsonValue psmActivityTimer = o.get("psmActivityTimer");
if (psmActivityTimer != null) {
Field psmActivityTimerField = lwM2mClientClass.getDeclaredField("psmActivityTimer");
psmActivityTimerField.setAccessible(true);
psmActivityTimerField.set(lwM2mClient, psmActivityTimer.asLong());
}
JsonValue pagingTransmissionWindow = o.get("pagingTransmissionWindow");
if (pagingTransmissionWindow != null) {
Field pagingTransmissionWindowField = lwM2mClientClass.getDeclaredField("pagingTransmissionWindow");
pagingTransmissionWindowField.setAccessible(true);
pagingTransmissionWindowField.set(lwM2mClient, pagingTransmissionWindow.asLong());
}
JsonValue registration = o.get("registration");
if (registration != null) {
lwM2mClient.setRegistration(RegistrationSerDes.deserialize(registration.asObject()));
}
lwM2mClient.setAsleep(o.get("asleep").asBoolean());
Field lastUplinkTimeField = lwM2mClientClass.getDeclaredField("lastUplinkTime");
lastUplinkTimeField.setAccessible(true);
lastUplinkTimeField.setLong(lwM2mClient, o.get("lastUplinkTime").asLong());
Field firstEdrxDownlinkField = lwM2mClientClass.getDeclaredField("firstEdrxDownlink");
firstEdrxDownlinkField.setAccessible(true);
firstEdrxDownlinkField.setBoolean(lwM2mClient, o.get("firstEdrxDownlink").asBoolean());
lwM2mClient.getRetryAttempts().set(o.get("retryAttempts").asInt());
JsonValue lastSentRpcId = o.get("lastSentRpcId");
if (lastSentRpcId != null) {
lwM2mClient.setLastSentRpcId(UUID.fromString(lastSentRpcId.asString()));
}
return lwM2mClient;
}
}

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.");

38
common/transport/lwm2m/src/test/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientTest.java

@ -0,0 +1,38 @@
/**
* 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.server.client;
import org.eclipse.leshan.core.link.Link;
import org.eclipse.leshan.core.request.Identity;
import org.eclipse.leshan.server.registration.Registration;
import org.junit.Test;
import org.junit.jupiter.api.Assertions;
import java.net.InetSocketAddress;
public class LwM2mClientTest {
@Test
public void setRegistration() {
LwM2mClient client = new LwM2mClient("nodeId", "testEndpoint");
Registration registration = new Registration
.Builder("test", "testEndpoint", Identity.unsecure(new InetSocketAddress(1000)))
.objectLinks(new Link[0])
.build();
Assertions.assertDoesNotThrow(() -> client.setRegistration(registration));
}
}

101
common/transport/lwm2m/src/test/org/thingsboard/server/transport/lwm2m/server/store/util/LwM2MClientSerDesTest.java

@ -0,0 +1,101 @@
/**
* 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.server.store.util;
import org.eclipse.leshan.core.link.Link;
import org.eclipse.leshan.core.request.Identity;
import org.eclipse.leshan.server.registration.Registration;
import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.server.common.data.device.data.PowerMode;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.transport.auth.TransportDeviceInfo;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientState;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient;
import java.net.InetSocketAddress;
import java.util.UUID;
public class LwM2MClientSerDesTest {
@Test
public void serializeDeserialize() {
LwM2mClient client = new LwM2mClient("nodeId", "testEndpoint");
TransportDeviceInfo tdi = new TransportDeviceInfo();
tdi.setPowerMode(PowerMode.PSM);
tdi.setPsmActivityTimer(10000L);
tdi.setPagingTransmissionWindow(2000L);
tdi.setEdrxCycle(3000L);
tdi.setTenantId(TenantId.fromUUID(UUID.randomUUID()));
tdi.setCustomerId(new CustomerId(UUID.randomUUID()));
tdi.setDeviceId(new DeviceId(UUID.randomUUID()));
tdi.setDeviceProfileId(new DeviceProfileId(UUID.randomUUID()));
tdi.setDeviceName("testDevice");
tdi.setDeviceType("testType");
ValidateDeviceCredentialsResponse credentialsResponse = ValidateDeviceCredentialsResponse.builder()
.deviceInfo(tdi)
.build();
client.init(credentialsResponse, UUID.randomUUID());
Registration registration =
new Registration.Builder("test", "testEndpoint", Identity
.unsecure(new InetSocketAddress(1000)))
.supportedContentFormats()
.objectLinks(new Link[]{new Link("/")})
.build();
client.setRegistration(registration);
client.setState(LwM2MClientState.REGISTERED);
client.getSharedAttributes().put("key1", TransportProtos.TsKvProto.newBuilder().setTs(0).setKv(TransportProtos.KeyValueProto.newBuilder().setStringV("test").build()).build());
client.getSharedAttributes().put("key2", TransportProtos.TsKvProto.newBuilder().setTs(1).setKv(TransportProtos.KeyValueProto.newBuilder().setDoubleV(1.02).build()).build());
byte[] bytes = LwM2MClientSerDes.serialize(client);
Assert.assertNotNull(bytes);
LwM2mClient desClient = LwM2MClientSerDes.deserialize(bytes);
Assert.assertEquals(client.getNodeId(), desClient.getNodeId());
Assert.assertEquals(client.getEndpoint(), desClient.getEndpoint());
Assert.assertEquals(client.getResources(), desClient.getResources());
Assert.assertEquals(client.getSharedAttributes(), desClient.getSharedAttributes());
Assert.assertEquals(client.getKeyTsLatestMap(), desClient.getKeyTsLatestMap());
Assert.assertEquals(client.getTenantId(), desClient.getTenantId());
Assert.assertEquals(client.getProfileId(), desClient.getProfileId());
Assert.assertEquals(client.getDeviceId(), desClient.getDeviceId());
Assert.assertEquals(client.getState(), desClient.getState());
Assert.assertEquals(client.getSession(), desClient.getSession());
Assert.assertEquals(client.getPowerMode(), desClient.getPowerMode());
Assert.assertEquals(client.getPsmActivityTimer(), desClient.getPsmActivityTimer());
Assert.assertEquals(client.getPagingTransmissionWindow(), desClient.getPagingTransmissionWindow());
Assert.assertEquals(client.getEdrxCycle(), desClient.getEdrxCycle());
Assert.assertEquals(client.getRegistration(), desClient.getRegistration());
Assert.assertEquals(client.isAsleep(), desClient.isAsleep());
Assert.assertEquals(client.getLastUplinkTime(), desClient.getLastUplinkTime());
Assert.assertEquals(client.getSleepTask(), desClient.getSleepTask());
Assert.assertEquals(client.getClientSupportContentFormats(), desClient.getClientSupportContentFormats());
Assert.assertEquals(client.getDefaultContentFormat(), desClient.getDefaultContentFormat());
Assert.assertEquals(client.getRetryAttempts().get(), desClient.getRetryAttempts().get());
Assert.assertEquals(client.getLastSentRpcId(), desClient.getLastSentRpcId());
}
}

14
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -242,9 +242,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
log.debug("[{}] Unsupported topic for provisioning requests: {}!", sessionId, topicName); log.debug("[{}] Unsupported topic for provisioning requests: {}!", sessionId, topicName);
ctx.close(); ctx.close();
} }
} catch (RuntimeException | AdaptorException e) { } catch (RuntimeException e) {
log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
ctx.close(); ctx.close();
} catch (AdaptorException e) {
log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
ctx.close();
} }
break; break;
case PINGREQ: case PINGREQ:
@ -354,9 +357,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
default: default:
ack(ctx, msgId); ack(ctx, msgId);
} }
} catch (RuntimeException | AdaptorException e) { } catch (RuntimeException e) {
log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
ctx.close(); ctx.close();
} catch (AdaptorException e) {
log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
ctx.close();
} }
} }
@ -444,7 +450,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
ack(ctx, msgId); ack(ctx, msgId);
} }
} catch (AdaptorException e) { } catch (AdaptorException e) {
log.warn("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e); log.debug("[{}] Failed to process publish msg [{}][{}]", sessionId, topicName, msgId, e);
log.info("[{}] Closing current session due to invalid publish msg [{}][{}]", sessionId, topicName, msgId); log.info("[{}] Closing current session due to invalid publish msg [{}][{}]", sessionId, topicName, msgId);
ctx.close(); ctx.close();
} }
@ -749,7 +755,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
} }
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}] Failed to process unsubscription [{}] to [{}]", sessionId, mqttMsg.variableHeader().messageId(), topicName); log.debug("[{}] Failed to process unsubscription [{}] to [{}]", sessionId, mqttMsg.variableHeader().messageId(), topicName);
} }
} }
if (!activityReported) { if (!activityReported) {

16
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java

@ -61,7 +61,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
try { try {
return JsonConverter.convertToTelemetryProto(new JsonParser().parse(payload)); return JsonConverter.convertToTelemetryProto(new JsonParser().parse(payload));
} catch (IllegalStateException | JsonSyntaxException ex) { } catch (IllegalStateException | JsonSyntaxException ex) {
log.warn("Failed to decode post telemetry request", ex); log.debug("Failed to decode post telemetry request", ex);
throw new AdaptorException(ex); throw new AdaptorException(ex);
} }
} }
@ -72,7 +72,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
try { try {
return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload)); return JsonConverter.convertToAttributesProto(new JsonParser().parse(payload));
} catch (IllegalStateException | JsonSyntaxException ex) { } catch (IllegalStateException | JsonSyntaxException ex) {
log.warn("Failed to decode post attributes request", ex); log.debug("Failed to decode post attributes request", ex);
throw new AdaptorException(ex); throw new AdaptorException(ex);
} }
} }
@ -83,7 +83,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
try { try {
return JsonConverter.convertToClaimDeviceProto(ctx.getDeviceId(), payload); return JsonConverter.convertToClaimDeviceProto(ctx.getDeviceId(), payload);
} catch (IllegalStateException | JsonSyntaxException ex) { } catch (IllegalStateException | JsonSyntaxException ex) {
log.warn("Failed to decode claim device request", ex); log.debug("Failed to decode claim device request", ex);
throw new AdaptorException(ex); throw new AdaptorException(ex);
} }
} }
@ -164,7 +164,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
try { try {
return new JsonParser().parse(payload); return new JsonParser().parse(payload);
} catch (JsonSyntaxException ex) { } catch (JsonSyntaxException ex) {
log.warn("Payload is in incorrect format: {}", payload); log.debug("Payload is in incorrect format: {}", payload);
throw new AdaptorException(ex); throw new AdaptorException(ex);
} }
} }
@ -186,7 +186,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
} }
return result.build(); return result.build();
} catch (RuntimeException e) { } catch (RuntimeException e) {
log.warn("Failed to decode get attributes request", e); log.debug("Failed to decode get attributes request", e);
throw new AdaptorException(e); throw new AdaptorException(e);
} }
} }
@ -198,7 +198,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
String payload = inbound.payload().toString(UTF8); String payload = inbound.payload().toString(UTF8);
return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(payload).build(); return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(payload).build();
} catch (RuntimeException e) { } catch (RuntimeException e) {
log.warn("Failed to decode rpc response", e); log.debug("Failed to decode rpc response", e);
throw new AdaptorException(e); throw new AdaptorException(e);
} }
} }
@ -210,7 +210,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
int requestId = getRequestId(topicName, topicBase); int requestId = getRequestId(topicName, topicBase);
return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), requestId); return JsonConverter.convertToServerRpcRequest(new JsonParser().parse(payload), requestId);
} catch (IllegalStateException | JsonSyntaxException ex) { } catch (IllegalStateException | JsonSyntaxException ex) {
log.warn("Failed to decode to server rpc request", ex); log.debug("Failed to decode to server rpc request", ex);
throw new AdaptorException(ex); throw new AdaptorException(ex);
} }
} }
@ -259,7 +259,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor {
private static String validatePayload(UUID sessionId, ByteBuf payloadData, boolean isEmptyPayloadAllowed) throws AdaptorException { private static String validatePayload(UUID sessionId, ByteBuf payloadData, boolean isEmptyPayloadAllowed) throws AdaptorException {
String payload = payloadData.toString(UTF8); String payload = payloadData.toString(UTF8);
if (payload == null) { if (payload == null) {
log.warn("[{}] Payload is empty!", sessionId); log.debug("[{}] Payload is empty!", sessionId);
if (!isEmptyPayloadAllowed) { if (!isEmptyPayloadAllowed) {
throw new AdaptorException(new IllegalArgumentException("Payload is empty!")); throw new AdaptorException(new IllegalArgumentException("Payload is empty!"));
} }

14
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java

@ -52,7 +52,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
try { try {
return JsonConverter.convertToTelemetryProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, telemetryDynamicMsgDescriptor))); return JsonConverter.convertToTelemetryProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, telemetryDynamicMsgDescriptor)));
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to decode post telemetry request", e); log.debug("Failed to decode post telemetry request", e);
throw new AdaptorException(e); throw new AdaptorException(e);
} }
} }
@ -65,7 +65,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
try { try {
return JsonConverter.convertToAttributesProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, attributesDynamicMessageDescriptor))); return JsonConverter.convertToAttributesProto(new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, attributesDynamicMessageDescriptor)));
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to decode post attributes request", e); log.debug("Failed to decode post attributes request", e);
throw new AdaptorException(e); throw new AdaptorException(e);
} }
} }
@ -76,7 +76,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
try { try {
return ProtoConverter.convertToClaimDeviceProto(ctx.getDeviceId(), bytes); return ProtoConverter.convertToClaimDeviceProto(ctx.getDeviceId(), bytes);
} catch (InvalidProtocolBufferException e) { } catch (InvalidProtocolBufferException e) {
log.warn("Failed to decode claim device request", e); log.debug("Failed to decode claim device request", e);
throw new AdaptorException(e); throw new AdaptorException(e);
} }
} }
@ -89,7 +89,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
int requestId = getRequestId(topicName, topicBase); int requestId = getRequestId(topicName, topicBase);
return ProtoConverter.convertToGetAttributeRequestMessage(bytes, requestId); return ProtoConverter.convertToGetAttributeRequestMessage(bytes, requestId);
} catch (InvalidProtocolBufferException e) { } catch (InvalidProtocolBufferException e) {
log.warn("Failed to decode get attributes request", e); log.debug("Failed to decode get attributes request", e);
throw new AdaptorException(e); throw new AdaptorException(e);
} }
} }
@ -105,7 +105,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
JsonElement response = new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, rpcResponseDynamicMessageDescriptor)); JsonElement response = new JsonParser().parse(ProtoConverter.dynamicMsgToJson(bytes, rpcResponseDynamicMessageDescriptor));
return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(response.toString()).build(); return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(response.toString()).build();
} catch (Exception e) { } catch (Exception e) {
log.warn("Failed to decode rpc response", e); log.debug("Failed to decode rpc response", e);
throw new AdaptorException(e); throw new AdaptorException(e);
} }
} }
@ -118,7 +118,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
int requestId = getRequestId(topicName, topicBase); int requestId = getRequestId(topicName, topicBase);
return ProtoConverter.convertToServerRpcRequest(bytes, requestId); return ProtoConverter.convertToServerRpcRequest(bytes, requestId);
} catch (InvalidProtocolBufferException e) { } catch (InvalidProtocolBufferException e) {
log.warn("Failed to decode to server rpc request", e); log.debug("Failed to decode to server rpc request", e);
throw new AdaptorException(e); throw new AdaptorException(e);
} }
} }
@ -129,7 +129,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor {
try { try {
return ProtoConverter.convertToProvisionRequestMsg(bytes); return ProtoConverter.convertToProvisionRequestMsg(bytes);
} catch (InvalidProtocolBufferException ex) { } catch (InvalidProtocolBufferException ex) {
log.warn("Failed to decode provision request", ex); log.debug("Failed to decode provision request", ex);
throw new AdaptorException(ex); throw new AdaptorException(ex);
} }
} }

11
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/limits/ProxyIpFilter.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.transport.mqtt.limits; package org.thingsboard.server.transport.mqtt.limits;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.haproxy.HAProxyMessage; import io.netty.handler.codec.haproxy.HAProxyMessage;
@ -60,7 +61,15 @@ public class ProxyIpFilter extends ChannelInboundHandlerAdapter {
private void closeChannel(ChannelHandlerContext ctx) { private void closeChannel(ChannelHandlerContext ctx) {
while (ctx.pipeline().last() != this) { while (ctx.pipeline().last() != this) {
ctx.pipeline().removeLast(); ChannelHandler handler = ctx.pipeline().removeLast();
if (handler instanceof ChannelInboundHandlerAdapter) {
try {
((ChannelInboundHandlerAdapter) handler).channelUnregistered(ctx);
} catch (Exception e) {
log.error("Failed to unregister channel: [{}]", ctx, e);
}
}
} }
ctx.pipeline().remove(this); ctx.pipeline().remove(this);
ctx.close(); ctx.close();

2
common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java

@ -73,7 +73,7 @@ public class SnmpTransportContext extends TransportContext {
private final Map<DeviceId, DeviceSessionContext> sessions = new ConcurrentHashMap<>(); private final Map<DeviceId, DeviceSessionContext> sessions = new ConcurrentHashMap<>();
private final Collection<DeviceId> allSnmpDevicesIds = new ConcurrentLinkedDeque<>(); private final Collection<DeviceId> allSnmpDevicesIds = new ConcurrentLinkedDeque<>();
@AfterStartUp(order = 2) @AfterStartUp(order = Integer.MAX_VALUE)
public void fetchDevicesAndEstablishSessions() { public void fetchDevicesAndEstablishSessions() {
log.info("Initializing SNMP devices sessions"); log.info("Initializing SNMP devices sessions");

37
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java

@ -25,9 +25,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmQuery;
@ -49,9 +47,7 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationsSearchParameters; import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -59,7 +55,6 @@ import javax.annotation.PreDestroy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -82,10 +77,10 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
private AlarmDao alarmDao; private AlarmDao alarmDao;
@Autowired @Autowired
private TenantDao tenantDao; private EntityService entityService;
@Autowired @Autowired
private EntityService entityService; private DataValidator<Alarm> alarmDataValidator;
protected ExecutorService readResultsProcessingExecutor; protected ExecutorService readResultsProcessingExecutor;
@ -412,32 +407,4 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
ListenableFuture<Alarm> entity = alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId()); ListenableFuture<Alarm> entity = alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId());
return Futures.transform(entity, function, readResultsProcessingExecutor); return Futures.transform(entity, function, readResultsProcessingExecutor);
} }
private DataValidator<Alarm> alarmDataValidator =
new DataValidator<>() {
@Override
protected void validateDataImpl(TenantId tenantId, Alarm alarm) {
if (StringUtils.isEmpty(alarm.getType())) {
throw new DataValidationException("Alarm type should be specified!");
}
if (alarm.getOriginator() == null) {
throw new DataValidationException("Alarm originator should be specified!");
}
if (alarm.getSeverity() == null) {
throw new DataValidationException("Alarm severity should be specified!");
}
if (alarm.getStatus() == null) {
throw new DataValidationException("Alarm status should be specified!");
}
if (alarm.getTenantId() == null) {
throw new DataValidationException("Alarm should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(alarm.getTenantId(), alarm.getTenantId().getId());
if (tenant == null) {
throw new DataValidationException("Alarm is referencing to non-existent tenant!");
}
}
}
};
} }

85
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -22,18 +22,12 @@ import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException; import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.asset.AssetInfo;
import org.thingsboard.server.common.data.asset.AssetSearchQuery; import org.thingsboard.server.common.data.asset.AssetSearchQuery;
@ -48,17 +42,13 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.cache.EntitiesCacheManager;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
@ -67,7 +57,6 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.CacheConstants.ASSET_CACHE; import static org.thingsboard.server.common.data.CacheConstants.ASSET_CACHE;
import static org.thingsboard.server.dao.DaoUtil.toUUIDs; import static org.thingsboard.server.dao.DaoUtil.toUUIDs;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validateIds; import static org.thingsboard.server.dao.service.Validator.validateIds;
import static org.thingsboard.server.dao.service.Validator.validatePageLink; import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@ -86,17 +75,10 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
private AssetDao assetDao; private AssetDao assetDao;
@Autowired @Autowired
private TenantDao tenantDao; private EntitiesCacheManager cacheManager;
@Autowired @Autowired
private CustomerDao customerDao; private DataValidator<Asset> assetValidator;
@Autowired
private CacheManager cacheManager;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override @Override
public AssetInfo findAssetInfoById(TenantId tenantId, AssetId assetId) { public AssetInfo findAssetInfoById(TenantId tenantId, AssetId assetId) {
@ -178,16 +160,11 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
throw new RuntimeException("Exception while finding entity views for assetId [" + assetId + "]", e); throw new RuntimeException("Exception while finding entity views for assetId [" + assetId + "]", e);
} }
removeAssetFromCacheByName(asset.getTenantId(), asset.getName()); cacheManager.removeAssetFromCacheByName(asset.getTenantId(), asset.getName());
assetDao.removeById(tenantId, assetId.getId()); assetDao.removeById(tenantId, assetId.getId());
} }
private void removeAssetFromCacheByName(TenantId tenantId, String name) {
Cache cache = cacheManager.getCache(ASSET_CACHE);
cache.evict(Arrays.asList(tenantId, name));
}
@Override @Override
public PageData<Asset> findAssetsByTenantId(TenantId tenantId, PageLink pageLink) { public PageData<Asset> findAssetsByTenantId(TenantId tenantId, PageLink pageLink) {
log.trace("Executing findAssetsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); log.trace("Executing findAssetsByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
@ -381,60 +358,6 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
return assetDao.findAssetsByTenantIdAndEdgeIdAndType(tenantId.getId(), edgeId.getId(), type, pageLink); return assetDao.findAssetsByTenantIdAndEdgeIdAndType(tenantId.getId(), edgeId.getId(), type, pageLink);
} }
private DataValidator<Asset> assetValidator =
new DataValidator<Asset>() {
@Override
protected void validateCreate(TenantId tenantId, Asset asset) {
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
if (!TB_SERVICE_QUEUE.equals(asset.getType())) {
long maxAssets = profileConfiguration.getMaxAssets();
validateNumberOfEntitiesPerTenant(tenantId, assetDao, maxAssets, EntityType.ASSET);
}
}
@Override
protected void validateUpdate(TenantId tenantId, Asset asset) {
Asset old = assetDao.findById(asset.getTenantId(), asset.getId().getId());
if (old == null) {
throw new DataValidationException("Can't update non existing asset!");
}
if (!old.getName().equals(asset.getName())) {
removeAssetFromCacheByName(tenantId, old.getName());
}
}
@Override
protected void validateDataImpl(TenantId tenantId, Asset asset) {
if (StringUtils.isEmpty(asset.getType())) {
throw new DataValidationException("Asset type should be specified!");
}
if (StringUtils.isEmpty(asset.getName())) {
throw new DataValidationException("Asset name should be specified!");
}
if (asset.getTenantId() == null) {
throw new DataValidationException("Asset should be assigned to tenant!");
} else {
Tenant tenant = tenantDao.findById(tenantId, asset.getTenantId().getId());
if (tenant == null) {
throw new DataValidationException("Asset is referencing to non-existent tenant!");
}
}
if (asset.getCustomerId() == null) {
asset.setCustomerId(new CustomerId(NULL_UUID));
} else if (!asset.getCustomerId().getId().equals(NULL_UUID)) {
Customer customer = customerDao.findById(tenantId, asset.getCustomerId().getId());
if (customer == null) {
throw new DataValidationException("Can't assign asset to non-existent customer!");
}
if (!customer.getTenantId().equals(asset.getTenantId())) {
throw new DataValidationException("Can't assign asset to customer from different tenant!");
}
}
}
};
private PaginatedRemover<TenantId, Asset> tenantAssetsRemover = private PaginatedRemover<TenantId, Asset> tenantAssetsRemover =
new PaginatedRemover<TenantId, Asset>() { new PaginatedRemover<TenantId, Asset>() {

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

Loading…
Cancel
Save