Commit 5e77e3ea9fe04b1fe82d22a061ebc66e6e7db7ca
Merge branch 'master' of github.com:thingsboard/thingsboard
Showing
14 changed files
with
555 additions
and
465 deletions
@@ -18,6 +18,7 @@ package org.thingsboard.server.controller; | @@ -18,6 +18,7 @@ package org.thingsboard.server.controller; | ||
18 | import com.fasterxml.jackson.core.type.TypeReference; | 18 | import com.fasterxml.jackson.core.type.TypeReference; |
19 | import com.fasterxml.jackson.databind.JsonNode; | 19 | import com.fasterxml.jackson.databind.JsonNode; |
20 | import com.fasterxml.jackson.databind.ObjectMapper; | 20 | import com.fasterxml.jackson.databind.ObjectMapper; |
21 | +import com.fasterxml.jackson.databind.node.ArrayNode; | ||
21 | import com.fasterxml.jackson.databind.node.ObjectNode; | 22 | import com.fasterxml.jackson.databind.node.ObjectNode; |
22 | import lombok.extern.slf4j.Slf4j; | 23 | import lombok.extern.slf4j.Slf4j; |
23 | import org.springframework.beans.factory.annotation.Autowired; | 24 | import org.springframework.beans.factory.annotation.Autowired; |
@@ -422,6 +423,25 @@ public class RuleChainController extends BaseController { | @@ -422,6 +423,25 @@ public class RuleChainController extends BaseController { | ||
422 | } | 423 | } |
423 | 424 | ||
424 | private String msgToOutput(TbMsg msg) throws Exception { | 425 | private String msgToOutput(TbMsg msg) throws Exception { |
426 | + JsonNode resultNode = convertMsgToOut(msg); | ||
427 | + return objectMapper.writeValueAsString(resultNode); | ||
428 | + } | ||
429 | + | ||
430 | + private String msgToOutput(List<TbMsg> msgs) throws Exception { | ||
431 | + JsonNode resultNode; | ||
432 | + if (msgs.size() > 1) { | ||
433 | + resultNode = objectMapper.createArrayNode(); | ||
434 | + for (TbMsg msg : msgs) { | ||
435 | + JsonNode convertedData = convertMsgToOut(msg); | ||
436 | + ((ArrayNode) resultNode).add(convertedData); | ||
437 | + } | ||
438 | + } else { | ||
439 | + resultNode = convertMsgToOut(msgs.get(0)); | ||
440 | + } | ||
441 | + return objectMapper.writeValueAsString(resultNode); | ||
442 | + } | ||
443 | + | ||
444 | + private JsonNode convertMsgToOut(TbMsg msg) throws Exception{ | ||
425 | ObjectNode msgData = objectMapper.createObjectNode(); | 445 | ObjectNode msgData = objectMapper.createObjectNode(); |
426 | if (!StringUtils.isEmpty(msg.getData())) { | 446 | if (!StringUtils.isEmpty(msg.getData())) { |
427 | msgData.set("msg", objectMapper.readTree(msg.getData())); | 447 | msgData.set("msg", objectMapper.readTree(msg.getData())); |
@@ -429,7 +449,8 @@ public class RuleChainController extends BaseController { | @@ -429,7 +449,8 @@ public class RuleChainController extends BaseController { | ||
429 | Map<String, String> metadata = msg.getMetaData().getData(); | 449 | Map<String, String> metadata = msg.getMetaData().getData(); |
430 | msgData.set("metadata", objectMapper.valueToTree(metadata)); | 450 | msgData.set("metadata", objectMapper.valueToTree(metadata)); |
431 | msgData.put("msgType", msg.getType()); | 451 | msgData.put("msgType", msg.getType()); |
432 | - return objectMapper.writeValueAsString(msgData); | 452 | + return msgData; |
433 | } | 453 | } |
434 | 454 | ||
455 | + | ||
435 | } | 456 | } |
@@ -108,13 +108,18 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S | @@ -108,13 +108,18 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S | ||
108 | } | 108 | } |
109 | 109 | ||
110 | @Override | 110 | @Override |
111 | - public TbMsg executeUpdate(TbMsg msg) throws ScriptException { | 111 | + public List<TbMsg> executeUpdate(TbMsg msg) throws ScriptException { |
112 | JsonNode result = executeScript(msg); | 112 | JsonNode result = executeScript(msg); |
113 | - if (!result.isObject()) { | 113 | + if (result.isObject()) { |
114 | + return Collections.singletonList(unbindMsg(result, msg)); | ||
115 | + } else if (result.isArray()){ | ||
116 | + List<TbMsg> res = new ArrayList<>(result.size()); | ||
117 | + result.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg))); | ||
118 | + return res; | ||
119 | + } else { | ||
114 | log.warn("Wrong result type: {}", result.getNodeType()); | 120 | log.warn("Wrong result type: {}", result.getNodeType()); |
115 | throw new ScriptException("Wrong result type: " + result.getNodeType()); | 121 | throw new ScriptException("Wrong result type: " + result.getNodeType()); |
116 | } | 122 | } |
117 | - return unbindMsg(result, msg); | ||
118 | } | 123 | } |
119 | 124 | ||
120 | @Override | 125 | @Override |
@@ -110,7 +110,7 @@ security: | @@ -110,7 +110,7 @@ security: | ||
110 | # Enable/disable claiming devices, if false -> the device's [claimingAllowed] SERVER_SCOPE attribute must be set to [true] to allow claiming specific device | 110 | # Enable/disable claiming devices, if false -> the device's [claimingAllowed] SERVER_SCOPE attribute must be set to [true] to allow claiming specific device |
111 | allowClaimingByDefault: "${SECURITY_CLAIM_ALLOW_CLAIMING_BY_DEFAULT:true}" | 111 | allowClaimingByDefault: "${SECURITY_CLAIM_ALLOW_CLAIMING_BY_DEFAULT:true}" |
112 | # Time allowed to claim the device in milliseconds | 112 | # Time allowed to claim the device in milliseconds |
113 | - duration: "${SECURITY_CLAIM_DURATION:60000}" # 1 minute, note this value must equal claimDevices.timeToLiveInMinutes value | 113 | + duration: "${SECURITY_CLAIM_DURATION:86400000}" # 1 minute, note this value must equal claimDevices.timeToLiveInMinutes value |
114 | basic: | 114 | basic: |
115 | enabled: "${SECURITY_BASIC_ENABLED:false}" | 115 | enabled: "${SECURITY_BASIC_ENABLED:false}" |
116 | oauth2: | 116 | oauth2: |
@@ -348,8 +348,8 @@ caffeine: | @@ -348,8 +348,8 @@ caffeine: | ||
348 | timeToLiveInMinutes: 1440 | 348 | timeToLiveInMinutes: 1440 |
349 | maxSize: 0 | 349 | maxSize: 0 |
350 | claimDevices: | 350 | claimDevices: |
351 | - timeToLiveInMinutes: 1 | ||
352 | - maxSize: 0 | 351 | + timeToLiveInMinutes: 1440 |
352 | + maxSize: 1000 | ||
353 | securitySettings: | 353 | securitySettings: |
354 | timeToLiveInMinutes: 1440 | 354 | timeToLiveInMinutes: 1440 |
355 | maxSize: 0 | 355 | maxSize: 0 |
@@ -54,7 +54,8 @@ | @@ -54,7 +54,8 @@ | ||
54 | <cassandra.version>4.10.0</cassandra.version> | 54 | <cassandra.version>4.10.0</cassandra.version> |
55 | <metrics.version>4.0.5</metrics.version> | 55 | <metrics.version>4.0.5</metrics.version> |
56 | <cassandra-unit.version>4.3.1.0</cassandra-unit.version> | 56 | <cassandra-unit.version>4.3.1.0</cassandra-unit.version> |
57 | - <cassandra-all.version>3.11.9</cassandra-all.version> | 57 | + <cassandra-all.version>3.11.10</cassandra-all.version> |
58 | + <cassandra-driver-core.version>3.11.0</cassandra-driver-core.version> | ||
58 | <takari-cpsuite.version>1.2.7</takari-cpsuite.version> | 59 | <takari-cpsuite.version>1.2.7</takari-cpsuite.version> |
59 | <guava.version>28.2-jre</guava.version> | 60 | <guava.version>28.2-jre</guava.version> |
60 | <caffeine.version>2.6.1</caffeine.version> | 61 | <caffeine.version>2.6.1</caffeine.version> |
@@ -1096,6 +1097,11 @@ | @@ -1096,6 +1097,11 @@ | ||
1096 | <version>${cassandra.version}</version> | 1097 | <version>${cassandra.version}</version> |
1097 | </dependency> | 1098 | </dependency> |
1098 | <dependency> | 1099 | <dependency> |
1100 | + <groupId>com.datastax.cassandra</groupId> | ||
1101 | + <artifactId>cassandra-driver-core</artifactId> | ||
1102 | + <version>${cassandra-driver-core.version}</version> | ||
1103 | + </dependency> | ||
1104 | + <dependency> | ||
1099 | <groupId>io.dropwizard.metrics</groupId> | 1105 | <groupId>io.dropwizard.metrics</groupId> |
1100 | <artifactId>metrics-jmx</artifactId> | 1106 | <artifactId>metrics-jmx</artifactId> |
1101 | <version>${metrics.version}</version> | 1107 | <version>${metrics.version}</version> |
@@ -25,7 +25,7 @@ import java.util.Set; | @@ -25,7 +25,7 @@ import java.util.Set; | ||
25 | 25 | ||
26 | public interface ScriptEngine { | 26 | public interface ScriptEngine { |
27 | 27 | ||
28 | - TbMsg executeUpdate(TbMsg msg) throws ScriptException; | 28 | + List<TbMsg> executeUpdate(TbMsg msg) throws ScriptException; |
29 | 29 | ||
30 | ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg); | 30 | ListenableFuture<List<TbMsg>> executeUpdateAsync(TbMsg msg); |
31 | 31 |
@@ -56,8 +56,8 @@ | @@ -56,8 +56,8 @@ | ||
56 | <artifactId>cassandra-all</artifactId> | 56 | <artifactId>cassandra-all</artifactId> |
57 | </dependency> | 57 | </dependency> |
58 | <dependency> | 58 | <dependency> |
59 | - <groupId>com.datastax.oss</groupId> | ||
60 | - <artifactId>java-driver-core</artifactId> | 59 | + <groupId>com.datastax.cassandra</groupId> |
60 | + <artifactId>cassandra-driver-core</artifactId> | ||
61 | </dependency> | 61 | </dependency> |
62 | <dependency> | 62 | <dependency> |
63 | <groupId>commons-io</groupId> | 63 | <groupId>commons-io</groupId> |
@@ -70,7 +70,7 @@ | @@ -70,7 +70,7 @@ | ||
70 | <plugins> | 70 | <plugins> |
71 | <plugin> | 71 | <plugin> |
72 | <artifactId>maven-assembly-plugin</artifactId> | 72 | <artifactId>maven-assembly-plugin</artifactId> |
73 | - <configuration> | 73 | + <configuration combine.self="override"> |
74 | <archive> | 74 | <archive> |
75 | <manifest> | 75 | <manifest> |
76 | <mainClass>org.thingsboard.client.tools.migrator.MigratorTool</mainClass> | 76 | <mainClass>org.thingsboard.client.tools.migrator.MigratorTool</mainClass> |
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.client.tools.migrator; | ||
17 | + | ||
18 | +import org.apache.commons.io.FileUtils; | ||
19 | +import org.apache.commons.io.LineIterator; | ||
20 | +import org.apache.commons.lang3.StringUtils; | ||
21 | + | ||
22 | +import java.io.File; | ||
23 | +import java.io.IOException; | ||
24 | +import java.util.HashMap; | ||
25 | +import java.util.Map; | ||
26 | + | ||
27 | +public class DictionaryParser { | ||
28 | + private Map<String, String> dictionaryParsed = new HashMap<>(); | ||
29 | + | ||
30 | + public DictionaryParser(File sourceFile) throws IOException { | ||
31 | + parseDictionaryDump(FileUtils.lineIterator(sourceFile)); | ||
32 | + } | ||
33 | + | ||
34 | + public String getKeyByKeyId(String keyId) { | ||
35 | + return dictionaryParsed.get(keyId); | ||
36 | + } | ||
37 | + | ||
38 | + private boolean isBlockFinished(String line) { | ||
39 | + return StringUtils.isBlank(line) || line.equals("\\."); | ||
40 | + } | ||
41 | + | ||
42 | + private boolean isBlockStarted(String line) { | ||
43 | + return line.startsWith("COPY public.ts_kv_dictionary ("); | ||
44 | + } | ||
45 | + | ||
46 | + private void parseDictionaryDump(LineIterator iterator) { | ||
47 | + try { | ||
48 | + String tempLine; | ||
49 | + while (iterator.hasNext()) { | ||
50 | + tempLine = iterator.nextLine(); | ||
51 | + | ||
52 | + if (isBlockStarted(tempLine)) { | ||
53 | + processBlock(iterator); | ||
54 | + } | ||
55 | + } | ||
56 | + } finally { | ||
57 | + iterator.close(); | ||
58 | + } | ||
59 | + } | ||
60 | + | ||
61 | + private void processBlock(LineIterator lineIterator) { | ||
62 | + String tempLine; | ||
63 | + String[] lineSplited; | ||
64 | + while(lineIterator.hasNext()) { | ||
65 | + tempLine = lineIterator.nextLine(); | ||
66 | + if(isBlockFinished(tempLine)) { | ||
67 | + return; | ||
68 | + } | ||
69 | + | ||
70 | + lineSplited = tempLine.split("\t"); | ||
71 | + dictionaryParsed.put(lineSplited[1], lineSplited[0]); | ||
72 | + } | ||
73 | + } | ||
74 | +} |
@@ -30,17 +30,26 @@ public class MigratorTool { | @@ -30,17 +30,26 @@ public class MigratorTool { | ||
30 | public static void main(String[] args) { | 30 | public static void main(String[] args) { |
31 | CommandLine cmd = parseArgs(args); | 31 | CommandLine cmd = parseArgs(args); |
32 | 32 | ||
33 | - | ||
34 | try { | 33 | try { |
35 | - File latestSource = new File(cmd.getOptionValue("latestTelemetryFrom")); | ||
36 | - File latestSaveDir = new File(cmd.getOptionValue("latestTelemetryOut")); | ||
37 | - File tsSource = new File(cmd.getOptionValue("telemetryFrom")); | ||
38 | - File tsSaveDir = new File(cmd.getOptionValue("telemetryOut")); | ||
39 | - File partitionsSaveDir = new File(cmd.getOptionValue("partitionsOut")); | ||
40 | boolean castEnable = Boolean.parseBoolean(cmd.getOptionValue("castEnable")); | 34 | boolean castEnable = Boolean.parseBoolean(cmd.getOptionValue("castEnable")); |
35 | + File allTelemetrySource = new File(cmd.getOptionValue("telemetryFrom")); | ||
36 | + File tsSaveDir = null; | ||
37 | + File partitionsSaveDir = null; | ||
38 | + File latestSaveDir = null; | ||
39 | + | ||
40 | + RelatedEntitiesParser allEntityIdsAndTypes = | ||
41 | + new RelatedEntitiesParser(new File(cmd.getOptionValue("relatedEntities"))); | ||
42 | + DictionaryParser dictionaryParser = new DictionaryParser(allTelemetrySource); | ||
43 | + | ||
44 | + if(cmd.getOptionValue("latestTelemetryOut") != null) { | ||
45 | + latestSaveDir = new File(cmd.getOptionValue("latestTelemetryOut")); | ||
46 | + } | ||
47 | + if(cmd.getOptionValue("telemetryOut") != null) { | ||
48 | + tsSaveDir = new File(cmd.getOptionValue("telemetryOut")); | ||
49 | + partitionsSaveDir = new File(cmd.getOptionValue("partitionsOut")); | ||
50 | + } | ||
41 | 51 | ||
42 | - PgCaLatestMigrator.migrateLatest(latestSource, latestSaveDir, castEnable); | ||
43 | - PostgresToCassandraTelemetryMigrator.migrateTs(tsSource, tsSaveDir, partitionsSaveDir, castEnable); | 52 | + new PgCaMigrator(allTelemetrySource, tsSaveDir, partitionsSaveDir, latestSaveDir, allEntityIdsAndTypes, dictionaryParser, castEnable).migrate(); |
44 | 53 | ||
45 | } catch (Throwable th) { | 54 | } catch (Throwable th) { |
46 | th.printStackTrace(); | 55 | th.printStackTrace(); |
@@ -52,30 +61,30 @@ public class MigratorTool { | @@ -52,30 +61,30 @@ public class MigratorTool { | ||
52 | private static CommandLine parseArgs(String[] args) { | 61 | private static CommandLine parseArgs(String[] args) { |
53 | Options options = new Options(); | 62 | Options options = new Options(); |
54 | 63 | ||
55 | - Option latestTsOpt = new Option("latestFrom", "latestTelemetryFrom", true, "latest telemetry source file path"); | ||
56 | - latestTsOpt.setRequired(true); | ||
57 | - options.addOption(latestTsOpt); | 64 | + Option telemetryAllFrom = new Option("telemetryFrom", "telemetryFrom", true, "telemetry source file"); |
65 | + telemetryAllFrom.setRequired(true); | ||
66 | + options.addOption(telemetryAllFrom); | ||
58 | 67 | ||
59 | Option latestTsOutOpt = new Option("latestOut", "latestTelemetryOut", true, "latest telemetry save dir"); | 68 | Option latestTsOutOpt = new Option("latestOut", "latestTelemetryOut", true, "latest telemetry save dir"); |
60 | - latestTsOutOpt.setRequired(true); | 69 | + latestTsOutOpt.setRequired(false); |
61 | options.addOption(latestTsOutOpt); | 70 | options.addOption(latestTsOutOpt); |
62 | 71 | ||
63 | - Option tsOpt = new Option("tsFrom", "telemetryFrom", true, "telemetry source file path"); | ||
64 | - tsOpt.setRequired(true); | ||
65 | - options.addOption(tsOpt); | ||
66 | - | ||
67 | Option tsOutOpt = new Option("tsOut", "telemetryOut", true, "sstable save dir"); | 72 | Option tsOutOpt = new Option("tsOut", "telemetryOut", true, "sstable save dir"); |
68 | - tsOutOpt.setRequired(true); | 73 | + tsOutOpt.setRequired(false); |
69 | options.addOption(tsOutOpt); | 74 | options.addOption(tsOutOpt); |
70 | 75 | ||
71 | Option partitionOutOpt = new Option("partitionsOut", "partitionsOut", true, "partitions save dir"); | 76 | Option partitionOutOpt = new Option("partitionsOut", "partitionsOut", true, "partitions save dir"); |
72 | - partitionOutOpt.setRequired(true); | 77 | + partitionOutOpt.setRequired(false); |
73 | options.addOption(partitionOutOpt); | 78 | options.addOption(partitionOutOpt); |
74 | 79 | ||
75 | Option castOpt = new Option("castEnable", "castEnable", true, "cast String to Double if possible"); | 80 | Option castOpt = new Option("castEnable", "castEnable", true, "cast String to Double if possible"); |
76 | castOpt.setRequired(true); | 81 | castOpt.setRequired(true); |
77 | options.addOption(castOpt); | 82 | options.addOption(castOpt); |
78 | 83 | ||
84 | + Option relatedOpt = new Option("relatedEntities", "relatedEntities", true, "related entities source file path"); | ||
85 | + relatedOpt.setRequired(true); | ||
86 | + options.addOption(relatedOpt); | ||
87 | + | ||
79 | HelpFormatter formatter = new HelpFormatter(); | 88 | HelpFormatter formatter = new HelpFormatter(); |
80 | CommandLineParser parser = new BasicParser(); | 89 | CommandLineParser parser = new BasicParser(); |
81 | 90 |
tools/src/main/java/org/thingsboard/client/tools/migrator/PgCaLatestMigrator.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.client.tools.migrator; | ||
17 | - | ||
18 | -import com.google.common.collect.Lists; | ||
19 | -import org.apache.cassandra.io.sstable.CQLSSTableWriter; | ||
20 | -import org.apache.commons.io.FileUtils; | ||
21 | -import org.apache.commons.io.LineIterator; | ||
22 | -import org.apache.commons.lang3.StringUtils; | ||
23 | -import org.apache.commons.lang3.math.NumberUtils; | ||
24 | - | ||
25 | -import java.io.File; | ||
26 | -import java.io.IOException; | ||
27 | -import java.util.ArrayList; | ||
28 | -import java.util.Arrays; | ||
29 | -import java.util.Date; | ||
30 | -import java.util.List; | ||
31 | -import java.util.UUID; | ||
32 | -import java.util.stream.Collectors; | ||
33 | - | ||
34 | -public class PgCaLatestMigrator { | ||
35 | - | ||
36 | - private static final long LOG_BATCH = 1000000; | ||
37 | - private static final long rowPerFile = 1000000; | ||
38 | - | ||
39 | - | ||
40 | - private static long linesProcessed = 0; | ||
41 | - private static long linesMigrated = 0; | ||
42 | - private static long castErrors = 0; | ||
43 | - private static long castedOk = 0; | ||
44 | - | ||
45 | - private static long currentWriterCount = 1; | ||
46 | - private static CQLSSTableWriter currentTsWriter = null; | ||
47 | - | ||
48 | - public static void migrateLatest(File sourceFile, File outDir, boolean castStringsIfPossible) throws IOException { | ||
49 | - long startTs = System.currentTimeMillis(); | ||
50 | - long stepLineTs = System.currentTimeMillis(); | ||
51 | - long stepOkLineTs = System.currentTimeMillis(); | ||
52 | - LineIterator iterator = FileUtils.lineIterator(sourceFile); | ||
53 | - currentTsWriter = WriterBuilder.getTsWriter(outDir); | ||
54 | - | ||
55 | - boolean isBlockStarted = false; | ||
56 | - boolean isBlockFinished = false; | ||
57 | - | ||
58 | - String line; | ||
59 | - while (iterator.hasNext()) { | ||
60 | - if (linesProcessed++ % LOG_BATCH == 0) { | ||
61 | - System.out.println(new Date() + " linesProcessed = " + linesProcessed + " in " + (System.currentTimeMillis() - stepLineTs) + " castOk " + castedOk + " castErr " + castErrors); | ||
62 | - stepLineTs = System.currentTimeMillis(); | ||
63 | - } | ||
64 | - | ||
65 | - line = iterator.nextLine(); | ||
66 | - | ||
67 | - if (isBlockFinished) { | ||
68 | - break; | ||
69 | - } | ||
70 | - | ||
71 | - if (!isBlockStarted) { | ||
72 | - if (isBlockStarted(line)) { | ||
73 | - System.out.println(); | ||
74 | - System.out.println(); | ||
75 | - System.out.println(line); | ||
76 | - System.out.println(); | ||
77 | - System.out.println(); | ||
78 | - isBlockStarted = true; | ||
79 | - } | ||
80 | - continue; | ||
81 | - } | ||
82 | - | ||
83 | - if (isBlockFinished(line)) { | ||
84 | - isBlockFinished = true; | ||
85 | - } else { | ||
86 | - try { | ||
87 | - List<String> raw = Arrays.stream(line.trim().split("\t")) | ||
88 | - .map(String::trim) | ||
89 | - .filter(StringUtils::isNotEmpty) | ||
90 | - .collect(Collectors.toList()); | ||
91 | - List<Object> values = toValues(raw); | ||
92 | - | ||
93 | - if (currentWriterCount == 0) { | ||
94 | - System.out.println(new Date() + " close writer " + new Date()); | ||
95 | - currentTsWriter.close(); | ||
96 | - currentTsWriter = WriterBuilder.getLatestWriter(outDir); | ||
97 | - } | ||
98 | - | ||
99 | - if (castStringsIfPossible) { | ||
100 | - currentTsWriter.addRow(castToNumericIfPossible(values)); | ||
101 | - } else { | ||
102 | - currentTsWriter.addRow(values); | ||
103 | - } | ||
104 | - currentWriterCount++; | ||
105 | - if (currentWriterCount >= rowPerFile) { | ||
106 | - currentWriterCount = 0; | ||
107 | - } | ||
108 | - | ||
109 | - if (linesMigrated++ % LOG_BATCH == 0) { | ||
110 | - System.out.println(new Date() + " migrated = " + linesMigrated + " in " + (System.currentTimeMillis() - stepOkLineTs)); | ||
111 | - stepOkLineTs = System.currentTimeMillis(); | ||
112 | - } | ||
113 | - } catch (Exception ex) { | ||
114 | - System.out.println(ex.getMessage() + " -> " + line); | ||
115 | - } | ||
116 | - | ||
117 | - } | ||
118 | - } | ||
119 | - | ||
120 | - long endTs = System.currentTimeMillis(); | ||
121 | - System.out.println(); | ||
122 | - System.out.println(new Date() + " Migrated rows " + linesMigrated + " in " + (endTs - startTs)); | ||
123 | - | ||
124 | - currentTsWriter.close(); | ||
125 | - System.out.println(); | ||
126 | - System.out.println("Finished migrate Latest Telemetry"); | ||
127 | - } | ||
128 | - | ||
129 | - | ||
130 | - private static List<Object> castToNumericIfPossible(List<Object> values) { | ||
131 | - try { | ||
132 | - if (values.get(6) != null && NumberUtils.isNumber(values.get(6).toString())) { | ||
133 | - Double casted = NumberUtils.createDouble(values.get(6).toString()); | ||
134 | - List<Object> numeric = Lists.newArrayList(); | ||
135 | - numeric.addAll(values); | ||
136 | - numeric.set(6, null); | ||
137 | - numeric.set(8, casted); | ||
138 | - castedOk++; | ||
139 | - return numeric; | ||
140 | - } | ||
141 | - } catch (Throwable th) { | ||
142 | - castErrors++; | ||
143 | - } | ||
144 | - return values; | ||
145 | - } | ||
146 | - | ||
147 | - private static List<Object> toValues(List<String> raw) { | ||
148 | - //expected Table structure: | ||
149 | -// COPY public.ts_kv_latest (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) FROM stdin; | ||
150 | - | ||
151 | - | ||
152 | - List<Object> result = new ArrayList<>(); | ||
153 | - result.add(raw.get(0)); | ||
154 | - result.add(fromString(raw.get(1))); | ||
155 | - result.add(raw.get(2)); | ||
156 | - | ||
157 | - long ts = Long.parseLong(raw.get(3)); | ||
158 | - result.add(ts); | ||
159 | - | ||
160 | - result.add(raw.get(4).equals("\\N") ? null : raw.get(4).equals("t") ? Boolean.TRUE : Boolean.FALSE); | ||
161 | - result.add(raw.get(5).equals("\\N") ? null : raw.get(5)); | ||
162 | - result.add(raw.get(6).equals("\\N") ? null : Long.parseLong(raw.get(6))); | ||
163 | - result.add(raw.get(7).equals("\\N") ? null : Double.parseDouble(raw.get(7))); | ||
164 | - return result; | ||
165 | - } | ||
166 | - | ||
167 | - public static UUID fromString(String src) { | ||
168 | - return UUID.fromString(src.substring(7, 15) + "-" + src.substring(3, 7) + "-1" | ||
169 | - + src.substring(0, 3) + "-" + src.substring(15, 19) + "-" + src.substring(19)); | ||
170 | - } | ||
171 | - | ||
172 | - private static boolean isBlockStarted(String line) { | ||
173 | - return line.startsWith("COPY public.ts_kv_latest"); | ||
174 | - } | ||
175 | - | ||
176 | - private static boolean isBlockFinished(String line) { | ||
177 | - return StringUtils.isBlank(line) || line.equals("\\."); | ||
178 | - } | ||
179 | -} |
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.client.tools.migrator; | ||
17 | + | ||
18 | +import com.google.common.collect.Lists; | ||
19 | +import org.apache.cassandra.io.sstable.CQLSSTableWriter; | ||
20 | +import org.apache.commons.io.FileUtils; | ||
21 | +import org.apache.commons.io.LineIterator; | ||
22 | +import org.apache.commons.lang3.StringUtils; | ||
23 | +import org.apache.commons.lang3.math.NumberUtils; | ||
24 | + | ||
25 | +import java.io.File; | ||
26 | +import java.io.IOException; | ||
27 | +import java.time.Instant; | ||
28 | +import java.time.LocalDateTime; | ||
29 | +import java.time.ZoneOffset; | ||
30 | +import java.time.temporal.ChronoUnit; | ||
31 | +import java.util.ArrayList; | ||
32 | +import java.util.Arrays; | ||
33 | +import java.util.Date; | ||
34 | +import java.util.HashSet; | ||
35 | +import java.util.List; | ||
36 | +import java.util.Set; | ||
37 | +import java.util.UUID; | ||
38 | +import java.util.function.Function; | ||
39 | +import java.util.stream.Collectors; | ||
40 | + | ||
41 | +public class PgCaMigrator { | ||
42 | + | ||
43 | + private final long LOG_BATCH = 1000000; | ||
44 | + private final long rowPerFile = 1000000; | ||
45 | + | ||
46 | + private long linesTsMigrated = 0; | ||
47 | + private long linesLatestMigrated = 0; | ||
48 | + private long castErrors = 0; | ||
49 | + private long castedOk = 0; | ||
50 | + | ||
51 | + private long currentWriterCount = 1; | ||
52 | + | ||
53 | + private final File sourceFile; | ||
54 | + private final boolean castStringIfPossible; | ||
55 | + | ||
56 | + private final RelatedEntitiesParser entityIdsAndTypes; | ||
57 | + private final DictionaryParser keyParser; | ||
58 | + private CQLSSTableWriter currentTsWriter; | ||
59 | + private CQLSSTableWriter currentPartitionsWriter; | ||
60 | + private CQLSSTableWriter currentTsLatestWriter; | ||
61 | + private final Set<String> partitions = new HashSet<>(); | ||
62 | + | ||
63 | + private File outTsDir; | ||
64 | + private File outTsLatestDir; | ||
65 | + | ||
66 | + public PgCaMigrator(File sourceFile, | ||
67 | + File ourTsDir, | ||
68 | + File outTsPartitionDir, | ||
69 | + File outTsLatestDir, | ||
70 | + RelatedEntitiesParser allEntityIdsAndTypes, | ||
71 | + DictionaryParser dictionaryParser, | ||
72 | + boolean castStringsIfPossible) { | ||
73 | + this.sourceFile = sourceFile; | ||
74 | + this.entityIdsAndTypes = allEntityIdsAndTypes; | ||
75 | + this.keyParser = dictionaryParser; | ||
76 | + this.castStringIfPossible = castStringsIfPossible; | ||
77 | + if(outTsLatestDir != null) { | ||
78 | + this.currentTsLatestWriter = WriterBuilder.getLatestWriter(outTsLatestDir); | ||
79 | + this.outTsLatestDir = outTsLatestDir; | ||
80 | + } | ||
81 | + if(ourTsDir != null) { | ||
82 | + this.currentTsWriter = WriterBuilder.getTsWriter(ourTsDir); | ||
83 | + this.currentPartitionsWriter = WriterBuilder.getPartitionWriter(outTsPartitionDir); | ||
84 | + this.outTsDir = ourTsDir; | ||
85 | + } | ||
86 | + } | ||
87 | + | ||
88 | + public void migrate() throws IOException { | ||
89 | + boolean isTsDone = false; | ||
90 | + boolean isLatestDone = false; | ||
91 | + String line; | ||
92 | + LineIterator iterator = FileUtils.lineIterator(this.sourceFile); | ||
93 | + | ||
94 | + try { | ||
95 | + while(iterator.hasNext()) { | ||
96 | + line = iterator.nextLine(); | ||
97 | + if(!isLatestDone && isBlockLatestStarted(line)) { | ||
98 | + System.out.println("START TO MIGRATE LATEST"); | ||
99 | + long start = System.currentTimeMillis(); | ||
100 | + processBlock(iterator, currentTsLatestWriter, outTsLatestDir, this::toValuesLatest); | ||
101 | + System.out.println("TOTAL LINES MIGRATED: " + linesLatestMigrated + ", FORMING OF SSL FOR LATEST TS FINISHED WITH TIME: " + (System.currentTimeMillis() - start) + " ms."); | ||
102 | + isLatestDone = true; | ||
103 | + } | ||
104 | + | ||
105 | + if(!isTsDone && isBlockTsStarted(line)) { | ||
106 | + System.out.println("START TO MIGRATE TS"); | ||
107 | + long start = System.currentTimeMillis(); | ||
108 | + processBlock(iterator, currentTsWriter, outTsDir, this::toValuesTs); | ||
109 | + System.out.println("TOTAL LINES MIGRATED: " + linesTsMigrated + ", FORMING OF SSL FOR TS FINISHED WITH TIME: " + (System.currentTimeMillis() - start) + " ms."); | ||
110 | + isTsDone = true; | ||
111 | + } | ||
112 | + } | ||
113 | + | ||
114 | + System.out.println("Partitions collected " + partitions.size()); | ||
115 | + long startTs = System.currentTimeMillis(); | ||
116 | + for (String partition : partitions) { | ||
117 | + String[] split = partition.split("\\|"); | ||
118 | + List<Object> values = Lists.newArrayList(); | ||
119 | + values.add(split[0]); | ||
120 | + values.add(UUID.fromString(split[1])); | ||
121 | + values.add(split[2]); | ||
122 | + values.add(Long.parseLong(split[3])); | ||
123 | + currentPartitionsWriter.addRow(values); | ||
124 | + } | ||
125 | + | ||
126 | + System.out.println(new Date() + " Migrated partitions " + partitions.size() + " in " + (System.currentTimeMillis() - startTs)); | ||
127 | + | ||
128 | + System.out.println(); | ||
129 | + System.out.println("Finished migrate Telemetry"); | ||
130 | + | ||
131 | + } finally { | ||
132 | + iterator.close(); | ||
133 | + currentTsLatestWriter.close(); | ||
134 | + currentTsWriter.close(); | ||
135 | + currentPartitionsWriter.close(); | ||
136 | + } | ||
137 | + } | ||
138 | + | ||
139 | + private void logLinesProcessed(long lines) { | ||
140 | + if (lines % LOG_BATCH == 0) { | ||
141 | + System.out.println(new Date() + " lines processed = " + lines + " in, castOk " + castedOk + " castErr " + castErrors); | ||
142 | + } | ||
143 | + } | ||
144 | + | ||
145 | + private void logLinesMigrated(long lines) { | ||
146 | + if(lines % LOG_BATCH == 0) { | ||
147 | + System.out.println(new Date() + " lines migrated = " + lines + " in, castOk " + castedOk + " castErr " + castErrors); | ||
148 | + } | ||
149 | + } | ||
150 | + | ||
151 | + private void addTypeIdKey(List<Object> result, List<String> raw) { | ||
152 | + result.add(entityIdsAndTypes.getEntityType(raw.get(0))); | ||
153 | + result.add(UUID.fromString(raw.get(0))); | ||
154 | + result.add(keyParser.getKeyByKeyId(raw.get(1))); | ||
155 | + } | ||
156 | + | ||
157 | + private void addPartitions(List<Object> result, List<String> raw) { | ||
158 | + long ts = Long.parseLong(raw.get(2)); | ||
159 | + long partition = toPartitionTs(ts); | ||
160 | + result.add(partition); | ||
161 | + result.add(ts); | ||
162 | + } | ||
163 | + | ||
164 | + private void addTimeseries(List<Object> result, List<String> raw) { | ||
165 | + result.add(Long.parseLong(raw.get(2))); | ||
166 | + } | ||
167 | + | ||
168 | + private void addValues(List<Object> result, List<String> raw) { | ||
169 | + result.add(raw.get(3).equals("\\N") ? null : raw.get(3).equals("t") ? Boolean.TRUE : Boolean.FALSE); | ||
170 | + result.add(raw.get(4).equals("\\N") ? null : raw.get(4)); | ||
171 | + result.add(raw.get(5).equals("\\N") ? null : Long.parseLong(raw.get(5))); | ||
172 | + result.add(raw.get(6).equals("\\N") ? null : Double.parseDouble(raw.get(6))); | ||
173 | + result.add(raw.get(7).equals("\\N") ? null : raw.get(7)); | ||
174 | + } | ||
175 | + | ||
176 | + private List<Object> toValuesTs(List<String> raw) { | ||
177 | + | ||
178 | + logLinesMigrated(linesTsMigrated++); | ||
179 | + | ||
180 | + List<Object> result = new ArrayList<>(); | ||
181 | + | ||
182 | + addTypeIdKey(result, raw); | ||
183 | + addPartitions(result, raw); | ||
184 | + addValues(result, raw); | ||
185 | + | ||
186 | + processPartitions(result); | ||
187 | + | ||
188 | + return result; | ||
189 | + } | ||
190 | + | ||
191 | + private List<Object> toValuesLatest(List<String> raw) { | ||
192 | + logLinesMigrated(linesLatestMigrated++); | ||
193 | + List<Object> result = new ArrayList<>(); | ||
194 | + | ||
195 | + addTypeIdKey(result, raw); | ||
196 | + addTimeseries(result, raw); | ||
197 | + addValues(result, raw); | ||
198 | + | ||
199 | + return result; | ||
200 | + } | ||
201 | + | ||
202 | + private long toPartitionTs(long ts) { | ||
203 | + LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); | ||
204 | + return time.truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1).toInstant(ZoneOffset.UTC).toEpochMilli(); | ||
205 | + } | ||
206 | + | ||
207 | + private void processPartitions(List<Object> values) { | ||
208 | + String key = values.get(0) + "|" + values.get(1) + "|" + values.get(2) + "|" + values.get(3); | ||
209 | + partitions.add(key); | ||
210 | + } | ||
211 | + | ||
212 | + private void processBlock(LineIterator iterator, CQLSSTableWriter writer, File outDir, Function<List<String>, List<Object>> function) { | ||
213 | + String currentLine; | ||
214 | + long linesProcessed = 0; | ||
215 | + while(iterator.hasNext()) { | ||
216 | + logLinesProcessed(linesProcessed++); | ||
217 | + currentLine = iterator.nextLine(); | ||
218 | + if(isBlockFinished(currentLine)) { | ||
219 | + return; | ||
220 | + } | ||
221 | + | ||
222 | + try { | ||
223 | + List<String> raw = Arrays.stream(currentLine.trim().split("\t")) | ||
224 | + .map(String::trim) | ||
225 | + .collect(Collectors.toList()); | ||
226 | + List<Object> values = function.apply(raw); | ||
227 | + | ||
228 | + if (this.currentWriterCount == 0) { | ||
229 | + System.out.println(new Date() + " close writer " + new Date()); | ||
230 | + writer.close(); | ||
231 | + writer = WriterBuilder.getLatestWriter(outDir); | ||
232 | + } | ||
233 | + | ||
234 | + if (this.castStringIfPossible) { | ||
235 | + writer.addRow(castToNumericIfPossible(values)); | ||
236 | + } else { | ||
237 | + writer.addRow(values); | ||
238 | + } | ||
239 | + | ||
240 | + currentWriterCount++; | ||
241 | + if (currentWriterCount >= rowPerFile) { | ||
242 | + currentWriterCount = 0; | ||
243 | + } | ||
244 | + } catch (Exception ex) { | ||
245 | + System.out.println(ex.getMessage() + " -> " + currentLine); | ||
246 | + } | ||
247 | + } | ||
248 | + } | ||
249 | + | ||
250 | + private List<Object> castToNumericIfPossible(List<Object> values) { | ||
251 | + try { | ||
252 | + if (values.get(6) != null && NumberUtils.isNumber(values.get(6).toString())) { | ||
253 | + Double casted = NumberUtils.createDouble(values.get(6).toString()); | ||
254 | + List<Object> numeric = Lists.newArrayList(); | ||
255 | + numeric.addAll(values); | ||
256 | + numeric.set(6, null); | ||
257 | + numeric.set(8, casted); | ||
258 | + castedOk++; | ||
259 | + return numeric; | ||
260 | + } | ||
261 | + } catch (Throwable th) { | ||
262 | + castErrors++; | ||
263 | + } | ||
264 | + | ||
265 | + processPartitions(values); | ||
266 | + | ||
267 | + return values; | ||
268 | + } | ||
269 | + | ||
270 | + private boolean isBlockFinished(String line) { | ||
271 | + return StringUtils.isBlank(line) || line.equals("\\."); | ||
272 | + } | ||
273 | + | ||
274 | + private boolean isBlockTsStarted(String line) { | ||
275 | + return line.startsWith("COPY public.ts_kv ("); | ||
276 | + } | ||
277 | + | ||
278 | + private boolean isBlockLatestStarted(String line) { | ||
279 | + return line.startsWith("COPY public.ts_kv_latest ("); | ||
280 | + } | ||
281 | + | ||
282 | +} |
tools/src/main/java/org/thingsboard/client/tools/migrator/PostgresToCassandraTelemetryMigrator.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.client.tools.migrator; | ||
17 | - | ||
18 | -import com.google.common.collect.Lists; | ||
19 | -import org.apache.cassandra.io.sstable.CQLSSTableWriter; | ||
20 | -import org.apache.commons.io.FileUtils; | ||
21 | -import org.apache.commons.io.LineIterator; | ||
22 | -import org.apache.commons.lang3.StringUtils; | ||
23 | -import org.apache.commons.lang3.math.NumberUtils; | ||
24 | - | ||
25 | -import java.io.File; | ||
26 | -import java.io.IOException; | ||
27 | -import java.time.Instant; | ||
28 | -import java.time.LocalDateTime; | ||
29 | -import java.time.ZoneOffset; | ||
30 | -import java.time.temporal.ChronoUnit; | ||
31 | -import java.util.ArrayList; | ||
32 | -import java.util.Arrays; | ||
33 | -import java.util.Date; | ||
34 | -import java.util.HashSet; | ||
35 | -import java.util.List; | ||
36 | -import java.util.Set; | ||
37 | -import java.util.UUID; | ||
38 | -import java.util.stream.Collectors; | ||
39 | - | ||
40 | -public class PostgresToCassandraTelemetryMigrator { | ||
41 | - | ||
42 | - private static final long LOG_BATCH = 1000000; | ||
43 | - private static final long rowPerFile = 1000000; | ||
44 | - | ||
45 | - | ||
46 | - private static long linesProcessed = 0; | ||
47 | - private static long linesMigrated = 0; | ||
48 | - private static long castErrors = 0; | ||
49 | - private static long castedOk = 0; | ||
50 | - | ||
51 | - private static long currentWriterCount = 1; | ||
52 | - private static CQLSSTableWriter currentTsWriter = null; | ||
53 | - private static CQLSSTableWriter currentPartitionWriter = null; | ||
54 | - | ||
55 | - private static Set<String> partitions = new HashSet<>(); | ||
56 | - | ||
57 | - | ||
58 | - public static void migrateTs(File sourceFile, File outTsDir, File outPartitionDir, boolean castStringsIfPossible) throws IOException { | ||
59 | - long startTs = System.currentTimeMillis(); | ||
60 | - long stepLineTs = System.currentTimeMillis(); | ||
61 | - long stepOkLineTs = System.currentTimeMillis(); | ||
62 | - LineIterator iterator = FileUtils.lineIterator(sourceFile); | ||
63 | - currentTsWriter = WriterBuilder.getTsWriter(outTsDir); | ||
64 | - currentPartitionWriter = WriterBuilder.getPartitionWriter(outPartitionDir); | ||
65 | - | ||
66 | - boolean isBlockStarted = false; | ||
67 | - boolean isBlockFinished = false; | ||
68 | - | ||
69 | - String line; | ||
70 | - while (iterator.hasNext()) { | ||
71 | - if (linesProcessed++ % LOG_BATCH == 0) { | ||
72 | - System.out.println(new Date() + " linesProcessed = " + linesProcessed + " in " + (System.currentTimeMillis() - stepLineTs) + " castOk " + castedOk + " castErr " + castErrors); | ||
73 | - stepLineTs = System.currentTimeMillis(); | ||
74 | - } | ||
75 | - | ||
76 | - line = iterator.nextLine(); | ||
77 | - | ||
78 | - if (isBlockFinished) { | ||
79 | - break; | ||
80 | - } | ||
81 | - | ||
82 | - if (!isBlockStarted) { | ||
83 | - if (isBlockStarted(line)) { | ||
84 | - System.out.println(); | ||
85 | - System.out.println(); | ||
86 | - System.out.println(line); | ||
87 | - System.out.println(); | ||
88 | - System.out.println(); | ||
89 | - isBlockStarted = true; | ||
90 | - } | ||
91 | - continue; | ||
92 | - } | ||
93 | - | ||
94 | - if (isBlockFinished(line)) { | ||
95 | - isBlockFinished = true; | ||
96 | - } else { | ||
97 | - try { | ||
98 | - List<String> raw = Arrays.stream(line.trim().split("\t")) | ||
99 | - .map(String::trim) | ||
100 | - .filter(StringUtils::isNotEmpty) | ||
101 | - .collect(Collectors.toList()); | ||
102 | - List<Object> values = toValues(raw); | ||
103 | - | ||
104 | - if (currentWriterCount == 0) { | ||
105 | - System.out.println(new Date() + " close writer " + new Date()); | ||
106 | - currentTsWriter.close(); | ||
107 | - currentTsWriter = WriterBuilder.getTsWriter(outTsDir); | ||
108 | - } | ||
109 | - | ||
110 | - if (castStringsIfPossible) { | ||
111 | - currentTsWriter.addRow(castToNumericIfPossible(values)); | ||
112 | - } else { | ||
113 | - currentTsWriter.addRow(values); | ||
114 | - } | ||
115 | - processPartitions(values); | ||
116 | - currentWriterCount++; | ||
117 | - if (currentWriterCount >= rowPerFile) { | ||
118 | - currentWriterCount = 0; | ||
119 | - } | ||
120 | - | ||
121 | - if (linesMigrated++ % LOG_BATCH == 0) { | ||
122 | - System.out.println(new Date() + " migrated = " + linesMigrated + " in " + (System.currentTimeMillis() - stepOkLineTs) + " partitions = " + partitions.size()); | ||
123 | - stepOkLineTs = System.currentTimeMillis(); | ||
124 | - } | ||
125 | - } catch (Exception ex) { | ||
126 | - System.out.println(ex.getMessage() + " -> " + line); | ||
127 | - } | ||
128 | - | ||
129 | - } | ||
130 | - } | ||
131 | - | ||
132 | - long endTs = System.currentTimeMillis(); | ||
133 | - System.out.println(); | ||
134 | - System.out.println(new Date() + " Migrated rows " + linesMigrated + " in " + (endTs - startTs)); | ||
135 | - System.out.println("Partitions collected " + partitions.size()); | ||
136 | - | ||
137 | - startTs = System.currentTimeMillis(); | ||
138 | - for (String partition : partitions) { | ||
139 | - String[] split = partition.split("\\|"); | ||
140 | - List<Object> values = Lists.newArrayList(); | ||
141 | - values.add(split[0]); | ||
142 | - values.add(UUID.fromString(split[1])); | ||
143 | - values.add(split[2]); | ||
144 | - values.add(Long.parseLong(split[3])); | ||
145 | - currentPartitionWriter.addRow(values); | ||
146 | - } | ||
147 | - currentPartitionWriter.close(); | ||
148 | - endTs = System.currentTimeMillis(); | ||
149 | - System.out.println(); | ||
150 | - System.out.println(); | ||
151 | - System.out.println(new Date() + " Migrated partitions " + partitions.size() + " in " + (endTs - startTs)); | ||
152 | - | ||
153 | - | ||
154 | - currentTsWriter.close(); | ||
155 | - System.out.println(); | ||
156 | - System.out.println("Finished migrate Telemetry"); | ||
157 | - } | ||
158 | - | ||
159 | - private static List<Object> castToNumericIfPossible(List<Object> values) { | ||
160 | - try { | ||
161 | - if (values.get(6) != null && NumberUtils.isNumber(values.get(6).toString())) { | ||
162 | - Double casted = NumberUtils.createDouble(values.get(6).toString()); | ||
163 | - List<Object> numeric = Lists.newArrayList(); | ||
164 | - numeric.addAll(values); | ||
165 | - numeric.set(6, null); | ||
166 | - numeric.set(8, casted); | ||
167 | - castedOk++; | ||
168 | - return numeric; | ||
169 | - } | ||
170 | - } catch (Throwable th) { | ||
171 | - castErrors++; | ||
172 | - } | ||
173 | - return values; | ||
174 | - } | ||
175 | - | ||
176 | - private static void processPartitions(List<Object> values) { | ||
177 | - String key = values.get(0) + "|" + values.get(1) + "|" + values.get(2) + "|" + values.get(3); | ||
178 | - partitions.add(key); | ||
179 | - } | ||
180 | - | ||
181 | - private static List<Object> toValues(List<String> raw) { | ||
182 | - //expected Table structure: | ||
183 | -// COPY public.ts_kv (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) FROM stdin; | ||
184 | - | ||
185 | - | ||
186 | - List<Object> result = new ArrayList<>(); | ||
187 | - result.add(raw.get(0)); | ||
188 | - result.add(fromString(raw.get(1))); | ||
189 | - result.add(raw.get(2)); | ||
190 | - | ||
191 | - long ts = Long.parseLong(raw.get(3)); | ||
192 | - long partition = toPartitionTs(ts); | ||
193 | - result.add(partition); | ||
194 | - result.add(ts); | ||
195 | - | ||
196 | - result.add(raw.get(4).equals("\\N") ? null : raw.get(4).equals("t") ? Boolean.TRUE : Boolean.FALSE); | ||
197 | - result.add(raw.get(5).equals("\\N") ? null : raw.get(5)); | ||
198 | - result.add(raw.get(6).equals("\\N") ? null : Long.parseLong(raw.get(6))); | ||
199 | - result.add(raw.get(7).equals("\\N") ? null : Double.parseDouble(raw.get(7))); | ||
200 | - return result; | ||
201 | - } | ||
202 | - | ||
203 | - public static UUID fromString(String src) { | ||
204 | - return UUID.fromString(src.substring(7, 15) + "-" + src.substring(3, 7) + "-1" | ||
205 | - + src.substring(0, 3) + "-" + src.substring(15, 19) + "-" + src.substring(19)); | ||
206 | - } | ||
207 | - | ||
208 | - private static long toPartitionTs(long ts) { | ||
209 | - LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); | ||
210 | - return time.truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1).toInstant(ZoneOffset.UTC).toEpochMilli(); | ||
211 | -// return TsPartitionDate.MONTHS.truncatedTo(time).toInstant(ZoneOffset.UTC).toEpochMilli(); | ||
212 | - } | ||
213 | - | ||
214 | - private static boolean isBlockStarted(String line) { | ||
215 | - return line.startsWith("COPY public.ts_kv"); | ||
216 | - } | ||
217 | - | ||
218 | - private static boolean isBlockFinished(String line) { | ||
219 | - return StringUtils.isBlank(line) || line.equals("\\."); | ||
220 | - } | ||
221 | - | ||
222 | -} |
@@ -21,37 +21,41 @@ It will generate single jar file with all required dependencies inside `target d | @@ -21,37 +21,41 @@ It will generate single jar file with all required dependencies inside `target d | ||
21 | #### Dump data from the source Postgres Database | 21 | #### Dump data from the source Postgres Database |
22 | *Do not use compression if possible because Tool can only work with uncompressed file | 22 | *Do not use compression if possible because Tool can only work with uncompressed file |
23 | 23 | ||
24 | -1. Dump table `ts_kv` table: | ||
25 | - | ||
26 | - `pg_dump -h localhost -U postgres -d thingsboard -t ts_kv > ts_kv.dmp` | ||
27 | - | ||
28 | -2. Dump table `ts_kv_latest` table: | ||
29 | - | ||
30 | - `pg_dump -h localhost -U postgres -d thingsboard -t ts_kv_latest > ts_kv_latest.dmp` | 24 | +1. Dump related tables that need to correct save telemetry |
25 | + | ||
26 | + `pg_dump -h localhost -U postgres -d thingsboard -T admin_settings -T attribute_kv -T audit_log -T component_discriptor -T device_credentials -T event -T oauth2_client_registration -T oauth2_client_registration_info -T oauth2_client_registration_template -T relation -T rule_node_state tb_schema_settings -T user_credentials > related_entities.dmp` | ||
27 | + | ||
28 | +2. Dump `ts_kv` and child: | ||
29 | + | ||
30 | + `pg_dump -h localhost -U postgres -d thingsboard --load-via-partition-root --data-only -t ts_kv* > ts_kv_all.dmp` | ||
31 | 31 | ||
32 | -3. [Optional] move table dumps to the instance where cassandra will be hosted | 32 | +3. [Optional] Move table dumps to the instance where cassandra will be hosted |
33 | 33 | ||
34 | #### Prepare directory structure for SSTables | 34 | #### Prepare directory structure for SSTables |
35 | Tool use 3 different directories for saving SSTables - `ts_kv_cf`, `ts_kv_latest_cf`, `ts_kv_partitions_cf` | 35 | Tool use 3 different directories for saving SSTables - `ts_kv_cf`, `ts_kv_latest_cf`, `ts_kv_partitions_cf` |
36 | 36 | ||
37 | Create 3 empty directories. For example: | 37 | Create 3 empty directories. For example: |
38 | 38 | ||
39 | - /home/ubunut/migration/ts | ||
40 | - /home/ubunut/migration/ts_latest | ||
41 | - /home/ubunut/migration/ts_partition | 39 | + /home/user/migration/ts |
40 | + /home/user/migration/ts_latest | ||
41 | + /home/user/migration/ts_partition | ||
42 | 42 | ||
43 | #### Run tool | 43 | #### Run tool |
44 | -*Note: if you run this tool on remote instance - don't forget to execute this command in `screen` to avoid unexpected termination | 44 | + |
45 | +**If you want to migrate just `ts_kv` without `ts_kv_latest` or vice versa don't use arguments (paths) for output files* | ||
46 | + | ||
47 | +**Note: if you run this tool on remote instance - don't forget to execute this command in `screen` to avoid unexpected termination* | ||
45 | 48 | ||
46 | ``` | 49 | ``` |
47 | -java -jar ./tools-2.4.1-SNAPSHOT-jar-with-dependencies.jar | ||
48 | - -latestFrom ./source/ts_kv_latest.dmp | ||
49 | - -latestOut /home/ubunut/migration/ts_latest | ||
50 | - -tsFrom ./source/ts_kv.dmp | ||
51 | - -tsOut /home/ubunut/migration/ts | ||
52 | - -partitionsOut /home/ubunut/migration/ts_partition | ||
53 | - -castEnable false | 50 | +java -jar ./tools-3.2.2-SNAPSHOT-jar-with-dependencies.jar |
51 | + -telemetryFrom /home/user/dump/ts_kv_all.dmp | ||
52 | + -relatedEntities /home/user/dump/related_entities.dmp | ||
53 | + -latestOut /home/user/migration/ts_latest | ||
54 | + -tsOut /home/user/migration/ts | ||
55 | + -partitionsOut /home/user/migration/ts_partition | ||
56 | + -castEnable false | ||
54 | ``` | 57 | ``` |
58 | +*Use your paths for program arguments* | ||
55 | 59 | ||
56 | Tool execution time depends on DB size, CPU resources and Disk throughput | 60 | Tool execution time depends on DB size, CPU resources and Disk throughput |
57 | 61 | ||
@@ -59,22 +63,23 @@ Tool execution time depends on DB size, CPU resources and Disk throughput | @@ -59,22 +63,23 @@ Tool execution time depends on DB size, CPU resources and Disk throughput | ||
59 | * Note that this this part works only for single node Cassandra Cluster. If you have more nodes - it is better to use `sstableloader` tool. | 63 | * Note that this this part works only for single node Cassandra Cluster. If you have more nodes - it is better to use `sstableloader` tool. |
60 | 64 | ||
61 | 1. [Optional] install Cassandra on the instance | 65 | 1. [Optional] install Cassandra on the instance |
62 | -2. [Optional] Using `cqlsh` create `thingsboard` keyspace and requred tables from this file `schema-ts.cql` | 66 | +2. [Optional] Using `cqlsh` create `thingsboard` keyspace and requred tables from this files `schema-ts.cql` and `schema-ts-latest.cql` using `source` command |
63 | 3. Stop Cassandra | 67 | 3. Stop Cassandra |
64 | -4. Copy generated SSTable files into cassandra data dir: | 68 | +4. Look at `/var/lib/cassandra/data/thingsboard` and check for names of data folders |
69 | +5. Copy generated SSTable files into cassandra data dir using next command: | ||
65 | 70 | ||
66 | ``` | 71 | ``` |
67 | - sudo find /home/ubunut/migration/ts -name '*.*' -exec mv {} /var/lib/cassandra/data/thingsboard/ts_kv_cf-0e9aaf00ee5511e9a5fa7d6f489ffd13/ \; | ||
68 | - sudo find /home/ubunut/migration/ts_latest -name '*.*' -exec mv {} /var/lib/cassandra/data/thingsboard/ts_kv_latest_cf-161449d0ee5511e9a5fa7d6f489ffd13/ \; | ||
69 | - sudo find /home/ubunut/migration/ts_partition -name '*.*' -exec mv {} /var/lib/cassandra/data/thingsboard/ts_kv_partitions_cf-12e8fa80ee5511e9a5fa7d6f489ffd13/ \; | 72 | + sudo find /home/user/migration/ts -name '*.*' -exec mv {} /var/lib/cassandra/data/thingsboard/ts_kv_cf-0e9aaf00ee5511e9a5fa7d6f489ffd13/ \; |
73 | + sudo find /home/user/migration/ts_latest -name '*.*' -exec mv {} /var/lib/cassandra/data/thingsboard/ts_kv_latest_cf-161449d0ee5511e9a5fa7d6f489ffd13/ \; | ||
74 | + sudo find /home/user/migration/ts_partition -name '*.*' -exec mv {} /var/lib/cassandra/data/thingsboard/ts_kv_partitions_cf-12e8fa80ee5511e9a5fa7d6f489ffd13/ \; | ||
70 | ``` | 75 | ``` |
71 | - | ||
72 | -5. Start Cassandra service and trigger compaction | 76 | + *Pay attention! Data folders have similar name `ts_kv_cf-0e9aaf00ee5511e9a5fa7d6f489ffd13`, but you have to use own* |
77 | +6. Start Cassandra service and trigger compaction | ||
78 | + | ||
79 | + Trigger compactions: `nodetool compact thingsboard` | ||
80 | + | ||
81 | + Check compaction status: `nodetool compactionstats` | ||
73 | 82 | ||
74 | -``` | ||
75 | - trigger compactions: nodetool compact thingsboard | ||
76 | - check compaction status: nodetool compactionstats | ||
77 | -``` | ||
78 | 83 | ||
79 | ## Switch Thignsboard into Hybrid Mode | 84 | ## Switch Thignsboard into Hybrid Mode |
80 | 85 |
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.client.tools.migrator; | ||
17 | + | ||
18 | +import org.apache.commons.io.FileUtils; | ||
19 | +import org.apache.commons.io.LineIterator; | ||
20 | +import org.apache.commons.lang3.StringUtils; | ||
21 | +import org.thingsboard.server.common.data.EntityType; | ||
22 | + | ||
23 | +import java.io.File; | ||
24 | +import java.io.IOException; | ||
25 | +import java.util.HashMap; | ||
26 | +import java.util.Map; | ||
27 | + | ||
28 | +public class RelatedEntitiesParser { | ||
29 | + private final Map<String, String> allEntityIdsAndTypes = new HashMap<>(); | ||
30 | + | ||
31 | + private final Map<String, EntityType> tableNameAndEntityType = Map.ofEntries( | ||
32 | + Map.entry("COPY public.alarm ", EntityType.ALARM), | ||
33 | + Map.entry("COPY public.asset ", EntityType.ASSET), | ||
34 | + Map.entry("COPY public.customer ", EntityType.CUSTOMER), | ||
35 | + Map.entry("COPY public.dashboard ", EntityType.DASHBOARD), | ||
36 | + Map.entry("COPY public.device ", EntityType.DEVICE), | ||
37 | + Map.entry("COPY public.rule_chain ", EntityType.RULE_CHAIN), | ||
38 | + Map.entry("COPY public.rule_node ", EntityType.RULE_NODE), | ||
39 | + Map.entry("COPY public.tenant ", EntityType.TENANT), | ||
40 | + Map.entry("COPY public.tb_user ", EntityType.USER), | ||
41 | + Map.entry("COPY public.entity_view ", EntityType.ENTITY_VIEW), | ||
42 | + Map.entry("COPY public.widgets_bundle ", EntityType.WIDGETS_BUNDLE), | ||
43 | + Map.entry("COPY public.widget_type ", EntityType.WIDGET_TYPE), | ||
44 | + Map.entry("COPY public.tenant_profile ", EntityType.TENANT_PROFILE), | ||
45 | + Map.entry("COPY public.device_profile ", EntityType.DEVICE_PROFILE), | ||
46 | + Map.entry("COPY public.api_usage_state ", EntityType.API_USAGE_STATE) | ||
47 | + ); | ||
48 | + | ||
49 | + public RelatedEntitiesParser(File source) throws IOException { | ||
50 | + processAllTables(FileUtils.lineIterator(source)); | ||
51 | + } | ||
52 | + | ||
53 | + public String getEntityType(String uuid) { | ||
54 | + return this.allEntityIdsAndTypes.get(uuid); | ||
55 | + } | ||
56 | + | ||
57 | + private boolean isBlockFinished(String line) { | ||
58 | + return StringUtils.isBlank(line) || line.equals("\\."); | ||
59 | + } | ||
60 | + | ||
61 | + private void processAllTables(LineIterator lineIterator) { | ||
62 | + String currentLine; | ||
63 | + try { | ||
64 | + while (lineIterator.hasNext()) { | ||
65 | + currentLine = lineIterator.nextLine(); | ||
66 | + for(Map.Entry<String, EntityType> entry : tableNameAndEntityType.entrySet()) { | ||
67 | + if(currentLine.startsWith(entry.getKey())) { | ||
68 | + processBlock(lineIterator, entry.getValue()); | ||
69 | + } | ||
70 | + } | ||
71 | + } | ||
72 | + } finally { | ||
73 | + lineIterator.close(); | ||
74 | + } | ||
75 | + } | ||
76 | + | ||
77 | + private void processBlock(LineIterator lineIterator, EntityType entityType) { | ||
78 | + String currentLine; | ||
79 | + while(lineIterator.hasNext()) { | ||
80 | + currentLine = lineIterator.nextLine(); | ||
81 | + if(isBlockFinished(currentLine)) { | ||
82 | + return; | ||
83 | + } | ||
84 | + allEntityIdsAndTypes.put(currentLine.split("\t")[0], entityType.name()); | ||
85 | + } | ||
86 | + } | ||
87 | +} |
@@ -31,6 +31,7 @@ public class WriterBuilder { | @@ -31,6 +31,7 @@ public class WriterBuilder { | ||
31 | " str_v text,\n" + | 31 | " str_v text,\n" + |
32 | " long_v bigint,\n" + | 32 | " long_v bigint,\n" + |
33 | " dbl_v double,\n" + | 33 | " dbl_v double,\n" + |
34 | + " json_v text,\n" + | ||
34 | " PRIMARY KEY (( entity_type, entity_id, key, partition ), ts)\n" + | 35 | " PRIMARY KEY (( entity_type, entity_id, key, partition ), ts)\n" + |
35 | ");"; | 36 | ");"; |
36 | 37 | ||
@@ -43,6 +44,7 @@ public class WriterBuilder { | @@ -43,6 +44,7 @@ public class WriterBuilder { | ||
43 | " str_v text,\n" + | 44 | " str_v text,\n" + |
44 | " long_v bigint,\n" + | 45 | " long_v bigint,\n" + |
45 | " dbl_v double,\n" + | 46 | " dbl_v double,\n" + |
47 | + " json_v text,\n" + | ||
46 | " PRIMARY KEY (( entity_type, entity_id ), key)\n" + | 48 | " PRIMARY KEY (( entity_type, entity_id ), key)\n" + |
47 | ") WITH compaction = { 'class' : 'LeveledCompactionStrategy' };"; | 49 | ") WITH compaction = { 'class' : 'LeveledCompactionStrategy' };"; |
48 | 50 | ||
@@ -59,8 +61,8 @@ public class WriterBuilder { | @@ -59,8 +61,8 @@ public class WriterBuilder { | ||
59 | return CQLSSTableWriter.builder() | 61 | return CQLSSTableWriter.builder() |
60 | .inDirectory(dir) | 62 | .inDirectory(dir) |
61 | .forTable(tsSchema) | 63 | .forTable(tsSchema) |
62 | - .using("INSERT INTO thingsboard.ts_kv_cf (entity_type, entity_id, key, partition, ts, bool_v, str_v, long_v, dbl_v) " + | ||
63 | - "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)") | 64 | + .using("INSERT INTO thingsboard.ts_kv_cf (entity_type, entity_id, key, partition, ts, bool_v, str_v, long_v, dbl_v, json_v) " + |
65 | + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)") | ||
64 | .build(); | 66 | .build(); |
65 | } | 67 | } |
66 | 68 | ||
@@ -68,8 +70,8 @@ public class WriterBuilder { | @@ -68,8 +70,8 @@ public class WriterBuilder { | ||
68 | return CQLSSTableWriter.builder() | 70 | return CQLSSTableWriter.builder() |
69 | .inDirectory(dir) | 71 | .inDirectory(dir) |
70 | .forTable(latestSchema) | 72 | .forTable(latestSchema) |
71 | - .using("INSERT INTO thingsboard.ts_kv_latest_cf (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v) " + | ||
72 | - "VALUES (?, ?, ?, ?, ?, ?, ?, ?)") | 73 | + .using("INSERT INTO thingsboard.ts_kv_latest_cf (entity_type, entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) " + |
74 | + "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)") | ||
73 | .build(); | 75 | .build(); |
74 | } | 76 | } |
75 | 77 |