Commit b197965ab8424935bd74e5b3cd012d8b93b4f781
Merge branch 'master' of https://github.com/thingsboard/thingsboard into feature/power-mode
Showing
58 changed files
with
1010 additions
and
666 deletions
@@ -18,17 +18,18 @@ CREATE OR REPLACE PROCEDURE drop_partitions_by_max_ttl(IN partition_type varchar | @@ -18,17 +18,18 @@ CREATE OR REPLACE PROCEDURE drop_partitions_by_max_ttl(IN partition_type varchar | ||
18 | LANGUAGE plpgsql AS | 18 | LANGUAGE plpgsql AS |
19 | $$ | 19 | $$ |
20 | DECLARE | 20 | DECLARE |
21 | - max_tenant_ttl bigint; | ||
22 | - max_customer_ttl bigint; | ||
23 | - max_ttl bigint; | ||
24 | - date timestamp; | ||
25 | - partition_by_max_ttl_date varchar; | ||
26 | - partition_month varchar; | ||
27 | - partition_day varchar; | ||
28 | - partition_year varchar; | ||
29 | - partition varchar; | ||
30 | - partition_to_delete varchar; | ||
31 | - | 21 | + max_tenant_ttl bigint; |
22 | + max_customer_ttl bigint; | ||
23 | + max_ttl bigint; | ||
24 | + date timestamp; | ||
25 | + partition_by_max_ttl_date varchar; | ||
26 | + partition_by_max_ttl_month varchar; | ||
27 | + partition_by_max_ttl_day varchar; | ||
28 | + partition_by_max_ttl_year varchar; | ||
29 | + partition varchar; | ||
30 | + partition_year integer; | ||
31 | + partition_month integer; | ||
32 | + partition_day integer; | ||
32 | 33 | ||
33 | BEGIN | 34 | BEGIN |
34 | SELECT max(attribute_kv.long_v) | 35 | SELECT max(attribute_kv.long_v) |
@@ -45,53 +46,138 @@ BEGIN | @@ -45,53 +46,138 @@ BEGIN | ||
45 | if max_ttl IS NOT NULL AND max_ttl > 0 THEN | 46 | if max_ttl IS NOT NULL AND max_ttl > 0 THEN |
46 | date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - max_ttl); | 47 | date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - max_ttl); |
47 | partition_by_max_ttl_date := get_partition_by_max_ttl_date(partition_type, date); | 48 | partition_by_max_ttl_date := get_partition_by_max_ttl_date(partition_type, date); |
49 | + RAISE NOTICE 'Date by max ttl: %', date; | ||
48 | RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date; | 50 | RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date; |
49 | IF partition_by_max_ttl_date IS NOT NULL THEN | 51 | IF partition_by_max_ttl_date IS NOT NULL THEN |
50 | CASE | 52 | CASE |
51 | WHEN partition_type = 'DAYS' THEN | 53 | WHEN partition_type = 'DAYS' THEN |
52 | - partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); | ||
53 | - partition_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); | ||
54 | - partition_day := SPLIT_PART(partition_by_max_ttl_date, '_', 5); | 54 | + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); |
55 | + partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); | ||
56 | + partition_by_max_ttl_day := SPLIT_PART(partition_by_max_ttl_date, '_', 5); | ||
55 | WHEN partition_type = 'MONTHS' THEN | 57 | WHEN partition_type = 'MONTHS' THEN |
56 | - partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); | ||
57 | - partition_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); | 58 | + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); |
59 | + partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); | ||
58 | ELSE | 60 | ELSE |
59 | - partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); | 61 | + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); |
60 | END CASE; | 62 | END CASE; |
61 | - FOR partition IN SELECT tablename | ||
62 | - FROM pg_tables | ||
63 | - WHERE schemaname = 'public' | ||
64 | - AND tablename like 'ts_kv_' || '%' | ||
65 | - AND tablename != 'ts_kv_latest' | ||
66 | - AND tablename != 'ts_kv_dictionary' | ||
67 | - AND tablename != 'ts_kv_indefinite' | ||
68 | - LOOP | ||
69 | - IF partition != partition_by_max_ttl_date THEN | ||
70 | - IF partition_year IS NOT NULL THEN | ||
71 | - IF SPLIT_PART(partition, '_', 3)::integer < partition_year::integer THEN | ||
72 | - partition_to_delete := partition; | ||
73 | - ELSE | ||
74 | - IF partition_month IS NOT NULL THEN | ||
75 | - IF SPLIT_PART(partition, '_', 4)::integer < partition_month::integer THEN | ||
76 | - partition_to_delete := partition; | 63 | + IF partition_by_max_ttl_year IS NULL THEN |
64 | + RAISE NOTICE 'Failed to remove partitions by max ttl date due to partition_by_max_ttl_year is null!'; | ||
65 | + ELSE | ||
66 | + IF partition_type = 'YEARS' THEN | ||
67 | + FOR partition IN SELECT tablename | ||
68 | + FROM pg_tables | ||
69 | + WHERE schemaname = 'public' | ||
70 | + AND tablename like 'ts_kv_' || '%' | ||
71 | + AND tablename != 'ts_kv_latest' | ||
72 | + AND tablename != 'ts_kv_dictionary' | ||
73 | + AND tablename != 'ts_kv_indefinite' | ||
74 | + AND tablename != partition_by_max_ttl_date | ||
75 | + LOOP | ||
76 | + partition_year := SPLIT_PART(partition, '_', 3)::integer; | ||
77 | + IF partition_year < partition_by_max_ttl_year::integer THEN | ||
78 | + RAISE NOTICE 'Partition to delete by max ttl: %', partition; | ||
79 | + EXECUTE format('DROP TABLE IF EXISTS %I', partition); | ||
80 | + deleted := deleted + 1; | ||
81 | + END IF; | ||
82 | + END LOOP; | ||
83 | + ELSE | ||
84 | + IF partition_type = 'MONTHS' THEN | ||
85 | + IF partition_by_max_ttl_month IS NULL THEN | ||
86 | + RAISE NOTICE 'Failed to remove months partitions by max ttl date due to partition_by_max_ttl_month is null!'; | ||
87 | + ELSE | ||
88 | + FOR partition IN SELECT tablename | ||
89 | + FROM pg_tables | ||
90 | + WHERE schemaname = 'public' | ||
91 | + AND tablename like 'ts_kv_' || '%' | ||
92 | + AND tablename != 'ts_kv_latest' | ||
93 | + AND tablename != 'ts_kv_dictionary' | ||
94 | + AND tablename != 'ts_kv_indefinite' | ||
95 | + AND tablename != partition_by_max_ttl_date | ||
96 | + LOOP | ||
97 | + partition_year := SPLIT_PART(partition, '_', 3)::integer; | ||
98 | + IF partition_year > partition_by_max_ttl_year::integer THEN | ||
99 | + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; | ||
100 | + CONTINUE; | ||
77 | ELSE | 101 | ELSE |
78 | - IF partition_day IS NOT NULL THEN | ||
79 | - IF SPLIT_PART(partition, '_', 5)::integer < partition_day::integer THEN | ||
80 | - partition_to_delete := partition; | 102 | + IF partition_year < partition_by_max_ttl_year::integer THEN |
103 | + RAISE NOTICE 'Partition to delete by max ttl: %', partition; | ||
104 | + EXECUTE format('DROP TABLE IF EXISTS %I', partition); | ||
105 | + deleted := deleted + 1; | ||
106 | + ELSE | ||
107 | + partition_month := SPLIT_PART(partition, '_', 4)::integer; | ||
108 | + IF partition_year = partition_by_max_ttl_year::integer THEN | ||
109 | + IF partition_month >= partition_by_max_ttl_month::integer THEN | ||
110 | + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; | ||
111 | + CONTINUE; | ||
112 | + ELSE | ||
113 | + RAISE NOTICE 'Partition to delete by max ttl: %', partition; | ||
114 | + EXECUTE format('DROP TABLE IF EXISTS %I', partition); | ||
115 | + deleted := deleted + 1; | ||
116 | + END IF; | ||
81 | END IF; | 117 | END IF; |
82 | END IF; | 118 | END IF; |
83 | END IF; | 119 | END IF; |
120 | + END LOOP; | ||
121 | + END IF; | ||
122 | + ELSE | ||
123 | + IF partition_type = 'DAYS' THEN | ||
124 | + IF partition_by_max_ttl_month IS NULL THEN | ||
125 | + RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_month is null!'; | ||
126 | + ELSE | ||
127 | + IF partition_by_max_ttl_day IS NULL THEN | ||
128 | + RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_day is null!'; | ||
129 | + ELSE | ||
130 | + FOR partition IN SELECT tablename | ||
131 | + FROM pg_tables | ||
132 | + WHERE schemaname = 'public' | ||
133 | + AND tablename like 'ts_kv_' || '%' | ||
134 | + AND tablename != 'ts_kv_latest' | ||
135 | + AND tablename != 'ts_kv_dictionary' | ||
136 | + AND tablename != 'ts_kv_indefinite' | ||
137 | + AND tablename != partition_by_max_ttl_date | ||
138 | + LOOP | ||
139 | + partition_year := SPLIT_PART(partition, '_', 3)::integer; | ||
140 | + IF partition_year > partition_by_max_ttl_year::integer THEN | ||
141 | + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; | ||
142 | + CONTINUE; | ||
143 | + ELSE | ||
144 | + IF partition_year < partition_by_max_ttl_year::integer THEN | ||
145 | + RAISE NOTICE 'Partition to delete by max ttl: %', partition; | ||
146 | + EXECUTE format('DROP TABLE IF EXISTS %I', partition); | ||
147 | + deleted := deleted + 1; | ||
148 | + ELSE | ||
149 | + partition_month := SPLIT_PART(partition, '_', 4)::integer; | ||
150 | + IF partition_month > partition_by_max_ttl_month::integer THEN | ||
151 | + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; | ||
152 | + CONTINUE; | ||
153 | + ELSE | ||
154 | + IF partition_month < partition_by_max_ttl_month::integer THEN | ||
155 | + RAISE NOTICE 'Partition to delete by max ttl: %', partition; | ||
156 | + EXECUTE format('DROP TABLE IF EXISTS %I', partition); | ||
157 | + deleted := deleted + 1; | ||
158 | + ELSE | ||
159 | + partition_day := SPLIT_PART(partition, '_', 5)::integer; | ||
160 | + IF partition_day >= partition_by_max_ttl_day::integer THEN | ||
161 | + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; | ||
162 | + CONTINUE; | ||
163 | + ELSE | ||
164 | + IF partition_day < partition_by_max_ttl_day::integer THEN | ||
165 | + RAISE NOTICE 'Partition to delete by max ttl: %', partition; | ||
166 | + EXECUTE format('DROP TABLE IF EXISTS %I', partition); | ||
167 | + deleted := deleted + 1; | ||
168 | + END IF; | ||
169 | + END IF; | ||
170 | + END IF; | ||
171 | + END IF; | ||
172 | + END IF; | ||
173 | + END IF; | ||
174 | + END LOOP; | ||
84 | END IF; | 175 | END IF; |
85 | END IF; | 176 | END IF; |
86 | END IF; | 177 | END IF; |
87 | - IF partition_to_delete IS NOT NULL THEN | ||
88 | - RAISE NOTICE 'Partition to delete by max ttl: %', partition_to_delete; | ||
89 | - EXECUTE format('DROP TABLE IF EXISTS %I', partition_to_delete); | ||
90 | - partition_to_delete := NULL; | ||
91 | - deleted := deleted + 1; | ||
92 | - END IF; | ||
93 | END IF; | 178 | END IF; |
94 | - END LOOP; | 179 | + END IF; |
180 | + END IF; | ||
95 | END IF; | 181 | END IF; |
96 | END IF; | 182 | END IF; |
97 | END | 183 | END |
@@ -107,8 +193,6 @@ BEGIN | @@ -107,8 +193,6 @@ BEGIN | ||
107 | partition := 'ts_kv_' || to_char(date, 'yyyy') || '_' || to_char(date, 'MM'); | 193 | partition := 'ts_kv_' || to_char(date, 'yyyy') || '_' || to_char(date, 'MM'); |
108 | WHEN partition_type = 'YEARS' THEN | 194 | WHEN partition_type = 'YEARS' THEN |
109 | partition := 'ts_kv_' || to_char(date, 'yyyy'); | 195 | partition := 'ts_kv_' || to_char(date, 'yyyy'); |
110 | - WHEN partition_type = 'INDEFINITE' THEN | ||
111 | - partition := NULL; | ||
112 | ELSE | 196 | ELSE |
113 | partition := NULL; | 197 | partition := NULL; |
114 | END CASE; | 198 | END CASE; |
@@ -86,7 +86,10 @@ public class DefaultTbResourceService implements TbResourceService { | @@ -86,7 +86,10 @@ public class DefaultTbResourceService implements TbResourceService { | ||
86 | } else { | 86 | } else { |
87 | throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText())); | 87 | throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText())); |
88 | } | 88 | } |
89 | - } catch (InvalidDDFFileException | IOException e) { | 89 | + } catch (InvalidDDFFileException e) { |
90 | + log.error("Failed to parse file {}", resource.getFileName(), e); | ||
91 | + throw new DataValidationException("Failed to parse file " + resource.getFileName()); | ||
92 | + } catch (IOException e) { | ||
90 | throw new ThingsboardException(e, ThingsboardErrorCode.GENERAL); | 93 | throw new ThingsboardException(e, ThingsboardErrorCode.GENERAL); |
91 | } | 94 | } |
92 | if (resource.getResourceType().equals(ResourceType.LWM2M_MODEL) && toLwM2mObject(resource, true) == null) { | 95 | if (resource.getResourceType().equals(ResourceType.LWM2M_MODEL) && toLwM2mObject(resource, true) == null) { |
@@ -194,8 +197,7 @@ public class DefaultTbResourceService implements TbResourceService { | @@ -194,8 +197,7 @@ public class DefaultTbResourceService implements TbResourceService { | ||
194 | if (isSave) { | 197 | if (isSave) { |
195 | LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false); | 198 | LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false); |
196 | resources.add(lwM2MResourceObserve); | 199 | resources.add(lwM2MResourceObserve); |
197 | - } | ||
198 | - else if (v.operations.isReadable()) { | 200 | + } else if (v.operations.isReadable()) { |
199 | LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false); | 201 | LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false); |
200 | resources.add(lwM2MResourceObserve); | 202 | resources.add(lwM2MResourceObserve); |
201 | } | 203 | } |
@@ -204,8 +206,7 @@ public class DefaultTbResourceService implements TbResourceService { | @@ -204,8 +206,7 @@ public class DefaultTbResourceService implements TbResourceService { | ||
204 | instance.setResources(resources.toArray(LwM2mResourceObserve[]::new)); | 206 | instance.setResources(resources.toArray(LwM2mResourceObserve[]::new)); |
205 | lwM2mObject.setInstances(new LwM2mInstance[]{instance}); | 207 | lwM2mObject.setInstances(new LwM2mInstance[]{instance}); |
206 | return lwM2mObject; | 208 | return lwM2mObject; |
207 | - } | ||
208 | - else { | 209 | + } else { |
209 | return null; | 210 | return null; |
210 | } | 211 | } |
211 | } | 212 | } |
@@ -15,8 +15,13 @@ | @@ -15,8 +15,13 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.service.ttl; | 16 | package org.thingsboard.server.service.ttl; |
17 | 17 | ||
18 | +import lombok.RequiredArgsConstructor; | ||
18 | import lombok.extern.slf4j.Slf4j; | 19 | import lombok.extern.slf4j.Slf4j; |
20 | +import org.springframework.beans.factory.annotation.Autowired; | ||
19 | import org.springframework.beans.factory.annotation.Value; | 21 | import org.springframework.beans.factory.annotation.Value; |
22 | +import org.thingsboard.server.common.data.id.TenantId; | ||
23 | +import org.thingsboard.server.common.msg.queue.ServiceType; | ||
24 | +import org.thingsboard.server.queue.discovery.PartitionService; | ||
20 | 25 | ||
21 | import java.sql.Connection; | 26 | import java.sql.Connection; |
22 | import java.sql.DriverManager; | 27 | import java.sql.DriverManager; |
@@ -27,43 +32,12 @@ import java.sql.Statement; | @@ -27,43 +32,12 @@ import java.sql.Statement; | ||
27 | 32 | ||
28 | 33 | ||
29 | @Slf4j | 34 | @Slf4j |
35 | +@RequiredArgsConstructor | ||
30 | public abstract class AbstractCleanUpService { | 36 | public abstract class AbstractCleanUpService { |
31 | 37 | ||
32 | - @Value("${spring.datasource.url}") | ||
33 | - protected String dbUrl; | 38 | + private final PartitionService partitionService; |
34 | 39 | ||
35 | - @Value("${spring.datasource.username}") | ||
36 | - protected String dbUserName; | ||
37 | - | ||
38 | - @Value("${spring.datasource.password}") | ||
39 | - protected String dbPassword; | ||
40 | - | ||
41 | - protected long executeQuery(Connection conn, String query) throws SQLException { | ||
42 | - try (Statement statement = conn.createStatement(); ResultSet resultSet = statement.executeQuery(query)) { | ||
43 | - if (log.isDebugEnabled()) { | ||
44 | - getWarnings(statement); | ||
45 | - } | ||
46 | - resultSet.next(); | ||
47 | - return resultSet.getLong(1); | ||
48 | - } | ||
49 | - } | ||
50 | - | ||
51 | - protected void getWarnings(Statement statement) throws SQLException { | ||
52 | - SQLWarning warnings = statement.getWarnings(); | ||
53 | - if (warnings != null) { | ||
54 | - log.debug("{}", warnings.getMessage()); | ||
55 | - SQLWarning nextWarning = warnings.getNextWarning(); | ||
56 | - while (nextWarning != null) { | ||
57 | - log.debug("{}", nextWarning.getMessage()); | ||
58 | - nextWarning = nextWarning.getNextWarning(); | ||
59 | - } | ||
60 | - } | 40 | + protected boolean isSystemTenantPartitionMine(){ |
41 | + return partitionService.resolve(ServiceType.TB_CORE, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID).isMyPartition(); | ||
61 | } | 42 | } |
62 | - | ||
63 | - protected abstract void doCleanUp(Connection connection) throws SQLException; | ||
64 | - | ||
65 | - protected Connection getConnection() throws SQLException { | ||
66 | - return DriverManager.getConnection(dbUrl, dbUserName, dbPassword); | ||
67 | - } | ||
68 | - | ||
69 | } | 43 | } |
application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
renamed from
application/src/main/java/org/thingsboard/server/service/ttl/alarms/AlarmsCleanUpService.java
@@ -13,7 +13,7 @@ | @@ -13,7 +13,7 @@ | ||
13 | * See the License for the specific language governing permissions and | 13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. | 14 | * limitations under the License. |
15 | */ | 15 | */ |
16 | -package org.thingsboard.server.service.ttl.alarms; | 16 | +package org.thingsboard.server.service.ttl; |
17 | 17 | ||
18 | import lombok.RequiredArgsConstructor; | 18 | import lombok.RequiredArgsConstructor; |
19 | import lombok.extern.slf4j.Slf4j; | 19 | import lombok.extern.slf4j.Slf4j; |
@@ -52,6 +52,7 @@ import java.util.concurrent.TimeUnit; | @@ -52,6 +52,7 @@ import java.util.concurrent.TimeUnit; | ||
52 | @Slf4j | 52 | @Slf4j |
53 | @RequiredArgsConstructor | 53 | @RequiredArgsConstructor |
54 | public class AlarmsCleanUpService { | 54 | public class AlarmsCleanUpService { |
55 | + | ||
55 | @Value("${sql.ttl.alarms.removal_batch_size}") | 56 | @Value("${sql.ttl.alarms.removal_batch_size}") |
56 | private Integer removalBatchSize; | 57 | private Integer removalBatchSize; |
57 | 58 |
application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java
renamed from
application/src/main/java/org/thingsboard/server/service/ttl/edge/EdgeEventsCleanUpService.java
@@ -13,20 +13,18 @@ | @@ -13,20 +13,18 @@ | ||
13 | * See the License for the specific language governing permissions and | 13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. | 14 | * limitations under the License. |
15 | */ | 15 | */ |
16 | -package org.thingsboard.server.service.ttl.edge; | 16 | +package org.thingsboard.server.service.ttl; |
17 | 17 | ||
18 | import lombok.extern.slf4j.Slf4j; | 18 | import lombok.extern.slf4j.Slf4j; |
19 | import org.springframework.beans.factory.annotation.Value; | 19 | import org.springframework.beans.factory.annotation.Value; |
20 | import org.springframework.scheduling.annotation.Scheduled; | 20 | import org.springframework.scheduling.annotation.Scheduled; |
21 | import org.springframework.stereotype.Service; | 21 | import org.springframework.stereotype.Service; |
22 | -import org.thingsboard.server.dao.util.PsqlDao; | 22 | +import org.thingsboard.server.dao.edge.EdgeService; |
23 | +import org.thingsboard.server.queue.discovery.PartitionService; | ||
24 | +import org.thingsboard.server.queue.util.TbCoreComponent; | ||
23 | import org.thingsboard.server.service.ttl.AbstractCleanUpService; | 25 | import org.thingsboard.server.service.ttl.AbstractCleanUpService; |
24 | 26 | ||
25 | -import java.sql.Connection; | ||
26 | -import java.sql.DriverManager; | ||
27 | -import java.sql.SQLException; | ||
28 | - | ||
29 | -@PsqlDao | 27 | +@TbCoreComponent |
30 | @Slf4j | 28 | @Slf4j |
31 | @Service | 29 | @Service |
32 | public class EdgeEventsCleanUpService extends AbstractCleanUpService { | 30 | public class EdgeEventsCleanUpService extends AbstractCleanUpService { |
@@ -37,20 +35,18 @@ public class EdgeEventsCleanUpService extends AbstractCleanUpService { | @@ -37,20 +35,18 @@ public class EdgeEventsCleanUpService extends AbstractCleanUpService { | ||
37 | @Value("${sql.ttl.edge_events.enabled}") | 35 | @Value("${sql.ttl.edge_events.enabled}") |
38 | private boolean ttlTaskExecutionEnabled; | 36 | private boolean ttlTaskExecutionEnabled; |
39 | 37 | ||
38 | + private final EdgeService edgeService; | ||
39 | + | ||
40 | + public EdgeEventsCleanUpService(PartitionService partitionService, EdgeService edgeService) { | ||
41 | + super(partitionService); | ||
42 | + this.edgeService = edgeService; | ||
43 | + } | ||
44 | + | ||
40 | @Scheduled(initialDelayString = "${sql.ttl.edge_events.execution_interval_ms}", fixedDelayString = "${sql.ttl.edge_events.execution_interval_ms}") | 45 | @Scheduled(initialDelayString = "${sql.ttl.edge_events.execution_interval_ms}", fixedDelayString = "${sql.ttl.edge_events.execution_interval_ms}") |
41 | public void cleanUp() { | 46 | public void cleanUp() { |
42 | - if (ttlTaskExecutionEnabled) { | ||
43 | - try (Connection conn = getConnection()) { | ||
44 | - doCleanUp(conn); | ||
45 | - } catch (SQLException e) { | ||
46 | - log.error("SQLException occurred during TTL task execution ", e); | ||
47 | - } | 47 | + if (ttlTaskExecutionEnabled && isSystemTenantPartitionMine()) { |
48 | + edgeService.cleanupEvents(ttl); | ||
48 | } | 49 | } |
49 | } | 50 | } |
50 | 51 | ||
51 | - @Override | ||
52 | - protected void doCleanUp(Connection connection) throws SQLException { | ||
53 | - long totalEdgeEventsRemoved = executeQuery(connection, "call cleanup_edge_events_by_ttl(" + ttl + ", 0);"); | ||
54 | - log.info("Total edge events removed by TTL: [{}]", totalEdgeEventsRemoved); | ||
55 | - } | ||
56 | } | 52 | } |
application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java
renamed from
application/src/main/java/org/thingsboard/server/service/ttl/events/EventsCleanUpService.java
@@ -13,20 +13,18 @@ | @@ -13,20 +13,18 @@ | ||
13 | * See the License for the specific language governing permissions and | 13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. | 14 | * limitations under the License. |
15 | */ | 15 | */ |
16 | -package org.thingsboard.server.service.ttl.events; | 16 | +package org.thingsboard.server.service.ttl; |
17 | 17 | ||
18 | import lombok.extern.slf4j.Slf4j; | 18 | import lombok.extern.slf4j.Slf4j; |
19 | import org.springframework.beans.factory.annotation.Value; | 19 | import org.springframework.beans.factory.annotation.Value; |
20 | import org.springframework.scheduling.annotation.Scheduled; | 20 | import org.springframework.scheduling.annotation.Scheduled; |
21 | import org.springframework.stereotype.Service; | 21 | import org.springframework.stereotype.Service; |
22 | -import org.thingsboard.server.dao.util.PsqlDao; | 22 | +import org.thingsboard.server.dao.event.EventService; |
23 | +import org.thingsboard.server.queue.discovery.PartitionService; | ||
24 | +import org.thingsboard.server.queue.util.TbCoreComponent; | ||
23 | import org.thingsboard.server.service.ttl.AbstractCleanUpService; | 25 | import org.thingsboard.server.service.ttl.AbstractCleanUpService; |
24 | 26 | ||
25 | -import java.sql.Connection; | ||
26 | -import java.sql.DriverManager; | ||
27 | -import java.sql.SQLException; | ||
28 | - | ||
29 | -@PsqlDao | 27 | +@TbCoreComponent |
30 | @Slf4j | 28 | @Slf4j |
31 | @Service | 29 | @Service |
32 | public class EventsCleanUpService extends AbstractCleanUpService { | 30 | public class EventsCleanUpService extends AbstractCleanUpService { |
@@ -40,20 +38,18 @@ public class EventsCleanUpService extends AbstractCleanUpService { | @@ -40,20 +38,18 @@ public class EventsCleanUpService extends AbstractCleanUpService { | ||
40 | @Value("${sql.ttl.events.enabled}") | 38 | @Value("${sql.ttl.events.enabled}") |
41 | private boolean ttlTaskExecutionEnabled; | 39 | private boolean ttlTaskExecutionEnabled; |
42 | 40 | ||
41 | + private final EventService eventService; | ||
42 | + | ||
43 | + public EventsCleanUpService(PartitionService partitionService, EventService eventService) { | ||
44 | + super(partitionService); | ||
45 | + this.eventService = eventService; | ||
46 | + } | ||
47 | + | ||
43 | @Scheduled(initialDelayString = "${sql.ttl.events.execution_interval_ms}", fixedDelayString = "${sql.ttl.events.execution_interval_ms}") | 48 | @Scheduled(initialDelayString = "${sql.ttl.events.execution_interval_ms}", fixedDelayString = "${sql.ttl.events.execution_interval_ms}") |
44 | public void cleanUp() { | 49 | public void cleanUp() { |
45 | - if (ttlTaskExecutionEnabled) { | ||
46 | - try (Connection conn = getConnection()) { | ||
47 | - doCleanUp(conn); | ||
48 | - } catch (SQLException e) { | ||
49 | - log.error("SQLException occurred during TTL task execution ", e); | ||
50 | - } | 50 | + if (ttlTaskExecutionEnabled && isSystemTenantPartitionMine()) { |
51 | + eventService.cleanupEvents(ttl, debugTtl); | ||
51 | } | 52 | } |
52 | } | 53 | } |
53 | 54 | ||
54 | - @Override | ||
55 | - protected void doCleanUp(Connection connection) throws SQLException { | ||
56 | - long totalEventsRemoved = executeQuery(connection, "call cleanup_events_by_ttl(" + ttl + ", " + debugTtl + ", 0);"); | ||
57 | - log.info("Total events removed by TTL: [{}]", totalEventsRemoved); | ||
58 | - } | ||
59 | } | 55 | } |
application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java
renamed from
application/src/main/java/org/thingsboard/server/service/ttl/timeseries/AbstractTimeseriesCleanUpService.java
@@ -13,19 +13,21 @@ | @@ -13,19 +13,21 @@ | ||
13 | * See the License for the specific language governing permissions and | 13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. | 14 | * limitations under the License. |
15 | */ | 15 | */ |
16 | -package org.thingsboard.server.service.ttl.timeseries; | 16 | +package org.thingsboard.server.service.ttl; |
17 | 17 | ||
18 | import lombok.extern.slf4j.Slf4j; | 18 | import lombok.extern.slf4j.Slf4j; |
19 | import org.springframework.beans.factory.annotation.Value; | 19 | import org.springframework.beans.factory.annotation.Value; |
20 | import org.springframework.scheduling.annotation.Scheduled; | 20 | import org.springframework.scheduling.annotation.Scheduled; |
21 | +import org.springframework.stereotype.Service; | ||
22 | +import org.thingsboard.server.dao.timeseries.TimeseriesService; | ||
23 | +import org.thingsboard.server.queue.discovery.PartitionService; | ||
24 | +import org.thingsboard.server.queue.util.TbCoreComponent; | ||
21 | import org.thingsboard.server.service.ttl.AbstractCleanUpService; | 25 | import org.thingsboard.server.service.ttl.AbstractCleanUpService; |
22 | 26 | ||
23 | -import java.sql.Connection; | ||
24 | -import java.sql.DriverManager; | ||
25 | -import java.sql.SQLException; | ||
26 | - | 27 | +@TbCoreComponent |
27 | @Slf4j | 28 | @Slf4j |
28 | -public abstract class AbstractTimeseriesCleanUpService extends AbstractCleanUpService { | 29 | +@Service |
30 | +public class TimeseriesCleanUpService extends AbstractCleanUpService { | ||
29 | 31 | ||
30 | @Value("${sql.ttl.ts.ts_key_value_ttl}") | 32 | @Value("${sql.ttl.ts.ts_key_value_ttl}") |
31 | protected long systemTtl; | 33 | protected long systemTtl; |
@@ -33,14 +35,17 @@ public abstract class AbstractTimeseriesCleanUpService extends AbstractCleanUpSe | @@ -33,14 +35,17 @@ public abstract class AbstractTimeseriesCleanUpService extends AbstractCleanUpSe | ||
33 | @Value("${sql.ttl.ts.enabled}") | 35 | @Value("${sql.ttl.ts.enabled}") |
34 | private boolean ttlTaskExecutionEnabled; | 36 | private boolean ttlTaskExecutionEnabled; |
35 | 37 | ||
38 | + private final TimeseriesService timeseriesService; | ||
39 | + | ||
40 | + public TimeseriesCleanUpService(PartitionService partitionService, TimeseriesService timeseriesService) { | ||
41 | + super(partitionService); | ||
42 | + this.timeseriesService = timeseriesService; | ||
43 | + } | ||
44 | + | ||
36 | @Scheduled(initialDelayString = "${sql.ttl.ts.execution_interval_ms}", fixedDelayString = "${sql.ttl.ts.execution_interval_ms}") | 45 | @Scheduled(initialDelayString = "${sql.ttl.ts.execution_interval_ms}", fixedDelayString = "${sql.ttl.ts.execution_interval_ms}") |
37 | public void cleanUp() { | 46 | public void cleanUp() { |
38 | - if (ttlTaskExecutionEnabled) { | ||
39 | - try (Connection conn = getConnection()) { | ||
40 | - doCleanUp(conn); | ||
41 | - } catch (SQLException e) { | ||
42 | - log.error("SQLException occurred during TTL task execution ", e); | ||
43 | - } | 47 | + if (ttlTaskExecutionEnabled && isSystemTenantPartitionMine()) { |
48 | + timeseriesService.cleanup(systemTtl); | ||
44 | } | 49 | } |
45 | } | 50 | } |
46 | 51 |
application/src/main/java/org/thingsboard/server/service/ttl/timeseries/PsqlTimeseriesCleanUpService.java
deleted
100644 → 0
1 | -/** | ||
2 | - * Copyright © 2016-2021 The Thingsboard Authors | ||
3 | - * | ||
4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | - * you may not use this file except in compliance with the License. | ||
6 | - * You may obtain a copy of the License at | ||
7 | - * | ||
8 | - * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | - * | ||
10 | - * Unless required by applicable law or agreed to in writing, software | ||
11 | - * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | - * See the License for the specific language governing permissions and | ||
14 | - * limitations under the License. | ||
15 | - */ | ||
16 | -package org.thingsboard.server.service.ttl.timeseries; | ||
17 | - | ||
18 | -import lombok.extern.slf4j.Slf4j; | ||
19 | -import org.springframework.beans.factory.annotation.Value; | ||
20 | -import org.springframework.stereotype.Service; | ||
21 | -import org.thingsboard.server.dao.model.ModelConstants; | ||
22 | -import org.thingsboard.server.dao.util.PsqlDao; | ||
23 | -import org.thingsboard.server.dao.util.SqlTsDao; | ||
24 | - | ||
25 | -import java.sql.Connection; | ||
26 | -import java.sql.SQLException; | ||
27 | - | ||
28 | -@SqlTsDao | ||
29 | -@PsqlDao | ||
30 | -@Service | ||
31 | -@Slf4j | ||
32 | -public class PsqlTimeseriesCleanUpService extends AbstractTimeseriesCleanUpService { | ||
33 | - | ||
34 | - @Value("${sql.postgres.ts_key_value_partitioning}") | ||
35 | - private String partitionType; | ||
36 | - | ||
37 | - @Override | ||
38 | - protected void doCleanUp(Connection connection) throws SQLException { | ||
39 | - long totalPartitionsRemoved = executeQuery(connection, "call drop_partitions_by_max_ttl('" + partitionType + "'," + systemTtl + ", 0);"); | ||
40 | - log.info("Total partitions removed by TTL: [{}]", totalPartitionsRemoved); | ||
41 | - long totalEntitiesTelemetryRemoved = executeQuery(connection, "call cleanup_timeseries_by_ttl('" + ModelConstants.NULL_UUID + "'," + systemTtl + ", 0);"); | ||
42 | - log.info("Total telemetry removed stats by TTL for entities: [{}]", totalEntitiesTelemetryRemoved); | ||
43 | - } | ||
44 | -} |
@@ -17,24 +17,47 @@ package org.thingsboard.server.transport.lwm2m; | @@ -17,24 +17,47 @@ package org.thingsboard.server.transport.lwm2m; | ||
17 | 17 | ||
18 | import com.fasterxml.jackson.core.type.TypeReference; | 18 | import com.fasterxml.jackson.core.type.TypeReference; |
19 | import org.apache.commons.io.IOUtils; | 19 | import org.apache.commons.io.IOUtils; |
20 | +import org.eclipse.californium.core.network.config.NetworkConfig; | ||
21 | +import org.eclipse.leshan.client.object.Security; | ||
20 | import org.eclipse.leshan.core.util.Hex; | 22 | import org.eclipse.leshan.core.util.Hex; |
23 | +import org.jetbrains.annotations.NotNull; | ||
21 | import org.junit.After; | 24 | import org.junit.After; |
22 | import org.junit.Assert; | 25 | import org.junit.Assert; |
23 | import org.junit.Before; | 26 | import org.junit.Before; |
27 | +import org.springframework.mock.web.MockMultipartFile; | ||
28 | +import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; | ||
29 | +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; | ||
24 | import org.thingsboard.common.util.JacksonUtil; | 30 | import org.thingsboard.common.util.JacksonUtil; |
31 | +import org.thingsboard.server.common.data.Device; | ||
25 | import org.thingsboard.server.common.data.DeviceProfile; | 32 | import org.thingsboard.server.common.data.DeviceProfile; |
26 | import org.thingsboard.server.common.data.DeviceProfileProvisionType; | 33 | import org.thingsboard.server.common.data.DeviceProfileProvisionType; |
27 | import org.thingsboard.server.common.data.DeviceProfileType; | 34 | import org.thingsboard.server.common.data.DeviceProfileType; |
28 | import org.thingsboard.server.common.data.DeviceTransportType; | 35 | import org.thingsboard.server.common.data.DeviceTransportType; |
36 | +import org.thingsboard.server.common.data.OtaPackageInfo; | ||
29 | import org.thingsboard.server.common.data.ResourceType; | 37 | import org.thingsboard.server.common.data.ResourceType; |
30 | import org.thingsboard.server.common.data.TbResource; | 38 | import org.thingsboard.server.common.data.TbResource; |
39 | +import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MClientCredentials; | ||
31 | import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; | 40 | import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileConfiguration; |
32 | import org.thingsboard.server.common.data.device.profile.DeviceProfileData; | 41 | import org.thingsboard.server.common.data.device.profile.DeviceProfileData; |
33 | import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; | 42 | import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; |
34 | import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; | 43 | import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; |
44 | +import org.thingsboard.server.common.data.query.EntityData; | ||
45 | +import org.thingsboard.server.common.data.query.EntityDataPageLink; | ||
46 | +import org.thingsboard.server.common.data.query.EntityDataQuery; | ||
47 | +import org.thingsboard.server.common.data.query.EntityKey; | ||
48 | +import org.thingsboard.server.common.data.query.EntityKeyType; | ||
49 | +import org.thingsboard.server.common.data.query.SingleEntityFilter; | ||
50 | +import org.thingsboard.server.common.data.security.DeviceCredentials; | ||
51 | +import org.thingsboard.server.common.data.security.DeviceCredentialsType; | ||
35 | import org.thingsboard.server.controller.AbstractWebsocketTest; | 52 | import org.thingsboard.server.controller.AbstractWebsocketTest; |
36 | import org.thingsboard.server.controller.TbTestWebSocketClient; | 53 | import org.thingsboard.server.controller.TbTestWebSocketClient; |
37 | import org.thingsboard.server.dao.service.DaoSqlTest; | 54 | import org.thingsboard.server.dao.service.DaoSqlTest; |
55 | +import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; | ||
56 | +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; | ||
57 | +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; | ||
58 | +import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; | ||
59 | +import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; | ||
60 | +import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; | ||
38 | 61 | ||
39 | import java.io.IOException; | 62 | import java.io.IOException; |
40 | import java.io.InputStream; | 63 | import java.io.InputStream; |
@@ -54,9 +77,15 @@ import java.security.spec.ECPrivateKeySpec; | @@ -54,9 +77,15 @@ import java.security.spec.ECPrivateKeySpec; | ||
54 | import java.security.spec.ECPublicKeySpec; | 77 | import java.security.spec.ECPublicKeySpec; |
55 | import java.security.spec.KeySpec; | 78 | import java.security.spec.KeySpec; |
56 | import java.util.Base64; | 79 | import java.util.Base64; |
80 | +import java.util.Collections; | ||
81 | +import java.util.List; | ||
57 | import java.util.concurrent.Executors; | 82 | import java.util.concurrent.Executors; |
58 | import java.util.concurrent.ScheduledExecutorService; | 83 | import java.util.concurrent.ScheduledExecutorService; |
59 | 84 | ||
85 | +import static org.eclipse.leshan.client.object.Security.noSec; | ||
86 | +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||
87 | +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; | ||
88 | + | ||
60 | @DaoSqlTest | 89 | @DaoSqlTest |
61 | public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { | 90 | public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { |
62 | 91 | ||
@@ -139,6 +168,15 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { | @@ -139,6 +168,15 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { | ||
139 | // certificates trustedby the server (should contain rootCA) | 168 | // certificates trustedby the server (should contain rootCA) |
140 | protected final Certificate[] trustedCertificates = new Certificate[1]; | 169 | protected final Certificate[] trustedCertificates = new Certificate[1]; |
141 | 170 | ||
171 | + protected static final int SECURE_PORT = 5686; | ||
172 | + protected static final NetworkConfig SECURE_COAP_CONFIG = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(SECURE_PORT)); | ||
173 | + protected static final String ENDPOINT = "deviceAEndpoint"; | ||
174 | + protected static final String SECURE_URI = "coaps://localhost:" + SECURE_PORT; | ||
175 | + | ||
176 | + protected static final int PORT = 5685; | ||
177 | + protected static final Security SECURITY = noSec("coap://localhost:" + PORT, 123); | ||
178 | + protected static final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT)); | ||
179 | + | ||
142 | public AbstractLwM2MIntegrationTest() { | 180 | public AbstractLwM2MIntegrationTest() { |
143 | // create client credentials | 181 | // create client credentials |
144 | try { | 182 | try { |
@@ -262,10 +300,95 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { | @@ -262,10 +300,95 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { | ||
262 | Assert.assertNotNull(deviceProfile); | 300 | Assert.assertNotNull(deviceProfile); |
263 | } | 301 | } |
264 | 302 | ||
303 | + @NotNull | ||
304 | + protected Device createDevice(LwM2MClientCredentials clientCredentials) throws Exception { | ||
305 | + Device device = new Device(); | ||
306 | + device.setName("Device A"); | ||
307 | + device.setDeviceProfileId(deviceProfile.getId()); | ||
308 | + device.setTenantId(tenantId); | ||
309 | + device = doPost("/api/device", device, Device.class); | ||
310 | + Assert.assertNotNull(device); | ||
311 | + | ||
312 | + DeviceCredentials deviceCredentials = | ||
313 | + doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class); | ||
314 | + Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId()); | ||
315 | + deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); | ||
316 | + | ||
317 | + LwM2MCredentials credentials = new LwM2MCredentials(); | ||
318 | + | ||
319 | + credentials.setClient(clientCredentials); | ||
320 | + | ||
321 | + deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials)); | ||
322 | + doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); | ||
323 | + return device; | ||
324 | + } | ||
325 | + | ||
326 | + | ||
327 | + protected OtaPackageInfo createFirmware() throws Exception { | ||
328 | + String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; | ||
329 | + | ||
330 | + OtaPackageInfo firmwareInfo = new OtaPackageInfo(); | ||
331 | + firmwareInfo.setDeviceProfileId(deviceProfile.getId()); | ||
332 | + firmwareInfo.setType(FIRMWARE); | ||
333 | + firmwareInfo.setTitle("My firmware"); | ||
334 | + firmwareInfo.setVersion("v1.0"); | ||
335 | + | ||
336 | + OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); | ||
337 | + | ||
338 | + MockMultipartFile testData = new MockMultipartFile("file", "filename.txt", "text/plain", new byte[]{1}); | ||
339 | + | ||
340 | + return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256"); | ||
341 | + } | ||
342 | + | ||
343 | + protected OtaPackageInfo savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception { | ||
344 | + MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params); | ||
345 | + postRequest.file(content); | ||
346 | + setJwtToken(postRequest); | ||
347 | + return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackageInfo.class); | ||
348 | + } | ||
349 | + | ||
265 | @After | 350 | @After |
266 | public void after() { | 351 | public void after() { |
267 | executor.shutdownNow(); | 352 | executor.shutdownNow(); |
268 | wsClient.close(); | 353 | wsClient.close(); |
269 | } | 354 | } |
270 | 355 | ||
356 | + public void basicTestConnectionObserveTelemetry(Security security, | ||
357 | + LwM2MClientCredentials credentials, | ||
358 | + NetworkConfig coapConfig, | ||
359 | + String endpoint) throws Exception { | ||
360 | + createDeviceProfile(TRANSPORT_CONFIGURATION); | ||
361 | + Device device = createDevice(credentials); | ||
362 | + | ||
363 | + SingleEntityFilter sef = new SingleEntityFilter(); | ||
364 | + sef.setSingleEntity(device.getId()); | ||
365 | + LatestValueCmd latestCmd = new LatestValueCmd(); | ||
366 | + latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel"))); | ||
367 | + EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null), | ||
368 | + Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); | ||
369 | + | ||
370 | + EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); | ||
371 | + TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); | ||
372 | + wrapper.setEntityDataCmds(Collections.singletonList(cmd)); | ||
373 | + | ||
374 | + wsClient.send(mapper.writeValueAsString(wrapper)); | ||
375 | + wsClient.waitForReply(); | ||
376 | + | ||
377 | + wsClient.registerWaitForUpdate(); | ||
378 | + LwM2MTestClient client = new LwM2MTestClient(executor, endpoint); | ||
379 | + | ||
380 | + client.init(security, coapConfig); | ||
381 | + String msg = wsClient.waitForUpdate(); | ||
382 | + | ||
383 | + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); | ||
384 | + Assert.assertEquals(1, update.getCmdId()); | ||
385 | + List<EntityData> eData = update.getUpdate(); | ||
386 | + Assert.assertNotNull(eData); | ||
387 | + Assert.assertEquals(1, eData.size()); | ||
388 | + Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); | ||
389 | + Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); | ||
390 | + var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel"); | ||
391 | + Assert.assertEquals(42, Long.parseLong(tsValue.getValue())); | ||
392 | + client.destroy(); | ||
393 | + } | ||
271 | } | 394 | } |
@@ -15,14 +15,8 @@ | @@ -15,14 +15,8 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.transport.lwm2m; | 16 | package org.thingsboard.server.transport.lwm2m; |
17 | 17 | ||
18 | -import org.eclipse.californium.core.network.config.NetworkConfig; | ||
19 | -import org.eclipse.leshan.client.object.Security; | ||
20 | import org.junit.Assert; | 18 | import org.junit.Assert; |
21 | import org.junit.Test; | 19 | import org.junit.Test; |
22 | -import org.springframework.mock.web.MockMultipartFile; | ||
23 | -import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; | ||
24 | -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; | ||
25 | -import org.thingsboard.common.util.JacksonUtil; | ||
26 | import org.thingsboard.server.common.data.Device; | 20 | import org.thingsboard.server.common.data.Device; |
27 | import org.thingsboard.server.common.data.OtaPackageInfo; | 21 | import org.thingsboard.server.common.data.OtaPackageInfo; |
28 | import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials; | 22 | import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials; |
@@ -32,116 +26,30 @@ import org.thingsboard.server.common.data.query.EntityDataQuery; | @@ -32,116 +26,30 @@ import org.thingsboard.server.common.data.query.EntityDataQuery; | ||
32 | import org.thingsboard.server.common.data.query.EntityKey; | 26 | import org.thingsboard.server.common.data.query.EntityKey; |
33 | import org.thingsboard.server.common.data.query.EntityKeyType; | 27 | import org.thingsboard.server.common.data.query.EntityKeyType; |
34 | import org.thingsboard.server.common.data.query.SingleEntityFilter; | 28 | import org.thingsboard.server.common.data.query.SingleEntityFilter; |
35 | -import org.thingsboard.server.common.data.security.DeviceCredentials; | ||
36 | -import org.thingsboard.server.common.data.security.DeviceCredentialsType; | ||
37 | import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; | 29 | import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; |
38 | import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; | 30 | import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; |
39 | import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; | 31 | import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; |
40 | import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; | 32 | import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; |
41 | import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; | 33 | import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; |
42 | -import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; | ||
43 | 34 | ||
44 | import java.util.Collections; | 35 | import java.util.Collections; |
45 | import java.util.List; | 36 | import java.util.List; |
46 | 37 | ||
47 | -import static org.eclipse.leshan.client.object.Security.noSec; | ||
48 | -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||
49 | -import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; | ||
50 | - | ||
51 | public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { | 38 | public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { |
52 | 39 | ||
53 | - private final int PORT = 5685; | ||
54 | - private final Security SECURITY = noSec("coap://localhost:" + PORT, 123); | ||
55 | - private final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT)); | ||
56 | - private final String ENDPOINT = "noSecEndpoint"; | ||
57 | - | ||
58 | - private Device createDevice() throws Exception { | ||
59 | - Device device = new Device(); | ||
60 | - device.setName("Device A"); | ||
61 | - device.setDeviceProfileId(deviceProfile.getId()); | ||
62 | - device.setTenantId(tenantId); | ||
63 | - device = doPost("/api/device", device, Device.class); | ||
64 | - Assert.assertNotNull(device); | ||
65 | - | ||
66 | - DeviceCredentials deviceCredentials = | ||
67 | - doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class); | ||
68 | - Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId()); | ||
69 | - deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); | ||
70 | - | ||
71 | - LwM2MCredentials noSecCredentials = new LwM2MCredentials(); | ||
72 | - NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); | ||
73 | - clientCredentials.setEndpoint(ENDPOINT); | ||
74 | - noSecCredentials.setClient(clientCredentials); | ||
75 | - deviceCredentials.setCredentialsValue(JacksonUtil.toString(noSecCredentials)); | ||
76 | - doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); | ||
77 | - return device; | ||
78 | - } | ||
79 | - | ||
80 | - private OtaPackageInfo createFirmware() throws Exception { | ||
81 | - String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; | ||
82 | - | ||
83 | - OtaPackageInfo firmwareInfo = new OtaPackageInfo(); | ||
84 | - firmwareInfo.setDeviceProfileId(deviceProfile.getId()); | ||
85 | - firmwareInfo.setType(FIRMWARE); | ||
86 | - firmwareInfo.setTitle("My firmware"); | ||
87 | - firmwareInfo.setVersion("v1.0"); | ||
88 | - | ||
89 | - OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); | ||
90 | - | ||
91 | - MockMultipartFile testData = new MockMultipartFile("file", "filename.txt", "text/plain", new byte[]{1}); | ||
92 | - | ||
93 | - return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256"); | ||
94 | - } | ||
95 | - | ||
96 | - protected OtaPackageInfo savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception { | ||
97 | - MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params); | ||
98 | - postRequest.file(content); | ||
99 | - setJwtToken(postRequest); | ||
100 | - return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackageInfo.class); | ||
101 | - } | ||
102 | - | ||
103 | @Test | 40 | @Test |
104 | public void testConnectAndObserveTelemetry() throws Exception { | 41 | public void testConnectAndObserveTelemetry() throws Exception { |
105 | - createDeviceProfile(TRANSPORT_CONFIGURATION); | ||
106 | - | ||
107 | - Device device = createDevice(); | ||
108 | - | ||
109 | - SingleEntityFilter sef = new SingleEntityFilter(); | ||
110 | - sef.setSingleEntity(device.getId()); | ||
111 | - LatestValueCmd latestCmd = new LatestValueCmd(); | ||
112 | - latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel"))); | ||
113 | - EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null), | ||
114 | - Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); | ||
115 | - | ||
116 | - EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); | ||
117 | - TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); | ||
118 | - wrapper.setEntityDataCmds(Collections.singletonList(cmd)); | ||
119 | - | ||
120 | - wsClient.send(mapper.writeValueAsString(wrapper)); | ||
121 | - wsClient.waitForReply(); | ||
122 | - | ||
123 | - wsClient.registerWaitForUpdate(); | ||
124 | - LwM2MTestClient client = new LwM2MTestClient(executor, ENDPOINT); | ||
125 | - client.init(SECURITY, COAP_CONFIG); | ||
126 | - String msg = wsClient.waitForUpdate(); | ||
127 | - | ||
128 | - EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); | ||
129 | - Assert.assertEquals(1, update.getCmdId()); | ||
130 | - List<EntityData> eData = update.getUpdate(); | ||
131 | - Assert.assertNotNull(eData); | ||
132 | - Assert.assertEquals(1, eData.size()); | ||
133 | - Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); | ||
134 | - Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); | ||
135 | - var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel"); | ||
136 | - Assert.assertEquals(42, Long.parseLong(tsValue.getValue())); | ||
137 | - client.destroy(); | 42 | + NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); |
43 | + clientCredentials.setEndpoint(ENDPOINT); | ||
44 | + super.basicTestConnectionObserveTelemetry(SECURITY, clientCredentials, COAP_CONFIG, ENDPOINT); | ||
138 | } | 45 | } |
139 | 46 | ||
140 | @Test | 47 | @Test |
141 | public void testFirmwareUpdateWithClientWithoutFirmwareInfo() throws Exception { | 48 | public void testFirmwareUpdateWithClientWithoutFirmwareInfo() throws Exception { |
142 | createDeviceProfile(TRANSPORT_CONFIGURATION); | 49 | createDeviceProfile(TRANSPORT_CONFIGURATION); |
143 | - | ||
144 | - Device device = createDevice(); | 50 | + NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); |
51 | + clientCredentials.setEndpoint(ENDPOINT); | ||
52 | + Device device = createDevice(clientCredentials); | ||
145 | 53 | ||
146 | OtaPackageInfo firmware = createFirmware(); | 54 | OtaPackageInfo firmware = createFirmware(); |
147 | 55 |
application/src/test/java/org/thingsboard/server/transport/lwm2m/PskLwm2mIntegrationTest.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2021 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.transport.lwm2m; | ||
17 | + | ||
18 | +import org.eclipse.leshan.client.object.Security; | ||
19 | +import org.eclipse.leshan.core.util.Hex; | ||
20 | +import org.junit.Test; | ||
21 | +import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials; | ||
22 | + | ||
23 | +import java.nio.charset.StandardCharsets; | ||
24 | + | ||
25 | +import static org.eclipse.leshan.client.object.Security.psk; | ||
26 | + | ||
27 | +public class PskLwm2mIntegrationTest extends AbstractLwM2MIntegrationTest { | ||
28 | + | ||
29 | + @Test | ||
30 | + public void testConnectWithPSKAndObserveTelemetry() throws Exception { | ||
31 | + String pskIdentity = "SOME_PSK_ID"; | ||
32 | + String pskKey = "73656372657450534b"; | ||
33 | + PSKClientCredentials clientCredentials = new PSKClientCredentials(); | ||
34 | + clientCredentials.setEndpoint(ENDPOINT); | ||
35 | + clientCredentials.setKey(pskKey); | ||
36 | + clientCredentials.setIdentity(pskIdentity); | ||
37 | + Security security = psk(SECURE_URI, | ||
38 | + 123, | ||
39 | + pskIdentity.getBytes(StandardCharsets.UTF_8), | ||
40 | + Hex.decodeHex(pskKey.toCharArray())); | ||
41 | + super.basicTestConnectionObserveTelemetry(security, clientCredentials, SECURE_COAP_CONFIG, ENDPOINT); | ||
42 | + } | ||
43 | +} |
application/src/test/java/org/thingsboard/server/transport/lwm2m/RpkLwM2MIntegrationTest.java
0 → 100644
1 | +/** | ||
2 | + * Copyright © 2016-2021 The Thingsboard Authors | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | +package org.thingsboard.server.transport.lwm2m; | ||
17 | + | ||
18 | +import org.eclipse.leshan.client.object.Security; | ||
19 | +import org.eclipse.leshan.core.util.Hex; | ||
20 | +import org.junit.Test; | ||
21 | +import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredentials; | ||
22 | + | ||
23 | +import static org.eclipse.leshan.client.object.Security.rpk; | ||
24 | + | ||
25 | +public class RpkLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { | ||
26 | + @Test | ||
27 | + public void testConnectWithRPKAndObserveTelemetry() throws Exception { | ||
28 | + RPKClientCredentials rpkClientCredentials = new RPKClientCredentials(); | ||
29 | + rpkClientCredentials.setEndpoint(ENDPOINT); | ||
30 | + rpkClientCredentials.setKey(Hex.encodeHexString(clientPublicKey.getEncoded())); | ||
31 | + Security security = rpk(SECURE_URI, | ||
32 | + 123, | ||
33 | + clientPublicKey.getEncoded(), | ||
34 | + clientPrivateKey.getEncoded(), | ||
35 | + serverX509Cert.getPublicKey().getEncoded()); | ||
36 | + super.basicTestConnectionObserveTelemetry(security, rpkClientCredentials, SECURE_COAP_CONFIG, ENDPOINT); | ||
37 | + } | ||
38 | + | ||
39 | +} |
@@ -15,147 +15,38 @@ | @@ -15,147 +15,38 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.transport.lwm2m; | 16 | package org.thingsboard.server.transport.lwm2m; |
17 | 17 | ||
18 | -import org.eclipse.californium.core.network.config.NetworkConfig; | ||
19 | import org.eclipse.leshan.client.object.Security; | 18 | import org.eclipse.leshan.client.object.Security; |
20 | -import org.jetbrains.annotations.NotNull; | ||
21 | -import org.junit.Assert; | ||
22 | -import org.junit.Ignore; | ||
23 | import org.junit.Test; | 19 | import org.junit.Test; |
24 | -import org.thingsboard.common.util.JacksonUtil; | ||
25 | -import org.thingsboard.server.common.data.Device; | ||
26 | import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials; | 20 | import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials; |
27 | -import org.thingsboard.server.common.data.query.EntityData; | ||
28 | -import org.thingsboard.server.common.data.query.EntityDataPageLink; | ||
29 | -import org.thingsboard.server.common.data.query.EntityDataQuery; | ||
30 | -import org.thingsboard.server.common.data.query.EntityKey; | ||
31 | -import org.thingsboard.server.common.data.query.EntityKeyType; | ||
32 | -import org.thingsboard.server.common.data.query.SingleEntityFilter; | ||
33 | -import org.thingsboard.server.common.data.security.DeviceCredentials; | ||
34 | -import org.thingsboard.server.common.data.security.DeviceCredentialsType; | ||
35 | import org.thingsboard.server.common.transport.util.SslUtil; | 21 | import org.thingsboard.server.common.transport.util.SslUtil; |
36 | -import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; | ||
37 | -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; | ||
38 | -import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; | ||
39 | -import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; | ||
40 | -import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; | ||
41 | -import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; | ||
42 | - | ||
43 | -import java.util.Collections; | ||
44 | -import java.util.List; | ||
45 | 22 | ||
46 | import static org.eclipse.leshan.client.object.Security.x509; | 23 | import static org.eclipse.leshan.client.object.Security.x509; |
47 | -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; | ||
48 | 24 | ||
49 | public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { | 25 | public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { |
50 | 26 | ||
51 | - private final int port = 5686; | ||
52 | - private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(port)); | ||
53 | - private final String endpoint = "deviceAEndpoint"; | ||
54 | - private final String serverUri = "coaps://localhost:" + port; | ||
55 | - | ||
56 | - private Device createDevice(X509ClientCredentials clientCredentials) throws Exception { | ||
57 | - Device device = new Device(); | ||
58 | - device.setName("Device A"); | ||
59 | - device.setDeviceProfileId(deviceProfile.getId()); | ||
60 | - device.setTenantId(tenantId); | ||
61 | - device = doPost("/api/device", device, Device.class); | ||
62 | - Assert.assertNotNull(device); | ||
63 | - | ||
64 | - DeviceCredentials deviceCredentials = | ||
65 | - doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class); | ||
66 | - Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId()); | ||
67 | - deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); | ||
68 | - | ||
69 | - LwM2MCredentials credentials = new LwM2MCredentials(); | ||
70 | - | ||
71 | - credentials.setClient(clientCredentials); | ||
72 | - | ||
73 | - deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials)); | ||
74 | - doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); | ||
75 | - return device; | ||
76 | - } | ||
77 | - | ||
78 | - //TODO: use different endpoints to isolate tests. | ||
79 | - @Ignore() | ||
80 | @Test | 27 | @Test |
81 | public void testConnectAndObserveTelemetry() throws Exception { | 28 | public void testConnectAndObserveTelemetry() throws Exception { |
82 | - createDeviceProfile(TRANSPORT_CONFIGURATION); | ||
83 | X509ClientCredentials credentials = new X509ClientCredentials(); | 29 | X509ClientCredentials credentials = new X509ClientCredentials(); |
84 | - credentials.setEndpoint(endpoint+1); | ||
85 | - Device device = createDevice(credentials); | ||
86 | - | ||
87 | - SingleEntityFilter sef = new SingleEntityFilter(); | ||
88 | - sef.setSingleEntity(device.getId()); | ||
89 | - LatestValueCmd latestCmd = new LatestValueCmd(); | ||
90 | - latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel"))); | ||
91 | - EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null), | ||
92 | - Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); | ||
93 | - | ||
94 | - EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); | ||
95 | - TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); | ||
96 | - wrapper.setEntityDataCmds(Collections.singletonList(cmd)); | ||
97 | - | ||
98 | - wsClient.send(mapper.writeValueAsString(wrapper)); | ||
99 | - wsClient.waitForReply(); | ||
100 | - | ||
101 | - wsClient.registerWaitForUpdate(); | ||
102 | - LwM2MTestClient client = new LwM2MTestClient(executor, endpoint+1); | ||
103 | - Security security = x509(serverUri, 123, clientX509Cert.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded()); | ||
104 | - client.init(security, coapConfig); | ||
105 | - String msg = wsClient.waitForUpdate(); | ||
106 | - | ||
107 | - EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); | ||
108 | - Assert.assertEquals(1, update.getCmdId()); | ||
109 | - List<EntityData> eData = update.getUpdate(); | ||
110 | - Assert.assertNotNull(eData); | ||
111 | - Assert.assertEquals(1, eData.size()); | ||
112 | - Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); | ||
113 | - Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); | ||
114 | - var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel"); | ||
115 | - Assert.assertEquals(42, Long.parseLong(tsValue.getValue())); | ||
116 | - client.destroy(); | 30 | + credentials.setEndpoint(ENDPOINT); |
31 | + Security security = x509(SECURE_URI, | ||
32 | + 123, | ||
33 | + clientX509Cert.getEncoded(), | ||
34 | + clientPrivateKeyFromCert.getEncoded(), | ||
35 | + serverX509Cert.getEncoded()); | ||
36 | + super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, ENDPOINT); | ||
117 | } | 37 | } |
118 | 38 | ||
119 | @Test | 39 | @Test |
120 | public void testConnectWithCertAndObserveTelemetry() throws Exception { | 40 | public void testConnectWithCertAndObserveTelemetry() throws Exception { |
121 | - createDeviceProfile(TRANSPORT_CONFIGURATION); | ||
122 | X509ClientCredentials credentials = new X509ClientCredentials(); | 41 | X509ClientCredentials credentials = new X509ClientCredentials(); |
123 | - credentials.setEndpoint(endpoint); | 42 | + credentials.setEndpoint(ENDPOINT); |
124 | credentials.setCert(SslUtil.getCertificateString(clientX509CertNotTrusted)); | 43 | credentials.setCert(SslUtil.getCertificateString(clientX509CertNotTrusted)); |
125 | - Device device = createDevice(credentials); | ||
126 | - | ||
127 | - SingleEntityFilter sef = new SingleEntityFilter(); | ||
128 | - sef.setSingleEntity(device.getId()); | ||
129 | - LatestValueCmd latestCmd = new LatestValueCmd(); | ||
130 | - latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel"))); | ||
131 | - EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null), | ||
132 | - Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); | ||
133 | - | ||
134 | - EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); | ||
135 | - TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); | ||
136 | - wrapper.setEntityDataCmds(Collections.singletonList(cmd)); | ||
137 | - | ||
138 | - wsClient.send(mapper.writeValueAsString(wrapper)); | ||
139 | - wsClient.waitForReply(); | ||
140 | - | ||
141 | - wsClient.registerWaitForUpdate(); | ||
142 | - LwM2MTestClient client = new LwM2MTestClient(executor, endpoint); | ||
143 | - | ||
144 | - Security security = x509(serverUri, 123, clientX509CertNotTrusted.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded()); | ||
145 | - | ||
146 | - client.init(security, coapConfig); | ||
147 | - String msg = wsClient.waitForUpdate(); | ||
148 | - | ||
149 | - EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); | ||
150 | - Assert.assertEquals(1, update.getCmdId()); | ||
151 | - List<EntityData> eData = update.getUpdate(); | ||
152 | - Assert.assertNotNull(eData); | ||
153 | - Assert.assertEquals(1, eData.size()); | ||
154 | - Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); | ||
155 | - Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); | ||
156 | - var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel"); | ||
157 | - Assert.assertEquals(42, Long.parseLong(tsValue.getValue())); | ||
158 | - client.destroy(); | 44 | + Security security = x509(SECURE_URI, |
45 | + 123, | ||
46 | + clientX509CertNotTrusted.getEncoded(), | ||
47 | + clientPrivateKeyFromCert.getEncoded(), | ||
48 | + serverX509Cert.getEncoded()); | ||
49 | + super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, ENDPOINT); | ||
159 | } | 50 | } |
160 | 51 | ||
161 | } | 52 | } |
@@ -93,4 +93,6 @@ public interface EdgeService { | @@ -93,4 +93,6 @@ public interface EdgeService { | ||
93 | Object activateInstance(String licenseSecret, String releaseDate); | 93 | Object activateInstance(String licenseSecret, String releaseDate); |
94 | 94 | ||
95 | String findMissingToRelatedRuleChains(TenantId tenantId, EdgeId edgeId); | 95 | String findMissingToRelatedRuleChains(TenantId tenantId, EdgeId edgeId); |
96 | + | ||
97 | + void cleanupEvents(long ttl); | ||
96 | } | 98 | } |
@@ -52,4 +52,6 @@ public interface TimeseriesService { | @@ -52,4 +52,6 @@ public interface TimeseriesService { | ||
52 | List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId); | 52 | List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId); |
53 | 53 | ||
54 | List<String> findAllKeysByEntityIds(TenantId tenantId, List<EntityId> entityIds); | 54 | List<String> findAllKeysByEntityIds(TenantId tenantId, List<EntityId> entityIds); |
55 | + | ||
56 | + void cleanup(long systemTtl); | ||
55 | } | 57 | } |
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentialsWithKey.java
renamed from
common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/HasKey.java
@@ -15,20 +15,25 @@ | @@ -15,20 +15,25 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.common.data.device.credentials.lwm2m; | 16 | package org.thingsboard.server.common.data.device.credentials.lwm2m; |
17 | 17 | ||
18 | +import com.fasterxml.jackson.annotation.JsonIgnore; | ||
19 | +import lombok.Getter; | ||
20 | +import lombok.Setter; | ||
18 | import lombok.SneakyThrows; | 21 | import lombok.SneakyThrows; |
19 | import org.apache.commons.codec.binary.Hex; | 22 | import org.apache.commons.codec.binary.Hex; |
20 | 23 | ||
21 | -public abstract class HasKey extends AbstractLwM2MClientCredentials { | ||
22 | - private byte[] key; | 24 | +public abstract class AbstractLwM2MClientCredentialsWithKey extends AbstractLwM2MClientCredentials { |
25 | + @Getter | ||
26 | + @Setter | ||
27 | + private String key; | ||
28 | + | ||
29 | + private byte[] keyInBytes; | ||
23 | 30 | ||
24 | @SneakyThrows | 31 | @SneakyThrows |
25 | - public void setKey(String key) { | ||
26 | - if (key != null) { | ||
27 | - this.key = Hex.decodeHex(key.toLowerCase().toCharArray()); | 32 | + @JsonIgnore |
33 | + public byte[] getDecodedKey() { | ||
34 | + if (keyInBytes == null) { | ||
35 | + keyInBytes = Hex.decodeHex(key.toLowerCase().toCharArray()); | ||
28 | } | 36 | } |
29 | - } | ||
30 | - | ||
31 | - public byte[] getKey() { | ||
32 | - return key; | 37 | + return keyInBytes; |
33 | } | 38 | } |
34 | } | 39 | } |
@@ -20,7 +20,7 @@ import lombok.Setter; | @@ -20,7 +20,7 @@ import lombok.Setter; | ||
20 | 20 | ||
21 | @Getter | 21 | @Getter |
22 | @Setter | 22 | @Setter |
23 | -public class PSKClientCredentials extends HasKey { | 23 | +public class PSKClientCredentials extends AbstractLwM2MClientCredentialsWithKey { |
24 | private String identity; | 24 | private String identity; |
25 | 25 | ||
26 | @Override | 26 | @Override |
@@ -15,7 +15,7 @@ | @@ -15,7 +15,7 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.common.data.device.credentials.lwm2m; | 16 | package org.thingsboard.server.common.data.device.credentials.lwm2m; |
17 | 17 | ||
18 | -public class RPKClientCredentials extends HasKey { | 18 | +public class RPKClientCredentials extends AbstractLwM2MClientCredentialsWithKey { |
19 | 19 | ||
20 | @Override | 20 | @Override |
21 | public LwM2MSecurityMode getSecurityConfigClientMode() { | 21 | public LwM2MSecurityMode getSecurityConfigClientMode() { |
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
@@ -64,6 +64,7 @@ import java.util.concurrent.ConcurrentHashMap; | @@ -64,6 +64,7 @@ import java.util.concurrent.ConcurrentHashMap; | ||
64 | import java.util.concurrent.ConcurrentMap; | 64 | import java.util.concurrent.ConcurrentMap; |
65 | import java.util.concurrent.TimeUnit; | 65 | import java.util.concurrent.TimeUnit; |
66 | import java.util.concurrent.atomic.AtomicInteger; | 66 | import java.util.concurrent.atomic.AtomicInteger; |
67 | +import java.util.stream.Collectors; | ||
67 | 68 | ||
68 | @Slf4j | 69 | @Slf4j |
69 | public class CoapTransportResource extends AbstractCoapTransportResource { | 70 | public class CoapTransportResource extends AbstractCoapTransportResource { |
@@ -75,9 +76,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | @@ -75,9 +76,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | ||
75 | private static final int REQUEST_ID_POSITION_CERTIFICATE_REQUEST = 4; | 76 | private static final int REQUEST_ID_POSITION_CERTIFICATE_REQUEST = 4; |
76 | private static final String DTLS_SESSION_ID_KEY = "DTLS_SESSION_ID"; | 77 | private static final String DTLS_SESSION_ID_KEY = "DTLS_SESSION_ID"; |
77 | 78 | ||
78 | - private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionInfoMap = new ConcurrentHashMap<>(); | ||
79 | - private final ConcurrentMap<String, AtomicInteger> tokenToObserveNotificationSeqMap = new ConcurrentHashMap<>(); | ||
80 | - private final ConcurrentMap<TransportProtos.SessionInfoProto, ObserveRelation> sessionInfoToObserveRelationMap = new ConcurrentHashMap<>(); | 79 | + private final ConcurrentMap<String, CoapObserveSessionInfo> tokenToCoapSessionInfoMap = new ConcurrentHashMap<>(); |
80 | + private final ConcurrentMap<CoapObserveSessionInfo, ObserveRelation> sessionInfoToObserveRelationMap = new ConcurrentHashMap<>(); | ||
81 | private final Set<UUID> rpcSubscriptions = ConcurrentHashMap.newKeySet(); | 81 | private final Set<UUID> rpcSubscriptions = ConcurrentHashMap.newKeySet(); |
82 | private final Set<UUID> attributeSubscriptions = ConcurrentHashMap.newKeySet(); | 82 | private final Set<UUID> attributeSubscriptions = ConcurrentHashMap.newKeySet(); |
83 | 83 | ||
@@ -93,7 +93,11 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | @@ -93,7 +93,11 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | ||
93 | this.timeout = coapServerService.getTimeout(); | 93 | this.timeout = coapServerService.getTimeout(); |
94 | this.sessionReportTimeout = ctx.getSessionReportTimeout(); | 94 | this.sessionReportTimeout = ctx.getSessionReportTimeout(); |
95 | ctx.getScheduler().scheduleAtFixedRate(() -> { | 95 | ctx.getScheduler().scheduleAtFixedRate(() -> { |
96 | - Set<TransportProtos.SessionInfoProto> observeSessions = sessionInfoToObserveRelationMap.keySet(); | 96 | + Set<CoapObserveSessionInfo> coapObserveSessionInfos = sessionInfoToObserveRelationMap.keySet(); |
97 | + Set<TransportProtos.SessionInfoProto> observeSessions = coapObserveSessionInfos | ||
98 | + .stream() | ||
99 | + .map(CoapObserveSessionInfo::getSessionInfoProto) | ||
100 | + .collect(Collectors.toSet()); | ||
97 | observeSessions.forEach(this::reportActivity); | 101 | observeSessions.forEach(this::reportActivity); |
98 | }, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS); | 102 | }, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS); |
99 | } | 103 | } |
@@ -111,17 +115,17 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | @@ -111,17 +115,17 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | ||
111 | relation.setEstablished(); | 115 | relation.setEstablished(); |
112 | addObserveRelation(relation); | 116 | addObserveRelation(relation); |
113 | } | 117 | } |
114 | - AtomicInteger notificationCounter = tokenToObserveNotificationSeqMap.computeIfAbsent(token, s -> new AtomicInteger(0)); | ||
115 | - response.getOptions().setObserve(notificationCounter.getAndIncrement()); | 118 | + AtomicInteger observeNotificationCounter = tokenToCoapSessionInfoMap.get(token).getObserveNotificationCounter(); |
119 | + response.getOptions().setObserve(observeNotificationCounter.getAndIncrement()); | ||
116 | } // ObserveLayer takes care of the else case | 120 | } // ObserveLayer takes care of the else case |
117 | } | 121 | } |
118 | 122 | ||
119 | - public void clearAndNotifyObserveRelation(ObserveRelation relation, CoAP.ResponseCode code) { | 123 | + private void clearAndNotifyObserveRelation(ObserveRelation relation, CoAP.ResponseCode code) { |
120 | relation.cancel(); | 124 | relation.cancel(); |
121 | relation.getExchange().sendResponse(new Response(code)); | 125 | relation.getExchange().sendResponse(new Response(code)); |
122 | } | 126 | } |
123 | 127 | ||
124 | - public Map<TransportProtos.SessionInfoProto, ObserveRelation> getSessionInfoToObserveRelationMap() { | 128 | + private Map<CoapObserveSessionInfo, ObserveRelation> getCoapSessionInfoToObserveRelationMap() { |
125 | return sessionInfoToObserveRelationMap; | 129 | return sessionInfoToObserveRelationMap; |
126 | } | 130 | } |
127 | 131 | ||
@@ -277,8 +281,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | @@ -277,8 +281,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | ||
277 | new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); | 281 | new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); |
278 | break; | 282 | break; |
279 | case SUBSCRIBE_ATTRIBUTES_REQUEST: | 283 | case SUBSCRIBE_ATTRIBUTES_REQUEST: |
280 | - TransportProtos.SessionInfoProto currentAttrSession = tokenToSessionInfoMap.get(getTokenFromRequest(request)); | ||
281 | - if (currentAttrSession == null) { | 284 | + CoapObserveSessionInfo currentCoapObserveAttrSessionInfo = tokenToCoapSessionInfoMap.get(getTokenFromRequest(request)); |
285 | + if (currentCoapObserveAttrSessionInfo == null) { | ||
282 | attributeSubscriptions.add(sessionId); | 286 | attributeSubscriptions.add(sessionId); |
283 | registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, | 287 | registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, |
284 | transportConfigurationContainer.getRpcRequestDynamicMessageBuilder(), getTokenFromRequest(request)); | 288 | transportConfigurationContainer.getRpcRequestDynamicMessageBuilder(), getTokenFromRequest(request)); |
@@ -290,20 +294,20 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | @@ -290,20 +294,20 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | ||
290 | } | 294 | } |
291 | break; | 295 | break; |
292 | case UNSUBSCRIBE_ATTRIBUTES_REQUEST: | 296 | case UNSUBSCRIBE_ATTRIBUTES_REQUEST: |
293 | - TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(getTokenFromRequest(request)); | ||
294 | - if (attrSession != null) { | 297 | + CoapObserveSessionInfo coapObserveAttrSessionInfo = lookupAsyncSessionInfo(getTokenFromRequest(request)); |
298 | + if (coapObserveAttrSessionInfo != null) { | ||
299 | + TransportProtos.SessionInfoProto attrSession = coapObserveAttrSessionInfo.getSessionInfoProto(); | ||
295 | UUID attrSessionId = toSessionId(attrSession); | 300 | UUID attrSessionId = toSessionId(attrSession); |
296 | attributeSubscriptions.remove(attrSessionId); | 301 | attributeSubscriptions.remove(attrSessionId); |
297 | - sessionInfoToObserveRelationMap.remove(attrSession); | ||
298 | transportService.process(attrSession, | 302 | transportService.process(attrSession, |
299 | TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(), | 303 | TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(), |
300 | new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); | 304 | new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); |
301 | - closeAndDeregister(sessionInfo); | ||
302 | } | 305 | } |
306 | + closeAndDeregister(sessionInfo); | ||
303 | break; | 307 | break; |
304 | case SUBSCRIBE_RPC_COMMANDS_REQUEST: | 308 | case SUBSCRIBE_RPC_COMMANDS_REQUEST: |
305 | - TransportProtos.SessionInfoProto currentRpcSession = tokenToSessionInfoMap.get(getTokenFromRequest(request)); | ||
306 | - if (currentRpcSession == null) { | 309 | + CoapObserveSessionInfo currentCoapObserveRpcSessionInfo = tokenToCoapSessionInfoMap.get(getTokenFromRequest(request)); |
310 | + if (currentCoapObserveRpcSessionInfo == null) { | ||
307 | rpcSubscriptions.add(sessionId); | 311 | rpcSubscriptions.add(sessionId); |
308 | registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, | 312 | registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, |
309 | transportConfigurationContainer.getRpcRequestDynamicMessageBuilder(), getTokenFromRequest(request)); | 313 | transportConfigurationContainer.getRpcRequestDynamicMessageBuilder(), getTokenFromRequest(request)); |
@@ -314,16 +318,16 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | @@ -314,16 +318,16 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | ||
314 | } | 318 | } |
315 | break; | 319 | break; |
316 | case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: | 320 | case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: |
317 | - TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(getTokenFromRequest(request)); | ||
318 | - if (rpcSession != null) { | 321 | + CoapObserveSessionInfo coapObserveRpcSessionInfo = lookupAsyncSessionInfo(getTokenFromRequest(request)); |
322 | + if (coapObserveRpcSessionInfo != null) { | ||
323 | + TransportProtos.SessionInfoProto rpcSession = coapObserveRpcSessionInfo.getSessionInfoProto(); | ||
319 | UUID rpcSessionId = toSessionId(rpcSession); | 324 | UUID rpcSessionId = toSessionId(rpcSession); |
320 | rpcSubscriptions.remove(rpcSessionId); | 325 | rpcSubscriptions.remove(rpcSessionId); |
321 | - sessionInfoToObserveRelationMap.remove(rpcSession); | ||
322 | transportService.process(rpcSession, | 326 | transportService.process(rpcSession, |
323 | TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(), | 327 | TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(), |
324 | new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); | 328 | new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); |
325 | - closeAndDeregister(sessionInfo); | ||
326 | } | 329 | } |
330 | + closeAndDeregister(sessionInfo); | ||
327 | break; | 331 | break; |
328 | case TO_DEVICE_RPC_RESPONSE: | 332 | case TO_DEVICE_RPC_RESPONSE: |
329 | transportService.process(sessionInfo, | 333 | transportService.process(sessionInfo, |
@@ -355,13 +359,12 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | @@ -355,13 +359,12 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | ||
355 | return new UUID(sessionInfoProto.getSessionIdMSB(), sessionInfoProto.getSessionIdLSB()); | 359 | return new UUID(sessionInfoProto.getSessionIdMSB(), sessionInfoProto.getSessionIdLSB()); |
356 | } | 360 | } |
357 | 361 | ||
358 | - private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) { | ||
359 | - tokenToObserveNotificationSeqMap.remove(token); | ||
360 | - return tokenToSessionInfoMap.remove(token); | 362 | + private CoapObserveSessionInfo lookupAsyncSessionInfo(String token) { |
363 | + return tokenToCoapSessionInfoMap.remove(token); | ||
361 | } | 364 | } |
362 | 365 | ||
363 | private void registerAsyncCoapSession(CoapExchange exchange, TransportProtos.SessionInfoProto sessionInfo, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder, String token) { | 366 | private void registerAsyncCoapSession(CoapExchange exchange, TransportProtos.SessionInfoProto sessionInfo, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder, String token) { |
364 | - tokenToSessionInfoMap.putIfAbsent(token, sessionInfo); | 367 | + tokenToCoapSessionInfoMap.putIfAbsent(token, new CoapObserveSessionInfo(sessionInfo)); |
365 | transportService.registerAsyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor, rpcRequestDynamicMessageBuilder, sessionInfo)); | 368 | transportService.registerAsyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor, rpcRequestDynamicMessageBuilder, sessionInfo)); |
366 | transportService.process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null); | 369 | transportService.process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null); |
367 | } | 370 | } |
@@ -476,45 +479,40 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | @@ -476,45 +479,40 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | ||
476 | } | 479 | } |
477 | 480 | ||
478 | @Override | 481 | @Override |
479 | - public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg) { | 482 | + public void onAttributeUpdate(UUID sessionId, TransportProtos.AttributeUpdateNotificationMsg msg) { |
483 | + log.trace("[{}] Received attributes update notification to device", sessionId); | ||
480 | try { | 484 | try { |
481 | exchange.respond(coapTransportAdaptor.convertToPublish(isConRequest(), msg)); | 485 | exchange.respond(coapTransportAdaptor.convertToPublish(isConRequest(), msg)); |
482 | } catch (AdaptorException e) { | 486 | } catch (AdaptorException e) { |
483 | log.trace("Failed to reply due to error", e); | 487 | log.trace("Failed to reply due to error", e); |
484 | - exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); | 488 | + closeObserveRelationAndNotify(sessionId, CoAP.ResponseCode.INTERNAL_SERVER_ERROR); |
489 | + closeAndDeregister(); | ||
485 | } | 490 | } |
486 | } | 491 | } |
487 | 492 | ||
488 | @Override | 493 | @Override |
489 | public void onRemoteSessionCloseCommand(UUID sessionId, TransportProtos.SessionCloseNotificationProto sessionCloseNotification) { | 494 | public void onRemoteSessionCloseCommand(UUID sessionId, TransportProtos.SessionCloseNotificationProto sessionCloseNotification) { |
490 | log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage()); | 495 | log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage()); |
491 | - Map<TransportProtos.SessionInfoProto, ObserveRelation> sessionToObserveRelationMap = coapTransportResource.getSessionInfoToObserveRelationMap(); | ||
492 | - if (coapTransportResource.getObserverCount() > 0 && !CollectionUtils.isEmpty(sessionToObserveRelationMap)) { | ||
493 | - Set<TransportProtos.SessionInfoProto> observeSessions = sessionToObserveRelationMap.keySet(); | ||
494 | - Optional<TransportProtos.SessionInfoProto> observeSessionToClose = observeSessions.stream().filter(sessionInfoProto -> { | ||
495 | - UUID observeSessionId = new UUID(sessionInfoProto.getSessionIdMSB(), sessionInfoProto.getSessionIdLSB()); | ||
496 | - return observeSessionId.equals(sessionId); | ||
497 | - }).findFirst(); | ||
498 | - if (observeSessionToClose.isPresent()) { | ||
499 | - TransportProtos.SessionInfoProto sessionInfoProto = observeSessionToClose.get(); | ||
500 | - ObserveRelation observeRelation = sessionToObserveRelationMap.get(sessionInfoProto); | ||
501 | - coapTransportResource.clearAndNotifyObserveRelation(observeRelation, CoAP.ResponseCode.SERVICE_UNAVAILABLE); | ||
502 | - } | ||
503 | - } | 496 | + closeObserveRelationAndNotify(sessionId, CoAP.ResponseCode.SERVICE_UNAVAILABLE); |
497 | + closeAndDeregister(); | ||
504 | } | 498 | } |
505 | 499 | ||
506 | @Override | 500 | @Override |
507 | - public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg msg) { | ||
508 | - boolean successful; | 501 | + public void onToDeviceRpcRequest(UUID sessionId, TransportProtos.ToDeviceRpcRequestMsg msg) { |
502 | + log.trace("[{}] Received RPC command to device", sessionId); | ||
503 | + boolean successful = true; | ||
509 | try { | 504 | try { |
510 | exchange.respond(coapTransportAdaptor.convertToPublish(isConRequest(), msg, rpcRequestDynamicMessageBuilder)); | 505 | exchange.respond(coapTransportAdaptor.convertToPublish(isConRequest(), msg, rpcRequestDynamicMessageBuilder)); |
511 | - successful = true; | ||
512 | } catch (AdaptorException e) { | 506 | } catch (AdaptorException e) { |
513 | log.trace("Failed to reply due to error", e); | 507 | log.trace("Failed to reply due to error", e); |
514 | - exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); | 508 | + closeObserveRelationAndNotify(sessionId, CoAP.ResponseCode.INTERNAL_SERVER_ERROR); |
515 | successful = false; | 509 | successful = false; |
510 | + } finally { | ||
511 | + coapTransportResource.transportService.process(sessionInfo, msg, !successful, TransportServiceCallback.EMPTY); | ||
512 | + if (!successful) { | ||
513 | + closeAndDeregister(); | ||
514 | + } | ||
516 | } | 515 | } |
517 | - coapTransportResource.transportService.process(sessionInfo, msg, !successful, TransportServiceCallback.EMPTY); | ||
518 | } | 516 | } |
519 | 517 | ||
520 | @Override | 518 | @Override |
@@ -530,6 +528,30 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | @@ -530,6 +528,30 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | ||
530 | private boolean isConRequest() { | 528 | private boolean isConRequest() { |
531 | return exchange.advanced().getRequest().isConfirmable(); | 529 | return exchange.advanced().getRequest().isConfirmable(); |
532 | } | 530 | } |
531 | + | ||
532 | + private void closeObserveRelationAndNotify(UUID sessionId, CoAP.ResponseCode responseCode) { | ||
533 | + Map<CoapObserveSessionInfo, ObserveRelation> sessionToObserveRelationMap = coapTransportResource.getCoapSessionInfoToObserveRelationMap(); | ||
534 | + if (coapTransportResource.getObserverCount() > 0 && !CollectionUtils.isEmpty(sessionToObserveRelationMap)) { | ||
535 | + Optional<CoapObserveSessionInfo> observeSessionToClose = sessionToObserveRelationMap.keySet().stream().filter(coapObserveSessionInfo -> { | ||
536 | + TransportProtos.SessionInfoProto sessionToDelete = coapObserveSessionInfo.getSessionInfoProto(); | ||
537 | + UUID observeSessionId = new UUID(sessionToDelete.getSessionIdMSB(), sessionToDelete.getSessionIdLSB()); | ||
538 | + return observeSessionId.equals(sessionId); | ||
539 | + }).findFirst(); | ||
540 | + if (observeSessionToClose.isPresent()) { | ||
541 | + CoapObserveSessionInfo coapObserveSessionInfo = observeSessionToClose.get(); | ||
542 | + ObserveRelation observeRelation = sessionToObserveRelationMap.get(coapObserveSessionInfo); | ||
543 | + coapTransportResource.clearAndNotifyObserveRelation(observeRelation, responseCode); | ||
544 | + } | ||
545 | + } | ||
546 | + } | ||
547 | + | ||
548 | + private void closeAndDeregister() { | ||
549 | + Request request = exchange.advanced().getRequest(); | ||
550 | + String token = coapTransportResource.getTokenFromRequest(request); | ||
551 | + CoapObserveSessionInfo deleted = coapTransportResource.lookupAsyncSessionInfo(token); | ||
552 | + coapTransportResource.closeAndDeregister(deleted.getSessionInfoProto()); | ||
553 | + } | ||
554 | + | ||
533 | } | 555 | } |
534 | 556 | ||
535 | public class CoapResourceObserver implements ResourceObserver { | 557 | public class CoapResourceObserver implements ResourceObserver { |
@@ -554,7 +576,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | @@ -554,7 +576,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | ||
554 | public void addedObserveRelation(ObserveRelation relation) { | 576 | public void addedObserveRelation(ObserveRelation relation) { |
555 | Request request = relation.getExchange().getRequest(); | 577 | Request request = relation.getExchange().getRequest(); |
556 | String token = getTokenFromRequest(request); | 578 | String token = getTokenFromRequest(request); |
557 | - sessionInfoToObserveRelationMap.putIfAbsent(tokenToSessionInfoMap.get(token), relation); | 579 | + sessionInfoToObserveRelationMap.putIfAbsent(tokenToCoapSessionInfoMap.get(token), relation); |
558 | log.trace("Added Observe relation for token: {}", token); | 580 | log.trace("Added Observe relation for token: {}", token); |
559 | } | 581 | } |
560 | 582 | ||
@@ -562,8 +584,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | @@ -562,8 +584,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | ||
562 | public void removedObserveRelation(ObserveRelation relation) { | 584 | public void removedObserveRelation(ObserveRelation relation) { |
563 | Request request = relation.getExchange().getRequest(); | 585 | Request request = relation.getExchange().getRequest(); |
564 | String token = getTokenFromRequest(request); | 586 | String token = getTokenFromRequest(request); |
565 | - TransportProtos.SessionInfoProto session = tokenToSessionInfoMap.get(token); | ||
566 | - sessionInfoToObserveRelationMap.remove(session); | 587 | + sessionInfoToObserveRelationMap.remove(tokenToCoapSessionInfoMap.get(token)); |
567 | log.trace("Relation removed for token: {}", token); | 588 | log.trace("Relation removed for token: {}", token); |
568 | } | 589 | } |
569 | } | 590 | } |
@@ -574,7 +595,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | @@ -574,7 +595,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | ||
574 | transportService.deregisterSession(session); | 595 | transportService.deregisterSession(session); |
575 | rpcSubscriptions.remove(sessionId); | 596 | rpcSubscriptions.remove(sessionId); |
576 | attributeSubscriptions.remove(sessionId); | 597 | attributeSubscriptions.remove(sessionId); |
577 | - sessionInfoToObserveRelationMap.remove(session); | ||
578 | } | 598 | } |
579 | 599 | ||
580 | private TransportConfigurationContainer getTransportConfigurationContainer(DeviceProfile deviceProfile) throws AdaptorException { | 600 | private TransportConfigurationContainer getTransportConfigurationContainer(DeviceProfile deviceProfile) throws AdaptorException { |
@@ -640,4 +660,17 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | @@ -640,4 +660,17 @@ public class CoapTransportResource extends AbstractCoapTransportResource { | ||
640 | this.jsonPayload = jsonPayload; | 660 | this.jsonPayload = jsonPayload; |
641 | } | 661 | } |
642 | } | 662 | } |
663 | + | ||
664 | + @Data | ||
665 | + private static class CoapObserveSessionInfo { | ||
666 | + | ||
667 | + private final TransportProtos.SessionInfoProto sessionInfoProto; | ||
668 | + private final AtomicInteger observeNotificationCounter; | ||
669 | + | ||
670 | + private CoapObserveSessionInfo(TransportProtos.SessionInfoProto sessionInfoProto) { | ||
671 | + this.sessionInfoProto = sessionInfoProto; | ||
672 | + this.observeNotificationCounter = new AtomicInteger(0); | ||
673 | + } | ||
674 | + } | ||
675 | + | ||
643 | } | 676 | } |
@@ -393,7 +393,8 @@ public class DeviceApiController implements TbTransportService { | @@ -393,7 +393,8 @@ public class DeviceApiController implements TbTransportService { | ||
393 | } | 393 | } |
394 | 394 | ||
395 | @Override | 395 | @Override |
396 | - public void onAttributeUpdate(AttributeUpdateNotificationMsg msg) { | 396 | + public void onAttributeUpdate(UUID sessionId, AttributeUpdateNotificationMsg msg) { |
397 | + log.trace("[{}] Received attributes update notification to device", sessionId); | ||
397 | responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg).toString(), HttpStatus.OK)); | 398 | responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg).toString(), HttpStatus.OK)); |
398 | } | 399 | } |
399 | 400 | ||
@@ -404,7 +405,8 @@ public class DeviceApiController implements TbTransportService { | @@ -404,7 +405,8 @@ public class DeviceApiController implements TbTransportService { | ||
404 | } | 405 | } |
405 | 406 | ||
406 | @Override | 407 | @Override |
407 | - public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg msg) { | 408 | + public void onToDeviceRpcRequest(UUID sessionId, ToDeviceRpcRequestMsg msg) { |
409 | + log.trace("[{}] Received RPC command to device", sessionId); | ||
408 | responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg, true).toString(), HttpStatus.OK)); | 410 | responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg, true).toString(), HttpStatus.OK)); |
409 | transportService.process(sessionInfo, msg, false, TransportServiceCallback.EMPTY); | 411 | transportService.process(sessionInfo, msg, false, TransportServiceCallback.EMPTY); |
410 | } | 412 | } |
@@ -21,10 +21,11 @@ import org.eclipse.leshan.core.request.BindingMode; | @@ -21,10 +21,11 @@ import org.eclipse.leshan.core.request.BindingMode; | ||
21 | import org.eclipse.leshan.core.util.Hex; | 21 | import org.eclipse.leshan.core.util.Hex; |
22 | import org.eclipse.leshan.server.bootstrap.BootstrapConfig; | 22 | import org.eclipse.leshan.server.bootstrap.BootstrapConfig; |
23 | 23 | ||
24 | +import java.io.Serializable; | ||
24 | import java.nio.charset.StandardCharsets; | 25 | import java.nio.charset.StandardCharsets; |
25 | 26 | ||
26 | @Data | 27 | @Data |
27 | -public class LwM2MBootstrapConfig { | 28 | +public class LwM2MBootstrapConfig implements Serializable { |
28 | /* | 29 | /* |
29 | interface BootstrapSecurityConfig | 30 | interface BootstrapSecurityConfig |
30 | servers: BootstrapServersSecurityConfig, | 31 | servers: BootstrapServersSecurityConfig, |
@@ -146,10 +146,10 @@ public class LwM2mCredentialsSecurityInfoValidator { | @@ -146,10 +146,10 @@ public class LwM2mCredentialsSecurityInfoValidator { | ||
146 | PSKClientCredentials pskConfig = (PSKClientCredentials) clientCredentialsConfig; | 146 | PSKClientCredentials pskConfig = (PSKClientCredentials) clientCredentialsConfig; |
147 | if (StringUtils.isNotEmpty(pskConfig.getIdentity())) { | 147 | if (StringUtils.isNotEmpty(pskConfig.getIdentity())) { |
148 | try { | 148 | try { |
149 | - if (pskConfig.getKey() != null && pskConfig.getKey().length > 0) { | 149 | + if (pskConfig.getDecodedKey() != null && pskConfig.getDecodedKey().length > 0) { |
150 | endpoint = StringUtils.isNotEmpty(pskConfig.getEndpoint()) ? pskConfig.getEndpoint() : endpoint; | 150 | endpoint = StringUtils.isNotEmpty(pskConfig.getEndpoint()) ? pskConfig.getEndpoint() : endpoint; |
151 | if (endpoint != null && !endpoint.isEmpty()) { | 151 | if (endpoint != null && !endpoint.isEmpty()) { |
152 | - result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endpoint, pskConfig.getIdentity(), pskConfig.getKey())); | 152 | + result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endpoint, pskConfig.getIdentity(), pskConfig.getDecodedKey())); |
153 | result.setSecurityMode(PSK); | 153 | result.setSecurityMode(PSK); |
154 | } | 154 | } |
155 | } | 155 | } |
@@ -164,8 +164,8 @@ public class LwM2mCredentialsSecurityInfoValidator { | @@ -164,8 +164,8 @@ public class LwM2mCredentialsSecurityInfoValidator { | ||
164 | private void createClientSecurityInfoRPK(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) { | 164 | private void createClientSecurityInfoRPK(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) { |
165 | RPKClientCredentials rpkConfig = (RPKClientCredentials) clientCredentialsConfig; | 165 | RPKClientCredentials rpkConfig = (RPKClientCredentials) clientCredentialsConfig; |
166 | try { | 166 | try { |
167 | - if (rpkConfig.getKey() != null) { | ||
168 | - PublicKey key = SecurityUtil.publicKey.decode(rpkConfig.getKey()); | 167 | + if (rpkConfig.getDecodedKey() != null) { |
168 | + PublicKey key = SecurityUtil.publicKey.decode(rpkConfig.getDecodedKey()); | ||
169 | result.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(endpoint, key)); | 169 | result.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(endpoint, key)); |
170 | result.setSecurityMode(RPK); | 170 | result.setSecurityMode(RPK); |
171 | } else { | 171 | } else { |
@@ -42,8 +42,8 @@ import org.thingsboard.server.common.transport.util.SslUtil; | @@ -42,8 +42,8 @@ import org.thingsboard.server.common.transport.util.SslUtil; | ||
42 | import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; | 42 | import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; |
43 | import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; | 43 | import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; |
44 | import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; | 44 | import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; |
45 | -import org.thingsboard.server.transport.lwm2m.server.store.TbEditableSecurityStore; | ||
46 | import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; | 45 | import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; |
46 | +import org.thingsboard.server.transport.lwm2m.server.store.TbMainSecurityStore; | ||
47 | 47 | ||
48 | import javax.annotation.PostConstruct; | 48 | import javax.annotation.PostConstruct; |
49 | import javax.security.auth.x500.X500Principal; | 49 | import javax.security.auth.x500.X500Principal; |
@@ -67,7 +67,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer | @@ -67,7 +67,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer | ||
67 | private final TbLwM2MDtlsSessionStore sessionStorage; | 67 | private final TbLwM2MDtlsSessionStore sessionStorage; |
68 | private final LwM2MTransportServerConfig config; | 68 | private final LwM2MTransportServerConfig config; |
69 | private final LwM2mCredentialsSecurityInfoValidator securityInfoValidator; | 69 | private final LwM2mCredentialsSecurityInfoValidator securityInfoValidator; |
70 | - private final TbEditableSecurityStore securityStore; | 70 | + private final TbMainSecurityStore securityStore; |
71 | 71 | ||
72 | @SuppressWarnings("deprecation") | 72 | @SuppressWarnings("deprecation") |
73 | private StaticCertificateVerifier staticCertificateVerifier; | 73 | private StaticCertificateVerifier staticCertificateVerifier; |
@@ -134,7 +134,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer | @@ -134,7 +134,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer | ||
134 | if (msg.hasDeviceInfo() && deviceProfile != null) { | 134 | if (msg.hasDeviceInfo() && deviceProfile != null) { |
135 | sessionStorage.put(endpoint, new TbX509DtlsSessionInfo(cert.getSubjectX500Principal().getName(), msg)); | 135 | sessionStorage.put(endpoint, new TbX509DtlsSessionInfo(cert.getSubjectX500Principal().getName(), msg)); |
136 | try { | 136 | try { |
137 | - securityStore.put(securityInfo); | 137 | + securityStore.putX509(securityInfo); |
138 | } catch (NonUniqueSecurityInfoException e) { | 138 | } catch (NonUniqueSecurityInfoException e) { |
139 | log.trace("Failed to add security info: {}", securityInfo, e); | 139 | log.trace("Failed to add security info: {}", securityInfo, e); |
140 | } | 140 | } |
@@ -23,8 +23,10 @@ import org.thingsboard.server.common.data.DeviceProfile; | @@ -23,8 +23,10 @@ import org.thingsboard.server.common.data.DeviceProfile; | ||
23 | import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; | 23 | import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; |
24 | import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig; | 24 | import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig; |
25 | 25 | ||
26 | +import java.io.Serializable; | ||
27 | + | ||
26 | @Data | 28 | @Data |
27 | -public class TbLwM2MSecurityInfo { | 29 | +public class TbLwM2MSecurityInfo implements Serializable { |
28 | private ValidateDeviceCredentialsResponse msg; | 30 | private ValidateDeviceCredentialsResponse msg; |
29 | private SecurityInfo securityInfo; | 31 | private SecurityInfo securityInfo; |
30 | private SecurityMode securityMode; | 32 | private SecurityMode securityMode; |
@@ -18,8 +18,10 @@ package org.thingsboard.server.transport.lwm2m.secure; | @@ -18,8 +18,10 @@ package org.thingsboard.server.transport.lwm2m.secure; | ||
18 | import lombok.Data; | 18 | import lombok.Data; |
19 | import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; | 19 | import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; |
20 | 20 | ||
21 | +import java.io.Serializable; | ||
22 | + | ||
21 | @Data | 23 | @Data |
22 | -public class TbX509DtlsSessionInfo { | 24 | +public class TbX509DtlsSessionInfo implements Serializable { |
23 | 25 | ||
24 | private final String x509CommonName; | 26 | private final String x509CommonName; |
25 | private final ValidateDeviceCredentialsResponse credentials; | 27 | private final ValidateDeviceCredentialsResponse credentials; |
@@ -55,7 +55,8 @@ public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? s | @@ -55,7 +55,8 @@ public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? s | ||
55 | } | 55 | } |
56 | 56 | ||
57 | @Override | 57 | @Override |
58 | - public void onAttributeUpdate(AttributeUpdateNotificationMsg attributeUpdateNotification) { | 58 | + public void onAttributeUpdate(UUID sessionId, AttributeUpdateNotificationMsg attributeUpdateNotification) { |
59 | + log.trace("[{}] Received attributes update notification to device", sessionId); | ||
59 | this.attributesService.onAttributesUpdate(attributeUpdateNotification, this.sessionInfo); | 60 | this.attributesService.onAttributesUpdate(attributeUpdateNotification, this.sessionInfo); |
60 | } | 61 | } |
61 | 62 | ||
@@ -80,7 +81,8 @@ public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? s | @@ -80,7 +81,8 @@ public class LwM2mSessionMsgListener implements GenericFutureListener<Future<? s | ||
80 | } | 81 | } |
81 | 82 | ||
82 | @Override | 83 | @Override |
83 | - public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) { | 84 | + public void onToDeviceRpcRequest(UUID sessionId, ToDeviceRpcRequestMsg toDeviceRequest) { |
85 | + log.trace("[{}] Received RPC command to device", sessionId); | ||
84 | this.rpcHandler.onToDeviceRpcRequest(toDeviceRequest, this.sessionInfo); | 86 | this.rpcHandler.onToDeviceRpcRequest(toDeviceRequest, this.sessionInfo); |
85 | transportService.process(sessionInfo, toDeviceRequest, false, TransportServiceCallback.EMPTY); | 87 | transportService.process(sessionInfo, toDeviceRequest, false, TransportServiceCallback.EMPTY); |
86 | } | 88 | } |
@@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.client; | @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.client; | ||
17 | 17 | ||
18 | import lombok.RequiredArgsConstructor; | 18 | import lombok.RequiredArgsConstructor; |
19 | import lombok.extern.slf4j.Slf4j; | 19 | import lombok.extern.slf4j.Slf4j; |
20 | +import org.eclipse.leshan.core.SecurityMode; | ||
20 | import org.eclipse.leshan.core.model.ResourceModel; | 21 | import org.eclipse.leshan.core.model.ResourceModel; |
21 | import org.eclipse.leshan.core.node.LwM2mPath; | 22 | import org.eclipse.leshan.core.node.LwM2mPath; |
22 | import org.eclipse.leshan.server.registration.Registration; | 23 | import org.eclipse.leshan.server.registration.Registration; |
@@ -30,7 +31,7 @@ import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; | @@ -30,7 +31,7 @@ import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; | ||
30 | import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; | 31 | import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; |
31 | import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; | 32 | import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; |
32 | import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; | 33 | import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; |
33 | -import org.thingsboard.server.transport.lwm2m.server.store.TbEditableSecurityStore; | 34 | +import org.thingsboard.server.transport.lwm2m.server.store.TbMainSecurityStore; |
34 | 35 | ||
35 | import java.util.Arrays; | 36 | import java.util.Arrays; |
36 | import java.util.Collection; | 37 | import java.util.Collection; |
@@ -54,7 +55,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { | @@ -54,7 +55,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { | ||
54 | 55 | ||
55 | private final LwM2mTransportContext context; | 56 | private final LwM2mTransportContext context; |
56 | private final LwM2MTransportServerConfig config; | 57 | private final LwM2MTransportServerConfig config; |
57 | - private final TbEditableSecurityStore securityStore; | 58 | + private final TbMainSecurityStore securityStore; |
58 | private final Map<String, LwM2mClient> lwM2mClientsByEndpoint = new ConcurrentHashMap<>(); | 59 | private final Map<String, LwM2mClient> lwM2mClientsByEndpoint = new ConcurrentHashMap<>(); |
59 | private final Map<String, LwM2mClient> lwM2mClientsByRegistrationId = new ConcurrentHashMap<>(); | 60 | private final Map<String, LwM2mClient> lwM2mClientsByRegistrationId = new ConcurrentHashMap<>(); |
60 | private final Map<UUID, Lwm2mDeviceProfileTransportConfiguration> profiles = new ConcurrentHashMap<>(); | 61 | private final Map<UUID, Lwm2mDeviceProfileTransportConfiguration> profiles = new ConcurrentHashMap<>(); |
@@ -75,6 +76,9 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { | @@ -75,6 +76,9 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { | ||
75 | oldSession = lwM2MClient.getSession(); | 76 | oldSession = lwM2MClient.getSession(); |
76 | TbLwM2MSecurityInfo securityInfo = securityStore.getTbLwM2MSecurityInfoByEndpoint(lwM2MClient.getEndpoint()); | 77 | TbLwM2MSecurityInfo securityInfo = securityStore.getTbLwM2MSecurityInfoByEndpoint(lwM2MClient.getEndpoint()); |
77 | if (securityInfo.getSecurityMode() != null) { | 78 | if (securityInfo.getSecurityMode() != null) { |
79 | + if (SecurityMode.X509.equals(securityInfo.getSecurityMode())) { | ||
80 | + securityStore.registerX509(registration.getEndpoint(), registration.getId()); | ||
81 | + } | ||
78 | if (securityInfo.getDeviceProfile() != null) { | 82 | if (securityInfo.getDeviceProfile() != null) { |
79 | profileUpdate(securityInfo.getDeviceProfile()); | 83 | profileUpdate(securityInfo.getDeviceProfile()); |
80 | if (securityInfo.getSecurityInfo() != null) { | 84 | if (securityInfo.getSecurityInfo() != null) { |
@@ -124,7 +128,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { | @@ -124,7 +128,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { | ||
124 | if (currentRegistration.getId().equals(registration.getId())) { | 128 | if (currentRegistration.getId().equals(registration.getId())) { |
125 | lwM2MClient.setState(LwM2MClientState.UNREGISTERED); | 129 | lwM2MClient.setState(LwM2MClientState.UNREGISTERED); |
126 | lwM2mClientsByEndpoint.remove(lwM2MClient.getEndpoint()); | 130 | lwM2mClientsByEndpoint.remove(lwM2MClient.getEndpoint()); |
127 | - this.securityStore.remove(lwM2MClient.getEndpoint()); | 131 | + this.securityStore.remove(lwM2MClient.getEndpoint(), registration.getId()); |
128 | UUID profileId = lwM2MClient.getProfileId(); | 132 | UUID profileId = lwM2MClient.getProfileId(); |
129 | if (profileId != null) { | 133 | if (profileId != null) { |
130 | Optional<LwM2mClient> otherClients = lwM2mClientsByRegistrationId.values().stream().filter(e -> e.getProfileId().equals(profileId)).findFirst(); | 134 | Optional<LwM2mClient> otherClients = lwM2mClientsByRegistrationId.values().stream().filter(e -> e.getProfileId().equals(profileId)).findFirst(); |
@@ -15,28 +15,29 @@ | @@ -15,28 +15,29 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.transport.lwm2m.server.store; | 16 | package org.thingsboard.server.transport.lwm2m.server.store; |
17 | 17 | ||
18 | -import com.fasterxml.jackson.databind.JsonNode; | 18 | +import org.nustaq.serialization.FSTConfiguration; |
19 | import org.springframework.data.redis.connection.RedisConnectionFactory; | 19 | import org.springframework.data.redis.connection.RedisConnectionFactory; |
20 | -import org.thingsboard.common.util.JacksonUtil; | ||
21 | import org.thingsboard.server.transport.lwm2m.secure.TbX509DtlsSessionInfo; | 20 | import org.thingsboard.server.transport.lwm2m.secure.TbX509DtlsSessionInfo; |
22 | 21 | ||
23 | public class TbLwM2MDtlsSessionRedisStore implements TbLwM2MDtlsSessionStore { | 22 | public class TbLwM2MDtlsSessionRedisStore implements TbLwM2MDtlsSessionStore { |
24 | 23 | ||
25 | private static final String SESSION_EP = "SESSION#EP#"; | 24 | private static final String SESSION_EP = "SESSION#EP#"; |
26 | - RedisConnectionFactory connectionFactory; | 25 | + private final RedisConnectionFactory connectionFactory; |
26 | + private final FSTConfiguration serializer; | ||
27 | 27 | ||
28 | public TbLwM2MDtlsSessionRedisStore(RedisConnectionFactory redisConnectionFactory) { | 28 | public TbLwM2MDtlsSessionRedisStore(RedisConnectionFactory redisConnectionFactory) { |
29 | this.connectionFactory = redisConnectionFactory; | 29 | this.connectionFactory = redisConnectionFactory; |
30 | + this.serializer = FSTConfiguration.createDefaultConfiguration(); | ||
30 | } | 31 | } |
31 | 32 | ||
32 | @Override | 33 | @Override |
33 | public void put(String endpoint, TbX509DtlsSessionInfo msg) { | 34 | public void put(String endpoint, TbX509DtlsSessionInfo msg) { |
34 | try (var c = connectionFactory.getConnection()) { | 35 | try (var c = connectionFactory.getConnection()) { |
35 | - var msgJson = JacksonUtil.convertValue(msg, JsonNode.class); | ||
36 | - if (msgJson != null) { | ||
37 | - c.set(getKey(endpoint), msgJson.toString().getBytes()); | 36 | + var serializedMsg = serializer.asByteArray(msg); |
37 | + if (serializedMsg != null) { | ||
38 | + c.set(getKey(endpoint), serializedMsg); | ||
38 | } else { | 39 | } else { |
39 | - throw new RuntimeException("Problem with serialization of message: " + msg.toString()); | 40 | + throw new RuntimeException("Problem with serialization of message: " + msg); |
40 | } | 41 | } |
41 | } | 42 | } |
42 | } | 43 | } |
@@ -46,7 +47,7 @@ public class TbLwM2MDtlsSessionRedisStore implements TbLwM2MDtlsSessionStore { | @@ -46,7 +47,7 @@ public class TbLwM2MDtlsSessionRedisStore implements TbLwM2MDtlsSessionStore { | ||
46 | try (var c = connectionFactory.getConnection()) { | 47 | try (var c = connectionFactory.getConnection()) { |
47 | var data = c.get(getKey(endpoint)); | 48 | var data = c.get(getKey(endpoint)); |
48 | if (data != null) { | 49 | if (data != null) { |
49 | - return JacksonUtil.fromString(new String(data), TbX509DtlsSessionInfo.class); | 50 | + return (TbX509DtlsSessionInfo) serializer.asObject(data); |
50 | } else { | 51 | } else { |
51 | return null; | 52 | return null; |
52 | } | 53 | } |
@@ -15,49 +15,55 @@ | @@ -15,49 +15,55 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.transport.lwm2m.server.store; | 16 | package org.thingsboard.server.transport.lwm2m.server.store; |
17 | 17 | ||
18 | -import org.eclipse.leshan.server.redis.serialization.SecurityInfoSerDes; | ||
19 | -import org.eclipse.leshan.server.security.EditableSecurityStore; | ||
20 | import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; | 18 | import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; |
21 | import org.eclipse.leshan.server.security.SecurityInfo; | 19 | import org.eclipse.leshan.server.security.SecurityInfo; |
22 | -import org.eclipse.leshan.server.security.SecurityStoreListener; | ||
23 | -import org.springframework.data.redis.connection.RedisClusterConnection; | 20 | +import org.nustaq.serialization.FSTConfiguration; |
24 | import org.springframework.data.redis.connection.RedisConnectionFactory; | 21 | import org.springframework.data.redis.connection.RedisConnectionFactory; |
25 | -import org.springframework.data.redis.core.Cursor; | ||
26 | -import org.springframework.data.redis.core.ScanOptions; | 22 | +import org.springframework.integration.redis.util.RedisLockRegistry; |
27 | import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; | 23 | import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; |
28 | 24 | ||
29 | -import java.util.ArrayList; | ||
30 | -import java.util.Collection; | ||
31 | -import java.util.LinkedList; | ||
32 | -import java.util.List; | 25 | +import java.util.concurrent.locks.Lock; |
33 | 26 | ||
34 | public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { | 27 | public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { |
35 | private static final String SEC_EP = "SEC#EP#"; | 28 | private static final String SEC_EP = "SEC#EP#"; |
36 | - | 29 | + private static final String LOCK_EP = "LOCK#EP#"; |
37 | private static final String PSKID_SEC = "PSKID#SEC"; | 30 | private static final String PSKID_SEC = "PSKID#SEC"; |
38 | 31 | ||
39 | private final RedisConnectionFactory connectionFactory; | 32 | private final RedisConnectionFactory connectionFactory; |
40 | - private SecurityStoreListener listener; | 33 | + private final FSTConfiguration serializer; |
34 | + private final RedisLockRegistry redisLock; | ||
41 | 35 | ||
42 | public TbLwM2mRedisSecurityStore(RedisConnectionFactory connectionFactory) { | 36 | public TbLwM2mRedisSecurityStore(RedisConnectionFactory connectionFactory) { |
43 | this.connectionFactory = connectionFactory; | 37 | this.connectionFactory = connectionFactory; |
38 | + redisLock = new RedisLockRegistry(connectionFactory, "Security"); | ||
39 | + serializer = FSTConfiguration.createDefaultConfiguration(); | ||
44 | } | 40 | } |
45 | 41 | ||
46 | @Override | 42 | @Override |
47 | public SecurityInfo getByEndpoint(String endpoint) { | 43 | public SecurityInfo getByEndpoint(String endpoint) { |
44 | + Lock lock = null; | ||
48 | try (var connection = connectionFactory.getConnection()) { | 45 | try (var connection = connectionFactory.getConnection()) { |
46 | + lock = redisLock.obtain(toLockKey(endpoint)); | ||
47 | + lock.lock(); | ||
49 | byte[] data = connection.get((SEC_EP + endpoint).getBytes()); | 48 | byte[] data = connection.get((SEC_EP + endpoint).getBytes()); |
50 | if (data == null) { | 49 | if (data == null) { |
51 | return null; | 50 | return null; |
52 | } else { | 51 | } else { |
53 | - return deserialize(data); | 52 | + return ((TbLwM2MSecurityInfo) serializer.asObject(data)).getSecurityInfo(); |
53 | + } | ||
54 | + } finally { | ||
55 | + if (lock != null) { | ||
56 | + lock.unlock(); | ||
54 | } | 57 | } |
55 | } | 58 | } |
56 | } | 59 | } |
57 | 60 | ||
58 | @Override | 61 | @Override |
59 | public SecurityInfo getByIdentity(String identity) { | 62 | public SecurityInfo getByIdentity(String identity) { |
63 | + Lock lock = null; | ||
60 | try (var connection = connectionFactory.getConnection()) { | 64 | try (var connection = connectionFactory.getConnection()) { |
65 | + lock = redisLock.obtain(toLockKey(identity)); | ||
66 | + lock.lock(); | ||
61 | byte[] ep = connection.hGet(PSKID_SEC.getBytes(), identity.getBytes()); | 67 | byte[] ep = connection.hGet(PSKID_SEC.getBytes(), identity.getBytes()); |
62 | if (ep == null) { | 68 | if (ep == null) { |
63 | return null; | 69 | return null; |
@@ -66,102 +72,86 @@ public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { | @@ -66,102 +72,86 @@ public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { | ||
66 | if (data == null) { | 72 | if (data == null) { |
67 | return null; | 73 | return null; |
68 | } else { | 74 | } else { |
69 | - return deserialize(data); | 75 | + return ((TbLwM2MSecurityInfo) serializer.asObject(data)).getSecurityInfo(); |
70 | } | 76 | } |
71 | } | 77 | } |
78 | + } finally { | ||
79 | + if (lock != null) { | ||
80 | + lock.unlock(); | ||
81 | + } | ||
72 | } | 82 | } |
73 | } | 83 | } |
74 | 84 | ||
75 | @Override | 85 | @Override |
76 | public void put(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException { | 86 | public void put(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException { |
77 | - //TODO: implement | 87 | + SecurityInfo info = tbSecurityInfo.getSecurityInfo(); |
88 | + byte[] tbSecurityInfoSerialized = serializer.asByteArray(tbSecurityInfo); | ||
89 | + Lock lock = null; | ||
90 | + try (var connection = connectionFactory.getConnection()) { | ||
91 | + lock = redisLock.obtain(tbSecurityInfo.getEndpoint()); | ||
92 | + lock.lock(); | ||
93 | + if (info != null && info.getIdentity() != null) { | ||
94 | + byte[] oldEndpointBytes = connection.hGet(PSKID_SEC.getBytes(), info.getIdentity().getBytes()); | ||
95 | + if (oldEndpointBytes != null) { | ||
96 | + String oldEndpoint = new String(oldEndpointBytes); | ||
97 | + if (!oldEndpoint.equals(info.getEndpoint())) { | ||
98 | + throw new NonUniqueSecurityInfoException("PSK Identity " + info.getIdentity() + " is already used"); | ||
99 | + } | ||
100 | + connection.hSet(PSKID_SEC.getBytes(), info.getIdentity().getBytes(), info.getEndpoint().getBytes()); | ||
101 | + } | ||
102 | + } | ||
103 | + | ||
104 | + byte[] previousData = connection.getSet((SEC_EP + tbSecurityInfo.getEndpoint()).getBytes(), tbSecurityInfoSerialized); | ||
105 | + if (previousData != null && info != null) { | ||
106 | + String previousIdentity = ((TbLwM2MSecurityInfo) serializer.asObject(previousData)).getSecurityInfo().getIdentity(); | ||
107 | + if (previousIdentity != null && !previousIdentity.equals(info.getIdentity())) { | ||
108 | + connection.hDel(PSKID_SEC.getBytes(), previousIdentity.getBytes()); | ||
109 | + } | ||
110 | + } | ||
111 | + } finally { | ||
112 | + if (lock != null) { | ||
113 | + lock.unlock(); | ||
114 | + } | ||
115 | + } | ||
78 | } | 116 | } |
79 | 117 | ||
80 | @Override | 118 | @Override |
81 | public TbLwM2MSecurityInfo getTbLwM2MSecurityInfoByEndpoint(String endpoint) { | 119 | public TbLwM2MSecurityInfo getTbLwM2MSecurityInfoByEndpoint(String endpoint) { |
82 | - //TODO: implement | ||
83 | - return null; | 120 | + Lock lock = null; |
121 | + try (var connection = connectionFactory.getConnection()) { | ||
122 | + lock = redisLock.obtain(endpoint); | ||
123 | + lock.lock(); | ||
124 | + byte[] data = connection.get((SEC_EP + endpoint).getBytes()); | ||
125 | + return (TbLwM2MSecurityInfo) serializer.asObject(data); | ||
126 | + } finally { | ||
127 | + if (lock != null) { | ||
128 | + lock.unlock(); | ||
129 | + } | ||
130 | + } | ||
84 | } | 131 | } |
85 | 132 | ||
86 | @Override | 133 | @Override |
87 | public void remove(String endpoint) { | 134 | public void remove(String endpoint) { |
88 | - //TODO: implement | ||
89 | - } | ||
90 | - | ||
91 | - // @Override | ||
92 | -// public Collection<SecurityInfo> getAll() { | ||
93 | -// try (var connection = connectionFactory.getConnection()) { | ||
94 | -// Collection<SecurityInfo> list = new LinkedList<>(); | ||
95 | -// ScanOptions scanOptions = ScanOptions.scanOptions().count(100).match(SEC_EP + "*").build(); | ||
96 | -// List<Cursor<byte[]>> scans = new ArrayList<>(); | ||
97 | -// if (connection instanceof RedisClusterConnection) { | ||
98 | -// ((RedisClusterConnection) connection).clusterGetNodes().forEach(node -> { | ||
99 | -// scans.add(((RedisClusterConnection) connection).scan(node, scanOptions)); | ||
100 | -// }); | ||
101 | -// } else { | ||
102 | -// scans.add(connection.scan(scanOptions)); | ||
103 | -// } | ||
104 | -// | ||
105 | -// scans.forEach(scan -> { | ||
106 | -// scan.forEachRemaining(key -> { | ||
107 | -// byte[] element = connection.get(key); | ||
108 | -// list.add(deserialize(element)); | ||
109 | -// }); | ||
110 | -// }); | ||
111 | -// return list; | ||
112 | -// } | ||
113 | -// } | ||
114 | -// | ||
115 | -// @Override | ||
116 | -// public SecurityInfo add(SecurityInfo info) throws NonUniqueSecurityInfoException { | ||
117 | -// byte[] data = serialize(info); | ||
118 | -// try (var connection = connectionFactory.getConnection()) { | ||
119 | -// if (info.getIdentity() != null) { | ||
120 | -// // populate the secondary index (security info by PSK id) | ||
121 | -// String oldEndpoint = new String(connection.hGet(PSKID_SEC.getBytes(), info.getIdentity().getBytes())); | ||
122 | -// if (!oldEndpoint.equals(info.getEndpoint())) { | ||
123 | -// throw new NonUniqueSecurityInfoException("PSK Identity " + info.getIdentity() + " is already used"); | ||
124 | -// } | ||
125 | -// connection.hSet(PSKID_SEC.getBytes(), info.getIdentity().getBytes(), info.getEndpoint().getBytes()); | ||
126 | -// } | ||
127 | -// | ||
128 | -// byte[] previousData = connection.getSet((SEC_EP + info.getEndpoint()).getBytes(), data); | ||
129 | -// SecurityInfo previous = previousData == null ? null : deserialize(previousData); | ||
130 | -// String previousIdentity = previous == null ? null : previous.getIdentity(); | ||
131 | -// if (previousIdentity != null && !previousIdentity.equals(info.getIdentity())) { | ||
132 | -// connection.hDel(PSKID_SEC.getBytes(), previousIdentity.getBytes()); | ||
133 | -// } | ||
134 | -// | ||
135 | -// return previous; | ||
136 | -// } | ||
137 | -// } | ||
138 | -// | ||
139 | -// @Override | ||
140 | -// public SecurityInfo remove(String endpoint, boolean infosAreCompromised) { | ||
141 | -// try (var connection = connectionFactory.getConnection()) { | ||
142 | -// byte[] data = connection.get((SEC_EP + endpoint).getBytes()); | ||
143 | -// | ||
144 | -// if (data != null) { | ||
145 | -// SecurityInfo info = deserialize(data); | ||
146 | -// if (info.getIdentity() != null) { | ||
147 | -// connection.hDel(PSKID_SEC.getBytes(), info.getIdentity().getBytes()); | ||
148 | -// } | ||
149 | -// connection.del((SEC_EP + endpoint).getBytes()); | ||
150 | -// if (listener != null) { | ||
151 | -// listener.securityInfoRemoved(infosAreCompromised, info); | ||
152 | -// } | ||
153 | -// return info; | ||
154 | -// } | ||
155 | -// } | ||
156 | -// return null; | ||
157 | -// } | ||
158 | - | ||
159 | - private byte[] serialize(SecurityInfo secInfo) { | ||
160 | - return SecurityInfoSerDes.serialize(secInfo); | 135 | + Lock lock = null; |
136 | + try (var connection = connectionFactory.getConnection()) { | ||
137 | + lock = redisLock.obtain(endpoint); | ||
138 | + lock.lock(); | ||
139 | + byte[] data = connection.get((SEC_EP + endpoint).getBytes()); | ||
140 | + if (data != null) { | ||
141 | + SecurityInfo info = ((TbLwM2MSecurityInfo) serializer.asObject(data)).getSecurityInfo(); | ||
142 | + if (info != null && info.getIdentity() != null) { | ||
143 | + connection.hDel(PSKID_SEC.getBytes(), info.getIdentity().getBytes()); | ||
144 | + } | ||
145 | + connection.del((SEC_EP + endpoint).getBytes()); | ||
146 | + } | ||
147 | + } finally { | ||
148 | + if (lock != null) { | ||
149 | + lock.unlock(); | ||
150 | + } | ||
151 | + } | ||
161 | } | 152 | } |
162 | 153 | ||
163 | - private SecurityInfo deserialize(byte[] data) { | ||
164 | - return SecurityInfoSerDes.deserialize(data); | 154 | + private String toLockKey(String endpoint) { |
155 | + return LOCK_EP + endpoint; | ||
165 | } | 156 | } |
166 | - | ||
167 | } | 157 | } |
@@ -22,13 +22,22 @@ import org.jetbrains.annotations.Nullable; | @@ -22,13 +22,22 @@ import org.jetbrains.annotations.Nullable; | ||
22 | import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; | 22 | import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; |
23 | import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; | 23 | import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; |
24 | 24 | ||
25 | +import java.util.HashSet; | ||
26 | +import java.util.Map; | ||
27 | +import java.util.Set; | ||
28 | +import java.util.concurrent.ConcurrentHashMap; | ||
29 | +import java.util.concurrent.ConcurrentMap; | ||
30 | +import java.util.concurrent.locks.Lock; | ||
31 | +import java.util.concurrent.locks.ReentrantLock; | ||
32 | + | ||
25 | import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT; | 33 | import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT; |
26 | 34 | ||
27 | @Slf4j | 35 | @Slf4j |
28 | -public class TbLwM2mSecurityStore implements TbEditableSecurityStore { | 36 | +public class TbLwM2mSecurityStore implements TbMainSecurityStore { |
29 | 37 | ||
30 | private final TbEditableSecurityStore securityStore; | 38 | private final TbEditableSecurityStore securityStore; |
31 | private final LwM2mCredentialsSecurityInfoValidator validator; | 39 | private final LwM2mCredentialsSecurityInfoValidator validator; |
40 | + private final ConcurrentMap<String, Set<String>> endpointRegistrations = new ConcurrentHashMap<>(); | ||
32 | 41 | ||
33 | public TbLwM2mSecurityStore(TbEditableSecurityStore securityStore, LwM2mCredentialsSecurityInfoValidator validator) { | 42 | public TbLwM2mSecurityStore(TbEditableSecurityStore securityStore, LwM2mCredentialsSecurityInfoValidator validator) { |
34 | this.securityStore = securityStore; | 43 | this.securityStore = securityStore; |
@@ -61,24 +70,42 @@ public class TbLwM2mSecurityStore implements TbEditableSecurityStore { | @@ -61,24 +70,42 @@ public class TbLwM2mSecurityStore implements TbEditableSecurityStore { | ||
61 | @Nullable | 70 | @Nullable |
62 | public SecurityInfo fetchAndPutSecurityInfo(String credentialsId) { | 71 | public SecurityInfo fetchAndPutSecurityInfo(String credentialsId) { |
63 | TbLwM2MSecurityInfo securityInfo = validator.getEndpointSecurityInfoByCredentialsId(credentialsId, CLIENT); | 72 | TbLwM2MSecurityInfo securityInfo = validator.getEndpointSecurityInfoByCredentialsId(credentialsId, CLIENT); |
64 | - try { | ||
65 | - if (securityInfo != null) { | 73 | + doPut(securityInfo); |
74 | + return securityInfo != null ? securityInfo.getSecurityInfo() : null; | ||
75 | + } | ||
76 | + | ||
77 | + private void doPut(TbLwM2MSecurityInfo securityInfo) { | ||
78 | + if (securityInfo != null) { | ||
79 | + try { | ||
66 | securityStore.put(securityInfo); | 80 | securityStore.put(securityInfo); |
81 | + } catch (NonUniqueSecurityInfoException e) { | ||
82 | + log.trace("Failed to add security info: {}", securityInfo, e); | ||
67 | } | 83 | } |
68 | - } catch (NonUniqueSecurityInfoException e) { | ||
69 | - log.trace("Failed to add security info: {}", securityInfo, e); | ||
70 | } | 84 | } |
71 | - return securityInfo != null ? securityInfo.getSecurityInfo() : null; | ||
72 | } | 85 | } |
73 | 86 | ||
74 | @Override | 87 | @Override |
75 | - public void put(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException { | ||
76 | - securityStore.put(tbSecurityInfo); | 88 | + public void putX509(TbLwM2MSecurityInfo securityInfo) throws NonUniqueSecurityInfoException { |
89 | + securityStore.put(securityInfo); | ||
77 | } | 90 | } |
78 | 91 | ||
79 | @Override | 92 | @Override |
80 | - public void remove(String endpoint) { | ||
81 | - //TODO: Make sure we delay removal of security store from endpoint due to reg/unreg race condition. | ||
82 | -// securityStore.remove(endpoint); | 93 | + public void registerX509(String endpoint, String registrationId) { |
94 | + endpointRegistrations.computeIfAbsent(endpoint, ep -> new HashSet<>()).add(registrationId); | ||
95 | + } | ||
96 | + | ||
97 | + @Override | ||
98 | + public void remove(String endpoint, String registrationId) { | ||
99 | + Set<String> epRegistrationIds = endpointRegistrations.get(endpoint); | ||
100 | + boolean shouldRemove; | ||
101 | + if (epRegistrationIds == null) { | ||
102 | + shouldRemove = true; | ||
103 | + } else { | ||
104 | + epRegistrationIds.remove(registrationId); | ||
105 | + shouldRemove = epRegistrationIds.isEmpty(); | ||
106 | + } | ||
107 | + if (shouldRemove) { | ||
108 | + securityStore.remove(endpoint); | ||
109 | + } | ||
83 | } | 110 | } |
84 | } | 111 | } |
@@ -51,7 +51,7 @@ public class TbLwM2mStoreFactory { | @@ -51,7 +51,7 @@ public class TbLwM2mStoreFactory { | ||
51 | } | 51 | } |
52 | 52 | ||
53 | @Bean | 53 | @Bean |
54 | - private TbEditableSecurityStore securityStore() { | 54 | + private TbMainSecurityStore securityStore() { |
55 | return new TbLwM2mSecurityStore(redisConfiguration.isPresent() && useRedis ? | 55 | return new TbLwM2mSecurityStore(redisConfiguration.isPresent() && useRedis ? |
56 | new TbLwM2mRedisSecurityStore(redisConfiguration.get().redisConnectionFactory()) : new TbInMemorySecurityStore(), validator); | 56 | new TbLwM2mRedisSecurityStore(redisConfiguration.get().redisConnectionFactory()) : new TbInMemorySecurityStore(), validator); |
57 | } | 57 | } |
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbMainSecurityStore.java
renamed from
application/src/main/java/org/thingsboard/server/service/ttl/timeseries/TimescaleTimeseriesCleanUpService.java
@@ -13,24 +13,17 @@ | @@ -13,24 +13,17 @@ | ||
13 | * See the License for the specific language governing permissions and | 13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. | 14 | * limitations under the License. |
15 | */ | 15 | */ |
16 | -package org.thingsboard.server.service.ttl.timeseries; | 16 | +package org.thingsboard.server.transport.lwm2m.server.store; |
17 | 17 | ||
18 | -import lombok.extern.slf4j.Slf4j; | ||
19 | -import org.springframework.stereotype.Service; | ||
20 | -import org.thingsboard.server.dao.model.ModelConstants; | ||
21 | -import org.thingsboard.server.dao.util.TimescaleDBTsDao; | 18 | +import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; |
19 | +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; | ||
22 | 20 | ||
23 | -import java.sql.Connection; | ||
24 | -import java.sql.SQLException; | 21 | +public interface TbMainSecurityStore extends TbSecurityStore { |
25 | 22 | ||
26 | -@TimescaleDBTsDao | ||
27 | -@Service | ||
28 | -@Slf4j | ||
29 | -public class TimescaleTimeseriesCleanUpService extends AbstractTimeseriesCleanUpService { | 23 | + void putX509(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException; |
24 | + | ||
25 | + void registerX509(String endpoint, String registrationId); | ||
26 | + | ||
27 | + void remove(String endpoint, String registrationId); | ||
30 | 28 | ||
31 | - @Override | ||
32 | - protected void doCleanUp(Connection connection) throws SQLException { | ||
33 | - long totalEntitiesTelemetryRemoved = executeQuery(connection, "call cleanup_timeseries_by_ttl('" + ModelConstants.NULL_UUID + "'," + systemTtl + ", 0);"); | ||
34 | - log.info("Total telemetry removed stats by TTL for entities: [{}]", totalEntitiesTelemetryRemoved); | ||
35 | - } | ||
36 | } | 29 | } |
@@ -796,7 +796,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | @@ -796,7 +796,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | ||
796 | } | 796 | } |
797 | 797 | ||
798 | @Override | 798 | @Override |
799 | - public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg notification) { | 799 | + public void onAttributeUpdate(UUID sessionId, TransportProtos.AttributeUpdateNotificationMsg notification) { |
800 | + log.trace("[{}] Received attributes update notification to device", sessionId); | ||
800 | try { | 801 | try { |
801 | deviceSessionCtx.getPayloadAdaptor().convertToPublish(deviceSessionCtx, notification).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); | 802 | deviceSessionCtx.getPayloadAdaptor().convertToPublish(deviceSessionCtx, notification).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); |
802 | } catch (Exception e) { | 803 | } catch (Exception e) { |
@@ -811,7 +812,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | @@ -811,7 +812,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement | ||
811 | } | 812 | } |
812 | 813 | ||
813 | @Override | 814 | @Override |
814 | - public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg rpcRequest) { | 815 | + public void onToDeviceRpcRequest(UUID sessionId, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) { |
815 | log.trace("[{}] Received RPC command to device", sessionId); | 816 | log.trace("[{}] Received RPC command to device", sessionId); |
816 | try { | 817 | try { |
817 | deviceSessionCtx.getPayloadAdaptor().convertToPublish(deviceSessionCtx, rpcRequest) | 818 | deviceSessionCtx.getPayloadAdaptor().convertToPublish(deviceSessionCtx, rpcRequest) |
@@ -84,7 +84,8 @@ public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext imple | @@ -84,7 +84,8 @@ public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext imple | ||
84 | } | 84 | } |
85 | 85 | ||
86 | @Override | 86 | @Override |
87 | - public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg notification) { | 87 | + public void onAttributeUpdate(UUID sessionId, TransportProtos.AttributeUpdateNotificationMsg notification) { |
88 | + log.trace("[{}] Received attributes update notification to device", sessionId); | ||
88 | try { | 89 | try { |
89 | parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), notification).ifPresent(parent::writeAndFlush); | 90 | parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), notification).ifPresent(parent::writeAndFlush); |
90 | } catch (Exception e) { | 91 | } catch (Exception e) { |
@@ -93,7 +94,8 @@ public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext imple | @@ -93,7 +94,8 @@ public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext imple | ||
93 | } | 94 | } |
94 | 95 | ||
95 | @Override | 96 | @Override |
96 | - public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg request) { | 97 | + public void onToDeviceRpcRequest(UUID sessionId, TransportProtos.ToDeviceRpcRequestMsg request) { |
98 | + log.trace("[{}] Received RPC command to device", sessionId); | ||
97 | try { | 99 | try { |
98 | parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), request).ifPresent( | 100 | parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), request).ifPresent( |
99 | payload -> { | 101 | payload -> { |
@@ -128,7 +128,8 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S | @@ -128,7 +128,8 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S | ||
128 | } | 128 | } |
129 | 129 | ||
130 | @Override | 130 | @Override |
131 | - public void onAttributeUpdate(AttributeUpdateNotificationMsg attributeUpdateNotification) { | 131 | + public void onAttributeUpdate(UUID sessionId, AttributeUpdateNotificationMsg attributeUpdateNotification) { |
132 | + log.trace("[{}] Received attributes update notification to device", sessionId); | ||
132 | snmpTransportContext.getSnmpTransportService().onAttributeUpdate(this, attributeUpdateNotification); | 133 | snmpTransportContext.getSnmpTransportService().onAttributeUpdate(this, attributeUpdateNotification); |
133 | } | 134 | } |
134 | 135 | ||
@@ -138,7 +139,8 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S | @@ -138,7 +139,8 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S | ||
138 | } | 139 | } |
139 | 140 | ||
140 | @Override | 141 | @Override |
141 | - public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) { | 142 | + public void onToDeviceRpcRequest(UUID sessionId, ToDeviceRpcRequestMsg toDeviceRequest) { |
143 | + log.trace("[{}] Received RPC command to device", sessionId); | ||
142 | snmpTransportContext.getSnmpTransportService().onToDeviceRpcRequest(this, toDeviceRequest); | 144 | snmpTransportContext.getSnmpTransportService().onToDeviceRpcRequest(this, toDeviceRequest); |
143 | snmpTransportContext.getTransportService().process(getSessionInfo(), toDeviceRequest, false, TransportServiceCallback.EMPTY); | 145 | snmpTransportContext.getTransportService().process(getSessionInfo(), toDeviceRequest, false, TransportServiceCallback.EMPTY); |
144 | } | 146 | } |
@@ -36,11 +36,11 @@ public interface SessionMsgListener { | @@ -36,11 +36,11 @@ public interface SessionMsgListener { | ||
36 | 36 | ||
37 | void onGetAttributesResponse(GetAttributeResponseMsg getAttributesResponse); | 37 | void onGetAttributesResponse(GetAttributeResponseMsg getAttributesResponse); |
38 | 38 | ||
39 | - void onAttributeUpdate(AttributeUpdateNotificationMsg attributeUpdateNotification); | 39 | + void onAttributeUpdate(UUID sessionId, AttributeUpdateNotificationMsg attributeUpdateNotification); |
40 | 40 | ||
41 | void onRemoteSessionCloseCommand(UUID sessionId, SessionCloseNotificationProto sessionCloseNotification); | 41 | void onRemoteSessionCloseCommand(UUID sessionId, SessionCloseNotificationProto sessionCloseNotification); |
42 | 42 | ||
43 | - void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest); | 43 | + void onToDeviceRpcRequest(UUID sessionId, ToDeviceRpcRequestMsg toDeviceRequest); |
44 | 44 | ||
45 | void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse); | 45 | void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse); |
46 | 46 |
@@ -20,7 +20,6 @@ import org.thingsboard.server.common.data.DeviceTransportType; | @@ -20,7 +20,6 @@ import org.thingsboard.server.common.data.DeviceTransportType; | ||
20 | import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; | 20 | import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; |
21 | import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; | 21 | import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; |
22 | import org.thingsboard.server.common.transport.service.SessionMetaData; | 22 | import org.thingsboard.server.common.transport.service.SessionMetaData; |
23 | -import org.thingsboard.server.gen.transport.TransportProtos; | ||
24 | import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; | 23 | import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; |
25 | import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; | 24 | import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; |
26 | import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceCredentialsRequestMsg; | 25 | import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceCredentialsRequestMsg; |
@@ -47,6 +46,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; | @@ -47,6 +46,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; | ||
47 | import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; | 46 | import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; |
48 | import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg; | 47 | import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg; |
49 | import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionInfoProto; | 48 | import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionInfoProto; |
49 | +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; | ||
50 | import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; | 50 | import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; |
51 | import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; | 51 | import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; |
52 | import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; | 52 | import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; |
@@ -109,7 +109,7 @@ public interface TransportService { | @@ -109,7 +109,7 @@ public interface TransportService { | ||
109 | 109 | ||
110 | void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback<Void> callback); | 110 | void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback<Void> callback); |
111 | 111 | ||
112 | - void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDeviceRpcRequestMsg msg, boolean isFailedRpc, TransportServiceCallback<Void> callback); | 112 | + void process(SessionInfoProto sessionInfo, ToDeviceRpcRequestMsg msg, boolean isFailedRpc, TransportServiceCallback<Void> callback); |
113 | 113 | ||
114 | void process(SessionInfoProto sessionInfo, SubscriptionInfoProto msg, TransportServiceCallback<Void> callback); | 114 | void process(SessionInfoProto sessionInfo, SubscriptionInfoProto msg, TransportServiceCallback<Void> callback); |
115 | 115 |
@@ -22,8 +22,10 @@ import org.thingsboard.server.common.data.id.DeviceId; | @@ -22,8 +22,10 @@ import org.thingsboard.server.common.data.id.DeviceId; | ||
22 | import org.thingsboard.server.common.data.id.DeviceProfileId; | 22 | import org.thingsboard.server.common.data.id.DeviceProfileId; |
23 | import org.thingsboard.server.common.data.id.TenantId; | 23 | import org.thingsboard.server.common.data.id.TenantId; |
24 | 24 | ||
25 | +import java.io.Serializable; | ||
26 | + | ||
25 | @Data | 27 | @Data |
26 | -public class TransportDeviceInfo { | 28 | +public class TransportDeviceInfo implements Serializable { |
27 | 29 | ||
28 | private TenantId tenantId; | 30 | private TenantId tenantId; |
29 | private CustomerId customerId; | 31 | private CustomerId customerId; |
@@ -19,9 +19,11 @@ import lombok.Builder; | @@ -19,9 +19,11 @@ import lombok.Builder; | ||
19 | import lombok.Data; | 19 | import lombok.Data; |
20 | import org.thingsboard.server.common.data.DeviceProfile; | 20 | import org.thingsboard.server.common.data.DeviceProfile; |
21 | 21 | ||
22 | +import java.io.Serializable; | ||
23 | + | ||
22 | @Data | 24 | @Data |
23 | @Builder | 25 | @Builder |
24 | -public class ValidateDeviceCredentialsResponse implements DeviceProfileAware { | 26 | +public class ValidateDeviceCredentialsResponse implements DeviceProfileAware, Serializable { |
25 | 27 | ||
26 | private final TransportDeviceInfo deviceInfo; | 28 | private final TransportDeviceInfo deviceInfo; |
27 | private final DeviceProfile deviceProfile; | 29 | private final DeviceProfile deviceProfile; |
@@ -776,7 +776,7 @@ public class DefaultTransportService implements TransportService { | @@ -776,7 +776,7 @@ public class DefaultTransportService implements TransportService { | ||
776 | listener.onGetAttributesResponse(toSessionMsg.getGetAttributesResponse()); | 776 | listener.onGetAttributesResponse(toSessionMsg.getGetAttributesResponse()); |
777 | } | 777 | } |
778 | if (toSessionMsg.hasAttributeUpdateNotification()) { | 778 | if (toSessionMsg.hasAttributeUpdateNotification()) { |
779 | - listener.onAttributeUpdate(toSessionMsg.getAttributeUpdateNotification()); | 779 | + listener.onAttributeUpdate(sessionId, toSessionMsg.getAttributeUpdateNotification()); |
780 | } | 780 | } |
781 | if (toSessionMsg.hasSessionCloseNotification()) { | 781 | if (toSessionMsg.hasSessionCloseNotification()) { |
782 | listener.onRemoteSessionCloseCommand(sessionId, toSessionMsg.getSessionCloseNotification()); | 782 | listener.onRemoteSessionCloseCommand(sessionId, toSessionMsg.getSessionCloseNotification()); |
@@ -785,7 +785,7 @@ public class DefaultTransportService implements TransportService { | @@ -785,7 +785,7 @@ public class DefaultTransportService implements TransportService { | ||
785 | listener.onToTransportUpdateCredentials(toSessionMsg.getToTransportUpdateCredentialsNotification()); | 785 | listener.onToTransportUpdateCredentials(toSessionMsg.getToTransportUpdateCredentialsNotification()); |
786 | } | 786 | } |
787 | if (toSessionMsg.hasToDeviceRequest()) { | 787 | if (toSessionMsg.hasToDeviceRequest()) { |
788 | - listener.onToDeviceRpcRequest(toSessionMsg.getToDeviceRequest()); | 788 | + listener.onToDeviceRpcRequest(sessionId, toSessionMsg.getToDeviceRequest()); |
789 | } | 789 | } |
790 | if (toSessionMsg.hasToServerResponse()) { | 790 | if (toSessionMsg.hasToServerResponse()) { |
791 | String requestId = sessionId + "-" + toSessionMsg.getToServerResponse().getRequestId(); | 791 | String requestId = sessionId + "-" + toSessionMsg.getToServerResponse().getRequestId(); |
@@ -176,4 +176,10 @@ public interface EdgeDao extends Dao<Edge> { | @@ -176,4 +176,10 @@ public interface EdgeDao extends Dao<Edge> { | ||
176 | * @return the list of rule chain objects | 176 | * @return the list of rule chain objects |
177 | */ | 177 | */ |
178 | ListenableFuture<List<Edge>> findEdgesByTenantIdAndDashboardId(UUID tenantId, UUID dashboardId); | 178 | ListenableFuture<List<Edge>> findEdgesByTenantIdAndDashboardId(UUID tenantId, UUID dashboardId); |
179 | + | ||
180 | + /** | ||
181 | + * Executes stored procedure to cleanup old edge events. | ||
182 | + * @param ttl the ttl for edge events in seconds | ||
183 | + */ | ||
184 | + void cleanupEvents(long ttl); | ||
179 | } | 185 | } |
@@ -627,6 +627,11 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic | @@ -627,6 +627,11 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic | ||
627 | return result.toString(); | 627 | return result.toString(); |
628 | } | 628 | } |
629 | 629 | ||
630 | + @Override | ||
631 | + public void cleanupEvents(long ttl) { | ||
632 | + edgeDao.cleanupEvents(ttl); | ||
633 | + } | ||
634 | + | ||
630 | private List<RuleChain> findEdgeRuleChains(TenantId tenantId, EdgeId edgeId) { | 635 | private List<RuleChain> findEdgeRuleChains(TenantId tenantId, EdgeId edgeId) { |
631 | List<RuleChain> result = new ArrayList<>(); | 636 | List<RuleChain> result = new ArrayList<>(); |
632 | PageLink pageLink = new PageLink(DEFAULT_LIMIT); | 637 | PageLink pageLink = new PageLink(DEFAULT_LIMIT); |
@@ -131,6 +131,11 @@ public class BaseEventService implements EventService { | @@ -131,6 +131,11 @@ public class BaseEventService implements EventService { | ||
131 | } while (eventPageData.hasNext()); | 131 | } while (eventPageData.hasNext()); |
132 | } | 132 | } |
133 | 133 | ||
134 | + @Override | ||
135 | + public void cleanupEvents(long ttl, long debugTtl) { | ||
136 | + eventDao.cleanupEvents(ttl, debugTtl); | ||
137 | + } | ||
138 | + | ||
134 | private DataValidator<Event> eventValidator = | 139 | private DataValidator<Event> eventValidator = |
135 | new DataValidator<Event>() { | 140 | new DataValidator<Event>() { |
136 | @Override | 141 | @Override |
@@ -102,4 +102,10 @@ public interface EventDao extends Dao<Event> { | @@ -102,4 +102,10 @@ public interface EventDao extends Dao<Event> { | ||
102 | */ | 102 | */ |
103 | List<Event> findLatestEvents(UUID tenantId, EntityId entityId, String eventType, int limit); | 103 | List<Event> findLatestEvents(UUID tenantId, EntityId entityId, String eventType, int limit); |
104 | 104 | ||
105 | + /** | ||
106 | + * Executes stored procedure to cleanup old events. Uses separate ttl for debug and other events. | ||
107 | + * @param otherEventsTtl the ttl for events in seconds | ||
108 | + * @param debugEventsTtl the ttl for debug events in seconds | ||
109 | + */ | ||
110 | + void cleanupEvents(long otherEventsTtl, long debugEventsTtl); | ||
105 | } | 111 | } |
@@ -15,11 +15,33 @@ | @@ -15,11 +15,33 @@ | ||
15 | */ | 15 | */ |
16 | package org.thingsboard.server.dao.sql; | 16 | package org.thingsboard.server.dao.sql; |
17 | 17 | ||
18 | +import lombok.extern.slf4j.Slf4j; | ||
18 | import org.springframework.beans.factory.annotation.Autowired; | 19 | import org.springframework.beans.factory.annotation.Autowired; |
19 | 20 | ||
21 | +import javax.sql.DataSource; | ||
22 | +import java.sql.SQLException; | ||
23 | +import java.sql.SQLWarning; | ||
24 | +import java.sql.Statement; | ||
25 | + | ||
26 | +@Slf4j | ||
20 | public abstract class JpaAbstractDaoListeningExecutorService { | 27 | public abstract class JpaAbstractDaoListeningExecutorService { |
21 | 28 | ||
22 | @Autowired | 29 | @Autowired |
23 | protected JpaExecutorService service; | 30 | protected JpaExecutorService service; |
24 | 31 | ||
32 | + @Autowired | ||
33 | + protected DataSource dataSource; | ||
34 | + | ||
35 | + protected void printWarnings(Statement statement) throws SQLException { | ||
36 | + SQLWarning warnings = statement.getWarnings(); | ||
37 | + if (warnings != null) { | ||
38 | + log.debug("{}", warnings.getMessage()); | ||
39 | + SQLWarning nextWarning = warnings.getNextWarning(); | ||
40 | + while (nextWarning != null) { | ||
41 | + log.debug("{}", nextWarning.getMessage()); | ||
42 | + nextWarning = nextWarning.getNextWarning(); | ||
43 | + } | ||
44 | + } | ||
45 | + } | ||
46 | + | ||
25 | } | 47 | } |
@@ -40,6 +40,10 @@ import org.thingsboard.server.dao.model.sql.EdgeInfoEntity; | @@ -40,6 +40,10 @@ import org.thingsboard.server.dao.model.sql.EdgeInfoEntity; | ||
40 | import org.thingsboard.server.dao.relation.RelationDao; | 40 | import org.thingsboard.server.dao.relation.RelationDao; |
41 | import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; | 41 | import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; |
42 | 42 | ||
43 | +import java.sql.Connection; | ||
44 | +import java.sql.PreparedStatement; | ||
45 | +import java.sql.ResultSet; | ||
46 | +import java.sql.SQLException; | ||
43 | import java.util.ArrayList; | 47 | import java.util.ArrayList; |
44 | import java.util.Collections; | 48 | import java.util.Collections; |
45 | import java.util.List; | 49 | import java.util.List; |
@@ -194,6 +198,24 @@ public class JpaEdgeDao extends JpaAbstractSearchTextDao<EdgeEntity, Edge> imple | @@ -194,6 +198,24 @@ public class JpaEdgeDao extends JpaAbstractSearchTextDao<EdgeEntity, Edge> imple | ||
194 | return transformFromRelationToEdge(tenantId, relations); | 198 | return transformFromRelationToEdge(tenantId, relations); |
195 | } | 199 | } |
196 | 200 | ||
201 | + @Override | ||
202 | + public void cleanupEvents(long ttl) { | ||
203 | + log.info("Going to cleanup old edge events using ttl: {}s", ttl); | ||
204 | + try (Connection connection = dataSource.getConnection(); | ||
205 | + PreparedStatement stmt = connection.prepareStatement("call cleanup_edge_events_by_ttl(?,?)")) { | ||
206 | + stmt.setLong(1, ttl); | ||
207 | + stmt.setLong(2, 0); | ||
208 | + stmt.execute(); | ||
209 | + printWarnings(stmt); | ||
210 | + try (ResultSet resultSet = stmt.getResultSet()) { | ||
211 | + resultSet.next(); | ||
212 | + log.info("Total edge events removed by TTL: [{}]", resultSet.getLong(1)); | ||
213 | + } | ||
214 | + } catch (SQLException e) { | ||
215 | + log.error("SQLException occurred during edge events TTL task execution ", e); | ||
216 | + } | ||
217 | + } | ||
218 | + | ||
197 | private ListenableFuture<List<Edge>> transformFromRelationToEdge(UUID tenantId, ListenableFuture<List<EntityRelation>> relations) { | 219 | private ListenableFuture<List<Edge>> transformFromRelationToEdge(UUID tenantId, ListenableFuture<List<EntityRelation>> relations) { |
198 | return Futures.transformAsync(relations, input -> { | 220 | return Futures.transformAsync(relations, input -> { |
199 | List<ListenableFuture<Edge>> edgeFutures = new ArrayList<>(input.size()); | 221 | List<ListenableFuture<Edge>> edgeFutures = new ArrayList<>(input.size()); |
@@ -27,7 +27,6 @@ import org.thingsboard.server.common.data.Event; | @@ -27,7 +27,6 @@ import org.thingsboard.server.common.data.Event; | ||
27 | import org.thingsboard.server.common.data.event.DebugEvent; | 27 | import org.thingsboard.server.common.data.event.DebugEvent; |
28 | import org.thingsboard.server.common.data.event.ErrorEventFilter; | 28 | import org.thingsboard.server.common.data.event.ErrorEventFilter; |
29 | import org.thingsboard.server.common.data.event.EventFilter; | 29 | import org.thingsboard.server.common.data.event.EventFilter; |
30 | -import org.thingsboard.server.common.data.event.EventType; | ||
31 | import org.thingsboard.server.common.data.event.LifeCycleEventFilter; | 30 | import org.thingsboard.server.common.data.event.LifeCycleEventFilter; |
32 | import org.thingsboard.server.common.data.event.StatisticsEventFilter; | 31 | import org.thingsboard.server.common.data.event.StatisticsEventFilter; |
33 | import org.thingsboard.server.common.data.id.EntityId; | 32 | import org.thingsboard.server.common.data.id.EntityId; |
@@ -40,6 +39,10 @@ import org.thingsboard.server.dao.event.EventDao; | @@ -40,6 +39,10 @@ import org.thingsboard.server.dao.event.EventDao; | ||
40 | import org.thingsboard.server.dao.model.sql.EventEntity; | 39 | import org.thingsboard.server.dao.model.sql.EventEntity; |
41 | import org.thingsboard.server.dao.sql.JpaAbstractDao; | 40 | import org.thingsboard.server.dao.sql.JpaAbstractDao; |
42 | 41 | ||
42 | +import java.sql.Connection; | ||
43 | +import java.sql.PreparedStatement; | ||
44 | +import java.sql.ResultSet; | ||
45 | +import java.sql.SQLException; | ||
43 | import java.util.List; | 46 | import java.util.List; |
44 | import java.util.Objects; | 47 | import java.util.Objects; |
45 | import java.util.Optional; | 48 | import java.util.Optional; |
@@ -256,6 +259,25 @@ public class JpaBaseEventDao extends JpaAbstractDao<EventEntity, Event> implemen | @@ -256,6 +259,25 @@ public class JpaBaseEventDao extends JpaAbstractDao<EventEntity, Event> implemen | ||
256 | return DaoUtil.convertDataList(latest); | 259 | return DaoUtil.convertDataList(latest); |
257 | } | 260 | } |
258 | 261 | ||
262 | + @Override | ||
263 | + public void cleanupEvents(long otherEventsTtl, long debugEventsTtl) { | ||
264 | + log.info("Going to cleanup old events using debug events ttl: {}s and other events ttl: {}s", debugEventsTtl, otherEventsTtl); | ||
265 | + try (Connection connection = dataSource.getConnection(); | ||
266 | + PreparedStatement stmt = connection.prepareStatement("call cleanup_events_by_ttl(?,?,?)")) { | ||
267 | + stmt.setLong(1, otherEventsTtl); | ||
268 | + stmt.setLong(2, debugEventsTtl); | ||
269 | + stmt.setLong(3, 0); | ||
270 | + stmt.execute(); | ||
271 | + printWarnings(stmt); | ||
272 | + try (ResultSet resultSet = stmt.getResultSet()){ | ||
273 | + resultSet.next(); | ||
274 | + log.info("Total events removed by TTL: [{}]", resultSet.getLong(1)); | ||
275 | + } | ||
276 | + } catch (SQLException e) { | ||
277 | + log.error("SQLException occurred during events TTL task execution ", e); | ||
278 | + } | ||
279 | + } | ||
280 | + | ||
259 | public Optional<Event> save(EventEntity entity, boolean ifNotExists) { | 281 | public Optional<Event> save(EventEntity entity, boolean ifNotExists) { |
260 | log.debug("Save event [{}] ", entity); | 282 | log.debug("Save event [{}] ", entity); |
261 | if (entity.getTenantId() == null) { | 283 | if (entity.getTenantId() == null) { |
@@ -25,9 +25,14 @@ import org.thingsboard.server.common.data.id.EntityId; | @@ -25,9 +25,14 @@ import org.thingsboard.server.common.data.id.EntityId; | ||
25 | import org.thingsboard.server.common.data.id.TenantId; | 25 | import org.thingsboard.server.common.data.id.TenantId; |
26 | import org.thingsboard.server.common.data.kv.ReadTsKvQuery; | 26 | import org.thingsboard.server.common.data.kv.ReadTsKvQuery; |
27 | import org.thingsboard.server.common.data.kv.TsKvEntry; | 27 | import org.thingsboard.server.common.data.kv.TsKvEntry; |
28 | +import org.thingsboard.server.dao.model.ModelConstants; | ||
28 | import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; | 29 | import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; |
29 | 30 | ||
30 | import javax.annotation.Nullable; | 31 | import javax.annotation.Nullable; |
32 | +import java.sql.Connection; | ||
33 | +import java.sql.PreparedStatement; | ||
34 | +import java.sql.ResultSet; | ||
35 | +import java.sql.SQLException; | ||
31 | import java.util.List; | 36 | import java.util.List; |
32 | import java.util.Objects; | 37 | import java.util.Objects; |
33 | import java.util.concurrent.TimeUnit; | 38 | import java.util.concurrent.TimeUnit; |
@@ -62,6 +67,24 @@ public abstract class AbstractSqlTimeseriesDao extends BaseAbstractSqlTimeseries | @@ -62,6 +67,24 @@ public abstract class AbstractSqlTimeseriesDao extends BaseAbstractSqlTimeseries | ||
62 | @Value("${sql.ttl.ts.ts_key_value_ttl:0}") | 67 | @Value("${sql.ttl.ts.ts_key_value_ttl:0}") |
63 | private long systemTtl; | 68 | private long systemTtl; |
64 | 69 | ||
70 | + public void cleanup(long systemTtl) { | ||
71 | + log.info("Going to cleanup old timeseries data using ttl: {}s", systemTtl); | ||
72 | + try (Connection connection = dataSource.getConnection(); | ||
73 | + PreparedStatement stmt = connection.prepareStatement("call cleanup_timeseries_by_ttl(?,?,?)")) { | ||
74 | + stmt.setObject(1, ModelConstants.NULL_UUID); | ||
75 | + stmt.setLong(2, systemTtl); | ||
76 | + stmt.setLong(3, 0); | ||
77 | + stmt.execute(); | ||
78 | + printWarnings(stmt); | ||
79 | + try (ResultSet resultSet = stmt.getResultSet()) { | ||
80 | + resultSet.next(); | ||
81 | + log.info("Total telemetry removed stats by TTL for entities: [{}]", resultSet.getLong(1)); | ||
82 | + } | ||
83 | + } catch (SQLException e) { | ||
84 | + log.error("SQLException occurred during timeseries TTL task execution ", e); | ||
85 | + } | ||
86 | + } | ||
87 | + | ||
65 | protected ListenableFuture<List<TsKvEntry>> processFindAllAsync(TenantId tenantId, EntityId entityId, List<ReadTsKvQuery> queries) { | 88 | protected ListenableFuture<List<TsKvEntry>> processFindAllAsync(TenantId tenantId, EntityId entityId, List<ReadTsKvQuery> queries) { |
66 | List<ListenableFuture<List<TsKvEntry>>> futures = queries | 89 | List<ListenableFuture<List<TsKvEntry>>> futures = queries |
67 | .stream() | 90 | .stream() |
@@ -54,4 +54,9 @@ public class JpaHsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa | @@ -54,4 +54,9 @@ public class JpaHsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa | ||
54 | return Futures.transform(tsQueue.add(entity), v -> dataPointDays, MoreExecutors.directExecutor()); | 54 | return Futures.transform(tsQueue.add(entity), v -> dataPointDays, MoreExecutors.directExecutor()); |
55 | } | 55 | } |
56 | 56 | ||
57 | + @Override | ||
58 | + public void cleanup(long systemTtl) { | ||
59 | + | ||
60 | + } | ||
61 | + | ||
57 | } | 62 | } |
@@ -35,6 +35,10 @@ import org.thingsboard.server.dao.timeseries.SqlTsPartitionDate; | @@ -35,6 +35,10 @@ import org.thingsboard.server.dao.timeseries.SqlTsPartitionDate; | ||
35 | import org.thingsboard.server.dao.util.PsqlDao; | 35 | import org.thingsboard.server.dao.util.PsqlDao; |
36 | import org.thingsboard.server.dao.util.SqlTsDao; | 36 | import org.thingsboard.server.dao.util.SqlTsDao; |
37 | 37 | ||
38 | +import java.sql.Connection; | ||
39 | +import java.sql.PreparedStatement; | ||
40 | +import java.sql.ResultSet; | ||
41 | +import java.sql.SQLException; | ||
38 | import java.time.Instant; | 42 | import java.time.Instant; |
39 | import java.time.LocalDateTime; | 43 | import java.time.LocalDateTime; |
40 | import java.time.ZoneOffset; | 44 | import java.time.ZoneOffset; |
@@ -62,6 +66,7 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa | @@ -62,6 +66,7 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa | ||
62 | @Value("${sql.postgres.ts_key_value_partitioning:MONTHS}") | 66 | @Value("${sql.postgres.ts_key_value_partitioning:MONTHS}") |
63 | private String partitioning; | 67 | private String partitioning; |
64 | 68 | ||
69 | + | ||
65 | @Override | 70 | @Override |
66 | protected void init() { | 71 | protected void init() { |
67 | super.init(); | 72 | super.init(); |
@@ -93,6 +98,30 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa | @@ -93,6 +98,30 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa | ||
93 | return Futures.transform(tsQueue.add(entity), v -> dataPointDays, MoreExecutors.directExecutor()); | 98 | return Futures.transform(tsQueue.add(entity), v -> dataPointDays, MoreExecutors.directExecutor()); |
94 | } | 99 | } |
95 | 100 | ||
101 | + @Override | ||
102 | + public void cleanup(long systemTtl) { | ||
103 | + cleanupPartitions(systemTtl); | ||
104 | + super.cleanup(systemTtl); | ||
105 | + } | ||
106 | + | ||
107 | + private void cleanupPartitions(long systemTtl) { | ||
108 | + log.info("Going to cleanup old timeseries data partitions using partition type: {} and ttl: {}s", partitioning, systemTtl); | ||
109 | + try (Connection connection = dataSource.getConnection(); | ||
110 | + PreparedStatement stmt = connection.prepareStatement("call drop_partitions_by_max_ttl(?,?,?)")) { | ||
111 | + stmt.setString(1, partitioning); | ||
112 | + stmt.setLong(2, systemTtl); | ||
113 | + stmt.setLong(3, 0); | ||
114 | + stmt.execute(); | ||
115 | + printWarnings(stmt); | ||
116 | + try (ResultSet resultSet = stmt.getResultSet()) { | ||
117 | + resultSet.next(); | ||
118 | + log.info("Total partitions removed by TTL: [{}]", resultSet.getLong(1)); | ||
119 | + } | ||
120 | + } catch (SQLException e) { | ||
121 | + log.error("SQLException occurred during TTL task execution ", e); | ||
122 | + } | ||
123 | + } | ||
124 | + | ||
96 | private void savePartitionIfNotExist(long ts) { | 125 | private void savePartitionIfNotExist(long ts) { |
97 | if (!tsFormat.equals(SqlTsPartitionDate.INDEFINITE) && ts >= 0) { | 126 | if (!tsFormat.equals(SqlTsPartitionDate.INDEFINITE) && ts >= 0) { |
98 | LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); | 127 | LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); |
@@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery; | @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery; | ||
34 | import org.thingsboard.server.common.data.kv.TsKvEntry; | 34 | import org.thingsboard.server.common.data.kv.TsKvEntry; |
35 | import org.thingsboard.server.common.stats.StatsFactory; | 35 | import org.thingsboard.server.common.stats.StatsFactory; |
36 | import org.thingsboard.server.dao.DaoUtil; | 36 | import org.thingsboard.server.dao.DaoUtil; |
37 | +import org.thingsboard.server.dao.model.ModelConstants; | ||
37 | import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; | 38 | import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; |
38 | import org.thingsboard.server.dao.model.sqlts.timescale.ts.TimescaleTsKvEntity; | 39 | import org.thingsboard.server.dao.model.sqlts.timescale.ts.TimescaleTsKvEntity; |
39 | import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; | 40 | import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; |
@@ -45,6 +46,9 @@ import org.thingsboard.server.dao.util.TimescaleDBTsDao; | @@ -45,6 +46,9 @@ import org.thingsboard.server.dao.util.TimescaleDBTsDao; | ||
45 | 46 | ||
46 | import javax.annotation.PostConstruct; | 47 | import javax.annotation.PostConstruct; |
47 | import javax.annotation.PreDestroy; | 48 | import javax.annotation.PreDestroy; |
49 | +import java.sql.CallableStatement; | ||
50 | +import java.sql.SQLException; | ||
51 | +import java.sql.Types; | ||
48 | import java.util.*; | 52 | import java.util.*; |
49 | import java.util.concurrent.CompletableFuture; | 53 | import java.util.concurrent.CompletableFuture; |
50 | import java.util.function.Function; | 54 | import java.util.function.Function; |
@@ -156,6 +160,11 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements | @@ -156,6 +160,11 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements | ||
156 | } | 160 | } |
157 | } | 161 | } |
158 | 162 | ||
163 | + @Override | ||
164 | + public void cleanup(long systemTtl) { | ||
165 | + super.cleanup(systemTtl); | ||
166 | + } | ||
167 | + | ||
159 | private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { | 168 | private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { |
160 | String strKey = query.getKey(); | 169 | String strKey = query.getKey(); |
161 | Integer keyId = getOrSaveKeyId(strKey); | 170 | Integer keyId = getOrSaveKeyId(strKey); |
@@ -127,6 +127,11 @@ public class BaseTimeseriesService implements TimeseriesService { | @@ -127,6 +127,11 @@ public class BaseTimeseriesService implements TimeseriesService { | ||
127 | } | 127 | } |
128 | 128 | ||
129 | @Override | 129 | @Override |
130 | + public void cleanup(long systemTtl) { | ||
131 | + timeseriesDao.cleanup(systemTtl); | ||
132 | + } | ||
133 | + | ||
134 | + @Override | ||
130 | public ListenableFuture<Integer> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { | 135 | public ListenableFuture<Integer> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { |
131 | validate(entityId); | 136 | validate(entityId); |
132 | if (tsKvEntry == null) { | 137 | if (tsKvEntry == null) { |
@@ -288,6 +288,11 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD | @@ -288,6 +288,11 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD | ||
288 | } | 288 | } |
289 | } | 289 | } |
290 | 290 | ||
291 | + @Override | ||
292 | + public void cleanup(long systemTtl) { | ||
293 | + //Cleanup by TTL is native for Cassandra | ||
294 | + } | ||
295 | + | ||
291 | private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { | 296 | private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { |
292 | long minPartition = toPartitionTs(query.getStartTs()); | 297 | long minPartition = toPartitionTs(query.getStartTs()); |
293 | long maxPartition = toPartitionTs(query.getEndTs()); | 298 | long maxPartition = toPartitionTs(query.getEndTs()); |
@@ -38,4 +38,6 @@ public interface TimeseriesDao { | @@ -38,4 +38,6 @@ public interface TimeseriesDao { | ||
38 | ListenableFuture<Void> remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query); | 38 | ListenableFuture<Void> remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query); |
39 | 39 | ||
40 | ListenableFuture<Void> removePartition(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query); | 40 | ListenableFuture<Void> removePartition(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query); |
41 | + | ||
42 | + void cleanup(long systemTtl); | ||
41 | } | 43 | } |
@@ -38,17 +38,18 @@ CREATE OR REPLACE PROCEDURE drop_partitions_by_max_ttl(IN partition_type varchar | @@ -38,17 +38,18 @@ CREATE OR REPLACE PROCEDURE drop_partitions_by_max_ttl(IN partition_type varchar | ||
38 | LANGUAGE plpgsql AS | 38 | LANGUAGE plpgsql AS |
39 | $$ | 39 | $$ |
40 | DECLARE | 40 | DECLARE |
41 | - max_tenant_ttl bigint; | ||
42 | - max_customer_ttl bigint; | ||
43 | - max_ttl bigint; | ||
44 | - date timestamp; | ||
45 | - partition_by_max_ttl_date varchar; | ||
46 | - partition_month varchar; | ||
47 | - partition_day varchar; | ||
48 | - partition_year varchar; | ||
49 | - partition varchar; | ||
50 | - partition_to_delete varchar; | ||
51 | - | 41 | + max_tenant_ttl bigint; |
42 | + max_customer_ttl bigint; | ||
43 | + max_ttl bigint; | ||
44 | + date timestamp; | ||
45 | + partition_by_max_ttl_date varchar; | ||
46 | + partition_by_max_ttl_month varchar; | ||
47 | + partition_by_max_ttl_day varchar; | ||
48 | + partition_by_max_ttl_year varchar; | ||
49 | + partition varchar; | ||
50 | + partition_year integer; | ||
51 | + partition_month integer; | ||
52 | + partition_day integer; | ||
52 | 53 | ||
53 | BEGIN | 54 | BEGIN |
54 | SELECT max(attribute_kv.long_v) | 55 | SELECT max(attribute_kv.long_v) |
@@ -65,53 +66,138 @@ BEGIN | @@ -65,53 +66,138 @@ BEGIN | ||
65 | if max_ttl IS NOT NULL AND max_ttl > 0 THEN | 66 | if max_ttl IS NOT NULL AND max_ttl > 0 THEN |
66 | date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - max_ttl); | 67 | date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - max_ttl); |
67 | partition_by_max_ttl_date := get_partition_by_max_ttl_date(partition_type, date); | 68 | partition_by_max_ttl_date := get_partition_by_max_ttl_date(partition_type, date); |
69 | + RAISE NOTICE 'Date by max ttl: %', date; | ||
68 | RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date; | 70 | RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date; |
69 | IF partition_by_max_ttl_date IS NOT NULL THEN | 71 | IF partition_by_max_ttl_date IS NOT NULL THEN |
70 | CASE | 72 | CASE |
71 | WHEN partition_type = 'DAYS' THEN | 73 | WHEN partition_type = 'DAYS' THEN |
72 | - partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); | ||
73 | - partition_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); | ||
74 | - partition_day := SPLIT_PART(partition_by_max_ttl_date, '_', 5); | 74 | + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); |
75 | + partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); | ||
76 | + partition_by_max_ttl_day := SPLIT_PART(partition_by_max_ttl_date, '_', 5); | ||
75 | WHEN partition_type = 'MONTHS' THEN | 77 | WHEN partition_type = 'MONTHS' THEN |
76 | - partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); | ||
77 | - partition_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); | 78 | + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); |
79 | + partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); | ||
78 | ELSE | 80 | ELSE |
79 | - partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); | 81 | + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); |
80 | END CASE; | 82 | END CASE; |
81 | - FOR partition IN SELECT tablename | ||
82 | - FROM pg_tables | ||
83 | - WHERE schemaname = 'public' | ||
84 | - AND tablename like 'ts_kv_' || '%' | ||
85 | - AND tablename != 'ts_kv_latest' | ||
86 | - AND tablename != 'ts_kv_dictionary' | ||
87 | - AND tablename != 'ts_kv_indefinite' | ||
88 | - LOOP | ||
89 | - IF partition != partition_by_max_ttl_date THEN | ||
90 | - IF partition_year IS NOT NULL THEN | ||
91 | - IF SPLIT_PART(partition, '_', 3)::integer < partition_year::integer THEN | ||
92 | - partition_to_delete := partition; | ||
93 | - ELSE | ||
94 | - IF partition_month IS NOT NULL THEN | ||
95 | - IF SPLIT_PART(partition, '_', 4)::integer < partition_month::integer THEN | ||
96 | - partition_to_delete := partition; | 83 | + IF partition_by_max_ttl_year IS NULL THEN |
84 | + RAISE NOTICE 'Failed to remove partitions by max ttl date due to partition_by_max_ttl_year is null!'; | ||
85 | + ELSE | ||
86 | + IF partition_type = 'YEARS' THEN | ||
87 | + FOR partition IN SELECT tablename | ||
88 | + FROM pg_tables | ||
89 | + WHERE schemaname = 'public' | ||
90 | + AND tablename like 'ts_kv_' || '%' | ||
91 | + AND tablename != 'ts_kv_latest' | ||
92 | + AND tablename != 'ts_kv_dictionary' | ||
93 | + AND tablename != 'ts_kv_indefinite' | ||
94 | + AND tablename != partition_by_max_ttl_date | ||
95 | + LOOP | ||
96 | + partition_year := SPLIT_PART(partition, '_', 3)::integer; | ||
97 | + IF partition_year < partition_by_max_ttl_year::integer THEN | ||
98 | + RAISE NOTICE 'Partition to delete by max ttl: %', partition; | ||
99 | + EXECUTE format('DROP TABLE IF EXISTS %I', partition); | ||
100 | + deleted := deleted + 1; | ||
101 | + END IF; | ||
102 | + END LOOP; | ||
103 | + ELSE | ||
104 | + IF partition_type = 'MONTHS' THEN | ||
105 | + IF partition_by_max_ttl_month IS NULL THEN | ||
106 | + RAISE NOTICE 'Failed to remove months partitions by max ttl date due to partition_by_max_ttl_month is null!'; | ||
107 | + ELSE | ||
108 | + FOR partition IN SELECT tablename | ||
109 | + FROM pg_tables | ||
110 | + WHERE schemaname = 'public' | ||
111 | + AND tablename like 'ts_kv_' || '%' | ||
112 | + AND tablename != 'ts_kv_latest' | ||
113 | + AND tablename != 'ts_kv_dictionary' | ||
114 | + AND tablename != 'ts_kv_indefinite' | ||
115 | + AND tablename != partition_by_max_ttl_date | ||
116 | + LOOP | ||
117 | + partition_year := SPLIT_PART(partition, '_', 3)::integer; | ||
118 | + IF partition_year > partition_by_max_ttl_year::integer THEN | ||
119 | + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; | ||
120 | + CONTINUE; | ||
97 | ELSE | 121 | ELSE |
98 | - IF partition_day IS NOT NULL THEN | ||
99 | - IF SPLIT_PART(partition, '_', 5)::integer < partition_day::integer THEN | ||
100 | - partition_to_delete := partition; | 122 | + IF partition_year < partition_by_max_ttl_year::integer THEN |
123 | + RAISE NOTICE 'Partition to delete by max ttl: %', partition; | ||
124 | + EXECUTE format('DROP TABLE IF EXISTS %I', partition); | ||
125 | + deleted := deleted + 1; | ||
126 | + ELSE | ||
127 | + partition_month := SPLIT_PART(partition, '_', 4)::integer; | ||
128 | + IF partition_year = partition_by_max_ttl_year::integer THEN | ||
129 | + IF partition_month >= partition_by_max_ttl_month::integer THEN | ||
130 | + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; | ||
131 | + CONTINUE; | ||
132 | + ELSE | ||
133 | + RAISE NOTICE 'Partition to delete by max ttl: %', partition; | ||
134 | + EXECUTE format('DROP TABLE IF EXISTS %I', partition); | ||
135 | + deleted := deleted + 1; | ||
136 | + END IF; | ||
101 | END IF; | 137 | END IF; |
102 | END IF; | 138 | END IF; |
103 | END IF; | 139 | END IF; |
140 | + END LOOP; | ||
141 | + END IF; | ||
142 | + ELSE | ||
143 | + IF partition_type = 'DAYS' THEN | ||
144 | + IF partition_by_max_ttl_month IS NULL THEN | ||
145 | + RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_month is null!'; | ||
146 | + ELSE | ||
147 | + IF partition_by_max_ttl_day IS NULL THEN | ||
148 | + RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_day is null!'; | ||
149 | + ELSE | ||
150 | + FOR partition IN SELECT tablename | ||
151 | + FROM pg_tables | ||
152 | + WHERE schemaname = 'public' | ||
153 | + AND tablename like 'ts_kv_' || '%' | ||
154 | + AND tablename != 'ts_kv_latest' | ||
155 | + AND tablename != 'ts_kv_dictionary' | ||
156 | + AND tablename != 'ts_kv_indefinite' | ||
157 | + AND tablename != partition_by_max_ttl_date | ||
158 | + LOOP | ||
159 | + partition_year := SPLIT_PART(partition, '_', 3)::integer; | ||
160 | + IF partition_year > partition_by_max_ttl_year::integer THEN | ||
161 | + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; | ||
162 | + CONTINUE; | ||
163 | + ELSE | ||
164 | + IF partition_year < partition_by_max_ttl_year::integer THEN | ||
165 | + RAISE NOTICE 'Partition to delete by max ttl: %', partition; | ||
166 | + EXECUTE format('DROP TABLE IF EXISTS %I', partition); | ||
167 | + deleted := deleted + 1; | ||
168 | + ELSE | ||
169 | + partition_month := SPLIT_PART(partition, '_', 4)::integer; | ||
170 | + IF partition_month > partition_by_max_ttl_month::integer THEN | ||
171 | + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; | ||
172 | + CONTINUE; | ||
173 | + ELSE | ||
174 | + IF partition_month < partition_by_max_ttl_month::integer THEN | ||
175 | + RAISE NOTICE 'Partition to delete by max ttl: %', partition; | ||
176 | + EXECUTE format('DROP TABLE IF EXISTS %I', partition); | ||
177 | + deleted := deleted + 1; | ||
178 | + ELSE | ||
179 | + partition_day := SPLIT_PART(partition, '_', 5)::integer; | ||
180 | + IF partition_day >= partition_by_max_ttl_day::integer THEN | ||
181 | + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; | ||
182 | + CONTINUE; | ||
183 | + ELSE | ||
184 | + IF partition_day < partition_by_max_ttl_day::integer THEN | ||
185 | + RAISE NOTICE 'Partition to delete by max ttl: %', partition; | ||
186 | + EXECUTE format('DROP TABLE IF EXISTS %I', partition); | ||
187 | + deleted := deleted + 1; | ||
188 | + END IF; | ||
189 | + END IF; | ||
190 | + END IF; | ||
191 | + END IF; | ||
192 | + END IF; | ||
193 | + END IF; | ||
194 | + END LOOP; | ||
104 | END IF; | 195 | END IF; |
105 | END IF; | 196 | END IF; |
106 | END IF; | 197 | END IF; |
107 | - IF partition_to_delete IS NOT NULL THEN | ||
108 | - RAISE NOTICE 'Partition to delete by max ttl: %', partition_to_delete; | ||
109 | - EXECUTE format('DROP TABLE IF EXISTS %I', partition_to_delete); | ||
110 | - partition_to_delete := NULL; | ||
111 | - deleted := deleted + 1; | ||
112 | - END IF; | ||
113 | END IF; | 198 | END IF; |
114 | - END LOOP; | 199 | + END IF; |
200 | + END IF; | ||
115 | END IF; | 201 | END IF; |
116 | END IF; | 202 | END IF; |
117 | END | 203 | END |
@@ -127,8 +213,6 @@ BEGIN | @@ -127,8 +213,6 @@ BEGIN | ||
127 | partition := 'ts_kv_' || to_char(date, 'yyyy') || '_' || to_char(date, 'MM'); | 213 | partition := 'ts_kv_' || to_char(date, 'yyyy') || '_' || to_char(date, 'MM'); |
128 | WHEN partition_type = 'YEARS' THEN | 214 | WHEN partition_type = 'YEARS' THEN |
129 | partition := 'ts_kv_' || to_char(date, 'yyyy'); | 215 | partition := 'ts_kv_' || to_char(date, 'yyyy'); |
130 | - WHEN partition_type = 'INDEFINITE' THEN | ||
131 | - partition := NULL; | ||
132 | ELSE | 216 | ELSE |
133 | partition := NULL; | 217 | partition := NULL; |
134 | END CASE; | 218 | END CASE; |
@@ -2378,7 +2378,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { | @@ -2378,7 +2378,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { | ||
2378 | 2378 | ||
2379 | public void setUserCredentialsEnabled(UserId userId, boolean userCredentialsEnabled) { | 2379 | public void setUserCredentialsEnabled(UserId userId, boolean userCredentialsEnabled) { |
2380 | restTemplate.postForLocation( | 2380 | restTemplate.postForLocation( |
2381 | - baseURL + "/api/user/{userId}/userCredentialsEnabled?serCredentialsEnabled={serCredentialsEnabled}", | 2381 | + baseURL + "/api/user/{userId}/userCredentialsEnabled?userCredentialsEnabled={userCredentialsEnabled}", |
2382 | null, | 2382 | null, |
2383 | userId.getId(), | 2383 | userId.getId(), |
2384 | userCredentialsEnabled); | 2384 | userCredentialsEnabled); |
@@ -2369,7 +2369,7 @@ | @@ -2369,7 +2369,7 @@ | ||
2369 | "delete-resource-text": "Be careful, after the confirmation the resource will become unrecoverable.", | 2369 | "delete-resource-text": "Be careful, after the confirmation the resource will become unrecoverable.", |
2370 | "delete-resource-title": "Are you sure you want to delete the resource '{{resourceTitle}}'?", | 2370 | "delete-resource-title": "Are you sure you want to delete the resource '{{resourceTitle}}'?", |
2371 | "delete-resources-action-title": "Delete { count, plural, 1 {1 resource} other {# resources} }", | 2371 | "delete-resources-action-title": "Delete { count, plural, 1 {1 resource} other {# resources} }", |
2372 | - "delete-resources-text": "Be careful, after the confirmation all selected resources will be removed.", | 2372 | + "delete-resources-text": "Please note that the selected resources, even if they are used in device profiles, will be deleted.", |
2373 | "delete-resources-title": "Are you sure you want to delete { count, plural, 1 {1 resource} other {# resources} }?", | 2373 | "delete-resources-title": "Are you sure you want to delete { count, plural, 1 {1 resource} other {# resources} }?", |
2374 | "download": "Download resource", | 2374 | "download": "Download resource", |
2375 | "drop-file": "Drop a resource file or click to select a file to upload.", | 2375 | "drop-file": "Drop a resource file or click to select a file to upload.", |