Commit 3b5a4941ff6161acfc2fba95fc6a91af059231cc
1 parent
1da32793
Cassandra MsqQueue initial implementation
Showing
25 changed files
with
1268 additions
and
81 deletions
... | ... | @@ -251,6 +251,9 @@ spring: |
251 | 251 | username: "${SPRING_DATASOURCE_USERNAME:sa}" |
252 | 252 | password: "${SPRING_DATASOURCE_PASSWORD:}" |
253 | 253 | |
254 | +rule: | |
255 | + queue: | |
256 | + msg_partitioning: "${QUEUE_MSG_PARTITIONING:HOURS}" | |
254 | 257 | |
255 | 258 | # PostgreSQL DAO Configuration |
256 | 259 | #spring: | ... | ... |
... | ... | @@ -555,48 +555,45 @@ CREATE TABLE IF NOT EXISTS thingsboard.msg_queue ( |
555 | 555 | partition bigint, |
556 | 556 | ts bigint, |
557 | 557 | msg blob, |
558 | - PRIMARY KEY ((node_id, cluster_hash, partition), ts) | |
559 | - WITH CLUSTERING ORDER BY (ts DESC) | |
560 | - AND compaction = { | |
561 | - 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', | |
562 | - 'min_threshold': '5', | |
563 | - 'base_time_seconds': '43200', | |
564 | - 'max_window_size_seconds': '43200' | |
565 | - 'tombstone_threshold': '0.9', | |
566 | - 'unchecked_tombstone_compaction': 'true', | |
567 | - }; | |
568 | -); | |
558 | + PRIMARY KEY ((node_id, clustered_hash, partition), ts)) | |
559 | +WITH CLUSTERING ORDER BY (ts DESC) | |
560 | +AND compaction = { | |
561 | + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', | |
562 | + 'min_threshold': '5', | |
563 | + 'base_time_seconds': '43200', | |
564 | + 'max_window_size_seconds': '43200', | |
565 | + 'tombstone_threshold': '0.9', | |
566 | + 'unchecked_tombstone_compaction': 'true' | |
567 | +}; | |
568 | + | |
569 | 569 | |
570 | 570 | CREATE TABLE IF NOT EXISTS thingsboard.msg_ack_queue ( |
571 | 571 | node_id timeuuid, |
572 | 572 | clustered_hash bigint, |
573 | 573 | partition bigint, |
574 | - ts bigint, | |
575 | 574 | msg_id timeuuid, |
576 | - PRIMARY KEY ((node_id, cluster_hash, partition), ts) | |
577 | - WITH CLUSTERING ORDER BY (ts DESC) | |
578 | - AND compaction = { | |
579 | - 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', | |
580 | - 'min_threshold': '5', | |
581 | - 'base_time_seconds': '43200', | |
582 | - 'max_window_size_seconds': '43200' | |
583 | - 'tombstone_threshold': '0.9', | |
584 | - 'unchecked_tombstone_compaction': 'true', | |
585 | - }; | |
586 | -); | |
575 | + PRIMARY KEY ((node_id, clustered_hash, partition), msg_id)) | |
576 | +WITH CLUSTERING ORDER BY (msg_id DESC) | |
577 | +AND compaction = { | |
578 | + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', | |
579 | + 'min_threshold': '5', | |
580 | + 'base_time_seconds': '43200', | |
581 | + 'max_window_size_seconds': '43200', | |
582 | + 'tombstone_threshold': '0.9', | |
583 | + 'unchecked_tombstone_compaction': 'true' | |
584 | +}; | |
587 | 585 | |
588 | 586 | CREATE TABLE IF NOT EXISTS thingsboard.processed_msg_partitions ( |
589 | 587 | node_id timeuuid, |
590 | 588 | clustered_hash bigint, |
591 | 589 | partition bigint, |
592 | - PRIMARY KEY ((node_id, cluster_hash), partition) | |
593 | - WITH CLUSTERING ORDER BY (partition DESC) | |
594 | - AND compaction = { | |
595 | - 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', | |
596 | - 'min_threshold': '5', | |
597 | - 'base_time_seconds': '43200', | |
598 | - 'max_window_size_seconds': '43200' | |
599 | - 'tombstone_threshold': '0.9', | |
600 | - 'unchecked_tombstone_compaction': 'true', | |
601 | - }; | |
602 | -); | |
\ No newline at end of file | ||
590 | + PRIMARY KEY ((node_id, clustered_hash), partition)) | |
591 | +WITH CLUSTERING ORDER BY (partition DESC) | |
592 | +AND compaction = { | |
593 | + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', | |
594 | + 'min_threshold': '5', | |
595 | + 'base_time_seconds': '43200', | |
596 | + 'max_window_size_seconds': '43200', | |
597 | + 'tombstone_threshold': '0.9', | |
598 | + 'unchecked_tombstone_compaction': 'true' | |
599 | +}; | ... | ... |
... | ... | @@ -19,6 +19,7 @@ import lombok.Data; |
19 | 19 | |
20 | 20 | import java.io.Serializable; |
21 | 21 | import java.util.Map; |
22 | +import java.util.concurrent.ConcurrentHashMap; | |
22 | 23 | |
23 | 24 | /** |
24 | 25 | * Created by ashvayka on 13.01.18. |
... | ... | @@ -26,7 +27,7 @@ import java.util.Map; |
26 | 27 | @Data |
27 | 28 | public final class TbMsgMetaData implements Serializable { |
28 | 29 | |
29 | - private Map<String, String> data; | |
30 | + private Map<String, String> data = new ConcurrentHashMap<>(); | |
30 | 31 | |
31 | 32 | public String getValue(String key) { |
32 | 33 | return data.get(key); | ... | ... |
... | ... | @@ -63,5 +63,107 @@ |
63 | 63 | <artifactId>rule-engine-api</artifactId> |
64 | 64 | <version>1.4.0-SNAPSHOT</version> |
65 | 65 | </dependency> |
66 | + <dependency> | |
67 | + <groupId>com.google.protobuf</groupId> | |
68 | + <artifactId>protobuf-java</artifactId> | |
69 | + <scope>provided</scope> | |
70 | + </dependency> | |
71 | + <dependency> | |
72 | + <groupId>com.google.guava</groupId> | |
73 | + <artifactId>guava</artifactId> | |
74 | + </dependency> | |
75 | + <dependency> | |
76 | + <groupId>com.datastax.cassandra</groupId> | |
77 | + <artifactId>cassandra-driver-core</artifactId> | |
78 | + </dependency> | |
79 | + <dependency> | |
80 | + <groupId>com.datastax.cassandra</groupId> | |
81 | + <artifactId>cassandra-driver-mapping</artifactId> | |
82 | + </dependency> | |
83 | + <dependency> | |
84 | + <groupId>com.datastax.cassandra</groupId> | |
85 | + <artifactId>cassandra-driver-extras</artifactId> | |
86 | + </dependency> | |
87 | + | |
88 | + <dependency> | |
89 | + <groupId>junit</groupId> | |
90 | + <artifactId>junit</artifactId> | |
91 | + <version>${junit.version}</version> | |
92 | + <scope>test</scope> | |
93 | + </dependency> | |
94 | + <dependency> | |
95 | + <groupId>org.cassandraunit</groupId> | |
96 | + <artifactId>cassandra-unit</artifactId> | |
97 | + <exclusions> | |
98 | + <exclusion> | |
99 | + <groupId>org.slf4j</groupId> | |
100 | + <artifactId>slf4j-log4j12</artifactId> | |
101 | + </exclusion> | |
102 | + </exclusions> | |
103 | + <scope>test</scope> | |
104 | + </dependency> | |
105 | + <dependency> | |
106 | + <groupId>org.mockito</groupId> | |
107 | + <artifactId>mockito-all</artifactId> | |
108 | + <scope>test</scope> | |
109 | + </dependency> | |
110 | + <dependency> | |
111 | + <groupId>org.junit.jupiter</groupId> | |
112 | + <artifactId>junit-jupiter-api</artifactId> | |
113 | + <version>RELEASE</version> | |
114 | + </dependency> | |
115 | + | |
116 | + <!--<dependency>--> | |
117 | + <!--<groupId>org.springframework.boot</groupId>--> | |
118 | + <!--<artifactId>spring-boot-starter-web</artifactId>--> | |
119 | + <!--</dependency>--> | |
120 | + | |
66 | 121 | </dependencies> |
122 | + | |
123 | + <build> | |
124 | + <plugins> | |
125 | + <plugin> | |
126 | + <groupId>org.apache.maven.plugins</groupId> | |
127 | + <artifactId>maven-dependency-plugin</artifactId> | |
128 | + </plugin> | |
129 | + <plugin> | |
130 | + <groupId>org.xolstice.maven.plugins</groupId> | |
131 | + <artifactId>protobuf-maven-plugin</artifactId> | |
132 | + </plugin> | |
133 | + <plugin> | |
134 | + <groupId>org.codehaus.mojo</groupId> | |
135 | + <artifactId>build-helper-maven-plugin</artifactId> | |
136 | + </plugin> | |
137 | + | |
138 | + | |
139 | + | |
140 | + | |
141 | + <plugin> | |
142 | + <groupId>org.springframework.boot</groupId> | |
143 | + <artifactId>spring-boot-maven-plugin</artifactId> | |
144 | + <configuration> | |
145 | + <mainClass>org.thingsboard.rule.engine.tool.QueueBenchmark</mainClass> | |
146 | + <classifier>boot</classifier> | |
147 | + <layout>ZIP</layout> | |
148 | + <executable>true</executable> | |
149 | + <excludeDevtools>true</excludeDevtools> | |
150 | + <!--<embeddedLaunchScriptProperties>--> | |
151 | + <!--<confFolder>${pkg.installFolder}/conf</confFolder>--> | |
152 | + <!--<logFolder>${pkg.unixLogFolder}</logFolder>--> | |
153 | + <!--<logFilename>${pkg.name}.out</logFilename>--> | |
154 | + <!--<initInfoProvides>${pkg.name}</initInfoProvides>--> | |
155 | + <!--</embeddedLaunchScriptProperties>--> | |
156 | + </configuration> | |
157 | + <executions> | |
158 | + <execution> | |
159 | + <goals> | |
160 | + <goal>repackage</goal> | |
161 | + </goals> | |
162 | + </execution> | |
163 | + </executions> | |
164 | + </plugin> | |
165 | + | |
166 | + </plugins> | |
167 | + </build> | |
168 | + | |
67 | 169 | </project> |
\ No newline at end of file | ... | ... |
... | ... | @@ -15,68 +15,65 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.rule.engine.queue.cassandra; |
17 | 17 | |
18 | +import com.datastax.driver.core.utils.UUIDs; | |
18 | 19 | import com.google.common.collect.Lists; |
19 | 20 | import com.google.common.util.concurrent.ListenableFuture; |
20 | -import org.springframework.beans.factory.annotation.Autowired; | |
21 | +import lombok.extern.slf4j.Slf4j; | |
21 | 22 | import org.springframework.stereotype.Component; |
22 | 23 | import org.thingsboard.rule.engine.api.MsqQueue; |
23 | 24 | import org.thingsboard.rule.engine.api.TbMsg; |
24 | 25 | import org.thingsboard.rule.engine.queue.cassandra.repository.AckRepository; |
25 | 26 | import org.thingsboard.rule.engine.queue.cassandra.repository.MsgRepository; |
26 | -import org.thingsboard.rule.engine.queue.cassandra.repository.ProcessedPartitionRepository; | |
27 | +import org.thingsboard.server.common.data.UUIDConverter; | |
27 | 28 | |
28 | -import java.util.Collections; | |
29 | 29 | import java.util.List; |
30 | -import java.util.Optional; | |
31 | 30 | import java.util.UUID; |
32 | 31 | |
33 | 32 | @Component |
33 | +@Slf4j | |
34 | 34 | public class CassandraMsqQueue implements MsqQueue { |
35 | 35 | |
36 | - @Autowired | |
37 | - private MsgRepository msgRepository; | |
36 | + private final MsgRepository msgRepository; | |
37 | + private final AckRepository ackRepository; | |
38 | + private final UnprocessedMsgFilter unprocessedMsgFilter; | |
39 | + private final QueuePartitioner queuePartitioner; | |
38 | 40 | |
39 | - @Autowired | |
40 | - private AckRepository ackRepository; | |
41 | - | |
42 | - @Autowired | |
43 | - private AckBuilder ackBuilder; | |
44 | - | |
45 | - @Autowired | |
46 | - private UnprocessedMsgFilter unprocessedMsgFilter; | |
41 | + public CassandraMsqQueue(MsgRepository msgRepository, AckRepository ackRepository, | |
42 | + UnprocessedMsgFilter unprocessedMsgFilter, QueuePartitioner queuePartitioner) { | |
43 | + this.msgRepository = msgRepository; | |
44 | + this.ackRepository = ackRepository; | |
45 | + this.unprocessedMsgFilter = unprocessedMsgFilter; | |
46 | + this.queuePartitioner = queuePartitioner; | |
47 | + } | |
47 | 48 | |
48 | - @Autowired | |
49 | - private ProcessedPartitionRepository processedPartitionRepository; | |
50 | 49 | |
51 | 50 | @Override |
52 | 51 | public ListenableFuture<Void> put(TbMsg msg, UUID nodeId, long clusteredHash) { |
53 | - return msgRepository.save(msg, nodeId, clusteredHash, getPartition(msg)); | |
52 | + long msgTime = getMsgTime(msg); | |
53 | + long partition = queuePartitioner.getPartition(msgTime); | |
54 | + return msgRepository.save(msg, nodeId, clusteredHash, partition, msgTime); | |
54 | 55 | } |
55 | 56 | |
56 | 57 | @Override |
57 | 58 | public ListenableFuture<Void> ack(TbMsg msg, UUID nodeId, long clusteredHash) { |
58 | - MsgAck ack = ackBuilder.build(msg, nodeId, clusteredHash); | |
59 | + long partition = queuePartitioner.getPartition(getMsgTime(msg)); | |
60 | + MsgAck ack = new MsgAck(msg.getId(), nodeId, clusteredHash, partition); | |
59 | 61 | return ackRepository.ack(ack); |
60 | 62 | } |
61 | 63 | |
62 | 64 | @Override |
63 | 65 | public Iterable<TbMsg> findUnprocessed(UUID nodeId, long clusteredHash) { |
64 | 66 | List<TbMsg> unprocessedMsgs = Lists.newArrayList(); |
65 | - for (Long partition : findUnprocessedPartitions(nodeId, clusteredHash)) { | |
66 | - Iterable<TbMsg> msgs = msgRepository.findMsgs(nodeId, clusteredHash, partition); | |
67 | - Iterable<MsgAck> acks = ackRepository.findAcks(nodeId, clusteredHash, partition); | |
67 | + for (Long partition : queuePartitioner.findUnprocessedPartitions(nodeId, clusteredHash)) { | |
68 | + List<TbMsg> msgs = msgRepository.findMsgs(nodeId, clusteredHash, partition); | |
69 | + List<MsgAck> acks = ackRepository.findAcks(nodeId, clusteredHash, partition); | |
68 | 70 | unprocessedMsgs.addAll(unprocessedMsgFilter.filter(msgs, acks)); |
69 | 71 | } |
70 | 72 | return unprocessedMsgs; |
71 | 73 | } |
72 | 74 | |
73 | - private List<Long> findUnprocessedPartitions(UUID nodeId, long clusteredHash) { | |
74 | - Optional<Long> lastPartition = processedPartitionRepository.findLastProcessedPartition(nodeId, clusteredHash); | |
75 | - return Collections.emptyList(); | |
76 | - } | |
77 | - | |
78 | - private long getPartition(TbMsg msg) { | |
79 | - return Long.MIN_VALUE; | |
75 | + private long getMsgTime(TbMsg msg) { | |
76 | + return UUIDs.unixTimestamp(msg.getId()); | |
80 | 77 | } |
81 | 78 | |
82 | 79 | } | ... | ... |
... | ... | @@ -15,17 +15,21 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.rule.engine.queue.cassandra; |
17 | 17 | |
18 | +import com.datastax.driver.core.utils.UUIDs; | |
18 | 19 | import lombok.Data; |
20 | +import lombok.EqualsAndHashCode; | |
21 | +import org.thingsboard.rule.engine.api.TbMsg; | |
22 | +import org.thingsboard.server.common.data.UUIDConverter; | |
19 | 23 | |
20 | 24 | import java.util.UUID; |
21 | 25 | |
22 | 26 | @Data |
27 | +@EqualsAndHashCode | |
23 | 28 | public class MsgAck { |
24 | 29 | |
25 | 30 | private final UUID msgId; |
26 | 31 | private final UUID nodeId; |
27 | 32 | private final long clusteredHash; |
28 | 33 | private final long partition; |
29 | - private final long ts; | |
30 | 34 | |
31 | 35 | } | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * <p> | |
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 | + * <p> | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * <p> | |
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.rule.engine.queue.cassandra; | |
17 | + | |
18 | +import com.google.common.collect.Lists; | |
19 | +import lombok.extern.slf4j.Slf4j; | |
20 | +import org.springframework.beans.factory.annotation.Value; | |
21 | +import org.springframework.stereotype.Component; | |
22 | +import org.thingsboard.rule.engine.queue.cassandra.repository.ProcessedPartitionRepository; | |
23 | +import org.thingsboard.server.dao.timeseries.TsPartitionDate; | |
24 | + | |
25 | +import java.time.Clock; | |
26 | +import java.time.Instant; | |
27 | +import java.time.LocalDateTime; | |
28 | +import java.time.ZoneOffset; | |
29 | +import java.util.List; | |
30 | +import java.util.Optional; | |
31 | +import java.util.UUID; | |
32 | + | |
33 | +@Component | |
34 | +@Slf4j | |
35 | +public class QueuePartitioner { | |
36 | + | |
37 | + private ProcessedPartitionRepository processedPartitionRepository; | |
38 | + | |
39 | + private final TsPartitionDate tsFormat; | |
40 | + private Clock clock = Clock.systemUTC(); | |
41 | + | |
42 | + public QueuePartitioner(@Value("${rule.queue.msg_partitioning}") String partitioning, | |
43 | + ProcessedPartitionRepository processedPartitionRepository) { | |
44 | + this.processedPartitionRepository = processedPartitionRepository; | |
45 | + Optional<TsPartitionDate> partition = TsPartitionDate.parse(partitioning); | |
46 | + if (partition.isPresent()) { | |
47 | + tsFormat = partition.get(); | |
48 | + } else { | |
49 | + log.warn("Incorrect configuration of partitioning {}", "MINUTES"); | |
50 | + throw new RuntimeException("Failed to parse partitioning property: " + "MINUTES" + "!"); | |
51 | + } | |
52 | + } | |
53 | + | |
54 | + public long getPartition(long ts) { | |
55 | + LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); | |
56 | + return tsFormat.truncatedTo(time).toInstant(ZoneOffset.UTC).toEpochMilli(); | |
57 | + } | |
58 | + | |
59 | + public List<Long> findUnprocessedPartitions(UUID nodeId, long clusteredHash) { | |
60 | + Optional<Long> lastPartitionOption = processedPartitionRepository.findLastProcessedPartition(nodeId, clusteredHash); | |
61 | + long lastPartition = lastPartitionOption.orElse(System.currentTimeMillis() - 7 * 24 * 60 * 60 * 100); | |
62 | + List<Long> unprocessedPartitions = Lists.newArrayList(); | |
63 | + | |
64 | + LocalDateTime current = LocalDateTime.ofInstant(Instant.ofEpochMilli(lastPartition), ZoneOffset.UTC); | |
65 | + LocalDateTime end = LocalDateTime.ofInstant(Instant.now(clock), ZoneOffset.UTC) | |
66 | + .plus(1L, tsFormat.getTruncateUnit()); | |
67 | + | |
68 | + while (current.isBefore(end)) { | |
69 | + current = current.plus(1L, tsFormat.getTruncateUnit()); | |
70 | + unprocessedPartitions.add(tsFormat.truncatedTo(current).toInstant(ZoneOffset.UTC).toEpochMilli()); | |
71 | + } | |
72 | + | |
73 | + return unprocessedPartitions; | |
74 | + } | |
75 | + | |
76 | + public void setClock(Clock clock) { | |
77 | + this.clock = clock; | |
78 | + } | |
79 | + | |
80 | + public void checkProcessedPartitions() { | |
81 | + //todo-vp: we need to implement this | |
82 | + } | |
83 | +} | ... | ... |
... | ... | @@ -15,14 +15,20 @@ |
15 | 15 | */ |
16 | 16 | package org.thingsboard.rule.engine.queue.cassandra; |
17 | 17 | |
18 | +import org.springframework.stereotype.Component; | |
18 | 19 | import org.thingsboard.rule.engine.api.TbMsg; |
19 | 20 | |
20 | 21 | import java.util.Collection; |
21 | -import java.util.Collections; | |
22 | +import java.util.List; | |
23 | +import java.util.Set; | |
24 | +import java.util.UUID; | |
25 | +import java.util.stream.Collectors; | |
22 | 26 | |
27 | +@Component | |
23 | 28 | public class UnprocessedMsgFilter { |
24 | 29 | |
25 | - public Collection<TbMsg> filter(Iterable<TbMsg> msgs, Iterable<MsgAck> acks) { | |
26 | - return Collections.emptyList(); | |
30 | + public Collection<TbMsg> filter(List<TbMsg> msgs, List<MsgAck> acks) { | |
31 | + Set<UUID> processedIds = acks.stream().map(MsgAck::getMsgId).collect(Collectors.toSet()); | |
32 | + return msgs.stream().filter(i -> !processedIds.contains(i.getId())).collect(Collectors.toList()); | |
27 | 33 | } |
28 | 34 | } | ... | ... |
... | ... | @@ -18,11 +18,12 @@ package org.thingsboard.rule.engine.queue.cassandra.repository; |
18 | 18 | import com.google.common.util.concurrent.ListenableFuture; |
19 | 19 | import org.thingsboard.rule.engine.queue.cassandra.MsgAck; |
20 | 20 | |
21 | +import java.util.List; | |
21 | 22 | import java.util.UUID; |
22 | 23 | |
23 | 24 | public interface AckRepository { |
24 | 25 | |
25 | 26 | ListenableFuture<Void> ack(MsgAck msgAck); |
26 | 27 | |
27 | - Iterable<MsgAck> findAcks(UUID nodeId, long clusteredHash, long partition); | |
28 | + List<MsgAck> findAcks(UUID nodeId, long clusteredHash, long partition); | |
28 | 29 | } | ... | ... |
... | ... | @@ -18,12 +18,13 @@ package org.thingsboard.rule.engine.queue.cassandra.repository; |
18 | 18 | import com.google.common.util.concurrent.ListenableFuture; |
19 | 19 | import org.thingsboard.rule.engine.api.TbMsg; |
20 | 20 | |
21 | +import java.util.List; | |
21 | 22 | import java.util.UUID; |
22 | 23 | |
23 | 24 | public interface MsgRepository { |
24 | 25 | |
25 | - ListenableFuture<Void> save(TbMsg msg, UUID nodeId, long clusteredHash, long partition); | |
26 | + ListenableFuture<Void> save(TbMsg msg, UUID nodeId, long clusteredHash, long partition, long msgTs); | |
26 | 27 | |
27 | - Iterable<TbMsg> findMsgs(UUID nodeId, long clusteredHash, long partition); | |
28 | + List<TbMsg> findMsgs(UUID nodeId, long clusteredHash, long partition); | |
28 | 29 | |
29 | 30 | } | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 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 | + */ | |
1 | 16 | package org.thingsboard.rule.engine.queue.cassandra.repository; |
2 | 17 | |
18 | +import com.google.common.util.concurrent.ListenableFuture; | |
19 | + | |
3 | 20 | import java.util.Optional; |
4 | 21 | import java.util.UUID; |
5 | 22 | |
6 | 23 | public interface ProcessedPartitionRepository { |
7 | 24 | |
8 | - void partitionProcessed(UUID nodeId, long clusteredHash, long partition); | |
25 | + ListenableFuture<Void> partitionProcessed(UUID nodeId, long clusteredHash, long partition); | |
9 | 26 | |
10 | 27 | Optional<Long> findLastProcessedPartition(UUID nodeId, long clusteredHash); |
11 | 28 | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * <p> | |
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 | + * <p> | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * <p> | |
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.rule.engine.queue.cassandra.repository.impl; | |
17 | + | |
18 | +import com.datastax.driver.core.*; | |
19 | +import com.google.common.base.Function; | |
20 | +import com.google.common.util.concurrent.Futures; | |
21 | +import com.google.common.util.concurrent.ListenableFuture; | |
22 | +import org.springframework.stereotype.Component; | |
23 | +import org.thingsboard.rule.engine.queue.cassandra.MsgAck; | |
24 | +import org.thingsboard.rule.engine.queue.cassandra.repository.AckRepository; | |
25 | + | |
26 | +import java.util.ArrayList; | |
27 | +import java.util.List; | |
28 | +import java.util.UUID; | |
29 | + | |
30 | +@Component | |
31 | +public class CassandraAckRepository extends SimpleAbstractCassandraDao implements AckRepository { | |
32 | + | |
33 | + private final int ackQueueTtl; | |
34 | + | |
35 | + public CassandraAckRepository(Session session, int ackQueueTtl) { | |
36 | + super(session); | |
37 | + this.ackQueueTtl = ackQueueTtl; | |
38 | + } | |
39 | + | |
40 | + @Override | |
41 | + public ListenableFuture<Void> ack(MsgAck msgAck) { | |
42 | + String insert = "INSERT INTO msg_ack_queue (node_id, clustered_hash, partition, msg_id) VALUES (?, ?, ?, ?) USING TTL ?"; | |
43 | + PreparedStatement statement = prepare(insert); | |
44 | + BoundStatement boundStatement = statement.bind(msgAck.getNodeId(), msgAck.getClusteredHash(), | |
45 | + msgAck.getPartition(), msgAck.getMsgId(), ackQueueTtl); | |
46 | + ResultSetFuture resultSetFuture = executeAsyncWrite(boundStatement); | |
47 | + return Futures.transform(resultSetFuture, (Function<ResultSet, Void>) input -> null); | |
48 | + } | |
49 | + | |
50 | + @Override | |
51 | + public List<MsgAck> findAcks(UUID nodeId, long clusteredHash, long partition) { | |
52 | + String select = "SELECT msg_id FROM msg_ack_queue WHERE " + | |
53 | + "node_id = ? AND clustered_hash = ? AND partition = ?"; | |
54 | + PreparedStatement statement = prepare(select); | |
55 | + BoundStatement boundStatement = statement.bind(nodeId, clusteredHash, partition); | |
56 | + ResultSet rows = executeRead(boundStatement); | |
57 | + List<MsgAck> msgs = new ArrayList<>(); | |
58 | + for (Row row : rows) { | |
59 | + msgs.add(new MsgAck(row.getUUID("msg_id"), nodeId, clusteredHash, partition)); | |
60 | + } | |
61 | + return msgs; | |
62 | + } | |
63 | + | |
64 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * <p> | |
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 | + * <p> | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * <p> | |
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.rule.engine.queue.cassandra.repository.impl; | |
17 | + | |
18 | +import com.datastax.driver.core.*; | |
19 | +import com.google.common.base.Function; | |
20 | +import com.google.common.util.concurrent.Futures; | |
21 | +import com.google.common.util.concurrent.ListenableFuture; | |
22 | +import com.google.protobuf.ByteString; | |
23 | +import com.google.protobuf.InvalidProtocolBufferException; | |
24 | +import org.springframework.stereotype.Component; | |
25 | +import org.thingsboard.rule.engine.api.TbMsg; | |
26 | +import org.thingsboard.rule.engine.api.TbMsgMetaData; | |
27 | +import org.thingsboard.rule.engine.queue.cassandra.repository.MsgRepository; | |
28 | +import org.thingsboard.rule.engine.queue.cassandra.repository.gen.MsgQueueProtos; | |
29 | +import org.thingsboard.server.common.data.id.EntityId; | |
30 | +import org.thingsboard.server.common.data.id.EntityIdFactory; | |
31 | + | |
32 | +import java.nio.ByteBuffer; | |
33 | +import java.util.ArrayList; | |
34 | +import java.util.List; | |
35 | +import java.util.UUID; | |
36 | + | |
37 | +@Component | |
38 | +public class CassandraMsgRepository extends SimpleAbstractCassandraDao implements MsgRepository { | |
39 | + | |
40 | + private final int msqQueueTtl; | |
41 | + | |
42 | + | |
43 | + public CassandraMsgRepository(Session session, int msqQueueTtl) { | |
44 | + super(session); | |
45 | + this.msqQueueTtl = msqQueueTtl; | |
46 | + } | |
47 | + | |
48 | + @Override | |
49 | + public ListenableFuture<Void> save(TbMsg msg, UUID nodeId, long clusteredHash, long partition, long msgTs) { | |
50 | + String insert = "INSERT INTO msg_queue (node_id, clustered_hash, partition, ts, msg) VALUES (?, ?, ?, ?, ?) USING TTL ?"; | |
51 | + PreparedStatement statement = prepare(insert); | |
52 | + BoundStatement boundStatement = statement.bind(nodeId, clusteredHash, partition, msgTs, toBytes(msg), msqQueueTtl); | |
53 | + ResultSetFuture resultSetFuture = executeAsyncWrite(boundStatement); | |
54 | + return Futures.transform(resultSetFuture, (Function<ResultSet, Void>) input -> null); | |
55 | + } | |
56 | + | |
57 | + @Override | |
58 | + public List<TbMsg> findMsgs(UUID nodeId, long clusteredHash, long partition) { | |
59 | + String select = "SELECT node_id, clustered_hash, partition, ts, msg FROM msg_queue WHERE " + | |
60 | + "node_id = ? AND clustered_hash = ? AND partition = ?"; | |
61 | + PreparedStatement statement = prepare(select); | |
62 | + BoundStatement boundStatement = statement.bind(nodeId, clusteredHash, partition); | |
63 | + ResultSet rows = executeRead(boundStatement); | |
64 | + List<TbMsg> msgs = new ArrayList<>(); | |
65 | + for (Row row : rows) { | |
66 | + msgs.add(fromBytes(row.getBytes("msg"))); | |
67 | + } | |
68 | + return msgs; | |
69 | + } | |
70 | + | |
71 | + private ByteBuffer toBytes(TbMsg msg) { | |
72 | + MsgQueueProtos.TbMsgProto.Builder builder = MsgQueueProtos.TbMsgProto.newBuilder(); | |
73 | + builder.setId(msg.getId().toString()); | |
74 | + builder.setType(msg.getType()); | |
75 | + if (msg.getOriginator() != null) { | |
76 | + builder.setEntityType(msg.getOriginator().getEntityType().name()); | |
77 | + builder.setEntityId(msg.getOriginator().getId().toString()); | |
78 | + } | |
79 | + | |
80 | + if (msg.getMetaData() != null) { | |
81 | + MsgQueueProtos.TbMsgProto.TbMsgMetaDataProto.Builder metadataBuilder = MsgQueueProtos.TbMsgProto.TbMsgMetaDataProto.newBuilder(); | |
82 | + metadataBuilder.putAllData(msg.getMetaData().getData()); | |
83 | + builder.addMetaData(metadataBuilder.build()); | |
84 | + } | |
85 | + | |
86 | + builder.setData(ByteString.copyFrom(msg.getData())); | |
87 | + byte[] bytes = builder.build().toByteArray(); | |
88 | + return ByteBuffer.wrap(bytes); | |
89 | + } | |
90 | + | |
91 | + private TbMsg fromBytes(ByteBuffer buffer) { | |
92 | + try { | |
93 | + MsgQueueProtos.TbMsgProto proto = MsgQueueProtos.TbMsgProto.parseFrom(buffer.array()); | |
94 | + TbMsgMetaData metaData = new TbMsgMetaData(); | |
95 | + if (proto.getMetaDataCount() > 0) { | |
96 | + metaData.setData(proto.getMetaData(0).getDataMap()); | |
97 | + } | |
98 | + | |
99 | + EntityId entityId = null; | |
100 | + if (proto.getEntityId() != null) { | |
101 | + entityId = EntityIdFactory.getByTypeAndId(proto.getEntityType(), proto.getEntityId()); | |
102 | + } | |
103 | + | |
104 | + return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, proto.getData().toByteArray()); | |
105 | + } catch (InvalidProtocolBufferException e) { | |
106 | + throw new IllegalStateException("Could not parse protobuf for TbMsg", e); | |
107 | + } | |
108 | + } | |
109 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * <p> | |
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 | + * <p> | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * <p> | |
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.rule.engine.queue.cassandra.repository.impl; | |
17 | + | |
18 | +import com.datastax.driver.core.*; | |
19 | +import com.google.common.base.Function; | |
20 | +import com.google.common.util.concurrent.Futures; | |
21 | +import com.google.common.util.concurrent.ListenableFuture; | |
22 | +import org.springframework.stereotype.Component; | |
23 | +import org.thingsboard.rule.engine.queue.cassandra.repository.ProcessedPartitionRepository; | |
24 | + | |
25 | +import java.util.Optional; | |
26 | +import java.util.UUID; | |
27 | + | |
28 | +@Component | |
29 | +public class CassandraProcessedPartitionRepository extends SimpleAbstractCassandraDao implements ProcessedPartitionRepository { | |
30 | + | |
31 | + private final int repositoryTtl; | |
32 | + | |
33 | + public CassandraProcessedPartitionRepository(Session session, int repositoryTtl) { | |
34 | + super(session); | |
35 | + this.repositoryTtl = repositoryTtl; | |
36 | + } | |
37 | + | |
38 | + @Override | |
39 | + public ListenableFuture<Void> partitionProcessed(UUID nodeId, long clusteredHash, long partition) { | |
40 | + String insert = "INSERT INTO processed_msg_partitions (node_id, clustered_hash, partition) VALUES (?, ?, ?) USING TTL ?"; | |
41 | + PreparedStatement prepared = prepare(insert); | |
42 | + BoundStatement boundStatement = prepared.bind(nodeId, clusteredHash, partition, repositoryTtl); | |
43 | + ResultSetFuture resultSetFuture = executeAsyncWrite(boundStatement); | |
44 | + return Futures.transform(resultSetFuture, (Function<ResultSet, Void>) input -> null); | |
45 | + } | |
46 | + | |
47 | + @Override | |
48 | + public Optional<Long> findLastProcessedPartition(UUID nodeId, long clusteredHash) { | |
49 | + String select = "SELECT partition FROM processed_msg_partitions WHERE " + | |
50 | + "node_id = ? AND clustered_hash = ?"; | |
51 | + PreparedStatement prepared = prepare(select); | |
52 | + BoundStatement boundStatement = prepared.bind(nodeId, clusteredHash); | |
53 | + Row row = executeRead(boundStatement).one(); | |
54 | + if (row == null) { | |
55 | + return Optional.empty(); | |
56 | + } | |
57 | + | |
58 | + return Optional.of(row.getLong("partition")); | |
59 | + } | |
60 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * <p> | |
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 | + * <p> | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * <p> | |
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.rule.engine.queue.cassandra.repository.impl; | |
17 | + | |
18 | +import com.datastax.driver.core.*; | |
19 | +import lombok.extern.slf4j.Slf4j; | |
20 | +import org.springframework.stereotype.Component; | |
21 | + | |
22 | +import java.util.Map; | |
23 | +import java.util.concurrent.ConcurrentHashMap; | |
24 | + | |
25 | +@Component | |
26 | +@Slf4j | |
27 | +public abstract class SimpleAbstractCassandraDao { | |
28 | + | |
29 | + private ConsistencyLevel defaultReadLevel = ConsistencyLevel.QUORUM; | |
30 | + private ConsistencyLevel defaultWriteLevel = ConsistencyLevel.QUORUM; | |
31 | + private Session session; | |
32 | + private Map<String, PreparedStatement> preparedStatementMap = new ConcurrentHashMap<>(); | |
33 | + | |
34 | + public SimpleAbstractCassandraDao(Session session) { | |
35 | + this.session = session; | |
36 | + } | |
37 | + | |
38 | + protected Session getSession() { | |
39 | + return session; | |
40 | + } | |
41 | + | |
42 | + protected ResultSet executeRead(Statement statement) { | |
43 | + return execute(statement, defaultReadLevel); | |
44 | + } | |
45 | + | |
46 | + protected ResultSet executeWrite(Statement statement) { | |
47 | + return execute(statement, defaultWriteLevel); | |
48 | + } | |
49 | + | |
50 | + protected ResultSetFuture executeAsyncRead(Statement statement) { | |
51 | + return executeAsync(statement, defaultReadLevel); | |
52 | + } | |
53 | + | |
54 | + protected ResultSetFuture executeAsyncWrite(Statement statement) { | |
55 | + return executeAsync(statement, defaultWriteLevel); | |
56 | + } | |
57 | + | |
58 | + protected PreparedStatement prepare(String query) { | |
59 | + return preparedStatementMap.computeIfAbsent(query, i -> getSession().prepare(i)); | |
60 | + } | |
61 | + | |
62 | + private ResultSet execute(Statement statement, ConsistencyLevel level) { | |
63 | + log.debug("Execute cassandra statement {}", statement); | |
64 | + if (statement.getConsistencyLevel() == null) { | |
65 | + statement.setConsistencyLevel(level); | |
66 | + } | |
67 | + return getSession().execute(statement); | |
68 | + } | |
69 | + | |
70 | + private ResultSetFuture executeAsync(Statement statement, ConsistencyLevel level) { | |
71 | + log.debug("Execute cassandra async statement {}", statement); | |
72 | + if (statement.getConsistencyLevel() == null) { | |
73 | + statement.setConsistencyLevel(level); | |
74 | + } | |
75 | + return getSession().executeAsync(statement); | |
76 | + } | |
77 | +} | ... | ... |
1 | +package org.thingsboard.rule.engine.tool; | |
2 | + | |
3 | +import com.datastax.driver.core.Cluster; | |
4 | +import com.datastax.driver.core.HostDistance; | |
5 | +import com.datastax.driver.core.PoolingOptions; | |
6 | +import com.datastax.driver.core.Session; | |
7 | +import com.datastax.driver.core.utils.UUIDs; | |
8 | +import com.google.common.util.concurrent.FutureCallback; | |
9 | +import com.google.common.util.concurrent.Futures; | |
10 | +import com.google.common.util.concurrent.ListenableFuture; | |
11 | +import lombok.extern.slf4j.Slf4j; | |
12 | +import org.springframework.beans.factory.annotation.Autowired; | |
13 | +import org.springframework.boot.CommandLineRunner; | |
14 | +import org.springframework.boot.SpringApplication; | |
15 | +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; | |
16 | +import org.springframework.boot.autoconfigure.SpringBootApplication; | |
17 | +import org.springframework.context.annotation.Bean; | |
18 | +import org.springframework.context.annotation.ComponentScan; | |
19 | +import org.thingsboard.rule.engine.api.MsqQueue; | |
20 | +import org.thingsboard.rule.engine.api.TbMsg; | |
21 | +import org.thingsboard.rule.engine.api.TbMsgMetaData; | |
22 | + | |
23 | +import javax.annotation.Nullable; | |
24 | +import java.net.InetSocketAddress; | |
25 | +import java.util.UUID; | |
26 | +import java.util.concurrent.CountDownLatch; | |
27 | +import java.util.concurrent.ExecutorService; | |
28 | +import java.util.concurrent.Executors; | |
29 | +import java.util.concurrent.TimeUnit; | |
30 | +import java.util.concurrent.atomic.AtomicLong; | |
31 | + | |
32 | +@SpringBootApplication | |
33 | +@EnableAutoConfiguration | |
34 | +@ComponentScan({"org.thingsboard.rule.engine"}) | |
35 | +//@PropertySource("classpath:processing-pipeline.properties") | |
36 | +@Slf4j | |
37 | +public class QueueBenchmark implements CommandLineRunner { | |
38 | + | |
39 | + public static void main(String[] args) { | |
40 | + try { | |
41 | + SpringApplication.run(QueueBenchmark.class, args); | |
42 | + } catch (Throwable th) { | |
43 | + th.printStackTrace(); | |
44 | + System.exit(0); | |
45 | + } | |
46 | + } | |
47 | + | |
48 | + @Autowired | |
49 | + private MsqQueue msqQueue; | |
50 | + | |
51 | + @Override | |
52 | + public void run(String... strings) throws Exception { | |
53 | + System.out.println("It works + " + msqQueue); | |
54 | + | |
55 | + | |
56 | + long start = System.currentTimeMillis(); | |
57 | + int msgCount = 10000000; | |
58 | + AtomicLong count = new AtomicLong(0); | |
59 | + ExecutorService service = Executors.newFixedThreadPool(100); | |
60 | + | |
61 | + CountDownLatch latch = new CountDownLatch(msgCount); | |
62 | + for (int i = 0; i < msgCount; i++) { | |
63 | + service.submit(() -> { | |
64 | + boolean isFinished = false; | |
65 | + while (!isFinished) { | |
66 | + try { | |
67 | + TbMsg msg = randomMsg(); | |
68 | + UUID nodeId = UUIDs.timeBased(); | |
69 | + ListenableFuture<Void> put = msqQueue.put(msg, nodeId, 100L); | |
70 | +// ListenableFuture<Void> put = msqQueue.ack(msg, nodeId, 100L); | |
71 | + Futures.addCallback(put, new FutureCallback<Void>() { | |
72 | + @Override | |
73 | + public void onSuccess(@Nullable Void result) { | |
74 | + latch.countDown(); | |
75 | + } | |
76 | + | |
77 | + @Override | |
78 | + public void onFailure(Throwable t) { | |
79 | +// t.printStackTrace(); | |
80 | + System.out.println("onFailure, because:" + t.getMessage()); | |
81 | + latch.countDown(); | |
82 | + } | |
83 | + }); | |
84 | + isFinished = true; | |
85 | + } catch (Throwable th) { | |
86 | +// th.printStackTrace(); | |
87 | + System.out.println("Repeat query, because:" + th.getMessage()); | |
88 | +// latch.countDown(); | |
89 | + } | |
90 | + } | |
91 | + }); | |
92 | + } | |
93 | + | |
94 | + long prev = 0L; | |
95 | + while (latch.getCount() != 0) { | |
96 | + TimeUnit.SECONDS.sleep(1); | |
97 | + long curr = latch.getCount(); | |
98 | + long rps = prev - curr; | |
99 | + prev = curr; | |
100 | + System.out.println("rps = " + rps); | |
101 | + } | |
102 | + | |
103 | + long end = System.currentTimeMillis(); | |
104 | + System.out.println("final rps = " + (msgCount / (end - start) * 1000)); | |
105 | + | |
106 | + System.out.println("Finished"); | |
107 | + | |
108 | + } | |
109 | + | |
110 | + private TbMsg randomMsg() { | |
111 | + TbMsgMetaData metaData = new TbMsgMetaData(); | |
112 | + metaData.putValue("key", "value"); | |
113 | + String dataStr = "someContent"; | |
114 | + return new TbMsg(UUIDs.timeBased(), "type", null, metaData, dataStr.getBytes()); | |
115 | + } | |
116 | + | |
117 | + @Bean | |
118 | + public Session session() { | |
119 | + Cluster thingsboard = Cluster.builder() | |
120 | + .addContactPointsWithPorts(new InetSocketAddress("127.0.0.1", 9042)) | |
121 | + .withClusterName("thingsboard") | |
122 | +// .withSocketOptions(socketOpts.getOpts()) | |
123 | + .withPoolingOptions(new PoolingOptions() | |
124 | + .setMaxRequestsPerConnection(HostDistance.LOCAL, 32768) | |
125 | + .setMaxRequestsPerConnection(HostDistance.REMOTE, 32768)).build(); | |
126 | + | |
127 | + Session session = thingsboard.connect("thingsboard"); | |
128 | + return session; | |
129 | + } | |
130 | + | |
131 | + @Bean | |
132 | + public int defaultTtl() { | |
133 | + return 6000; | |
134 | + } | |
135 | + | |
136 | +} | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 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 | +syntax = "proto3"; | |
17 | +package msgqueue; | |
18 | + | |
19 | +option java_package = "org.thingsboard.rule.engine.queue.cassandra.repository.gen"; | |
20 | +option java_outer_classname = "MsgQueueProtos"; | |
21 | + | |
22 | + | |
23 | +message TbMsgProto { | |
24 | + string id = 1; | |
25 | + string type = 2; | |
26 | + string entityType = 3; | |
27 | + string entityId = 4; | |
28 | + | |
29 | + message TbMsgMetaDataProto { | |
30 | + map<string, string> data = 1; | |
31 | + } | |
32 | + | |
33 | + repeated TbMsgMetaDataProto metaData = 5; | |
34 | + | |
35 | + bytes data = 6; | |
36 | +} | |
\ No newline at end of file | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 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.rule.engine.queue.cassandra; | |
17 | + | |
18 | +import org.junit.Before; | |
19 | +import org.junit.Test; | |
20 | +import org.mockito.Mock; | |
21 | +import org.thingsboard.rule.engine.queue.cassandra.repository.AckRepository; | |
22 | +import org.thingsboard.rule.engine.queue.cassandra.repository.MsgRepository; | |
23 | + | |
24 | +public class CassandraMsqQueueTest { | |
25 | + | |
26 | + private CassandraMsqQueue msqQueue; | |
27 | + | |
28 | + @Mock | |
29 | + private MsgRepository msgRepository; | |
30 | + @Mock | |
31 | + private AckRepository ackRepository; | |
32 | + @Mock | |
33 | + private UnprocessedMsgFilter unprocessedMsgFilter; | |
34 | + @Mock | |
35 | + private QueuePartitioner queuePartitioner; | |
36 | + | |
37 | + @Before | |
38 | + public void init() { | |
39 | + msqQueue = new CassandraMsqQueue(msgRepository, ackRepository, unprocessedMsgFilter, queuePartitioner); | |
40 | + } | |
41 | + | |
42 | + @Test | |
43 | + public void msgCanBeSaved() { | |
44 | +// todo-vp: implement | |
45 | + } | |
46 | + | |
47 | + | |
48 | +} | |
\ No newline at end of file | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * <p> | |
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 | + * <p> | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * <p> | |
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.rule.engine.queue.cassandra; | |
17 | + | |
18 | + | |
19 | +import org.junit.Before; | |
20 | +import org.junit.Test; | |
21 | +import org.junit.runner.RunWith; | |
22 | +import org.mockito.Mock; | |
23 | +import org.mockito.runners.MockitoJUnitRunner; | |
24 | +import org.thingsboard.rule.engine.queue.cassandra.repository.ProcessedPartitionRepository; | |
25 | + | |
26 | +import java.time.Clock; | |
27 | +import java.time.Instant; | |
28 | +import java.time.ZoneOffset; | |
29 | +import java.time.temporal.ChronoUnit; | |
30 | +import java.util.List; | |
31 | +import java.util.Optional; | |
32 | +import java.util.UUID; | |
33 | + | |
34 | +import static org.junit.Assert.assertEquals; | |
35 | +import static org.mockito.Mockito.when; | |
36 | + | |
37 | +@RunWith(MockitoJUnitRunner.class) | |
38 | +public class QueuePartitionerTest { | |
39 | + | |
40 | + private QueuePartitioner queuePartitioner; | |
41 | + | |
42 | + @Mock | |
43 | + private ProcessedPartitionRepository partitionRepo; | |
44 | + | |
45 | + private Instant startInstant; | |
46 | + private Instant endInstant; | |
47 | + | |
48 | + @Before | |
49 | + public void init() { | |
50 | + queuePartitioner = new QueuePartitioner("MINUTES", partitionRepo); | |
51 | + startInstant = Instant.now(); | |
52 | + endInstant = startInstant.plus(2, ChronoUnit.MINUTES); | |
53 | + queuePartitioner.setClock(Clock.fixed(endInstant, ZoneOffset.UTC)); | |
54 | + } | |
55 | + | |
56 | + @Test | |
57 | + public void partitionCalculated() { | |
58 | + long time = 1519390191425L; | |
59 | + long partition = queuePartitioner.getPartition(time); | |
60 | + assertEquals(1519390140000L, partition); | |
61 | + } | |
62 | + | |
63 | + @Test | |
64 | + public void unprocessedPartitionsReturned() { | |
65 | + UUID nodeId = UUID.randomUUID(); | |
66 | + long clusteredHash = 101L; | |
67 | + when(partitionRepo.findLastProcessedPartition(nodeId, clusteredHash)).thenReturn(Optional.of(startInstant.toEpochMilli())); | |
68 | + List<Long> actual = queuePartitioner.findUnprocessedPartitions(nodeId, clusteredHash); | |
69 | + assertEquals(3, actual.size()); | |
70 | + } | |
71 | + | |
72 | + @Test | |
73 | + public void defaultShiftUsedIfNoPartitionWasProcessed() { | |
74 | + UUID nodeId = UUID.randomUUID(); | |
75 | + long clusteredHash = 101L; | |
76 | + when(partitionRepo.findLastProcessedPartition(nodeId, clusteredHash)).thenReturn(Optional.empty()); | |
77 | + List<Long> actual = queuePartitioner.findUnprocessedPartitions(nodeId, clusteredHash); | |
78 | + assertEquals(1011, actual.size()); | |
79 | + } | |
80 | + | |
81 | +} | |
\ No newline at end of file | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 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.rule.engine.queue.cassandra; | |
17 | + | |
18 | +import com.google.common.collect.Lists; | |
19 | +import org.junit.Test; | |
20 | +import org.thingsboard.rule.engine.api.TbMsg; | |
21 | + | |
22 | +import java.util.Collection; | |
23 | +import java.util.List; | |
24 | +import java.util.UUID; | |
25 | + | |
26 | +import static org.junit.jupiter.api.Assertions.assertEquals; | |
27 | + | |
28 | +public class UnprocessedMsgFilterTest { | |
29 | + | |
30 | + private UnprocessedMsgFilter msgFilter = new UnprocessedMsgFilter(); | |
31 | + | |
32 | + @Test | |
33 | + public void acknowledgedMsgsAreFilteredOut() { | |
34 | + UUID id1 = UUID.randomUUID(); | |
35 | + UUID id2 = UUID.randomUUID(); | |
36 | + TbMsg msg1 = new TbMsg(id1, "T", null, null, null); | |
37 | + TbMsg msg2 = new TbMsg(id2, "T", null, null, null); | |
38 | + List<TbMsg> msgs = Lists.newArrayList(msg1, msg2); | |
39 | + List<MsgAck> acks = Lists.newArrayList(new MsgAck(id2, UUID.randomUUID(), 1L, 1L)); | |
40 | + Collection<TbMsg> actual = msgFilter.filter(msgs, acks); | |
41 | + assertEquals(1, actual.size()); | |
42 | + assertEquals(msg1, actual.iterator().next()); | |
43 | + } | |
44 | + | |
45 | +} | |
\ No newline at end of file | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 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.rule.engine.queue.cassandra.repository.impl; | |
17 | + | |
18 | +import com.datastax.driver.core.utils.UUIDs; | |
19 | +import com.google.common.collect.Lists; | |
20 | +import com.google.common.util.concurrent.ListenableFuture; | |
21 | +import org.junit.Before; | |
22 | +import org.junit.Test; | |
23 | +import org.thingsboard.rule.engine.queue.cassandra.MsgAck; | |
24 | + | |
25 | +import java.util.List; | |
26 | +import java.util.UUID; | |
27 | +import java.util.concurrent.ExecutionException; | |
28 | +import java.util.concurrent.TimeUnit; | |
29 | + | |
30 | +import static org.junit.Assert.assertEquals; | |
31 | +import static org.junit.Assert.assertTrue; | |
32 | + | |
33 | +public class CassandraAckRepositoryTest extends SimpleAbstractCassandraDaoTest { | |
34 | + | |
35 | + private CassandraAckRepository ackRepository; | |
36 | + | |
37 | + @Before | |
38 | + public void init() { | |
39 | + ackRepository = new CassandraAckRepository(cassandraUnit.session, 1); | |
40 | + } | |
41 | + | |
42 | + @Test | |
43 | + public void acksInPartitionCouldBeFound() { | |
44 | + UUID nodeId = UUID.fromString("055eee50-1883-11e8-b380-65b5d5335ba9"); | |
45 | + | |
46 | + List<MsgAck> extectedAcks = Lists.newArrayList( | |
47 | + new MsgAck(UUID.fromString("bebaeb60-1888-11e8-bf21-65b5d5335ba9"), nodeId, 101L, 300L), | |
48 | + new MsgAck(UUID.fromString("12baeb60-1888-11e8-bf21-65b5d5335ba9"), nodeId, 101L, 300L) | |
49 | + ); | |
50 | + | |
51 | + List<MsgAck> actualAcks = ackRepository.findAcks(nodeId, 101L, 300L); | |
52 | + assertEquals(extectedAcks, actualAcks); | |
53 | + } | |
54 | + | |
55 | + @Test | |
56 | + public void ackCanBeSavedAndRead() throws ExecutionException, InterruptedException { | |
57 | + UUID msgId = UUIDs.timeBased(); | |
58 | + UUID nodeId = UUIDs.timeBased(); | |
59 | + MsgAck ack = new MsgAck(msgId, nodeId, 10L, 20L); | |
60 | + ListenableFuture<Void> future = ackRepository.ack(ack); | |
61 | + future.get(); | |
62 | + List<MsgAck> actualAcks = ackRepository.findAcks(nodeId, 10L, 20L); | |
63 | + assertEquals(1, actualAcks.size()); | |
64 | + assertEquals(ack, actualAcks.get(0)); | |
65 | + } | |
66 | + | |
67 | + @Test | |
68 | + public void expiredAcksAreNotReturned() throws ExecutionException, InterruptedException { | |
69 | + UUID msgId = UUIDs.timeBased(); | |
70 | + UUID nodeId = UUIDs.timeBased(); | |
71 | + MsgAck ack = new MsgAck(msgId, nodeId, 30L, 40L); | |
72 | + ListenableFuture<Void> future = ackRepository.ack(ack); | |
73 | + future.get(); | |
74 | + List<MsgAck> actualAcks = ackRepository.findAcks(nodeId, 30L, 40L); | |
75 | + assertEquals(1, actualAcks.size()); | |
76 | + TimeUnit.SECONDS.sleep(2); | |
77 | + assertTrue(ackRepository.findAcks(nodeId, 30L, 40L).isEmpty()); | |
78 | + } | |
79 | + | |
80 | + | |
81 | +} | |
\ No newline at end of file | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 The Thingsboard Authors | |
3 | + * <p> | |
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 | + * <p> | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * <p> | |
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.rule.engine.queue.cassandra.repository.impl; | |
17 | + | |
18 | +//import static org.junit.jupiter.api.Assertions.*; | |
19 | + | |
20 | +import com.datastax.driver.core.utils.UUIDs; | |
21 | +import com.google.common.util.concurrent.ListenableFuture; | |
22 | +import org.junit.Before; | |
23 | +import org.junit.Test; | |
24 | +import org.thingsboard.rule.engine.api.TbMsg; | |
25 | +import org.thingsboard.rule.engine.api.TbMsgMetaData; | |
26 | +import org.thingsboard.server.common.data.id.DeviceId; | |
27 | + | |
28 | +import java.util.List; | |
29 | +import java.util.UUID; | |
30 | +import java.util.concurrent.ExecutionException; | |
31 | +import java.util.concurrent.TimeUnit; | |
32 | + | |
33 | +import static org.junit.Assert.assertEquals; | |
34 | +import static org.junit.Assert.assertTrue; | |
35 | + | |
36 | +public class CassandraMsgRepositoryTest extends SimpleAbstractCassandraDaoTest { | |
37 | + | |
38 | + private CassandraMsgRepository msgRepository; | |
39 | + | |
40 | + @Before | |
41 | + public void init() { | |
42 | + msgRepository = new CassandraMsgRepository(cassandraUnit.session, 1); | |
43 | + } | |
44 | + | |
45 | + @Test | |
46 | + public void msgCanBeSavedAndRead() throws ExecutionException, InterruptedException { | |
47 | + TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, new byte[4]); | |
48 | + UUID nodeId = UUIDs.timeBased(); | |
49 | + ListenableFuture<Void> future = msgRepository.save(msg, nodeId, 1L, 1L, 1L); | |
50 | + future.get(); | |
51 | + List<TbMsg> msgs = msgRepository.findMsgs(nodeId, 1L, 1L); | |
52 | + assertEquals(1, msgs.size()); | |
53 | + } | |
54 | + | |
55 | + @Test | |
56 | + public void expiredMsgsAreNotReturned() throws ExecutionException, InterruptedException { | |
57 | + TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), null, new byte[4]); | |
58 | + UUID nodeId = UUIDs.timeBased(); | |
59 | + ListenableFuture<Void> future = msgRepository.save(msg, nodeId, 2L, 2L, 2L); | |
60 | + future.get(); | |
61 | + List<TbMsg> msgs = msgRepository.findMsgs(nodeId, 2L, 2L); | |
62 | + assertEquals(1, msgs.size()); | |
63 | + TimeUnit.SECONDS.sleep(2); | |
64 | + assertTrue(msgRepository.findMsgs(nodeId, 2L, 2L).isEmpty()); | |
65 | + } | |
66 | + | |
67 | + @Test | |
68 | + public void protoBufConverterWorkAsExpected() throws ExecutionException, InterruptedException { | |
69 | + TbMsgMetaData metaData = new TbMsgMetaData(); | |
70 | + metaData.putValue("key", "value"); | |
71 | + String dataStr = "someContent"; | |
72 | + TbMsg msg = new TbMsg(UUIDs.timeBased(), "type", new DeviceId(UUIDs.timeBased()), metaData, dataStr.getBytes()); | |
73 | + UUID nodeId = UUIDs.timeBased(); | |
74 | + ListenableFuture<Void> future = msgRepository.save(msg, nodeId, 1L, 1L, 1L); | |
75 | + future.get(); | |
76 | + List<TbMsg> msgs = msgRepository.findMsgs(nodeId, 1L, 1L); | |
77 | + assertEquals(1, msgs.size()); | |
78 | + assertEquals(msg, msgs.get(0)); | |
79 | + } | |
80 | + | |
81 | + | |
82 | +} | |
\ No newline at end of file | ... | ... |
1 | +/** | |
2 | + * Copyright © 2016-2017 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.rule.engine.queue.cassandra.repository.impl; | |
17 | + | |
18 | +import com.datastax.driver.core.utils.UUIDs; | |
19 | +import com.google.common.util.concurrent.Futures; | |
20 | +import com.google.common.util.concurrent.ListenableFuture; | |
21 | +import org.junit.Before; | |
22 | +import org.junit.Test; | |
23 | + | |
24 | +import java.util.List; | |
25 | +import java.util.Optional; | |
26 | +import java.util.UUID; | |
27 | +import java.util.concurrent.ExecutionException; | |
28 | +import java.util.concurrent.TimeUnit; | |
29 | + | |
30 | +import static org.junit.Assert.*; | |
31 | + | |
32 | +public class CassandraProcessedPartitionRepositoryTest extends SimpleAbstractCassandraDaoTest { | |
33 | + | |
34 | + private CassandraProcessedPartitionRepository partitionRepository; | |
35 | + | |
36 | + @Before | |
37 | + public void init() { | |
38 | + partitionRepository = new CassandraProcessedPartitionRepository(cassandraUnit.session, 1); | |
39 | + } | |
40 | + | |
41 | + @Test | |
42 | + public void lastProcessedPartitionCouldBeFound() { | |
43 | + UUID nodeId = UUID.fromString("055eee50-1883-11e8-b380-65b5d5335ba9"); | |
44 | + Optional<Long> lastProcessedPartition = partitionRepository.findLastProcessedPartition(nodeId, 101L); | |
45 | + assertTrue(lastProcessedPartition.isPresent()); | |
46 | + assertEquals((Long) 777L, lastProcessedPartition.get()); | |
47 | + } | |
48 | + | |
49 | + @Test | |
50 | + public void highestProcessedPartitionReturned() throws ExecutionException, InterruptedException { | |
51 | + UUID nodeId = UUIDs.timeBased(); | |
52 | + ListenableFuture<Void> future1 = partitionRepository.partitionProcessed(nodeId, 303L, 100L); | |
53 | + ListenableFuture<Void> future2 = partitionRepository.partitionProcessed(nodeId, 303L, 200L); | |
54 | + ListenableFuture<Void> future3 = partitionRepository.partitionProcessed(nodeId, 303L, 10L); | |
55 | + ListenableFuture<List<Void>> allFutures = Futures.allAsList(future1, future2, future3); | |
56 | + allFutures.get(); | |
57 | + Optional<Long> actual = partitionRepository.findLastProcessedPartition(nodeId, 303L); | |
58 | + assertTrue(actual.isPresent()); | |
59 | + assertEquals((Long) 200L, actual.get()); | |
60 | + } | |
61 | + | |
62 | + @Test | |
63 | + public void expiredPartitionsAreNotReturned() throws ExecutionException, InterruptedException { | |
64 | + UUID nodeId = UUIDs.timeBased(); | |
65 | + ListenableFuture<Void> future = partitionRepository.partitionProcessed(nodeId, 404L, 10L); | |
66 | + future.get(); | |
67 | + Optional<Long> actual = partitionRepository.findLastProcessedPartition(nodeId, 404L); | |
68 | + assertEquals((Long) 10L, actual.get()); | |
69 | + TimeUnit.SECONDS.sleep(2); | |
70 | + assertFalse(partitionRepository.findLastProcessedPartition(nodeId, 404L).isPresent()); | |
71 | + } | |
72 | + | |
73 | + @Test | |
74 | + public void ifNoPartitionsWereProcessedEmptyResultReturned() { | |
75 | + UUID nodeId = UUIDs.timeBased(); | |
76 | + Optional<Long> actual = partitionRepository.findLastProcessedPartition(nodeId, 505L); | |
77 | + assertFalse(actual.isPresent()); | |
78 | + } | |
79 | + | |
80 | +} | |
\ No newline at end of file | ... | ... |
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/queue/cassandra/repository/impl/SimpleAbstractCassandraDaoTest.java
renamed from
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/queue/cassandra/AckBuilder.java
... | ... | @@ -13,17 +13,18 @@ |
13 | 13 | * See the License for the specific language governing permissions and |
14 | 14 | * limitations under the License. |
15 | 15 | */ |
16 | -package org.thingsboard.rule.engine.queue.cassandra; | |
16 | +package org.thingsboard.rule.engine.queue.cassandra.repository.impl; | |
17 | 17 | |
18 | -import org.springframework.stereotype.Component; | |
19 | -import org.thingsboard.rule.engine.api.TbMsg; | |
18 | +import org.cassandraunit.CassandraCQLUnit; | |
19 | +import org.cassandraunit.dataset.cql.ClassPathCQLDataSet; | |
20 | +import org.junit.ClassRule; | |
20 | 21 | |
21 | -import java.util.UUID; | |
22 | 22 | |
23 | -@Component | |
24 | -public class AckBuilder { | |
23 | +public abstract class SimpleAbstractCassandraDaoTest { | |
25 | 24 | |
26 | - public MsgAck build(TbMsg msg, UUID nodeId, long clusteredHash) { | |
27 | - return null; | |
28 | - } | |
29 | -} | |
25 | + @ClassRule | |
26 | + public static CassandraCQLUnit cassandraUnit = new CassandraCQLUnit( | |
27 | + new ClassPathCQLDataSet("cassandra/system-test.cql", "thingsboard")); | |
28 | + | |
29 | + | |
30 | +} | |
\ No newline at end of file | ... | ... |
1 | +CREATE TABLE IF NOT EXISTS thingsboard.msg_queue ( | |
2 | + node_id timeuuid, | |
3 | + clustered_hash bigint, | |
4 | + partition bigint, | |
5 | + ts bigint, | |
6 | + msg blob, | |
7 | + PRIMARY KEY ((node_id, clustered_hash, partition), ts)) | |
8 | +WITH CLUSTERING ORDER BY (ts DESC) | |
9 | +AND compaction = { | |
10 | + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', | |
11 | + 'min_threshold': '5', | |
12 | + 'base_time_seconds': '43200', | |
13 | + 'max_window_size_seconds': '43200', | |
14 | + 'tombstone_threshold': '0.9', | |
15 | + 'unchecked_tombstone_compaction': 'true' | |
16 | +}; | |
17 | + | |
18 | + | |
19 | +CREATE TABLE IF NOT EXISTS thingsboard.msg_ack_queue ( | |
20 | + node_id timeuuid, | |
21 | + clustered_hash bigint, | |
22 | + partition bigint, | |
23 | + msg_id timeuuid, | |
24 | + PRIMARY KEY ((node_id, clustered_hash, partition), msg_id)) | |
25 | +WITH CLUSTERING ORDER BY (msg_id DESC) | |
26 | +AND compaction = { | |
27 | + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', | |
28 | + 'min_threshold': '5', | |
29 | + 'base_time_seconds': '43200', | |
30 | + 'max_window_size_seconds': '43200', | |
31 | + 'tombstone_threshold': '0.9', | |
32 | + 'unchecked_tombstone_compaction': 'true' | |
33 | +}; | |
34 | + | |
35 | +CREATE TABLE IF NOT EXISTS thingsboard.processed_msg_partitions ( | |
36 | + node_id timeuuid, | |
37 | + clustered_hash bigint, | |
38 | + partition bigint, | |
39 | + PRIMARY KEY ((node_id, clustered_hash), partition)) | |
40 | +WITH CLUSTERING ORDER BY (partition DESC) | |
41 | +AND compaction = { | |
42 | + 'class': 'org.apache.cassandra.db.compaction.DateTieredCompactionStrategy', | |
43 | + 'min_threshold': '5', | |
44 | + 'base_time_seconds': '43200', | |
45 | + 'max_window_size_seconds': '43200', | |
46 | + 'tombstone_threshold': '0.9', | |
47 | + 'unchecked_tombstone_compaction': 'true' | |
48 | +}; | |
49 | + | |
50 | + | |
51 | + | |
52 | +-- msg_queue dataset | |
53 | + | |
54 | +INSERT INTO thingsboard.msg_queue (node_id, clustered_hash, partition, ts, msg) | |
55 | + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 200, 201, null); | |
56 | +INSERT INTO thingsboard.msg_queue (node_id, clustered_hash, partition, ts, msg) | |
57 | + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 200, 202, null); | |
58 | +INSERT INTO thingsboard.msg_queue (node_id, clustered_hash, partition, ts, msg) | |
59 | + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 300, 301, null); | |
60 | + | |
61 | +-- ack_queue dataset | |
62 | +INSERT INTO msg_ack_queue (node_id, clustered_hash, partition, msg_id) | |
63 | + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 300, bebaeb60-1888-11e8-bf21-65b5d5335ba9); | |
64 | +INSERT INTO msg_ack_queue (node_id, clustered_hash, partition, msg_id) | |
65 | + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 300, 12baeb60-1888-11e8-bf21-65b5d5335ba9); | |
66 | + INSERT INTO msg_ack_queue (node_id, clustered_hash, partition, msg_id) | |
67 | + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 200, 32baeb60-1888-11e8-bf21-65b5d5335ba9); | |
68 | + | |
69 | +-- processed partition dataset | |
70 | +INSERT INTO processed_msg_partitions (node_id, clustered_hash, partition) | |
71 | + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 100); | |
72 | +INSERT INTO processed_msg_partitions (node_id, clustered_hash, partition) | |
73 | + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 101, 777); | |
74 | +INSERT INTO processed_msg_partitions (node_id, clustered_hash, partition) | |
75 | + VALUES (055eee50-1883-11e8-b380-65b5d5335ba9, 202, 200); | |
\ No newline at end of file | ... | ... |