Browse Source

Merge remote-tracking branch 'origin/develop/2.5.1' into feature/edge

pull/2436/head
Volodymyr Babak 6 years ago
parent
commit
3d3dd88710
  1. 298
      application/pom.xml
  2. 248
      application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql
  3. 113
      application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql
  4. 5
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  5. 9
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  6. 7
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  7. 15
      application/src/main/java/org/thingsboard/server/controller/AssetController.java
  8. 20
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  9. 3
      application/src/main/java/org/thingsboard/server/controller/CustomerController.java
  10. 21
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  11. 5
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  12. 5
      application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
  13. 13
      application/src/main/java/org/thingsboard/server/controller/RpcController.java
  14. 5
      application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
  15. 4
      application/src/main/java/org/thingsboard/server/controller/TenantController.java
  16. 12
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  17. 9
      application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java
  18. 8
      application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java
  19. 9
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  20. 3
      application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java
  21. 2
      application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java
  22. 1
      application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java
  23. 4
      application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseSchemaService.java
  24. 114
      application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java
  25. 87
      application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java
  26. 15
      application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
  27. 45
      application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java
  28. 59
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java
  29. 4
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java
  30. 4
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java
  31. 24
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
  32. 9
      application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
  33. 18
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  34. 2
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java
  35. 2
      application/src/main/java/org/thingsboard/server/service/subscription/TbAttributeSubscriptionScope.java
  36. 4
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
  37. 25
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  38. 23
      application/src/main/resources/thingsboard.yml
  39. 9
      application/src/main/scripts/control/rpm/postinst
  40. 24
      application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
  41. 12
      application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java
  42. 304
      application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java
  43. 14
      application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java
  44. 11
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java
  45. 10
      application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
  46. 480
      application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
  47. 14
      application/src/test/java/org/thingsboard/server/controller/BaseWidgetTypeControllerTest.java
  48. 11
      application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java
  49. 50
      application/src/test/java/org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java
  50. 2
      common/dao-api/pom.xml
  51. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2User.java
  52. 2
      common/data/pom.xml
  53. 2
      common/edge-api/pom.xml
  54. 2
      common/message/pom.xml
  55. 30
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
  56. 1
      common/message/src/main/proto/tbmsg.proto
  57. 2
      common/pom.xml
  58. 2
      common/queue/pom.xml
  59. 2
      common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusConsumerTemplate.java
  60. 7
      common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java
  61. 9
      common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java
  62. 8
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java
  63. 2
      common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java
  64. 2
      common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java
  65. 2
      common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubConsumerTemplate.java
  66. 2
      common/transport/coap/pom.xml
  67. 2
      common/transport/http/pom.xml
  68. 2
      common/transport/mqtt/pom.xml
  69. 2
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  70. 2
      common/transport/pom.xml
  71. 6
      common/transport/transport-api/pom.xml
  72. 2
      common/util/pom.xml
  73. 2
      dao/pom.xml
  74. 2
      dao/src/main/java/org/thingsboard/server/dao/model/sql/EventEntity.java
  75. 2
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java
  76. 2
      dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java
  77. 2
      dao/src/main/resources/sql/schema-timescale.sql
  78. 2
      dao/src/main/resources/sql/schema-ts-psql.sql
  79. 1
      docker/.env
  80. 26
      docker/compose-utils.sh
  81. 71
      docker/docker-compose.aws-sqs.yml
  82. 8
      docker/docker-compose.cassandra.yml
  83. 82
      docker/docker-compose.kafka.yml
  84. 8
      docker/docker-compose.postgres.yml
  85. 71
      docker/docker-compose.pubsub.yml
  86. 71
      docker/docker-compose.rabbitmq.yml
  87. 71
      docker/docker-compose.service-bus.yml
  88. 29
      docker/docker-compose.yml
  89. 6
      docker/docker-install-tb.sh
  90. 4
      docker/docker-remove-services.sh
  91. 4
      docker/docker-start-services.sh
  92. 4
      docker/docker-stop-services.sh
  93. 6
      docker/docker-update-service.sh
  94. 8
      docker/docker-upgrade-tb.sh
  95. 4
      docker/queue-aws-sqs.env
  96. 2
      docker/queue-kafka.env
  97. 3
      docker/queue-pubsub.env
  98. 5
      docker/queue-rabbitmq.env
  99. 4
      docker/queue-service-bus.env
  100. 3
      docker/tb-coap-transport.env

298
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>application</artifactId> <artifactId>application</artifactId>
@ -34,10 +34,15 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<main.dir>${basedir}/..</main.dir> <main.dir>${basedir}/..</main.dir>
<pkg.type>java</pkg.type>
<pkg.disabled>false</pkg.disabled>
<pkg.process-resources.phase>process-resources</pkg.process-resources.phase>
<pkg.package.phase>package</pkg.package.phase>
<pkg.name>thingsboard</pkg.name> <pkg.name>thingsboard</pkg.name>
<pkg.unixLogFolder>/var/log/${pkg.name}</pkg.unixLogFolder>
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder>
<pkg.win.dist>${project.build.directory}/windows</pkg.win.dist> <pkg.win.dist>${project.build.directory}/windows</pkg.win.dist>
<pkg.copyInstallScripts>true</pkg.copyInstallScripts>
<pkg.implementationTitle>ThingsBoard</pkg.implementationTitle>
<pkg.mainClass>org.thingsboard.server.ThingsboardServerApplication</pkg.mainClass>
</properties> </properties>
<dependencies> <dependencies>
@ -311,6 +316,10 @@
</resource> </resource>
</resources> </resources>
<plugins> <plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
@ -327,313 +336,30 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId> <artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-conf</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/conf</outputDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>logback.xml</exclude>
</excludes>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
<execution>
<id>copy-service-conf</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/conf</outputDirectory>
<resources>
<resource>
<directory>src/main/conf</directory>
<filtering>true</filtering>
</resource>
</resources>
<filters>
<filter>src/main/filters/unix.properties</filter>
</filters>
</configuration>
</execution>
<execution>
<id>copy-win-conf</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${pkg.win.dist}/conf</outputDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>logback.xml</exclude>
</excludes>
<filtering>false</filtering>
</resource>
<resource>
<directory>src/main/conf</directory>
<excludes>
<exclude>thingsboard.conf</exclude>
</excludes>
<filtering>true</filtering>
</resource>
</resources>
<filters>
<filter>src/main/filters/windows.properties</filter>
</filters>
</configuration>
</execution>
<execution>
<id>copy-control</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/control</outputDirectory>
<resources>
<resource>
<directory>src/main/scripts/control</directory>
<filtering>true</filtering>
</resource>
</resources>
<filters>
<filter>src/main/filters/unix.properties</filter>
</filters>
</configuration>
</execution>
<execution>
<id>copy-install</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/bin/install</outputDirectory>
<resources>
<resource>
<directory>src/main/scripts/install</directory>
<includes>
<include>**/*.sh</include>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
<filters>
<filter>src/main/filters/unix.properties</filter>
</filters>
</configuration>
</execution>
<execution>
<id>copy-windows-control</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${pkg.win.dist}</outputDirectory>
<resources>
<resource>
<directory>src/main/scripts/windows</directory>
<filtering>true</filtering>
</resource>
</resources>
<filters>
<filter>src/main/filters/windows.properties</filter>
</filters>
</configuration>
</execution>
<execution>
<id>copy-windows-install</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${pkg.win.dist}/install</outputDirectory>
<resources>
<resource>
<directory>src/main/scripts/install</directory>
<includes>
<include>logback.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
<filters>
<filter>src/main/filters/windows.properties</filter>
</filters>
</configuration>
</execution>
<execution>
<id>copy-data</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/data</outputDirectory>
<resources>
<resource>
<directory>src/main/data</directory>
</resource>
<resource>
<directory>../dao/src/main/resources</directory>
<includes>
<include>**/*.cql</include>
<include>**/*.sql</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId> <artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-winsw-service</id>
<phase>package</phase>
<goals>
<goal>copy</goal>
</goals>
<configuration>
<artifactItems>
<artifactItem>
<groupId>com.sun.winsw</groupId>
<artifactId>winsw</artifactId>
<classifier>bin</classifier>
<type>exe</type>
<destFileName>service.exe</destFileName>
</artifactItem>
</artifactItems>
<outputDirectory>${pkg.win.dist}</outputDirectory>
</configuration>
</execution>
</executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>maven-jar-plugin</artifactId>
<configuration>
<excludes>
<exclude>**/logback.xml</exclude>
</excludes>
<archive>
<manifestEntries>
<Implementation-Title>ThingsBoard</Implementation-Title>
<Implementation-Version>${project.version}</Implementation-Version>
</manifestEntries>
</archive>
</configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>org.thingsboard.server.ThingsboardServerApplication</mainClass>
<classifier>boot</classifier>
<layout>ZIP</layout>
<executable>true</executable>
<excludeDevtools>true</excludeDevtools>
<embeddedLaunchScriptProperties>
<confFolder>${pkg.installFolder}/conf</confFolder>
<logFolder>${pkg.unixLogFolder}</logFolder>
<logFilename>${pkg.name}.out</logFilename>
<initInfoProvides>${pkg.name}</initInfoProvides>
</embeddedLaunchScriptProperties>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<artifactId>gradle-maven-plugin</artifactId> <artifactId>gradle-maven-plugin</artifactId>
<configuration>
<tasks>
<task>build</task>
<task>buildDeb</task>
<task>buildRpm</task>
</tasks>
<args>
<arg>-PprojectBuildDir=${project.build.directory}</arg>
<arg>-PprojectVersion=${project.version}</arg>
<arg>-PmainJar=${project.build.directory}/${project.build.finalName}-boot.${project.packaging}
</arg>
<arg>-PpkgName=${pkg.name}</arg>
<arg>-PpkgInstallFolder=${pkg.installFolder}</arg>
<arg>-PpkgLogFolder=${pkg.unixLogFolder}</arg>
</args>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>invoke</goal>
</goals>
</execution>
</executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId> <artifactId>maven-assembly-plugin</artifactId>
<configuration>
<finalName>${pkg.name}</finalName>
<descriptors>
<descriptor>src/main/assembly/windows.xml</descriptor>
</descriptors>
</configuration>
<executions>
<execution>
<id>assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-install-plugin</artifactId> <artifactId>maven-install-plugin</artifactId>
<configuration>
<file>${project.build.directory}/${pkg.name}.deb</file>
<artifactId>${project.artifactId}</artifactId>
<groupId>${project.groupId}</groupId>
<version>${project.version}</version>
<classifier>deb</classifier>
<packaging>deb</packaging>
</configuration>
<executions>
<execution>
<id>install-deb</id>
<phase>package</phase>
<goals>
<goal>install-file</goal>
</goals>
</execution>
</executions>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.xolstice.maven.plugins</groupId> <groupId>org.xolstice.maven.plugins</groupId>

248
application/src/main/data/upgrade/2.4.3/schema_update_psql_ts.sql

@ -16,50 +16,85 @@
-- call create_partition_ts_kv_table(); -- call create_partition_ts_kv_table();
CREATE OR REPLACE PROCEDURE create_partition_ts_kv_table() LANGUAGE plpgsql AS $$ CREATE OR REPLACE PROCEDURE create_partition_ts_kv_table()
LANGUAGE plpgsql AS
$$
BEGIN BEGIN
ALTER TABLE ts_kv ALTER TABLE ts_kv
RENAME TO ts_kv_old; DROP CONSTRAINT IF EXISTS ts_kv_unq_key;
ALTER TABLE ts_kv_old ALTER TABLE ts_kv
RENAME CONSTRAINT ts_kv_pkey TO ts_kv_pkey_old; DROP CONSTRAINT IF EXISTS ts_kv_pkey;
CREATE TABLE IF NOT EXISTS ts_kv ALTER TABLE ts_kv
( ADD CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_type, entity_id, key, ts);
LIKE ts_kv_old ALTER TABLE ts_kv
) RENAME TO ts_kv_old;
PARTITION BY RANGE (ts); ALTER TABLE ts_kv_old
ALTER TABLE ts_kv RENAME CONSTRAINT ts_kv_pkey TO ts_kv_pkey_old;
DROP COLUMN entity_type; CREATE TABLE IF NOT EXISTS ts_kv
ALTER TABLE ts_kv (
ALTER COLUMN entity_id TYPE uuid USING entity_id::uuid; LIKE ts_kv_old
ALTER TABLE ts_kv )
ALTER COLUMN key TYPE integer USING key::integer; PARTITION BY RANGE (ts);
ALTER TABLE ts_kv ALTER TABLE ts_kv
ADD CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_id, key, ts); DROP COLUMN entity_type;
ALTER TABLE ts_kv
ALTER COLUMN entity_id TYPE uuid USING entity_id::uuid;
ALTER TABLE ts_kv
ALTER COLUMN key TYPE integer USING key::integer;
ALTER TABLE ts_kv
ADD CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_id, key, ts);
CREATE TABLE IF NOT EXISTS ts_kv_indefinite PARTITION OF ts_kv DEFAULT;
END; END;
$$; $$;
-- call create_new_ts_kv_latest_table(); -- call create_new_ts_kv_latest_table();
CREATE OR REPLACE PROCEDURE create_new_ts_kv_latest_table() LANGUAGE plpgsql AS $$ CREATE OR REPLACE PROCEDURE create_new_ts_kv_latest_table()
LANGUAGE plpgsql AS
$$
BEGIN BEGIN
ALTER TABLE ts_kv_latest IF NOT EXISTS(SELECT FROM pg_tables WHERE schemaname = 'public' AND tablename = 'ts_kv_latest_old') THEN
RENAME TO ts_kv_latest_old; ALTER TABLE ts_kv_latest
ALTER TABLE ts_kv_latest_old DROP CONSTRAINT IF EXISTS ts_kv_latest_unq_key;
RENAME CONSTRAINT ts_kv_latest_pkey TO ts_kv_latest_pkey_old; ALTER TABLE ts_kv_latest
CREATE TABLE IF NOT EXISTS ts_kv_latest DROP CONSTRAINT IF EXISTS ts_kv_latest_pkey;
( ALTER TABLE ts_kv_latest
LIKE ts_kv_latest_old ADD CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_type, entity_id, key);
); ALTER TABLE ts_kv_latest
ALTER TABLE ts_kv_latest RENAME TO ts_kv_latest_old;
DROP COLUMN entity_type; ALTER TABLE ts_kv_latest_old
ALTER TABLE ts_kv_latest RENAME CONSTRAINT ts_kv_latest_pkey TO ts_kv_latest_pkey_old;
ALTER COLUMN entity_id TYPE uuid USING entity_id::uuid; CREATE TABLE IF NOT EXISTS ts_kv_latest
ALTER TABLE ts_kv_latest (
ALTER COLUMN key TYPE integer USING key::integer; LIKE ts_kv_latest_old
ALTER TABLE ts_kv_latest );
ADD CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key); ALTER TABLE ts_kv_latest
DROP COLUMN entity_type;
ALTER TABLE ts_kv_latest
ALTER COLUMN entity_id TYPE uuid USING entity_id::uuid;
ALTER TABLE ts_kv_latest
ALTER COLUMN key TYPE integer USING key::integer;
ALTER TABLE ts_kv_latest
ADD CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key);
ELSE
RAISE NOTICE 'ts_kv_latest_old table already exists!';
IF NOT EXISTS(SELECT FROM pg_tables WHERE schemaname = 'public' AND tablename = 'ts_kv_latest') THEN
CREATE TABLE IF NOT EXISTS ts_kv_latest
(
entity_id uuid NOT NULL,
key int NOT NULL,
ts bigint NOT NULL,
bool_v boolean,
str_v varchar(10000000),
long_v bigint,
dbl_v double precision,
json_v json,
CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key)
);
END IF;
END IF;
END; END;
$$; $$;
@ -93,8 +128,9 @@ BEGIN
RETURN QUERY SELECT SUBSTRING(year_date.year, 1, 4) AS partition_date, RETURN QUERY SELECT SUBSTRING(year_date.year, 1, 4) AS partition_date,
(extract(epoch from (year_date.year)::timestamp) * 1000)::bigint AS from_ts, (extract(epoch from (year_date.year)::timestamp) * 1000)::bigint AS from_ts,
(extract(epoch from (year_date.year::date + INTERVAL '1 YEAR')::timestamp) * (extract(epoch from (year_date.year::date + INTERVAL '1 YEAR')::timestamp) *
1000)::bigint AS to_ts 1000)::bigint AS to_ts
FROM (SELECT DISTINCT TO_CHAR(TO_TIMESTAMP(ts / 1000), 'YYYY_01_01') AS year FROM ts_kv_old) AS year_date; FROM (SELECT DISTINCT TO_CHAR(TO_TIMESTAMP(ts / 1000), 'YYYY_01_01') AS year
FROM ts_kv_old) AS year_date;
ELSE ELSE
RAISE EXCEPTION 'Failed to parse partitioning property: % !', partition_type; RAISE EXCEPTION 'Failed to parse partitioning property: % !', partition_type;
END CASE; END CASE;
@ -103,13 +139,16 @@ $$ LANGUAGE plpgsql;
-- call create_partitions(); -- call create_partitions();
CREATE OR REPLACE PROCEDURE create_partitions(IN partition_type varchar) LANGUAGE plpgsql AS $$ CREATE OR REPLACE PROCEDURE create_partitions(IN partition_type varchar)
LANGUAGE plpgsql AS
$$
DECLARE DECLARE
partition_date varchar; partition_date varchar;
from_ts bigint; from_ts bigint;
to_ts bigint; to_ts bigint;
partitions_cursor CURSOR FOR SELECT * FROM get_partitions_data(partition_type); partitions_cursor CURSOR FOR SELECT *
FROM get_partitions_data(partition_type);
BEGIN BEGIN
OPEN partitions_cursor; OPEN partitions_cursor;
LOOP LOOP
@ -127,21 +166,25 @@ $$;
-- call create_ts_kv_dictionary_table(); -- call create_ts_kv_dictionary_table();
CREATE OR REPLACE PROCEDURE create_ts_kv_dictionary_table() LANGUAGE plpgsql AS $$ CREATE OR REPLACE PROCEDURE create_ts_kv_dictionary_table()
LANGUAGE plpgsql AS
$$
BEGIN BEGIN
CREATE TABLE IF NOT EXISTS ts_kv_dictionary CREATE TABLE IF NOT EXISTS ts_kv_dictionary
( (
key varchar(255) NOT NULL, key varchar(255) NOT NULL,
key_id serial UNIQUE, key_id serial UNIQUE,
CONSTRAINT ts_key_id_pkey PRIMARY KEY (key) CONSTRAINT ts_key_id_pkey PRIMARY KEY (key)
); );
END; END;
$$; $$;
-- call insert_into_dictionary(); -- call insert_into_dictionary();
CREATE OR REPLACE PROCEDURE insert_into_dictionary() LANGUAGE plpgsql AS $$ CREATE OR REPLACE PROCEDURE insert_into_dictionary()
LANGUAGE plpgsql AS
$$
DECLARE DECLARE
insert_record RECORD; insert_record RECORD;
@ -164,31 +207,89 @@ BEGIN
END; END;
$$; $$;
-- call insert_into_ts_kv(); CREATE OR REPLACE FUNCTION to_uuid(IN entity_id varchar, OUT uuid_id uuid) AS
$$
BEGIN
uuid_id := substring(entity_id, 8, 8) || '-' || substring(entity_id, 4, 4) || '-1' || substring(entity_id, 1, 3) ||
'-' || substring(entity_id, 16, 4) || '-' || substring(entity_id, 20, 12);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE PROCEDURE insert_into_ts_kv() LANGUAGE plpgsql AS $$ CREATE OR REPLACE PROCEDURE insert_into_ts_kv(IN path_to_file varchar)
DECLARE LANGUAGE plpgsql AS
insert_size CONSTANT integer := 10000; $$
insert_counter integer DEFAULT 0; BEGIN
insert_record RECORD; EXECUTE format('COPY (SELECT to_uuid(entity_id) AS entity_id,
insert_cursor CURSOR FOR SELECT CONCAT(entity_id_uuid_first_part, '-', entity_id_uuid_second_part, '-1', entity_id_uuid_third_part, '-', entity_id_uuid_fourth_part, '-', entity_id_uuid_fifth_part)::uuid AS entity_id,
ts_kv_records.key AS key, ts_kv_records.key AS key,
ts_kv_records.ts AS ts, ts_kv_records.ts AS ts,
ts_kv_records.bool_v AS bool_v, ts_kv_records.bool_v AS bool_v,
ts_kv_records.str_v AS str_v, ts_kv_records.str_v AS str_v,
ts_kv_records.long_v AS long_v, ts_kv_records.long_v AS long_v,
ts_kv_records.dbl_v AS dbl_v ts_kv_records.dbl_v AS dbl_v
FROM (SELECT SUBSTRING(entity_id, 8, 8) AS entity_id_uuid_first_part, FROM (SELECT entity_id AS entity_id,
SUBSTRING(entity_id, 4, 4) AS entity_id_uuid_second_part, key_id AS key,
SUBSTRING(entity_id, 1, 3) AS entity_id_uuid_third_part, ts,
SUBSTRING(entity_id, 16, 4) AS entity_id_uuid_fourth_part, bool_v,
SUBSTRING(entity_id, 20) AS entity_id_uuid_fifth_part, str_v,
long_v,
dbl_v
FROM ts_kv_old
INNER JOIN ts_kv_dictionary ON (ts_kv_old.key = ts_kv_dictionary.key)) AS ts_kv_records) TO %L;',
path_to_file);
EXECUTE format('COPY ts_kv FROM %L', path_to_file);
END
$$;
-- call insert_into_ts_kv_latest();
CREATE OR REPLACE PROCEDURE insert_into_ts_kv_latest(IN path_to_file varchar)
LANGUAGE plpgsql AS
$$
BEGIN
EXECUTE format('COPY (SELECT to_uuid(entity_id) AS entity_id,
ts_kv_latest_records.key AS key,
ts_kv_latest_records.ts AS ts,
ts_kv_latest_records.bool_v AS bool_v,
ts_kv_latest_records.str_v AS str_v,
ts_kv_latest_records.long_v AS long_v,
ts_kv_latest_records.dbl_v AS dbl_v
FROM (SELECT entity_id AS entity_id,
key_id AS key, key_id AS key,
ts, ts,
bool_v, bool_v,
str_v, str_v,
long_v, long_v,
dbl_v dbl_v
FROM ts_kv_latest_old
INNER JOIN ts_kv_dictionary ON (ts_kv_latest_old.key = ts_kv_dictionary.key)) AS ts_kv_latest_records) TO %L;',
path_to_file);
EXECUTE format('COPY ts_kv_latest FROM %L', path_to_file);
END;
$$;
-- call insert_into_ts_kv_cursor();
CREATE OR REPLACE PROCEDURE insert_into_ts_kv_cursor()
LANGUAGE plpgsql AS
$$
DECLARE
insert_size CONSTANT integer := 10000;
insert_counter integer DEFAULT 0;
insert_record RECORD;
insert_cursor CURSOR FOR SELECT to_uuid(entity_id) AS entity_id,
ts_kv_records.key AS key,
ts_kv_records.ts AS ts,
ts_kv_records.bool_v AS bool_v,
ts_kv_records.str_v AS str_v,
ts_kv_records.long_v AS long_v,
ts_kv_records.dbl_v AS dbl_v
FROM (SELECT entity_id AS entity_id,
key_id AS key,
ts,
bool_v,
str_v,
long_v,
dbl_v
FROM ts_kv_old FROM ts_kv_old
INNER JOIN ts_kv_dictionary ON (ts_kv_old.key = ts_kv_dictionary.key)) AS ts_kv_records; INNER JOIN ts_kv_dictionary ON (ts_kv_old.key = ts_kv_dictionary.key)) AS ts_kv_records;
BEGIN BEGIN
@ -211,26 +312,24 @@ BEGIN
END; END;
$$; $$;
-- call insert_into_ts_kv_latest(); -- call insert_into_ts_kv_latest_cursor();
CREATE OR REPLACE PROCEDURE insert_into_ts_kv_latest() LANGUAGE plpgsql AS $$ CREATE OR REPLACE PROCEDURE insert_into_ts_kv_latest_cursor()
LANGUAGE plpgsql AS
$$
DECLARE DECLARE
insert_size CONSTANT integer := 10000; insert_size CONSTANT integer := 10000;
insert_counter integer DEFAULT 0; insert_counter integer DEFAULT 0;
insert_record RECORD; insert_record RECORD;
insert_cursor CURSOR FOR SELECT CONCAT(entity_id_uuid_first_part, '-', entity_id_uuid_second_part, '-1', entity_id_uuid_third_part, '-', entity_id_uuid_fourth_part, '-', entity_id_uuid_fifth_part)::uuid AS entity_id, insert_cursor CURSOR FOR SELECT to_uuid(entity_id) AS entity_id,
ts_kv_latest_records.key AS key, ts_kv_latest_records.key AS key,
ts_kv_latest_records.ts AS ts, ts_kv_latest_records.ts AS ts,
ts_kv_latest_records.bool_v AS bool_v, ts_kv_latest_records.bool_v AS bool_v,
ts_kv_latest_records.str_v AS str_v, ts_kv_latest_records.str_v AS str_v,
ts_kv_latest_records.long_v AS long_v, ts_kv_latest_records.long_v AS long_v,
ts_kv_latest_records.dbl_v AS dbl_v ts_kv_latest_records.dbl_v AS dbl_v
FROM (SELECT SUBSTRING(entity_id, 8, 8) AS entity_id_uuid_first_part, FROM (SELECT entity_id AS entity_id,
SUBSTRING(entity_id, 4, 4) AS entity_id_uuid_second_part, key_id AS key,
SUBSTRING(entity_id, 1, 3) AS entity_id_uuid_third_part,
SUBSTRING(entity_id, 16, 4) AS entity_id_uuid_fourth_part,
SUBSTRING(entity_id, 20) AS entity_id_uuid_fifth_part,
key_id AS key,
ts, ts,
bool_v, bool_v,
str_v, str_v,
@ -258,4 +357,3 @@ BEGIN
END; END;
$$; $$;

113
application/src/main/data/upgrade/2.4.3/schema_update_timescale_ts.sql

@ -96,51 +96,36 @@ BEGIN
END; END;
$$; $$;
-- call insert_into_ts_kv(); CREATE OR REPLACE FUNCTION to_uuid(IN entity_id varchar, OUT uuid_id uuid) AS
$$
BEGIN
uuid_id := substring(entity_id, 8, 8) || '-' || substring(entity_id, 4, 4) || '-1' || substring(entity_id, 1, 3) ||
'-' || substring(entity_id, 16, 4) || '-' || substring(entity_id, 20, 12);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE PROCEDURE insert_into_ts_kv() LANGUAGE plpgsql AS $$ -- call insert_into_ts_kv();
DECLARE CREATE OR REPLACE PROCEDURE insert_into_ts_kv(IN path_to_file varchar) LANGUAGE plpgsql AS $$
insert_size CONSTANT integer := 10000;
insert_counter integer DEFAULT 0;
insert_record RECORD;
insert_cursor CURSOR FOR SELECT CONCAT(entity_id_uuid_first_part, '-', entity_id_uuid_second_part, '-1', entity_id_uuid_third_part, '-', entity_id_uuid_fourth_part, '-', entity_id_uuid_fifth_part)::uuid AS entity_id,
new_ts_kv_records.key AS key,
new_ts_kv_records.ts AS ts,
new_ts_kv_records.bool_v AS bool_v,
new_ts_kv_records.str_v AS str_v,
new_ts_kv_records.long_v AS long_v,
new_ts_kv_records.dbl_v AS dbl_v
FROM (SELECT SUBSTRING(entity_id, 8, 8) AS entity_id_uuid_first_part,
SUBSTRING(entity_id, 4, 4) AS entity_id_uuid_second_part,
SUBSTRING(entity_id, 1, 3) AS entity_id_uuid_third_part,
SUBSTRING(entity_id, 16, 4) AS entity_id_uuid_fourth_part,
SUBSTRING(entity_id, 20) AS entity_id_uuid_fifth_part,
key_id AS key,
ts,
bool_v,
str_v,
long_v,
dbl_v
FROM tenant_ts_kv_old
INNER JOIN ts_kv_dictionary ON (tenant_ts_kv_old.key = ts_kv_dictionary.key)) AS new_ts_kv_records;
BEGIN BEGIN
OPEN insert_cursor;
LOOP EXECUTE format ('COPY (SELECT to_uuid(entity_id) AS entity_id,
insert_counter := insert_counter + 1; new_ts_kv_records.key AS key,
FETCH insert_cursor INTO insert_record; new_ts_kv_records.ts AS ts,
IF NOT FOUND THEN new_ts_kv_records.bool_v AS bool_v,
RAISE NOTICE '% records have been inserted into the new ts_kv table!',insert_counter - 1; new_ts_kv_records.str_v AS str_v,
EXIT; new_ts_kv_records.long_v AS long_v,
END IF; new_ts_kv_records.dbl_v AS dbl_v
INSERT INTO ts_kv(entity_id, key, ts, bool_v, str_v, long_v, dbl_v) FROM (SELECT entity_id AS entity_id,
VALUES (insert_record.entity_id, insert_record.key, insert_record.ts, insert_record.bool_v, insert_record.str_v, key_id AS key,
insert_record.long_v, insert_record.dbl_v); ts,
IF MOD(insert_counter, insert_size) = 0 THEN bool_v,
RAISE NOTICE '% records have been inserted into the new ts_kv table!',insert_counter; str_v,
END IF; long_v,
END LOOP; dbl_v
CLOSE insert_cursor; FROM tenant_ts_kv_old
INNER JOIN ts_kv_dictionary ON (tenant_ts_kv_old.key = ts_kv_dictionary.key)) AS new_ts_kv_records) TO %L;', path_to_file);
EXECUTE format ('COPY ts_kv FROM %L', path_to_file);
END; END;
$$; $$;
@ -177,3 +162,47 @@ BEGIN
CLOSE insert_cursor; CLOSE insert_cursor;
END; END;
$$; $$;
-- call insert_into_ts_kv_cursor();
CREATE OR REPLACE PROCEDURE insert_into_ts_kv_cursor() LANGUAGE plpgsql AS $$
DECLARE
insert_size CONSTANT integer := 10000;
insert_counter integer DEFAULT 0;
insert_record RECORD;
insert_cursor CURSOR FOR SELECT to_uuid(entity_id) AS entity_id,
new_ts_kv_records.key AS key,
new_ts_kv_records.ts AS ts,
new_ts_kv_records.bool_v AS bool_v,
new_ts_kv_records.str_v AS str_v,
new_ts_kv_records.long_v AS long_v,
new_ts_kv_records.dbl_v AS dbl_v
FROM (SELECT entity_id AS entity_id,
key_id AS key,
ts,
bool_v,
str_v,
long_v,
dbl_v
FROM tenant_ts_kv_old
INNER JOIN ts_kv_dictionary ON (tenant_ts_kv_old.key = ts_kv_dictionary.key)) AS new_ts_kv_records;
BEGIN
OPEN insert_cursor;
LOOP
insert_counter := insert_counter + 1;
FETCH insert_cursor INTO insert_record;
IF NOT FOUND THEN
RAISE NOTICE '% records have been inserted into the new ts_kv table!',insert_counter - 1;
EXIT;
END IF;
INSERT INTO ts_kv(entity_id, key, ts, bool_v, str_v, long_v, dbl_v)
VALUES (insert_record.entity_id, insert_record.key, insert_record.ts, insert_record.bool_v, insert_record.str_v,
insert_record.long_v, insert_record.dbl_v);
IF MOD(insert_counter, insert_size) = 0 THEN
RAISE NOTICE '% records have been inserted into the new ts_kv table!',insert_counter;
END IF;
END LOOP;
CLOSE insert_cursor;
END;
$$;

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

@ -56,6 +56,7 @@ import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.cassandra.CassandraCluster; import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.ClaimDevicesService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
@ -219,6 +220,10 @@ public class ActorSystemContext {
@Getter @Getter
private MailService mailService; private MailService mailService;
@Autowired
@Getter
private ClaimDevicesService claimDevicesService;
//TODO: separate context for TbCore and TbRuleEngine //TODO: separate context for TbCore and TbRuleEngine
@Autowired(required = false) @Autowired(required = false)
@Getter @Getter

9
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java

@ -39,6 +39,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg; import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry; import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
@ -232,9 +233,17 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
if (msg.hasSubscriptionInfo()) { if (msg.hasSubscriptionInfo()) {
handleSessionActivity(context, msg.getSessionInfo(), msg.getSubscriptionInfo()); handleSessionActivity(context, msg.getSessionInfo(), msg.getSubscriptionInfo());
} }
if (msg.hasClaimDevice()) {
handleClaimDeviceMsg(context, msg.getSessionInfo(), msg.getClaimDevice());
}
callback.onSuccess(); callback.onSuccess();
} }
private void handleClaimDeviceMsg(ActorContext context, SessionInfoProto sessionInfo, TransportProtos.ClaimDeviceMsg msg) {
DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB()));
systemContext.getClaimDevicesService().registerClaimingInfo(tenantId, deviceId, msg.getSecretKey(), msg.getDurationMs());
}
private void reportSessionOpen() { private void reportSessionOpen() {
systemContext.getDeviceStateService().onDeviceConnect(deviceId); systemContext.getDeviceStateService().onDeviceConnect(deviceId);
} }

7
application/src/main/java/org/thingsboard/server/controller/AlarmController.java

@ -28,7 +28,6 @@ import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
@ -37,6 +36,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageData;
@ -84,8 +84,9 @@ public class AlarmController extends BaseController {
public Alarm saveAlarm(@RequestBody Alarm alarm) throws ThingsboardException { public Alarm saveAlarm(@RequestBody Alarm alarm) throws ThingsboardException {
try { try {
alarm.setTenantId(getCurrentUser().getTenantId()); alarm.setTenantId(getCurrentUser().getTenantId());
Operation operation = alarm.getId() == null ? Operation.CREATE : Operation.WRITE;
accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, operation, alarm.getId(), alarm); checkEntity(alarm.getId(), alarm, Resource.ALARM);
Alarm savedAlarm = checkNotNull(alarmService.createOrUpdateAlarm(alarm)); Alarm savedAlarm = checkNotNull(alarmService.createOrUpdateAlarm(alarm));
logEntityAction(savedAlarm.getId(), savedAlarm, logEntityAction(savedAlarm.getId(), savedAlarm,
getCurrentUser().getCustomerId(), getCurrentUser().getCustomerId(),

15
application/src/main/java/org/thingsboard/server/controller/AssetController.java

@ -82,18 +82,15 @@ public class AssetController extends BaseController {
try { try {
asset.setTenantId(getCurrentUser().getTenantId()); asset.setTenantId(getCurrentUser().getTenantId());
Operation operation = asset.getId() == null ? Operation.CREATE : Operation.WRITE; checkEntity(asset.getId(), asset, Resource.ASSET);
accessControlService.checkPermission(getCurrentUser(), Resource.ASSET, operation, Asset savedAsset = checkNotNull(assetService.saveAsset(asset));
asset.getId(), asset);
Asset savedAsset = checkNotNull(assetService.saveAsset(asset));
logEntityAction(savedAsset.getId(), savedAsset, logEntityAction(savedAsset.getId(), savedAsset,
savedAsset.getCustomerId(), savedAsset.getCustomerId(),
asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
return savedAsset; return savedAsset;
} catch (Exception e) { } catch (Exception e) {
logEntityAction(emptyId(EntityType.ASSET), asset, logEntityAction(emptyId(EntityType.ASSET), asset,
null, asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); null, asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
@ -144,7 +141,7 @@ public class AssetController extends BaseController {
savedAsset.getCustomerId(), savedAsset.getCustomerId(),
ActionType.ASSIGNED_TO_CUSTOMER, null, strAssetId, strCustomerId, customer.getName()); ActionType.ASSIGNED_TO_CUSTOMER, null, strAssetId, strCustomerId, customer.getName());
return savedAsset; return savedAsset;
} catch (Exception e) { } catch (Exception e) {
logEntityAction(emptyId(EntityType.ASSET), null, logEntityAction(emptyId(EntityType.ASSET), null,
@ -224,7 +221,7 @@ public class AssetController extends BaseController {
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
if (type != null && type.trim().length()>0) { if (type != null && type.trim().length() > 0) {
return checkNotNull(assetService.findAssetsByTenantIdAndType(tenantId, type, pageLink)); return checkNotNull(assetService.findAssetsByTenantIdAndType(tenantId, type, pageLink));
} else { } else {
return checkNotNull(assetService.findAssetsByTenantId(tenantId, pageLink)); return checkNotNull(assetService.findAssetsByTenantId(tenantId, pageLink));
@ -263,7 +260,7 @@ public class AssetController extends BaseController {
CustomerId customerId = new CustomerId(toUUID(strCustomerId)); CustomerId customerId = new CustomerId(toUUID(strCustomerId));
checkCustomerId(customerId, Operation.READ); checkCustomerId(customerId, Operation.READ);
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset); TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
if (type != null && type.trim().length()>0) { if (type != null && type.trim().length() > 0) {
return checkNotNull(assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); return checkNotNull(assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
} else { } else {
return checkNotNull(assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); return checkNotNull(assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink));

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

@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.DashboardInfo;
@ -107,7 +108,6 @@ import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import javax.mail.MessagingException; import javax.mail.MessagingException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
@ -337,11 +337,23 @@ public abstract class BaseController {
} }
} }
protected <I extends EntityId, T extends HasTenantId> void checkEntity(I entityId, T entity, Resource resource) throws ThingsboardException {
if (entityId == null) {
accessControlService
.checkPermission(getCurrentUser(), resource, Operation.CREATE, null, entity);
} else {
checkEntityId(entityId, Operation.WRITE);
}
}
protected void checkEntityId(EntityId entityId, Operation operation) throws ThingsboardException { protected void checkEntityId(EntityId entityId, Operation operation) throws ThingsboardException {
try { try {
checkNotNull(entityId); checkNotNull(entityId);
validateId(entityId.getId(), "Incorrect entityId " + entityId); validateId(entityId.getId(), "Incorrect entityId " + entityId);
switch (entityId.getEntityType()) { switch (entityId.getEntityType()) {
case ALARM:
checkAlarmId(new AlarmId(entityId.getId()), operation);
return;
case DEVICE: case DEVICE:
checkDeviceId(new DeviceId(entityId.getId()), operation); checkDeviceId(new DeviceId(entityId.getId()), operation);
return; return;
@ -372,6 +384,12 @@ public abstract class BaseController {
case EDGE: case EDGE:
checkEdgeId(new EdgeId(entityId.getId()), operation); checkEdgeId(new EdgeId(entityId.getId()), operation);
return; return;
case WIDGETS_BUNDLE:
checkWidgetsBundleId(new WidgetsBundleId(entityId.getId()), operation);
return;
case WIDGET_TYPE:
checkWidgetTypeId(new WidgetTypeId(entityId.getId()), operation);
return;
default: default:
throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType());
} }

3
application/src/main/java/org/thingsboard/server/controller/CustomerController.java

@ -100,8 +100,7 @@ public class CustomerController extends BaseController {
try { try {
customer.setTenantId(getCurrentUser().getTenantId()); customer.setTenantId(getCurrentUser().getTenantId());
Operation operation = customer.getId() == null ? Operation.CREATE : Operation.WRITE; checkEntity(customer.getId(), customer, Resource.CUSTOMER);
accessControlService.checkPermission(getCurrentUser(), Resource.CUSTOMER, operation, customer.getId(), customer);
Customer savedCustomer = checkNotNull(customerService.saveCustomer(customer)); Customer savedCustomer = checkNotNull(customerService.saveCustomer(customer));

21
application/src/main/java/org/thingsboard/server/controller/DashboardController.java

@ -103,15 +103,12 @@ public class DashboardController extends BaseController {
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/dashboard", method = RequestMethod.POST) @RequestMapping(value = "/dashboard", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Dashboard saveDashboard(@RequestBody Dashboard dashboard) throws ThingsboardException { public Dashboard saveDashboard(@RequestBody Dashboard dashboard) throws ThingsboardException {
try { try {
dashboard.setTenantId(getCurrentUser().getTenantId()); dashboard.setTenantId(getCurrentUser().getTenantId());
Operation operation = dashboard.getId() == null ? Operation.CREATE : Operation.WRITE; checkEntity(dashboard.getId(), dashboard, Resource.DASHBOARD);
accessControlService.checkPermission(getCurrentUser(), Resource.DASHBOARD, operation,
dashboard.getId(), dashboard);
Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard)); Dashboard savedDashboard = checkNotNull(dashboardService.saveDashboard(dashboard));
@ -155,9 +152,9 @@ public class DashboardController extends BaseController {
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.POST) @RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Dashboard assignDashboardToCustomer(@PathVariable("customerId") String strCustomerId, public Dashboard assignDashboardToCustomer(@PathVariable("customerId") String strCustomerId,
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter("customerId", strCustomerId); checkParameter("customerId", strCustomerId);
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { try {
@ -166,7 +163,7 @@ public class DashboardController extends BaseController {
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER); checkDashboardId(dashboardId, Operation.ASSIGN_TO_CUSTOMER);
Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(getCurrentUser().getTenantId(), dashboardId, customerId)); Dashboard savedDashboard = checkNotNull(dashboardService.assignDashboardToCustomer(getCurrentUser().getTenantId(), dashboardId, customerId));
logEntityAction(dashboardId, savedDashboard, logEntityAction(dashboardId, savedDashboard,
@ -187,7 +184,7 @@ public class DashboardController extends BaseController {
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.DELETE) @RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
public Dashboard unassignDashboardFromCustomer(@PathVariable("customerId") String strCustomerId, public Dashboard unassignDashboardFromCustomer(@PathVariable("customerId") String strCustomerId,
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter("customerId", strCustomerId); checkParameter("customerId", strCustomerId);
@ -421,7 +418,7 @@ public class DashboardController extends BaseController {
} }
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenant/{tenantId}/dashboards", params = { "limit" }, method = RequestMethod.GET) @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TextPageData<DashboardInfo> getTenantDashboards( public TextPageData<DashboardInfo> getTenantDashboards(
@PathVariable("tenantId") String strTenantId, @PathVariable("tenantId") String strTenantId,
@ -440,7 +437,7 @@ public class DashboardController extends BaseController {
} }
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/dashboards", params = { "limit" }, method = RequestMethod.GET) @RequestMapping(value = "/tenant/dashboards", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TextPageData<DashboardInfo> getTenantDashboards( public TextPageData<DashboardInfo> getTenantDashboards(
@RequestParam int limit, @RequestParam int limit,
@ -457,7 +454,7 @@ public class DashboardController extends BaseController {
} }
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/dashboards", params = { "limit" }, method = RequestMethod.GET) @RequestMapping(value = "/customer/{customerId}/dashboards", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TimePageData<DashboardInfo> getCustomerDashboards( public TimePageData<DashboardInfo> getCustomerDashboards(
@PathVariable("customerId") String strCustomerId, @PathVariable("customerId") String strCustomerId,

5
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -99,10 +99,7 @@ public class DeviceController extends BaseController {
try { try {
device.setTenantId(getCurrentUser().getTenantId()); device.setTenantId(getCurrentUser().getTenantId());
Operation operation = device.getId() == null ? Operation.CREATE : Operation.WRITE; checkEntity(device.getId(), device, Resource.DEVICE);
accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation,
device.getId(), device);
Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken)); Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken));

5
application/src/main/java/org/thingsboard/server/controller/EntityViewController.java

@ -97,10 +97,7 @@ public class EntityViewController extends BaseController {
try { try {
entityView.setTenantId(getCurrentUser().getTenantId()); entityView.setTenantId(getCurrentUser().getTenantId());
Operation operation = entityView.getId() == null ? Operation.CREATE : Operation.WRITE; checkEntity(entityView.getId(), entityView, Resource.ENTITY_VIEW);
accessControlService.checkPermission(getCurrentUser(), Resource.ENTITY_VIEW, operation,
entityView.getId(), entityView);
EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView)); EntityView savedEntityView = checkNotNull(entityViewService.saveEntityView(entityView));
List<ListenableFuture<List<Void>>> futures = new ArrayList<>(); List<ListenableFuture<List<Void>>> futures = new ArrayList<>();

13
application/src/main/java/org/thingsboard/server/controller/RpcController.java

@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.FutureCallback;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
@ -65,7 +66,6 @@ import java.util.UUID;
@Slf4j @Slf4j
public class RpcController extends BaseController { public class RpcController extends BaseController {
public static final int DEFAULT_TIMEOUT = 10000;
protected final ObjectMapper jsonMapper = new ObjectMapper(); protected final ObjectMapper jsonMapper = new ObjectMapper();
@Autowired @Autowired
@ -74,6 +74,12 @@ public class RpcController extends BaseController {
@Autowired @Autowired
private AccessValidator accessValidator; private AccessValidator accessValidator;
@Value("${server.rest.server_side_rpc.min_timeout:5000}")
private long minTimeout;
@Value("${server.rest.server_side_rpc.default_timeout:10000}")
private long defaultTimeout;
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST) @RequestMapping(value = "/oneway/{deviceId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -100,7 +106,8 @@ public class RpcController extends BaseController {
SecurityUser currentUser = getCurrentUser(); SecurityUser currentUser = getCurrentUser();
TenantId tenantId = currentUser.getTenantId(); TenantId tenantId = currentUser.getTenantId();
final DeferredResult<ResponseEntity> response = new DeferredResult<>(); final DeferredResult<ResponseEntity> response = new DeferredResult<>();
long timeout = System.currentTimeMillis() + (cmd.getTimeout() != null ? cmd.getTimeout() : DEFAULT_TIMEOUT); long timeout = cmd.getTimeout() != null ? cmd.getTimeout() : defaultTimeout;
long expTime = System.currentTimeMillis() + Math.max(minTimeout, timeout);
ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData()); ToDeviceRpcRequestBody body = new ToDeviceRpcRequestBody(cmd.getMethodName(), cmd.getRequestData());
accessValidator.validate(currentUser, Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<DeferredResult<ResponseEntity>>() { accessValidator.validate(currentUser, Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback<DeferredResult<ResponseEntity>>() {
@Override @Override
@ -109,7 +116,7 @@ public class RpcController extends BaseController {
tenantId, tenantId,
deviceId, deviceId,
oneWay, oneWay,
timeout, expTime,
body body
); );
deviceRpcService.processRestApiRpcRequest(rpcRequest, fromDeviceRpcResponse -> reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse)); deviceRpcService.processRestApiRpcRequest(rpcRequest, fromDeviceRpcResponse -> reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse));

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

@ -130,10 +130,7 @@ public class RuleChainController extends BaseController {
boolean created = ruleChain.getId() == null; boolean created = ruleChain.getId() == null;
ruleChain.setTenantId(getCurrentUser().getTenantId()); ruleChain.setTenantId(getCurrentUser().getTenantId());
Operation operation = created ? Operation.CREATE : Operation.WRITE; checkEntity(ruleChain.getId(), ruleChain, Resource.RULE_CHAIN);
accessControlService.checkPermission(getCurrentUser(), Resource.RULE_CHAIN, operation,
ruleChain.getId(), ruleChain);
RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain)); RuleChain savedRuleChain = checkNotNull(ruleChainService.saveRuleChain(ruleChain));

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

@ -72,10 +72,8 @@ public class TenantController extends BaseController {
try { try {
boolean newTenant = tenant.getId() == null; boolean newTenant = tenant.getId() == null;
Operation operation = newTenant ? Operation.CREATE : Operation.WRITE; checkEntity(tenant.getId(), tenant, Resource.TENANT);
accessControlService.checkPermission(getCurrentUser(), Resource.TENANT, operation,
tenant.getId(), tenant);
tenant = checkNotNull(tenantService.saveTenant(tenant)); tenant = checkNotNull(tenantService.saveTenant(tenant));
if (newTenant) { if (newTenant) {
installScripts.createDefaultRuleChains(tenant.getId()); installScripts.createDefaultRuleChains(tenant.getId());

12
application/src/main/java/org/thingsboard/server/controller/UserController.java

@ -132,17 +132,13 @@ public class UserController extends BaseController {
@ResponseBody @ResponseBody
public User saveUser(@RequestBody User user, public User saveUser(@RequestBody User user,
@RequestParam(required = false, defaultValue = "true") boolean sendActivationMail, @RequestParam(required = false, defaultValue = "true") boolean sendActivationMail,
HttpServletRequest request) throws ThingsboardException { HttpServletRequest request) throws ThingsboardException {
try { try {
if (getCurrentUser().getAuthority() == Authority.TENANT_ADMIN) { if (getCurrentUser().getAuthority() == Authority.TENANT_ADMIN) {
user.setTenantId(getCurrentUser().getTenantId()); user.setTenantId(getCurrentUser().getTenantId());
} }
Operation operation = user.getId() == null ? Operation.CREATE : Operation.WRITE; checkEntity(user.getId(), user, Resource.USER);
accessControlService.checkPermission(getCurrentUser(), Resource.USER, operation,
user.getId(), user);
boolean sendEmail = user.getId() == null && sendActivationMail; boolean sendEmail = user.getId() == null && sendActivationMail;
User savedUser = checkNotNull(userService.saveUser(user)); User savedUser = checkNotNull(userService.saveUser(user));
@ -250,7 +246,7 @@ public class UserController extends BaseController {
} }
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenant/{tenantId}/users", params = { "limit" }, method = RequestMethod.GET) @RequestMapping(value = "/tenant/{tenantId}/users", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TextPageData<User> getTenantAdmins( public TextPageData<User> getTenantAdmins(
@PathVariable("tenantId") String strTenantId, @PathVariable("tenantId") String strTenantId,
@ -269,7 +265,7 @@ public class UserController extends BaseController {
} }
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/users", params = { "limit" }, method = RequestMethod.GET) @RequestMapping(value = "/customer/{customerId}/users", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TextPageData<User> getCustomerUsers( public TextPageData<User> getCustomerUsers(
@PathVariable("customerId") String strCustomerId, @PathVariable("customerId") String strCustomerId,

9
application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java

@ -66,10 +66,7 @@ public class WidgetTypeController extends BaseController {
widgetType.setTenantId(getCurrentUser().getTenantId()); widgetType.setTenantId(getCurrentUser().getTenantId());
} }
Operation operation = widgetType.getId() == null ? Operation.CREATE : Operation.WRITE; checkEntity(widgetType.getId(), widgetType, Resource.WIDGET_TYPE);
accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, operation,
widgetType.getId(), widgetType);
return checkNotNull(widgetTypeService.saveWidgetType(widgetType)); return checkNotNull(widgetTypeService.saveWidgetType(widgetType));
} catch (Exception e) { } catch (Exception e) {
@ -92,7 +89,7 @@ public class WidgetTypeController extends BaseController {
} }
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/widgetTypes", params = { "isSystem", "bundleAlias"}, method = RequestMethod.GET) @RequestMapping(value = "/widgetTypes", params = {"isSystem", "bundleAlias"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public List<WidgetType> getBundleWidgetTypes( public List<WidgetType> getBundleWidgetTypes(
@RequestParam boolean isSystem, @RequestParam boolean isSystem,
@ -111,7 +108,7 @@ public class WidgetTypeController extends BaseController {
} }
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/widgetType", params = { "isSystem", "bundleAlias", "alias" }, method = RequestMethod.GET) @RequestMapping(value = "/widgetType", params = {"isSystem", "bundleAlias", "alias"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public WidgetType getWidgetType( public WidgetType getWidgetType(
@RequestParam boolean isSystem, @RequestParam boolean isSystem,

8
application/src/main/java/org/thingsboard/server/controller/WidgetsBundleController.java

@ -67,11 +67,7 @@ public class WidgetsBundleController extends BaseController {
widgetsBundle.setTenantId(getCurrentUser().getTenantId()); widgetsBundle.setTenantId(getCurrentUser().getTenantId());
} }
Operation operation = widgetsBundle.getId() == null ? Operation.CREATE : Operation.WRITE; checkEntity(widgetsBundle.getId(), widgetsBundle, Resource.WIDGETS_BUNDLE);
accessControlService.checkPermission(getCurrentUser(), Resource.WIDGETS_BUNDLE, operation,
widgetsBundle.getId(), widgetsBundle);
return checkNotNull(widgetsBundleService.saveWidgetsBundle(widgetsBundle)); return checkNotNull(widgetsBundleService.saveWidgetsBundle(widgetsBundle));
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
@ -93,7 +89,7 @@ public class WidgetsBundleController extends BaseController {
} }
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/widgetsBundles", params = { "limit" }, method = RequestMethod.GET) @RequestMapping(value = "/widgetsBundles", params = {"limit"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TextPageData<WidgetsBundle> getWidgetsBundles( public TextPageData<WidgetsBundle> getWidgetsBundles(
@RequestParam int limit, @RequestParam int limit,

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

@ -133,13 +133,20 @@ public class ThingsboardInstallService {
databaseEntitiesUpgradeService.upgradeDatabase("2.4.2"); databaseEntitiesUpgradeService.upgradeDatabase("2.4.2");
case "2.4.3": case "2.4.3":
log.info("Upgrading ThingsBoard from version 2.4.3 to 2.5 ..."); log.info("Upgrading ThingsBoard from version 2.4.3 to 2.5.0 ...");
if (databaseTsUpgradeService != null) { if (databaseTsUpgradeService != null) {
databaseTsUpgradeService.upgradeDatabase("2.4.3"); databaseTsUpgradeService.upgradeDatabase("2.4.3");
} }
databaseEntitiesUpgradeService.upgradeDatabase("2.4.3"); databaseEntitiesUpgradeService.upgradeDatabase("2.4.3");
case "2.5.0":
log.info("Upgrading ThingsBoard from version 2.5.0 to 2.5.1 ...");
if (databaseTsUpgradeService != null) {
databaseTsUpgradeService.upgradeDatabase("2.5.0");
}
log.info("Updating system data..."); log.info("Updating system data...");
systemDataLoaderService.deleteSystemWidgetBundle("charts"); systemDataLoaderService.deleteSystemWidgetBundle("charts");

3
application/src/main/java/org/thingsboard/server/service/install/AbstractSqlTsDatabaseUpgradeService.java

@ -34,6 +34,9 @@ public abstract class AbstractSqlTsDatabaseUpgradeService {
protected static final String CALL_REGEX = "call "; protected static final String CALL_REGEX = "call ";
protected static final String DROP_TABLE = "DROP TABLE "; protected static final String DROP_TABLE = "DROP TABLE ";
protected static final String DROP_PROCEDURE_IF_EXISTS = "DROP PROCEDURE IF EXISTS "; protected static final String DROP_PROCEDURE_IF_EXISTS = "DROP PROCEDURE IF EXISTS ";
protected static final String TS_KV_SQL = "ts_kv.sql";
protected static final String PATH_TO_USERS_PUBLIC_FOLDER = "C:\\Users\\Public";
protected static final String THINGSBOARD_WINDOWS_UPGRADE_DIR = "THINGSBOARD_WINDOWS_UPGRADE_DIR";
@Value("${spring.datasource.url}") @Value("${spring.datasource.url}")
protected String dbUrl; protected String dbUrl;

2
application/src/main/java/org/thingsboard/server/service/install/CassandraTsDatabaseUpgradeService.java

@ -48,6 +48,8 @@ public class CassandraTsDatabaseUpgradeService extends AbstractCassandraDatabase
} }
log.info("Schema updated."); log.info("Schema updated.");
break; break;
case "2.5.0":
break;
default: default:
throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
} }

1
application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java

@ -131,6 +131,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService {
node.put("username", ""); node.put("username", "");
node.put("password", ""); node.put("password", "");
node.put("tlsVersion", "TLSv1.2");//NOSONAR, key used to identify password field (not password value itself) node.put("tlsVersion", "TLSv1.2");//NOSONAR, key used to identify password field (not password value itself)
node.put("enableProxy", false);
mailSettings.setJsonValue(node); mailSettings.setJsonValue(node);
adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, mailSettings); adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, mailSettings);
} }

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

@ -37,8 +37,6 @@ public class PsqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaServic
@Override @Override
public void createDatabaseSchema() throws Exception { public void createDatabaseSchema() throws Exception {
super.createDatabaseSchema(); super.createDatabaseSchema();
if (partitionType.equals("INDEFINITE")) { executeQuery("CREATE TABLE IF NOT EXISTS ts_kv_indefinite PARTITION OF ts_kv DEFAULT;");
executeQuery("CREATE TABLE ts_kv_indefinite PARTITION OF ts_kv DEFAULT;");
}
} }
} }

114
application/src/main/java/org/thingsboard/server/service/install/PsqlTsDatabaseUpgradeService.java

@ -16,12 +16,17 @@
package org.thingsboard.server.service.install; package org.thingsboard.server.service.install;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao; import org.thingsboard.server.dao.util.SqlTsDao;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.sql.Connection; import java.sql.Connection;
@ -37,6 +42,7 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
@Value("${sql.postgres.ts_key_value_partitioning:MONTHS}") @Value("${sql.postgres.ts_key_value_partitioning:MONTHS}")
private String partitionType; private String partitionType;
private static final String TS_KV_LATEST_SQL = "ts_kv_latest.sql";
private static final String LOAD_FUNCTIONS_SQL = "schema_update_psql_ts.sql"; private static final String LOAD_FUNCTIONS_SQL = "schema_update_psql_ts.sql";
private static final String LOAD_TTL_FUNCTIONS_SQL = "schema_update_ttl.sql"; private static final String LOAD_TTL_FUNCTIONS_SQL = "schema_update_ttl.sql";
private static final String LOAD_DROP_PARTITIONS_FUNCTIONS_SQL = "schema_update_psql_drop_partitions.sql"; private static final String LOAD_DROP_PARTITIONS_FUNCTIONS_SQL = "schema_update_psql_drop_partitions.sql";
@ -49,15 +55,17 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
private static final String CREATE_PARTITIONS = "create_partitions(IN partition_type varchar)"; private static final String CREATE_PARTITIONS = "create_partitions(IN partition_type varchar)";
private static final String CREATE_TS_KV_DICTIONARY_TABLE = "create_ts_kv_dictionary_table()"; private static final String CREATE_TS_KV_DICTIONARY_TABLE = "create_ts_kv_dictionary_table()";
private static final String INSERT_INTO_DICTIONARY = "insert_into_dictionary()"; private static final String INSERT_INTO_DICTIONARY = "insert_into_dictionary()";
private static final String INSERT_INTO_TS_KV = "insert_into_ts_kv()"; private static final String INSERT_INTO_TS_KV = "insert_into_ts_kv(IN path_to_file varchar)";
private static final String INSERT_INTO_TS_KV_LATEST = "insert_into_ts_kv_latest()"; private static final String INSERT_INTO_TS_KV_LATEST = "insert_into_ts_kv_latest(IN path_to_file varchar)";
private static final String INSERT_INTO_TS_KV_CURSOR = "insert_into_ts_kv_cursor()";
private static final String INSERT_INTO_TS_KV_LATEST_CURSOR = "insert_into_ts_kv_latest_cursor()";
private static final String CALL_CREATE_PARTITION_TS_KV_TABLE = CALL_REGEX + CREATE_PARTITION_TS_KV_TABLE; private static final String CALL_CREATE_PARTITION_TS_KV_TABLE = CALL_REGEX + CREATE_PARTITION_TS_KV_TABLE;
private static final String CALL_CREATE_NEW_TS_KV_LATEST_TABLE = CALL_REGEX + CREATE_NEW_TS_KV_LATEST_TABLE; private static final String CALL_CREATE_NEW_TS_KV_LATEST_TABLE = CALL_REGEX + CREATE_NEW_TS_KV_LATEST_TABLE;
private static final String CALL_CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + CREATE_TS_KV_DICTIONARY_TABLE; private static final String CALL_CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + CREATE_TS_KV_DICTIONARY_TABLE;
private static final String CALL_INSERT_INTO_DICTIONARY = CALL_REGEX + INSERT_INTO_DICTIONARY; private static final String CALL_INSERT_INTO_DICTIONARY = CALL_REGEX + INSERT_INTO_DICTIONARY;
private static final String CALL_INSERT_INTO_TS_KV = CALL_REGEX + INSERT_INTO_TS_KV; private static final String CALL_INSERT_INTO_TS_KV_CURSOR = CALL_REGEX + INSERT_INTO_TS_KV_CURSOR;
private static final String CALL_INSERT_INTO_TS_KV_LATEST = CALL_REGEX + INSERT_INTO_TS_KV_LATEST; private static final String CALL_INSERT_INTO_TS_KV_LATEST_CURSOR = CALL_REGEX + INSERT_INTO_TS_KV_LATEST_CURSOR;
private static final String DROP_TABLE_TS_KV_OLD = DROP_TABLE + TS_KV_OLD; private static final String DROP_TABLE_TS_KV_OLD = DROP_TABLE + TS_KV_OLD;
private static final String DROP_TABLE_TS_KV_LATEST_OLD = DROP_TABLE + TS_KV_LATEST_OLD; private static final String DROP_TABLE_TS_KV_LATEST_OLD = DROP_TABLE + TS_KV_LATEST_OLD;
@ -69,6 +77,8 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
private static final String DROP_PROCEDURE_INSERT_INTO_DICTIONARY = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_DICTIONARY; private static final String DROP_PROCEDURE_INSERT_INTO_DICTIONARY = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_DICTIONARY;
private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV; private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV;
private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_LATEST; private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_LATEST;
private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_CURSOR = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_CURSOR;
private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST_CURSOR = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_LATEST_CURSOR;
private static final String DROP_FUNCTION_GET_PARTITION_DATA = "DROP FUNCTION IF EXISTS get_partitions_data;"; private static final String DROP_FUNCTION_GET_PARTITION_DATA = "DROP FUNCTION IF EXISTS get_partitions_data;";
@Override @Override
@ -89,14 +99,63 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
executeQuery(conn, CALL_CREATE_PARTITION_TS_KV_TABLE); executeQuery(conn, CALL_CREATE_PARTITION_TS_KV_TABLE);
if (!partitionType.equals("INDEFINITE")) { if (!partitionType.equals("INDEFINITE")) {
executeQuery(conn, "call create_partitions('" + partitionType + "')"); executeQuery(conn, "call create_partitions('" + partitionType + "')");
} else {
executeQuery(conn, "CREATE TABLE IF NOT EXISTS ts_kv_indefinite PARTITION OF ts_kv DEFAULT;");
} }
executeQuery(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE); executeQuery(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE);
executeQuery(conn, CALL_INSERT_INTO_DICTIONARY); executeQuery(conn, CALL_INSERT_INTO_DICTIONARY);
executeQuery(conn, CALL_INSERT_INTO_TS_KV);
executeQuery(conn, CALL_CREATE_NEW_TS_KV_LATEST_TABLE); Path pathToTempTsKvFile = null;
executeQuery(conn, CALL_INSERT_INTO_TS_KV_LATEST); Path pathToTempTsKvLatestFile = null;
if (SystemUtils.IS_OS_WINDOWS) {
log.info("Lookup for environment variable: {} ...", THINGSBOARD_WINDOWS_UPGRADE_DIR);
Path pathToDir;
String thingsboardWindowsUpgradeDir = System.getenv("THINGSBOARD_WINDOWS_UPGRADE_DIR");
if (StringUtils.isNotEmpty(thingsboardWindowsUpgradeDir)) {
log.info("Environment variable: {} was found!", THINGSBOARD_WINDOWS_UPGRADE_DIR);
pathToDir = Paths.get(thingsboardWindowsUpgradeDir);
} else {
log.info("Failed to lookup environment variable: {}", THINGSBOARD_WINDOWS_UPGRADE_DIR);
pathToDir = Paths.get(PATH_TO_USERS_PUBLIC_FOLDER);
}
log.info("Directory: {} will be used for creation temporary upgrade files!", pathToDir);
try {
Path tsKvFile = Files.createTempFile(pathToDir, "ts_kv", ".sql");
Path tsKvLatestFile = Files.createTempFile(pathToDir, "ts_kv_latest", ".sql");
pathToTempTsKvFile = tsKvFile.toAbsolutePath();
pathToTempTsKvLatestFile = tsKvLatestFile.toAbsolutePath();
try {
copyTimeseries(conn, pathToTempTsKvFile, pathToTempTsKvLatestFile);
} catch (Exception e) {
insertTimeseries(conn);
}
} catch (IOException | SecurityException e) {
log.warn("Failed to create time-series upgrade files due to: {}", e.getMessage());
insertTimeseries(conn);
}
} else {
try {
Path tempDirPath = Files.createTempDirectory("ts_kv");
File tempDirAsFile = tempDirPath.toFile();
boolean writable = tempDirAsFile.setWritable(true, false);
boolean readable = tempDirAsFile.setReadable(true, false);
boolean executable = tempDirAsFile.setExecutable(true, false);
pathToTempTsKvFile = tempDirPath.resolve(TS_KV_SQL).toAbsolutePath();
pathToTempTsKvLatestFile = tempDirPath.resolve(TS_KV_LATEST_SQL).toAbsolutePath();
try {
if (writable && readable && executable) {
copyTimeseries(conn, pathToTempTsKvFile, pathToTempTsKvLatestFile);
} else {
throw new RuntimeException("Failed to grant write permissions for the: " + tempDirPath + "folder!");
}
} catch (Exception e) {
insertTimeseries(conn);
}
} catch (IOException | SecurityException e) {
log.warn("Failed to create time-series upgrade files due to: {}", e.getMessage());
insertTimeseries(conn);
}
}
removeUpgradeFiles(pathToTempTsKvFile, pathToTempTsKvLatestFile);
executeQuery(conn, DROP_TABLE_TS_KV_OLD); executeQuery(conn, DROP_TABLE_TS_KV_OLD);
executeQuery(conn, DROP_TABLE_TS_KV_LATEST_OLD); executeQuery(conn, DROP_TABLE_TS_KV_LATEST_OLD);
@ -108,6 +167,8 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV); executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV);
executeQuery(conn, DROP_PROCEDURE_CREATE_NEW_TS_KV_LATEST_TABLE); executeQuery(conn, DROP_PROCEDURE_CREATE_NEW_TS_KV_LATEST_TABLE);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST); executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_CURSOR);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST_CURSOR);
executeQuery(conn, DROP_FUNCTION_GET_PARTITION_DATA); executeQuery(conn, DROP_FUNCTION_GET_PARTITION_DATA);
executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN IF NOT EXISTS json_v json;"); executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN IF NOT EXISTS json_v json;");
@ -128,11 +189,46 @@ public class PsqlTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgradeSe
} }
} }
break; break;
case "2.5.0":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
executeQuery(conn, "CREATE TABLE IF NOT EXISTS ts_kv_indefinite PARTITION OF ts_kv DEFAULT;");
executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001");
}
break;
default: default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
} }
} }
private void removeUpgradeFiles(Path pathToTempTsKvFile, Path pathToTempTsKvLatestFile) {
if (pathToTempTsKvFile != null && pathToTempTsKvFile.toFile().exists()) {
boolean deleteTsKvFile = pathToTempTsKvFile.toFile().delete();
if (deleteTsKvFile) {
log.info("Successfully deleted the temp file for ts_kv table upgrade!");
}
}
if (pathToTempTsKvLatestFile != null && pathToTempTsKvLatestFile.toFile().exists()) {
boolean deleteTsKvLatestFile = pathToTempTsKvLatestFile.toFile().delete();
if (deleteTsKvLatestFile) {
log.info("Successfully deleted the temp file for ts_kv_latest table upgrade!");
}
}
}
private void copyTimeseries(Connection conn, Path pathToTempTsKvFile, Path pathToTempTsKvLatestFile) {
executeQuery(conn, "call insert_into_ts_kv('" + pathToTempTsKvFile + "')");
executeQuery(conn, CALL_CREATE_NEW_TS_KV_LATEST_TABLE);
executeQuery(conn, "call insert_into_ts_kv_latest('" + pathToTempTsKvLatestFile + "')");
}
private void insertTimeseries(Connection conn) {
log.warn("Upgrade script failed using the copy to/from files strategy!" +
" Trying to perfrom the upgrade using Inserts strategy ...");
executeQuery(conn, CALL_INSERT_INTO_TS_KV_CURSOR);
executeQuery(conn, CALL_CREATE_NEW_TS_KV_LATEST_TABLE);
executeQuery(conn, CALL_INSERT_INTO_TS_KV_LATEST_CURSOR);
}
@Override @Override
protected void loadSql(Connection conn, String fileName) { protected void loadSql(Connection conn, String fileName) {
Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", fileName); Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", fileName);

87
application/src/main/java/org/thingsboard/server/service/install/TimescaleTsDatabaseUpgradeService.java

@ -16,6 +16,8 @@
package org.thingsboard.server.service.install; package org.thingsboard.server.service.install;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Profile; import org.springframework.context.annotation.Profile;
@ -23,6 +25,9 @@ import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.PsqlDao;
import org.thingsboard.server.dao.util.TimescaleDBTsDao; import org.thingsboard.server.dao.util.TimescaleDBTsDao;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.sql.Connection; import java.sql.Connection;
@ -47,15 +52,16 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
private static final String CREATE_NEW_TS_KV_TABLE = "create_new_ts_kv_table()"; private static final String CREATE_NEW_TS_KV_TABLE = "create_new_ts_kv_table()";
private static final String CREATE_TS_KV_DICTIONARY_TABLE = "create_ts_kv_dictionary_table()"; private static final String CREATE_TS_KV_DICTIONARY_TABLE = "create_ts_kv_dictionary_table()";
private static final String INSERT_INTO_DICTIONARY = "insert_into_dictionary()"; private static final String INSERT_INTO_DICTIONARY = "insert_into_dictionary()";
private static final String INSERT_INTO_TS_KV = "insert_into_ts_kv()"; private static final String INSERT_INTO_TS_KV = "insert_into_ts_kv(IN path_to_file varchar)";
private static final String INSERT_INTO_TS_KV_CURSOR = "insert_into_ts_kv_cursor()";
private static final String INSERT_INTO_TS_KV_LATEST = "insert_into_ts_kv_latest()"; private static final String INSERT_INTO_TS_KV_LATEST = "insert_into_ts_kv_latest()";
private static final String CALL_CREATE_TS_KV_LATEST_TABLE = CALL_REGEX + CREATE_TS_KV_LATEST_TABLE; private static final String CALL_CREATE_TS_KV_LATEST_TABLE = CALL_REGEX + CREATE_TS_KV_LATEST_TABLE;
private static final String CALL_CREATE_NEW_TENANT_TS_KV_TABLE = CALL_REGEX + CREATE_NEW_TS_KV_TABLE; private static final String CALL_CREATE_NEW_TENANT_TS_KV_TABLE = CALL_REGEX + CREATE_NEW_TS_KV_TABLE;
private static final String CALL_CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + CREATE_TS_KV_DICTIONARY_TABLE; private static final String CALL_CREATE_TS_KV_DICTIONARY_TABLE = CALL_REGEX + CREATE_TS_KV_DICTIONARY_TABLE;
private static final String CALL_INSERT_INTO_DICTIONARY = CALL_REGEX + INSERT_INTO_DICTIONARY; private static final String CALL_INSERT_INTO_DICTIONARY = CALL_REGEX + INSERT_INTO_DICTIONARY;
private static final String CALL_INSERT_INTO_TS_KV = CALL_REGEX + INSERT_INTO_TS_KV;
private static final String CALL_INSERT_INTO_TS_KV_LATEST = CALL_REGEX + INSERT_INTO_TS_KV_LATEST; private static final String CALL_INSERT_INTO_TS_KV_LATEST = CALL_REGEX + INSERT_INTO_TS_KV_LATEST;
private static final String CALL_INSERT_INTO_TS_KV_CURSOR = CALL_REGEX + INSERT_INTO_TS_KV_CURSOR;
private static final String DROP_OLD_TENANT_TS_KV_TABLE = DROP_TABLE + TENANT_TS_KV_OLD_TABLE; private static final String DROP_OLD_TENANT_TS_KV_TABLE = DROP_TABLE + TENANT_TS_KV_OLD_TABLE;
@ -63,7 +69,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
private static final String DROP_PROCEDURE_CREATE_TENANT_TS_KV_TABLE_COPY = DROP_PROCEDURE_IF_EXISTS + CREATE_NEW_TS_KV_TABLE; private static final String DROP_PROCEDURE_CREATE_TENANT_TS_KV_TABLE_COPY = DROP_PROCEDURE_IF_EXISTS + CREATE_NEW_TS_KV_TABLE;
private static final String DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE = DROP_PROCEDURE_IF_EXISTS + CREATE_TS_KV_DICTIONARY_TABLE; private static final String DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE = DROP_PROCEDURE_IF_EXISTS + CREATE_TS_KV_DICTIONARY_TABLE;
private static final String DROP_PROCEDURE_INSERT_INTO_DICTIONARY = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_DICTIONARY; private static final String DROP_PROCEDURE_INSERT_INTO_DICTIONARY = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_DICTIONARY;
private static final String DROP_PROCEDURE_INSERT_INTO_TENANT_TS_KV = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV; private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV;
private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_CURSOR = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_CURSOR;
private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_LATEST; private static final String DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST = DROP_PROCEDURE_IF_EXISTS + INSERT_INTO_TS_KV_LATEST;
@Autowired @Autowired
@ -91,7 +98,56 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
executeQuery(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE); executeQuery(conn, CALL_CREATE_TS_KV_DICTIONARY_TABLE);
executeQuery(conn, CALL_INSERT_INTO_DICTIONARY); executeQuery(conn, CALL_INSERT_INTO_DICTIONARY);
executeQuery(conn, CALL_INSERT_INTO_TS_KV);
Path pathToTempTsKvFile = null;
if (SystemUtils.IS_OS_WINDOWS) {
Path pathToDir;
log.info("Lookup for environment variable: {} ...", THINGSBOARD_WINDOWS_UPGRADE_DIR);
String thingsboardWindowsUpgradeDir = System.getenv(THINGSBOARD_WINDOWS_UPGRADE_DIR);
if (StringUtils.isNotEmpty(thingsboardWindowsUpgradeDir)) {
log.info("Environment variable: {} was found!", THINGSBOARD_WINDOWS_UPGRADE_DIR);
pathToDir = Paths.get(thingsboardWindowsUpgradeDir);
} else {
log.info("Failed to lookup environment variable: {}", THINGSBOARD_WINDOWS_UPGRADE_DIR);
pathToDir = Paths.get(PATH_TO_USERS_PUBLIC_FOLDER);
}
log.info("Directory: {} will be used for creation temporary upgrade file!", pathToDir);
try {
Path tsKvFile = Files.createTempFile(pathToDir, "ts_kv", ".sql");
pathToTempTsKvFile = tsKvFile.toAbsolutePath();
try {
executeQuery(conn, "call insert_into_ts_kv('" + pathToTempTsKvFile + "')");
} catch (Exception e) {
insertTimeseries(conn);
}
} catch (IOException | SecurityException e) {
log.warn("Failed to create time-series upgrade files due to: {}", e.getMessage());
insertTimeseries(conn);
}
} else {
try {
Path tempDirPath = Files.createTempDirectory("ts_kv");
File tempDirAsFile = tempDirPath.toFile();
boolean writable = tempDirAsFile.setWritable(true, false);
boolean readable = tempDirAsFile.setReadable(true, false);
boolean executable = tempDirAsFile.setExecutable(true, false);
pathToTempTsKvFile = tempDirPath.resolve(TS_KV_SQL).toAbsolutePath();
try {
if (writable && readable && executable) {
executeQuery(conn, "call insert_into_ts_kv('" + pathToTempTsKvFile + "')");
} else {
throw new RuntimeException("Failed to grant write permissions for the: " + tempDirPath + "folder!");
}
} catch (Exception e) {
insertTimeseries(conn);
}
} catch (IOException | SecurityException e) {
log.warn("Failed to create time-series upgrade files due to: {}", e.getMessage());
insertTimeseries(conn);
}
}
removeUpgradeFile(pathToTempTsKvFile);
executeQuery(conn, CALL_INSERT_INTO_TS_KV_LATEST); executeQuery(conn, CALL_INSERT_INTO_TS_KV_LATEST);
executeQuery(conn, DROP_OLD_TENANT_TS_KV_TABLE); executeQuery(conn, DROP_OLD_TENANT_TS_KV_TABLE);
@ -100,7 +156,8 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
executeQuery(conn, DROP_PROCEDURE_CREATE_TENANT_TS_KV_TABLE_COPY); executeQuery(conn, DROP_PROCEDURE_CREATE_TENANT_TS_KV_TABLE_COPY);
executeQuery(conn, DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE); executeQuery(conn, DROP_PROCEDURE_CREATE_TS_KV_DICTIONARY_TABLE);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_DICTIONARY); executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_DICTIONARY);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TENANT_TS_KV); executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_CURSOR);
executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST); executeQuery(conn, DROP_PROCEDURE_INSERT_INTO_TS_KV_LATEST);
executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN IF NOT EXISTS json_v json;"); executeQuery(conn, "ALTER TABLE ts_kv ADD COLUMN IF NOT EXISTS json_v json;");
@ -115,11 +172,31 @@ public class TimescaleTsDatabaseUpgradeService extends AbstractSqlTsDatabaseUpgr
} }
} }
break; break;
case "2.5.0":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
executeQuery(conn, "UPDATE tb_schema_settings SET schema_version = 2005001");
}
break;
default: default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion); throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
} }
} }
private void insertTimeseries(Connection conn) {
log.warn("Upgrade script failed using the copy to/from files strategy!" +
" Trying to perfrom the upgrade using Inserts strategy ...");
executeQuery(conn, CALL_INSERT_INTO_TS_KV_CURSOR);
}
private void removeUpgradeFile(Path pathToTempTsKvFile) {
if (pathToTempTsKvFile != null && pathToTempTsKvFile.toFile().exists()) {
boolean deleteTsKvFile = pathToTempTsKvFile.toFile().delete();
if (deleteTsKvFile) {
log.info("Successfully deleted the temp file for ts_kv table upgrade!");
}
}
}
@Override @Override
protected void loadSql(Connection conn, String fileName) { protected void loadSql(Connection conn, String fileName) {
Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", fileName); Path schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "2.4.3", fileName);

15
application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java

@ -115,6 +115,21 @@ public class DefaultMailService implements MailService {
if (enableTls && jsonConfig.has("tlsVersion") && StringUtils.isNoneEmpty(jsonConfig.get("tlsVersion").asText())) { if (enableTls && jsonConfig.has("tlsVersion") && StringUtils.isNoneEmpty(jsonConfig.get("tlsVersion").asText())) {
javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", jsonConfig.get("tlsVersion").asText()); javaMailProperties.put(MAIL_PROP + protocol + ".ssl.protocols", jsonConfig.get("tlsVersion").asText());
} }
boolean enableProxy = jsonConfig.has("enableProxy") && jsonConfig.get("enableProxy").asBoolean();
if (enableProxy) {
javaMailProperties.put(MAIL_PROP + protocol + ".proxy.host", jsonConfig.get("proxyHost").asText());
javaMailProperties.put(MAIL_PROP + protocol + ".proxy.port", jsonConfig.get("proxyPort").asText());
String proxyUser = jsonConfig.get("proxyUser").asText();
if (StringUtils.isNoneEmpty(proxyUser)) {
javaMailProperties.put(MAIL_PROP + protocol + ".proxy.user", proxyUser);
}
String proxyPassword = jsonConfig.get("proxyPassword").asText();
if (StringUtils.isNoneEmpty(proxyPassword)) {
javaMailProperties.put(MAIL_PROP + protocol + ".proxy.password", proxyPassword);
}
}
return javaMailProperties; return javaMailProperties;
} }

45
application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java

@ -46,6 +46,9 @@ import java.util.concurrent.atomic.AtomicInteger;
@Service @Service
public class RemoteJsInvokeService extends AbstractJsInvokeService { public class RemoteJsInvokeService extends AbstractJsInvokeService {
@Value("${queue.js.max_eval_requests_timeout}")
private long maxEvalRequestsTimeout;
@Value("${queue.js.max_requests_timeout}") @Value("${queue.js.max_requests_timeout}")
private long maxRequestsTimeout; private long maxRequestsTimeout;
@ -59,22 +62,22 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
@Value("${js.remote.stats.enabled:false}") @Value("${js.remote.stats.enabled:false}")
private boolean statsEnabled; private boolean statsEnabled;
private final AtomicInteger kafkaPushedMsgs = new AtomicInteger(0); private final AtomicInteger queuePushedMsgs = new AtomicInteger(0);
private final AtomicInteger kafkaInvokeMsgs = new AtomicInteger(0); private final AtomicInteger queueInvokeMsgs = new AtomicInteger(0);
private final AtomicInteger kafkaEvalMsgs = new AtomicInteger(0); private final AtomicInteger queueEvalMsgs = new AtomicInteger(0);
private final AtomicInteger kafkaFailedMsgs = new AtomicInteger(0); private final AtomicInteger queueFailedMsgs = new AtomicInteger(0);
private final AtomicInteger kafkaTimeoutMsgs = new AtomicInteger(0); private final AtomicInteger queueTimeoutMsgs = new AtomicInteger(0);
@Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}") @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}")
public void printStats() { public void printStats() {
if (statsEnabled) { if (statsEnabled) {
int pushedMsgs = kafkaPushedMsgs.getAndSet(0); int pushedMsgs = queuePushedMsgs.getAndSet(0);
int invokeMsgs = kafkaInvokeMsgs.getAndSet(0); int invokeMsgs = queueInvokeMsgs.getAndSet(0);
int evalMsgs = kafkaEvalMsgs.getAndSet(0); int evalMsgs = queueEvalMsgs.getAndSet(0);
int failed = kafkaFailedMsgs.getAndSet(0); int failed = queueFailedMsgs.getAndSet(0);
int timedOut = kafkaTimeoutMsgs.getAndSet(0); int timedOut = queueTimeoutMsgs.getAndSet(0);
if (pushedMsgs > 0 || invokeMsgs > 0 || evalMsgs > 0 || failed > 0 || timedOut > 0) { if (pushedMsgs > 0 || invokeMsgs > 0 || evalMsgs > 0 || failed > 0 || timedOut > 0) {
log.info("Kafka JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]", log.info("Queue JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]",
pushedMsgs, invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed, timedOut); pushedMsgs, invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed, timedOut);
} }
} }
@ -113,22 +116,22 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
log.trace("Post compile request for scriptId [{}]", scriptId); log.trace("Post compile request for scriptId [{}]", scriptId);
ListenableFuture<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> future = requestTemplate.send(new TbProtoJsQueueMsg<>(UUID.randomUUID(), jsRequestWrapper)); ListenableFuture<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>> future = requestTemplate.send(new TbProtoJsQueueMsg<>(UUID.randomUUID(), jsRequestWrapper));
if (maxRequestsTimeout > 0) { if (maxEvalRequestsTimeout > 0) {
future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); future = Futures.withTimeout(future, maxEvalRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
} }
kafkaPushedMsgs.incrementAndGet(); queuePushedMsgs.incrementAndGet();
Futures.addCallback(future, new FutureCallback<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>>() { Futures.addCallback(future, new FutureCallback<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>>() {
@Override @Override
public void onSuccess(@Nullable TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse> result) { public void onSuccess(@Nullable TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse> result) {
kafkaEvalMsgs.incrementAndGet(); queueEvalMsgs.incrementAndGet();
} }
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) {
kafkaTimeoutMsgs.incrementAndGet(); queueTimeoutMsgs.incrementAndGet();
} }
kafkaFailedMsgs.incrementAndGet(); queueFailedMsgs.incrementAndGet();
} }
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
return Futures.transform(future, response -> { return Futures.transform(future, response -> {
@ -170,20 +173,20 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService {
if (maxRequestsTimeout > 0) { if (maxRequestsTimeout > 0) {
future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService);
} }
kafkaPushedMsgs.incrementAndGet(); queuePushedMsgs.incrementAndGet();
Futures.addCallback(future, new FutureCallback<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>>() { Futures.addCallback(future, new FutureCallback<TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse>>() {
@Override @Override
public void onSuccess(@Nullable TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse> result) { public void onSuccess(@Nullable TbProtoQueueMsg<JsInvokeProtos.RemoteJsResponse> result) {
kafkaInvokeMsgs.incrementAndGet(); queueInvokeMsgs.incrementAndGet();
} }
@Override @Override
public void onFailure(Throwable t) { public void onFailure(Throwable t) {
onScriptExecutionError(scriptId); onScriptExecutionError(scriptId);
if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) {
kafkaTimeoutMsgs.incrementAndGet(); queueTimeoutMsgs.incrementAndGet();
} }
kafkaFailedMsgs.incrementAndGet(); queueFailedMsgs.incrementAndGet();
} }
}, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor());
return Futures.transform(future, response -> { return Futures.transform(future, response -> {

59
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java

@ -15,6 +15,9 @@
*/ */
package org.thingsboard.server.service.security.auth.oauth2; package org.thingsboard.server.service.security.auth.oauth2;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
@ -22,14 +25,21 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DashboardInfo;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.IdBased;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.dao.oauth2.OAuth2User;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
@ -40,11 +50,15 @@ import org.thingsboard.server.service.security.model.UserPrincipal;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@Slf4j @Slf4j
public abstract class AbstractOAuth2ClientMapper { public abstract class AbstractOAuth2ClientMapper {
private static final int DASHBOARDS_REQUEST_LIMIT = 10;
private static final ObjectMapper objectMapper = new ObjectMapper();
@Autowired @Autowired
private UserService userService; private UserService userService;
@ -58,6 +72,9 @@ public abstract class AbstractOAuth2ClientMapper {
@Autowired @Autowired
private CustomerService customerService; private CustomerService customerService;
@Autowired
private DashboardService dashboardService;
@Autowired @Autowired
private InstallScripts installScripts; private InstallScripts installScripts;
@ -92,6 +109,20 @@ public abstract class AbstractOAuth2ClientMapper {
user.setEmail(oauth2User.getEmail()); user.setEmail(oauth2User.getEmail());
user.setFirstName(oauth2User.getFirstName()); user.setFirstName(oauth2User.getFirstName());
user.setLastName(oauth2User.getLastName()); user.setLastName(oauth2User.getLastName());
if (!StringUtils.isEmpty(oauth2User.getDefaultDashboardName())) {
Optional<DashboardId> dashboardIdOpt =
user.getAuthority() == Authority.TENANT_ADMIN ?
getDashboardId(tenantId, oauth2User.getDefaultDashboardName())
: getDashboardId(tenantId, customerId, oauth2User.getDefaultDashboardName());
if (dashboardIdOpt.isPresent()) {
ObjectNode additionalInfo = objectMapper.createObjectNode();
additionalInfo.put("defaultDashboardFullscreen", oauth2User.isAlwaysFullScreen());
additionalInfo.put("defaultDashboardId", dashboardIdOpt.get().getId().toString());
user.setAdditionalInfo(additionalInfo);
}
}
user = userService.saveUser(user); user = userService.saveUser(user);
if (activateUser) { if (activateUser) {
UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId());
@ -143,4 +174,32 @@ public abstract class AbstractOAuth2ClientMapper {
return customerService.saveCustomer(customer).getId(); return customerService.saveCustomer(customer).getId();
} }
} }
private Optional<DashboardId> getDashboardId(TenantId tenantId, String dashboardName) {
TextPageLink searchTextLink = new TextPageLink(1, dashboardName);
TextPageData<DashboardInfo> dashboardsPage = dashboardService.findDashboardsByTenantId(tenantId, searchTextLink);
return dashboardsPage.getData().stream()
.findAny()
.map(IdBased::getId);
}
private Optional<DashboardId> getDashboardId(TenantId tenantId, CustomerId customerId, String dashboardName) {
TimePageData<DashboardInfo> dashboardsPage = null;
do {
TimePageLink timePageLink = dashboardsPage != null ?
dashboardsPage.getNextPageLink() : new TimePageLink(DASHBOARDS_REQUEST_LIMIT);
try {
dashboardsPage = dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, timePageLink).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException("Failed to get customer's dashboards.", e);
}
Optional<DashboardInfo> dashboardInfoOpt = dashboardsPage.getData().stream()
.filter(dashboardInfo -> dashboardName.equals(dashboardInfo.getName()))
.findAny();
if (dashboardInfoOpt.isPresent()) {
return dashboardInfoOpt.map(DashboardInfo::getId);
}
} while (dashboardsPage.hasNext());
return Optional.empty();
}
} }

4
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java

@ -56,6 +56,10 @@ public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen
String customerName = sub.replace(config.getBasic().getCustomerNamePattern()); String customerName = sub.replace(config.getBasic().getCustomerNamePattern());
oauth2User.setCustomerName(customerName); oauth2User.setCustomerName(customerName);
} }
oauth2User.setAlwaysFullScreen(config.getBasic().isAlwaysFullScreen());
if (!StringUtils.isEmpty(config.getBasic().getDefaultDashboardName())) {
oauth2User.setDefaultDashboardName(config.getBasic().getDefaultDashboardName());
}
return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser()); return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.isAllowUserCreation(), config.isActivateUser());
} }

4
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java

@ -56,8 +56,8 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme
try { try {
return restTemplate.postForEntity(custom.getUrl(), request, OAuth2User.class).getBody(); return restTemplate.postForEntity(custom.getUrl(), request, OAuth2User.class).getBody();
} catch (Exception e) { } catch (Exception e) {
log.error("Can't connect to custom mapper endpoint", e); log.error("There was an error during connection to custom mapper endpoint", e);
throw new RuntimeException("Can't connect to custom mapper endpoint", e); throw new RuntimeException("Unable to login. Please contact your Administrator!");
} }
} }
} }

24
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java

@ -32,6 +32,8 @@ import org.thingsboard.server.utils.MiscUtils;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.io.IOException; import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@Component(value = "oauth2AuthenticationSuccessHandler") @Component(value = "oauth2AuthenticationSuccessHandler")
@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true") @ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
@ -57,16 +59,22 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
public void onAuthenticationSuccess(HttpServletRequest request, public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, HttpServletResponse response,
Authentication authentication) throws IOException { Authentication authentication) throws IOException {
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
OAuth2Client oauth2Client = oauth2Configuration.getClientByRegistrationId(token.getAuthorizedClientRegistrationId()); String baseUrl = MiscUtils.constructBaseUrl(request);
OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(oauth2Client.getMapperConfig().getType()); try {
SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oauth2Client.getMapperConfig()); OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); OAuth2Client oauth2Client = oauth2Configuration.getClientByRegistrationId(token.getAuthorizedClientRegistrationId());
JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(oauth2Client.getMapperConfig().getType());
SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oauth2Client.getMapperConfig());
String baseUrl = MiscUtils.constructBaseUrl(request); JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken()); JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken());
} catch (Exception e) {
getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" +
URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.toString()));
}
} }
} }

9
application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java

@ -47,4 +47,13 @@ public enum Resource {
public Optional<EntityType> getEntityType() { public Optional<EntityType> getEntityType() {
return Optional.ofNullable(entityType); return Optional.ofNullable(entityType);
} }
public static Resource of(EntityType entityType) {
for (Resource resource : Resource.values()) {
if (resource.getEntityType().get() == entityType) {
return resource;
}
}
throw new IllegalArgumentException("Unknown EntityType: " + entityType.name());
}
} }

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

@ -292,28 +292,38 @@ public class DefaultDeviceStateService implements DeviceStateService {
} }
} }
volatile Set<TopicPartitionInfo> pendingPartitions;
@Override @Override
public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) {
if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) { if (ServiceType.TB_CORE.equals(partitionChangeEvent.getServiceType())) {
synchronized (this) { synchronized (this) {
pendingPartitions = partitionChangeEvent.getPartitions();
if (!clusterUpdatePending) { if (!clusterUpdatePending) {
clusterUpdatePending = true; clusterUpdatePending = true;
queueExecutor.submit(() -> { queueExecutor.submit(() -> {
clusterUpdatePending = false; clusterUpdatePending = false;
initStateFromDB(partitionChangeEvent.getPartitions()); initStateFromDB();
}); });
} }
} }
} }
} }
private void initStateFromDB(Set<TopicPartitionInfo> partitions) { private void initStateFromDB() {
try { try {
Set<TopicPartitionInfo> addedPartitions = new HashSet<>(partitions); log.info("CURRENT PARTITIONS: {}", partitionedDevices.keySet());
log.info("NEW PARTITIONS: {}", pendingPartitions);
Set<TopicPartitionInfo> addedPartitions = new HashSet<>(pendingPartitions);
addedPartitions.removeAll(partitionedDevices.keySet()); addedPartitions.removeAll(partitionedDevices.keySet());
log.info("ADDED PARTITIONS: {}", addedPartitions);
Set<TopicPartitionInfo> removedPartitions = new HashSet<>(partitionedDevices.keySet()); Set<TopicPartitionInfo> removedPartitions = new HashSet<>(partitionedDevices.keySet());
removedPartitions.removeAll(partitions); removedPartitions.removeAll(pendingPartitions);
log.info("REMOVED PARTITIONS: {}", removedPartitions);
// We no longer manage current partition of devices; // We no longer manage current partition of devices;
removedPartitions.forEach(partition -> { removedPartitions.forEach(partition -> {

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

@ -224,7 +224,7 @@ public class DefaultSubscriptionManagerService implements SubscriptionManagerSer
return null; return null;
} }
}, },
s -> (StringUtils.isEmpty(s.getScope()) || scope.equals(s.getScope().name())), s -> (TbAttributeSubscriptionScope.ANY_SCOPE.equals(s.getScope()) || scope.equals(s.getScope().name())),
s -> { s -> {
List<TsKvEntry> subscriptionUpdate = null; List<TsKvEntry> subscriptionUpdate = null;
for (AttributeKvEntry kv : attributes) { for (AttributeKvEntry kv : attributes) {

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

@ -17,6 +17,6 @@ package org.thingsboard.server.service.subscription;
public enum TbAttributeSubscriptionScope { public enum TbAttributeSubscriptionScope {
CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE ANY_SCOPE, CLIENT_SCOPE, SHARED_SCOPE, SERVER_SCOPE
} }

4
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java

@ -345,7 +345,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
keys.forEach(key -> subState.put(key, 0L)); keys.forEach(key -> subState.put(key, 0L));
attributesData.forEach(v -> subState.put(v.getKey(), v.getTs())); attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
TbAttributeSubscriptionScope scope = StringUtils.isEmpty(cmd.getScope()) ? TbAttributeSubscriptionScope.SERVER_SCOPE : TbAttributeSubscriptionScope.valueOf(cmd.getScope()); TbAttributeSubscriptionScope scope = StringUtils.isEmpty(cmd.getScope()) ? TbAttributeSubscriptionScope.ANY_SCOPE : TbAttributeSubscriptionScope.valueOf(cmd.getScope());
TbAttributeSubscription sub = TbAttributeSubscription.builder() TbAttributeSubscription sub = TbAttributeSubscription.builder()
.serviceId(serviceId) .serviceId(serviceId)
@ -442,7 +442,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
Map<String, Long> subState = new HashMap<>(attributesData.size()); Map<String, Long> subState = new HashMap<>(attributesData.size());
attributesData.forEach(v -> subState.put(v.getKey(), v.getTs())); attributesData.forEach(v -> subState.put(v.getKey(), v.getTs()));
TbAttributeSubscriptionScope scope = StringUtils.isEmpty(cmd.getScope()) ? TbAttributeSubscriptionScope.SERVER_SCOPE : TbAttributeSubscriptionScope.valueOf(cmd.getScope()); TbAttributeSubscriptionScope scope = StringUtils.isEmpty(cmd.getScope()) ? TbAttributeSubscriptionScope.ANY_SCOPE : TbAttributeSubscriptionScope.valueOf(cmd.getScope());
TbAttributeSubscription sub = TbAttributeSubscription.builder() TbAttributeSubscription sub = TbAttributeSubscription.builder()
.serviceId(serviceId) .serviceId(serviceId)

25
application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.transport;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
@ -24,13 +25,18 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.dao.device.DeviceCredentialsService; import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.relation.RelationService;
@ -48,6 +54,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509Ce
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.executors.DbCallbackExecutorService;
import org.thingsboard.server.service.queue.TbClusterService;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
import java.util.UUID; import java.util.UUID;
@ -82,6 +89,9 @@ public class DefaultTransportApiService implements TransportApiService {
@Autowired @Autowired
private DbCallbackExecutorService dbCallbackExecutorService; private DbCallbackExecutorService dbCallbackExecutorService;
@Autowired
protected TbClusterService tbClusterService;
private ReentrantLock deviceCreationLock = new ReentrantLock(); private ReentrantLock deviceCreationLock = new ReentrantLock();
@Override @Override
@ -119,14 +129,27 @@ public class DefaultTransportApiService implements TransportApiService {
try { try {
Device device = deviceService.findDeviceByTenantIdAndName(gateway.getTenantId(), requestMsg.getDeviceName()); Device device = deviceService.findDeviceByTenantIdAndName(gateway.getTenantId(), requestMsg.getDeviceName());
if (device == null) { if (device == null) {
TenantId tenantId = gateway.getTenantId();
device = new Device(); device = new Device();
device.setTenantId(gateway.getTenantId()); device.setTenantId(tenantId);
device.setName(requestMsg.getDeviceName()); device.setName(requestMsg.getDeviceName());
device.setType(requestMsg.getDeviceType()); device.setType(requestMsg.getDeviceType());
device.setCustomerId(gateway.getCustomerId()); device.setCustomerId(gateway.getCustomerId());
device = deviceService.saveDevice(device); device = deviceService.saveDevice(device);
relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created")); relationService.saveRelationAsync(TenantId.SYS_TENANT_ID, new EntityRelation(gateway.getId(), device.getId(), "Created"));
deviceStateService.onDeviceAdded(device); deviceStateService.onDeviceAdded(device);
TbMsgMetaData metaData = new TbMsgMetaData();
CustomerId customerId = gateway.getCustomerId();
if (customerId != null && !customerId.isNullUid()) {
metaData.putValue("customerId", customerId.toString());
}
metaData.putValue("gatewayId", gatewayId.toString());
DeviceId deviceId = device.getId();
ObjectNode entityNode = mapper.valueToTree(device);
TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, mapper.writeValueAsString(entityNode));
tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, null);
} }
return TransportApiResponseMsg.newBuilder() return TransportApiResponseMsg.newBuilder()
.setGetOrCreateDeviceResponseMsg(GetOrCreateDeviceFromGatewayResponseMsg.newBuilder().setDeviceInfo(getDeviceInfoProto(device)).build()).build(); .setGetOrCreateDeviceResponseMsg(GetOrCreateDeviceFromGatewayResponseMsg.newBuilder().setDeviceInfo(getDeviceInfoProto(device)).build()).build();

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

@ -54,6 +54,13 @@ server:
customer: customer:
enabled: "${TB_SERVER_REST_LIMITS_CUSTOMER_ENABLED:false}" enabled: "${TB_SERVER_REST_LIMITS_CUSTOMER_ENABLED:false}"
configuration: "${TB_SERVER_REST_LIMITS_CUSTOMER_CONFIGURATION:50:1,1000:60}" configuration: "${TB_SERVER_REST_LIMITS_CUSTOMER_CONFIGURATION:50:1,1000:60}"
server_side_rpc:
# Minimum value of the server side RPC timeout. May override value provided in the REST API call.
# Since 2.5 migration to queues, the RPC delay depends on the size of the pending messages in the queue,
# so default UI parameter of 500ms may not be sufficient for loaded environments.
min_timeout: "${MIN_SERVER_SIDE_RPC_TIMEOUT:5000}"
# Default value of the server side RPC timeout.
default_timeout: "${DEFAULT_SERVER_SIDE_RPC_TIMEOUT:10000}"
# Zookeeper connection parameters. Used for service discovery. # Zookeeper connection parameters. Used for service discovery.
zk: zk:
@ -148,6 +155,10 @@ security:
# If this field is not empty, user will be created as a user under defined Customer # If this field is not empty, user will be created as a user under defined Customer
# %{attribute_key} as placeholder for attribute value of attributes of external user object # %{attribute_key} as placeholder for attribute value of attributes of external user object
customerNamePattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_CUSTOMER_NAME_PATTERN:}" customerNamePattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_CUSTOMER_NAME_PATTERN:}"
# If this field is not empty, user will be created with default defined Dashboard
defaultDashboardName: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_DEFAULT_DASHBOARD_NAME:}"
# If this field is set 'true' along with non-empty 'defaultDashboardName', user will start from the defined Dashboard in fullscreen mode
alwaysFullScreen: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_ALWAYS_FULL_SCREEN:false}"
custom: custom:
url: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_URL:}" url: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_URL:}"
username: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_USERNAME:}" username: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_USERNAME:}"
@ -676,7 +687,7 @@ queue:
topic: "${TB_QUEUE_CORE_TOPIC:tb_core}" topic: "${TB_QUEUE_CORE_TOPIC:tb_core}"
poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}"
partitions: "${TB_QUEUE_CORE_PARTITIONS:10}" partitions: "${TB_QUEUE_CORE_PARTITIONS:10}"
pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:60000}" pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:2000}"
stats: stats:
enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}" enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}"
print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:60000}" print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:60000}"
@ -688,6 +699,8 @@ queue:
# JS Eval max pending requests # JS Eval max pending requests
max_pending_requests: "${REMOTE_JS_MAX_PENDING_REQUESTS:10000}" max_pending_requests: "${REMOTE_JS_MAX_PENDING_REQUESTS:10000}"
# JS Eval max request timeout # JS Eval max request timeout
max_eval_requests_timeout: "${REMOTE_JS_MAX_EVAL_REQUEST_TIMEOUT:60000}"
# JS max request timeout
max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}" max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}"
# JS response poll interval # JS response poll interval
response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}" response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}"
@ -696,7 +709,7 @@ queue:
rule-engine: rule-engine:
topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}" topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}"
poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}" poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}"
pack-processing-timeout: "${TB_QUEUE_RULE_ENGINE_PACK_PROCESSING_TIMEOUT_MS:60000}" pack-processing-timeout: "${TB_QUEUE_RULE_ENGINE_PACK_PROCESSING_TIMEOUT_MS:2000}"
stats: stats:
enabled: "${TB_QUEUE_RULE_ENGINE_STATS_ENABLED:true}" enabled: "${TB_QUEUE_RULE_ENGINE_STATS_ENABLED:true}"
print-interval-ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:60000}" print-interval-ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:60000}"
@ -705,7 +718,7 @@ queue:
topic: "${TB_QUEUE_RE_MAIN_TOPIC:tb_rule_engine.main}" topic: "${TB_QUEUE_RE_MAIN_TOPIC:tb_rule_engine.main}"
poll-interval: "${TB_QUEUE_RE_MAIN_POLL_INTERVAL_MS:25}" poll-interval: "${TB_QUEUE_RE_MAIN_POLL_INTERVAL_MS:25}"
partitions: "${TB_QUEUE_RE_MAIN_PARTITIONS:10}" partitions: "${TB_QUEUE_RE_MAIN_PARTITIONS:10}"
pack-processing-timeout: "${TB_QUEUE_RE_MAIN_PACK_PROCESSING_TIMEOUT_MS:60000}" pack-processing-timeout: "${TB_QUEUE_RE_MAIN_PACK_PROCESSING_TIMEOUT_MS:2000}"
submit-strategy: submit-strategy:
type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
# For BATCH only # For BATCH only
@ -720,7 +733,7 @@ queue:
topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}" topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}"
poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}" poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}"
partitions: "${TB_QUEUE_RE_HP_PARTITIONS:10}" partitions: "${TB_QUEUE_RE_HP_PARTITIONS:10}"
pack-processing-timeout: "${TB_QUEUE_RE_HP_PACK_PROCESSING_TIMEOUT_MS:60000}" pack-processing-timeout: "${TB_QUEUE_RE_HP_PACK_PROCESSING_TIMEOUT_MS:2000}"
submit-strategy: submit-strategy:
type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
# For BATCH only # For BATCH only
@ -735,7 +748,7 @@ queue:
topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}" topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}"
poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}" poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}"
partitions: "${TB_QUEUE_RE_SQ_PARTITIONS:10}" partitions: "${TB_QUEUE_RE_SQ_PARTITIONS:10}"
pack-processing-timeout: "${TB_QUEUE_RE_SQ_PACK_PROCESSING_TIMEOUT_MS:60000}" pack-processing-timeout: "${TB_QUEUE_RE_SQ_PACK_PROCESSING_TIMEOUT_MS:2000}"
submit-strategy: submit-strategy:
type: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_BY_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL type: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_BY_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL
# For BATCH only # For BATCH only

9
application/src/main/scripts/control/rpm/postinst

@ -1,9 +0,0 @@
#!/bin/sh
chown -R ${pkg.user}: ${pkg.logFolder}
chown -R ${pkg.user}: ${pkg.installFolder}
if [ $1 -eq 1 ] ; then
# Initial installation
systemctl --no-reload enable ${pkg.name}.service >/dev/null 2>&1 || :
fi

24
application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java

@ -33,7 +33,6 @@ import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher; import org.junit.rules.TestWatcher;
import org.junit.runner.Description; import org.junit.runner.Description;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootContextLoader; import org.springframework.boot.test.context.SpringBootContextLoader;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
@ -49,7 +48,6 @@ import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
@ -75,7 +73,6 @@ import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest;
import org.thingsboard.server.service.security.auth.rest.LoginRequest; import org.thingsboard.server.service.security.auth.rest.LoginRequest;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
@ -223,6 +220,27 @@ public abstract class AbstractControllerTest {
login(CUSTOMER_USER_EMAIL, CUSTOMER_USER_PASSWORD); login(CUSTOMER_USER_EMAIL, CUSTOMER_USER_PASSWORD);
} }
private Tenant savedDifferentTenant;
protected void loginDifferentTenant() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("Different tenant");
savedDifferentTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedDifferentTenant);
User differentTenantAdmin = new User();
differentTenantAdmin.setAuthority(Authority.TENANT_ADMIN);
differentTenantAdmin.setTenantId(savedDifferentTenant.getId());
differentTenantAdmin.setEmail("different_tenant@thingsboard.org");
createUserAndLogin(differentTenantAdmin, "testPassword");
}
protected void deleteDifferentTenant() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString())
.andExpect(status().isOk());
}
protected User createUserAndLogin(User user, String password) throws Exception { protected User createUserAndLogin(User user, String password) throws Exception {
User savedUser = doPost("/api/user", user, User.class); User savedUser = doPost("/api/user", user, User.class);
logout(); logout();

12
application/src/test/java/org/thingsboard/server/controller/BaseAssetControllerTest.java

@ -98,6 +98,18 @@ public abstract class BaseAssetControllerTest extends AbstractControllerTest {
Assert.assertEquals(foundAsset.getName(), savedAsset.getName()); Assert.assertEquals(foundAsset.getName(), savedAsset.getName());
} }
@Test
public void testUpdateAssetFromDifferentTenant() throws Exception {
Asset asset = new Asset();
asset.setName("My asset");
asset.setType("default");
Asset savedAsset = doPost("/api/asset", asset, Asset.class);
loginDifferentTenant();
doPost("/api/asset", savedAsset, Asset.class, status().isForbidden());
deleteDifferentTenant();
}
@Test @Test
public void testFindAssetById() throws Exception { public void testFindAssetById() throws Exception {
Asset asset = new Asset(); Asset asset = new Asset();

304
application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java

@ -23,6 +23,8 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After;
import org.junit.Before;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
@ -38,25 +40,39 @@ import com.fasterxml.jackson.core.type.TypeReference;
public abstract class BaseCustomerControllerTest extends AbstractControllerTest { public abstract class BaseCustomerControllerTest extends AbstractControllerTest {
private IdComparator<Customer> idComparator = new IdComparator<>(); private IdComparator<Customer> idComparator = new IdComparator<>();
@Test private Tenant savedTenant;
public void testSaveCustomer() throws Exception { private User tenantAdmin;
@Before
public void beforeTest() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
User tenantAdmin = new User(); tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId()); tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org"); tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe"); tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs"); tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
}
@After
public void afterTest() throws Exception {
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test
public void testSaveCustomer() throws Exception {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("My customer"); customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
@ -66,266 +82,159 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
Assert.assertEquals(customer.getTitle(), savedCustomer.getTitle()); Assert.assertEquals(customer.getTitle(), savedCustomer.getTitle());
savedCustomer.setTitle("My new customer"); savedCustomer.setTitle("My new customer");
doPost("/api/customer", savedCustomer, Customer.class); doPost("/api/customer", savedCustomer, Customer.class);
Customer foundCustomer = doGet("/api/customer/"+savedCustomer.getId().getId().toString(), Customer.class); Customer foundCustomer = doGet("/api/customer/"+savedCustomer.getId().getId().toString(), Customer.class);
Assert.assertEquals(foundCustomer.getTitle(), savedCustomer.getTitle()); Assert.assertEquals(foundCustomer.getTitle(), savedCustomer.getTitle());
doDelete("/api/customer/"+savedCustomer.getId().getId().toString()) doDelete("/api/customer/"+savedCustomer.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
} }
@Test @Test
public void testFindCustomerById() throws Exception { public void testUpdateCustomerFromDifferentTenant() throws Exception {
Customer customer = new Customer();
loginSysAdmin(); customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
doPost("/api/customer", savedCustomer, Customer.class);
Tenant tenant = new Tenant(); loginDifferentTenant();
tenant.setTitle("My tenant"); doPost("/api/customer", savedCustomer, Customer.class, status().isForbidden());
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); deleteDifferentTenant();
Assert.assertNotNull(savedTenant);
login(tenantAdmin.getName(), "testPassword1");
User tenantAdmin = new User(); doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); .andExpect(status().isOk());
tenantAdmin.setTenantId(savedTenant.getId()); }
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe"); @Test
tenantAdmin.setLastName("Downs"); public void testFindCustomerById() throws Exception {
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("My customer"); customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
Customer foundCustomer = doGet("/api/customer/"+savedCustomer.getId().getId().toString(), Customer.class); Customer foundCustomer = doGet("/api/customer/" + savedCustomer.getId().getId().toString(), Customer.class);
Assert.assertNotNull(foundCustomer); Assert.assertNotNull(foundCustomer);
Assert.assertEquals(savedCustomer, foundCustomer); Assert.assertEquals(savedCustomer, foundCustomer);
doDelete("/api/customer/"+savedCustomer.getId().getId().toString()) doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
} }
@Test @Test
public void testDeleteCustomer() throws Exception { public void testDeleteCustomer() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("My customer"); customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
doDelete("/api/customer/"+savedCustomer.getId().getId().toString())
.andExpect(status().isOk());
doGet("/api/customer/"+savedCustomer.getId().getId().toString()) doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isNotFound()); .andExpect(status().isOk());
loginSysAdmin(); doGet("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isNotFound());
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
} }
@Test @Test
public void testSaveCustomerWithEmptyTitle() throws Exception { public void testSaveCustomerWithEmptyTitle() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
Customer customer = new Customer(); Customer customer = new Customer();
doPost("/api/customer", customer) doPost("/api/customer", customer)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Customer title should be specified"))); .andExpect(statusReason(containsString("Customer title should be specified")));
loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
} }
@Test @Test
public void testSaveCustomerWithInvalidEmail() throws Exception { public void testSaveCustomerWithInvalidEmail() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("My customer"); customer.setTitle("My customer");
customer.setEmail("invalid@mail"); customer.setEmail("invalid@mail");
doPost("/api/customer", customer) doPost("/api/customer", customer)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Invalid email address format 'invalid@mail'"))); .andExpect(statusReason(containsString("Invalid email address format 'invalid@mail'")));
loginSysAdmin(); // loginSysAdmin();
//
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) // doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk()); // .andExpect(status().isOk());
} }
@Test @Test
public void testFindCustomers() throws Exception { public void testFindCustomers() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
TenantId tenantId = savedTenant.getId(); TenantId tenantId = savedTenant.getId();
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(tenantId);
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
List<Customer> customers = new ArrayList<>(); List<Customer> customers = new ArrayList<>();
for (int i=0;i<135;i++) { for (int i = 0; i < 135; i++) {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTenantId(tenantId); customer.setTenantId(tenantId);
customer.setTitle("Customer"+i); customer.setTitle("Customer" + i);
customers.add(doPost("/api/customer", customer, Customer.class)); customers.add(doPost("/api/customer", customer, Customer.class));
} }
List<Customer> loadedCustomers = new ArrayList<>(); List<Customer> loadedCustomers = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(23); TextPageLink pageLink = new TextPageLink(23);
TextPageData<Customer> pageData = null; TextPageData<Customer> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>() {
}, pageLink);
loadedCustomers.addAll(pageData.getData()); loadedCustomers.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(customers, idComparator); Collections.sort(customers, idComparator);
Collections.sort(loadedCustomers, idComparator); Collections.sort(loadedCustomers, idComparator);
Assert.assertEquals(customers, loadedCustomers); Assert.assertEquals(customers, loadedCustomers);
loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
} }
@Test @Test
public void testFindCustomersByTitle() throws Exception { public void testFindCustomersByTitle() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
TenantId tenantId = savedTenant.getId(); TenantId tenantId = savedTenant.getId();
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(tenantId);
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
String title1 = "Customer title 1"; String title1 = "Customer title 1";
List<Customer> customersTitle1 = new ArrayList<>(); List<Customer> customersTitle1 = new ArrayList<>();
for (int i=0;i<143;i++) { for (int i = 0; i < 143; i++) {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTenantId(tenantId); customer.setTenantId(tenantId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
String title = title1+suffix; String title = title1 + suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
customer.setTitle(title); customer.setTitle(title);
customersTitle1.add(doPost("/api/customer", customer, Customer.class)); customersTitle1.add(doPost("/api/customer", customer, Customer.class));
} }
String title2 = "Customer title 2"; String title2 = "Customer title 2";
List<Customer> customersTitle2 = new ArrayList<>(); List<Customer> customersTitle2 = new ArrayList<>();
for (int i=0;i<175;i++) { for (int i = 0; i < 175; i++) {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTenantId(tenantId); customer.setTenantId(tenantId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
String title = title2+suffix; String title = title2 + suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
customer.setTitle(title); customer.setTitle(title);
customersTitle2.add(doPost("/api/customer", customer, Customer.class)); customersTitle2.add(doPost("/api/customer", customer, Customer.class));
} }
List<Customer> loadedCustomersTitle1 = new ArrayList<>(); List<Customer> loadedCustomersTitle1 = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(15, title1); TextPageLink pageLink = new TextPageLink(15, title1);
TextPageData<Customer> pageData = null; TextPageData<Customer> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>() {
}, pageLink);
loadedCustomersTitle1.addAll(pageData.getData()); loadedCustomersTitle1.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(customersTitle1, idComparator); Collections.sort(customersTitle1, idComparator);
Collections.sort(loadedCustomersTitle1, idComparator); Collections.sort(loadedCustomersTitle1, idComparator);
Assert.assertEquals(customersTitle1, loadedCustomersTitle1); Assert.assertEquals(customersTitle1, loadedCustomersTitle1);
List<Customer> loadedCustomersTitle2 = new ArrayList<>(); List<Customer> loadedCustomersTitle2 = new ArrayList<>();
pageLink = new TextPageLink(4, title2); pageLink = new TextPageLink(4, title2);
do { do {
pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>() {
}, pageLink);
loadedCustomersTitle2.addAll(pageData.getData()); loadedCustomersTitle2.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
@ -334,33 +243,30 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
Collections.sort(customersTitle2, idComparator); Collections.sort(customersTitle2, idComparator);
Collections.sort(loadedCustomersTitle2, idComparator); Collections.sort(loadedCustomersTitle2, idComparator);
Assert.assertEquals(customersTitle2, loadedCustomersTitle2); Assert.assertEquals(customersTitle2, loadedCustomersTitle2);
for (Customer customer : loadedCustomersTitle1) { for (Customer customer : loadedCustomersTitle1) {
doDelete("/api/customer/"+customer.getId().getId().toString()) doDelete("/api/customer/" + customer.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4, title1); pageLink = new TextPageLink(4, title1);
pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (Customer customer : loadedCustomersTitle2) { for (Customer customer : loadedCustomersTitle2) {
doDelete("/api/customer/"+customer.getId().getId().toString()) doDelete("/api/customer/" + customer.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4, title2); pageLink = new TextPageLink(4, title2);
pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<TextPageData<Customer>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
.andExpect(status().isOk());
} }
} }

14
application/src/test/java/org/thingsboard/server/controller/BaseDashboardControllerTest.java

@ -16,10 +16,8 @@
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.sql.Time;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -33,7 +31,6 @@ import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.page.TimePageData; import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.model.ModelConstants;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@ -93,6 +90,17 @@ public abstract class BaseDashboardControllerTest extends AbstractControllerTest
Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); Dashboard foundDashboard = doGet("/api/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class);
Assert.assertEquals(foundDashboard.getTitle(), savedDashboard.getTitle()); Assert.assertEquals(foundDashboard.getTitle(), savedDashboard.getTitle());
} }
@Test
public void testUpdateDashboardFromDifferentTenant() throws Exception {
Dashboard dashboard = new Dashboard();
dashboard.setTitle("My dashboard");
Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class);
loginDifferentTenant();
doPost("/api/dashboard", savedDashboard, Dashboard.class, status().isForbidden());
deleteDifferentTenant();
}
@Test @Test
public void testFindDashboardById() throws Exception { public void testFindDashboardById() throws Exception {

11
application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java

@ -107,6 +107,17 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class);
Assert.assertEquals(foundDevice.getName(), savedDevice.getName()); Assert.assertEquals(foundDevice.getName(), savedDevice.getName());
} }
@Test
public void testUpdateDeviceFromDifferentTenant() throws Exception {
Device device = new Device();
device.setName("My device");
device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
loginDifferentTenant();
doPost("/api/device", savedDevice, Device.class, status().isForbidden());
deleteDifferentTenant();
}
@Test @Test
public void testFindDeviceById() throws Exception { public void testFindDeviceById() throws Exception {

10
application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java

@ -25,7 +25,6 @@ import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
@ -132,6 +131,15 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
assertEquals(foundEntityView.getKeys(), telemetry); assertEquals(foundEntityView.getKeys(), telemetry);
} }
@Test
public void testUpdateEntityViewFromDifferentTenant() throws Exception {
EntityView savedView = getNewSavedEntityView("Test entity view");
loginDifferentTenant();
doPost("/api/entityView", savedView, EntityView.class, status().isForbidden());
deleteDifferentTenant();
}
@Test @Test
public void testDeleteEntityView() throws Exception { public void testDeleteEntityView() throws Exception {
EntityView view = getNewSavedEntityView("Test entity view"); EntityView view = getNewSavedEntityView("Test entity view");

480
application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java

@ -38,21 +38,23 @@ import java.util.List;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public abstract class BaseUserControllerTest extends AbstractControllerTest { public abstract class BaseUserControllerTest extends AbstractControllerTest {
private IdComparator<User> idComparator = new IdComparator<>(); private IdComparator<User> idComparator = new IdComparator<>();
@Test @Test
public void testSaveUser() throws Exception { public void testSaveUser() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
String email = "tenant2@thingsboard.org"; String email = "tenant2@thingsboard.org";
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
@ -66,13 +68,13 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
Assert.assertTrue(savedUser.getCreatedTime() > 0); Assert.assertTrue(savedUser.getCreatedTime() > 0);
Assert.assertEquals(user.getEmail(), savedUser.getEmail()); Assert.assertEquals(user.getEmail(), savedUser.getEmail());
User foundUser = doGet("/api/user/"+savedUser.getId().getId().toString(), User.class); User foundUser = doGet("/api/user/" + savedUser.getId().getId().toString(), User.class);
Assert.assertEquals(foundUser, savedUser); Assert.assertEquals(foundUser, savedUser);
logout(); logout();
doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken) doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken)
.andExpect(status().isSeeOther()) .andExpect(status().isSeeOther())
.andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken)); .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken));
JsonNode activateRequest = new ObjectMapper().createObjectNode() JsonNode activateRequest = new ObjectMapper().createObjectNode()
.put("activateToken", TestMailService.currentActivateToken) .put("activateToken", TestMailService.currentActivateToken)
@ -82,36 +84,61 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
validateAndSetJwtToken(tokenInfo, email); validateAndSetJwtToken(tokenInfo, email);
doGet("/api/auth/user") doGet("/api/auth/user")
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.authority",is(Authority.TENANT_ADMIN.name()))) .andExpect(jsonPath("$.authority", is(Authority.TENANT_ADMIN.name())))
.andExpect(jsonPath("$.email",is(email))); .andExpect(jsonPath("$.email", is(email)));
logout(); logout();
login(email, "testPassword"); login(email, "testPassword");
doGet("/api/auth/user") doGet("/api/auth/user")
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.authority",is(Authority.TENANT_ADMIN.name()))) .andExpect(jsonPath("$.authority", is(Authority.TENANT_ADMIN.name())))
.andExpect(jsonPath("$.email",is(email))); .andExpect(jsonPath("$.email", is(email)));
loginSysAdmin(); loginSysAdmin();
doDelete("/api/user/"+savedUser.getId().getId().toString()) doDelete("/api/user/" + savedUser.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test
public void testUpdateUserFromDifferentTenant() throws Exception {
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
loginDifferentTenant();
doPost("/api/user", tenantAdmin, User.class, status().isForbidden());
deleteDifferentTenant();
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
}
@Test @Test
public void testResetPassword() throws Exception { public void testResetPassword() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
String email = "tenant2@thingsboard.org"; String email = "tenant2@thingsboard.org";
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
@ -119,7 +146,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
user.setEmail(email); user.setEmail(email);
user.setFirstName("Joe"); user.setFirstName("Joe");
user.setLastName("Downs"); user.setLastName("Downs");
User savedUser = createUserAndLogin(user, "testPassword1"); User savedUser = createUserAndLogin(user, "testPassword1");
logout(); logout();
@ -127,10 +154,10 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
.put("email", email); .put("email", email);
doPost("/api/noauth/resetPasswordByEmail", resetPasswordByEmailRequest) doPost("/api/noauth/resetPasswordByEmail", resetPasswordByEmailRequest)
.andExpect(status().isOk()); .andExpect(status().isOk());
doGet("/api/noauth/resetPassword?resetToken={resetToken}", TestMailService.currentResetPasswordToken) doGet("/api/noauth/resetPassword?resetToken={resetToken}", TestMailService.currentResetPasswordToken)
.andExpect(status().isSeeOther()) .andExpect(status().isSeeOther())
.andExpect(header().string(HttpHeaders.LOCATION, "/login/resetPassword?resetToken=" + TestMailService.currentResetPasswordToken)); .andExpect(header().string(HttpHeaders.LOCATION, "/login/resetPassword?resetToken=" + TestMailService.currentResetPasswordToken));
JsonNode resetPasswordRequest = new ObjectMapper().createObjectNode() JsonNode resetPasswordRequest = new ObjectMapper().createObjectNode()
.put("resetToken", TestMailService.currentResetPasswordToken) .put("resetToken", TestMailService.currentResetPasswordToken)
@ -140,35 +167,35 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
validateAndSetJwtToken(tokenInfo, email); validateAndSetJwtToken(tokenInfo, email);
doGet("/api/auth/user") doGet("/api/auth/user")
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.authority",is(Authority.TENANT_ADMIN.name()))) .andExpect(jsonPath("$.authority", is(Authority.TENANT_ADMIN.name())))
.andExpect(jsonPath("$.email",is(email))); .andExpect(jsonPath("$.email", is(email)));
logout(); logout();
login(email, "testPassword2"); login(email, "testPassword2");
doGet("/api/auth/user") doGet("/api/auth/user")
.andExpect(status().isOk()) .andExpect(status().isOk())
.andExpect(jsonPath("$.authority",is(Authority.TENANT_ADMIN.name()))) .andExpect(jsonPath("$.authority", is(Authority.TENANT_ADMIN.name())))
.andExpect(jsonPath("$.email",is(email))); .andExpect(jsonPath("$.email", is(email)));
loginSysAdmin(); loginSysAdmin();
doDelete("/api/user/"+savedUser.getId().getId().toString()) doDelete("/api/user/" + savedUser.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testFindUserById() throws Exception { public void testFindUserById() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
String email = "tenant2@thingsboard.org"; String email = "tenant2@thingsboard.org";
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
@ -176,25 +203,25 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
user.setEmail(email); user.setEmail(email);
user.setFirstName("Joe"); user.setFirstName("Joe");
user.setLastName("Downs"); user.setLastName("Downs");
User savedUser = doPost("/api/user", user, User.class); User savedUser = doPost("/api/user", user, User.class);
User foundUser = doGet("/api/user/"+savedUser.getId().getId().toString(), User.class); User foundUser = doGet("/api/user/" + savedUser.getId().getId().toString(), User.class);
Assert.assertNotNull(foundUser); Assert.assertNotNull(foundUser);
Assert.assertEquals(savedUser, foundUser); Assert.assertEquals(savedUser, foundUser);
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testSaveUserWithSameEmail() throws Exception { public void testSaveUserWithSameEmail() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
String email = TENANT_ADMIN_EMAIL; String email = TENANT_ADMIN_EMAIL;
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
@ -202,24 +229,24 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
user.setEmail(email); user.setEmail(email);
user.setFirstName("Joe"); user.setFirstName("Joe");
user.setLastName("Downs"); user.setLastName("Downs");
doPost("/api/user", user) doPost("/api/user", user)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("User with email '" + email + "' already present in database"))); .andExpect(statusReason(containsString("User with email '" + email + "' already present in database")));
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testSaveUserWithInvalidEmail() throws Exception { public void testSaveUserWithInvalidEmail() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
String email = "tenant_thingsboard.org"; String email = "tenant_thingsboard.org";
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
@ -227,62 +254,62 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
user.setEmail(email); user.setEmail(email);
user.setFirstName("Joe"); user.setFirstName("Joe");
user.setLastName("Downs"); user.setLastName("Downs");
doPost("/api/user", user) doPost("/api/user", user)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Invalid email address format '" + email + "'"))); .andExpect(statusReason(containsString("Invalid email address format '" + email + "'")));
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testSaveUserWithEmptyEmail() throws Exception { public void testSaveUserWithEmptyEmail() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(savedTenant.getId()); user.setTenantId(savedTenant.getId());
user.setFirstName("Joe"); user.setFirstName("Joe");
user.setLastName("Downs"); user.setLastName("Downs");
doPost("/api/user", user) doPost("/api/user", user)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("User email should be specified"))); .andExpect(statusReason(containsString("User email should be specified")));
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testSaveUserWithoutTenant() throws Exception { public void testSaveUserWithoutTenant() throws Exception {
loginSysAdmin(); loginSysAdmin();
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
user.setEmail("tenant2@thingsboard.org"); user.setEmail("tenant2@thingsboard.org");
user.setFirstName("Joe"); user.setFirstName("Joe");
user.setLastName("Downs"); user.setLastName("Downs");
doPost("/api/user", user) doPost("/api/user", user)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Tenant administrator should be assigned to tenant"))); .andExpect(statusReason(containsString("Tenant administrator should be assigned to tenant")));
} }
@Test @Test
public void testDeleteUser() throws Exception { public void testDeleteUser() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
String email = "tenant2@thingsboard.org"; String email = "tenant2@thingsboard.org";
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
@ -290,176 +317,182 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
user.setEmail(email); user.setEmail(email);
user.setFirstName("Joe"); user.setFirstName("Joe");
user.setLastName("Downs"); user.setLastName("Downs");
User savedUser = doPost("/api/user", user, User.class); User savedUser = doPost("/api/user", user, User.class);
User foundUser = doGet("/api/user/"+savedUser.getId().getId().toString(), User.class); User foundUser = doGet("/api/user/" + savedUser.getId().getId().toString(), User.class);
Assert.assertNotNull(foundUser); Assert.assertNotNull(foundUser);
doDelete("/api/user/"+savedUser.getId().getId().toString()) doDelete("/api/user/" + savedUser.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
doGet("/api/user/"+savedUser.getId().getId().toString()) doGet("/api/user/" + savedUser.getId().getId().toString())
.andExpect(status().isNotFound()); .andExpect(status().isNotFound());
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testFindTenantAdmins() throws Exception { public void testFindTenantAdmins() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
TenantId tenantId = savedTenant.getId(); TenantId tenantId = savedTenant.getId();
List<User> tenantAdmins = new ArrayList<>(); List<User> tenantAdmins = new ArrayList<>();
for (int i=0;i<64;i++) { for (int i = 0; i < 64; i++) {
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(tenantId); user.setTenantId(tenantId);
user.setEmail("testTenant" + i + "@thingsboard.org"); user.setEmail("testTenant" + i + "@thingsboard.org");
tenantAdmins.add(doPost("/api/user", user, User.class)); tenantAdmins.add(doPost("/api/user", user, User.class));
} }
List<User> loadedTenantAdmins = new ArrayList<>(); List<User> loadedTenantAdmins = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(33); TextPageLink pageLink = new TextPageLink(33);
TextPageData<User> pageData = null; TextPageData<User> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
loadedTenantAdmins.addAll(pageData.getData()); loadedTenantAdmins.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(tenantAdmins, idComparator); Collections.sort(tenantAdmins, idComparator);
Collections.sort(loadedTenantAdmins, idComparator); Collections.sort(loadedTenantAdmins, idComparator);
Assert.assertEquals(tenantAdmins, loadedTenantAdmins); Assert.assertEquals(tenantAdmins, loadedTenantAdmins);
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
pageLink = new TextPageLink(33); pageLink = new TextPageLink(33);
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertTrue(pageData.getData().isEmpty()); Assert.assertTrue(pageData.getData().isEmpty());
} }
@Test @Test
public void testFindTenantAdminsByEmail() throws Exception { public void testFindTenantAdminsByEmail() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
TenantId tenantId = savedTenant.getId(); TenantId tenantId = savedTenant.getId();
String email1 = "testEmail1"; String email1 = "testEmail1";
List<User> tenantAdminsEmail1 = new ArrayList<>(); List<User> tenantAdminsEmail1 = new ArrayList<>();
for (int i=0;i<124;i++) { for (int i = 0; i < 124; i++) {
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(tenantId); user.setTenantId(tenantId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
String email = email1+suffix+ "@thingsboard.org"; String email = email1 + suffix + "@thingsboard.org";
email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase();
user.setEmail(email); user.setEmail(email);
tenantAdminsEmail1.add(doPost("/api/user", user, User.class)); tenantAdminsEmail1.add(doPost("/api/user", user, User.class));
} }
String email2 = "testEmail2"; String email2 = "testEmail2";
List<User> tenantAdminsEmail2 = new ArrayList<>(); List<User> tenantAdminsEmail2 = new ArrayList<>();
for (int i=0;i<112;i++) { for (int i = 0; i < 112; i++) {
User user = new User(); User user = new User();
user.setAuthority(Authority.TENANT_ADMIN); user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(tenantId); user.setTenantId(tenantId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
String email = email2+suffix+ "@thingsboard.org"; String email = email2 + suffix + "@thingsboard.org";
email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase();
user.setEmail(email); user.setEmail(email);
tenantAdminsEmail2.add(doPost("/api/user", user, User.class)); tenantAdminsEmail2.add(doPost("/api/user", user, User.class));
} }
List<User> loadedTenantAdminsEmail1 = new ArrayList<>(); List<User> loadedTenantAdminsEmail1 = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(33, email1); TextPageLink pageLink = new TextPageLink(33, email1);
TextPageData<User> pageData = null; TextPageData<User> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
loadedTenantAdminsEmail1.addAll(pageData.getData()); loadedTenantAdminsEmail1.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(tenantAdminsEmail1, idComparator); Collections.sort(tenantAdminsEmail1, idComparator);
Collections.sort(loadedTenantAdminsEmail1, idComparator); Collections.sort(loadedTenantAdminsEmail1, idComparator);
Assert.assertEquals(tenantAdminsEmail1, loadedTenantAdminsEmail1); Assert.assertEquals(tenantAdminsEmail1, loadedTenantAdminsEmail1);
List<User> loadedTenantAdminsEmail2 = new ArrayList<>(); List<User> loadedTenantAdminsEmail2 = new ArrayList<>();
pageLink = new TextPageLink(16, email2); pageLink = new TextPageLink(16, email2);
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
loadedTenantAdminsEmail2.addAll(pageData.getData()); loadedTenantAdminsEmail2.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(tenantAdminsEmail2, idComparator); Collections.sort(tenantAdminsEmail2, idComparator);
Collections.sort(loadedTenantAdminsEmail2, idComparator); Collections.sort(loadedTenantAdminsEmail2, idComparator);
Assert.assertEquals(tenantAdminsEmail2, loadedTenantAdminsEmail2); Assert.assertEquals(tenantAdminsEmail2, loadedTenantAdminsEmail2);
for (User user : loadedTenantAdminsEmail1) { for (User user : loadedTenantAdminsEmail1) {
doDelete("/api/user/"+user.getId().getId().toString()) doDelete("/api/user/" + user.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4, email1); pageLink = new TextPageLink(4, email1);
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (User user : loadedTenantAdminsEmail2) { for (User user : loadedTenantAdminsEmail2) {
doDelete("/api/user/"+user.getId().getId().toString()) doDelete("/api/user/" + user.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4, email2); pageLink = new TextPageLink(4, email2);
pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testFindCustomerUsers() throws Exception { public void testFindCustomerUsers() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
TenantId tenantId = savedTenant.getId(); TenantId tenantId = savedTenant.getId();
User tenantAdmin = new User(); User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
@ -467,59 +500,60 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
tenantAdmin.setEmail("tenant2@thingsboard.org"); tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe"); tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs"); tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("My customer"); customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
CustomerId customerId = savedCustomer.getId(); CustomerId customerId = savedCustomer.getId();
List<User> customerUsers = new ArrayList<>(); List<User> customerUsers = new ArrayList<>();
for (int i=0;i<56;i++) { for (int i = 0; i < 56; i++) {
User user = new User(); User user = new User();
user.setAuthority(Authority.CUSTOMER_USER); user.setAuthority(Authority.CUSTOMER_USER);
user.setCustomerId(customerId); user.setCustomerId(customerId);
user.setEmail("testCustomer" + i + "@thingsboard.org"); user.setEmail("testCustomer" + i + "@thingsboard.org");
customerUsers.add(doPost("/api/user", user, User.class)); customerUsers.add(doPost("/api/user", user, User.class));
} }
List<User> loadedCustomerUsers = new ArrayList<>(); List<User> loadedCustomerUsers = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(33); TextPageLink pageLink = new TextPageLink(33);
TextPageData<User> pageData = null; TextPageData<User> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
loadedCustomerUsers.addAll(pageData.getData()); loadedCustomerUsers.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(customerUsers, idComparator); Collections.sort(customerUsers, idComparator);
Collections.sort(loadedCustomerUsers, idComparator); Collections.sort(loadedCustomerUsers, idComparator);
Assert.assertEquals(customerUsers, loadedCustomerUsers); Assert.assertEquals(customerUsers, loadedCustomerUsers);
doDelete("/api/customer/"+customerId.getId().toString()) doDelete("/api/customer/" + customerId.getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
loginSysAdmin(); loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testFindCustomerUsersByEmail() throws Exception { public void testFindCustomerUsersByEmail() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant); Assert.assertNotNull(savedTenant);
TenantId tenantId = savedTenant.getId(); TenantId tenantId = savedTenant.getId();
User tenantAdmin = new User(); User tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN); tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
@ -527,105 +561,109 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
tenantAdmin.setEmail("tenant2@thingsboard.org"); tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe"); tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs"); tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("My customer"); customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
CustomerId customerId = savedCustomer.getId(); CustomerId customerId = savedCustomer.getId();
String email1 = "testEmail1"; String email1 = "testEmail1";
List<User> customerUsersEmail1 = new ArrayList<>(); List<User> customerUsersEmail1 = new ArrayList<>();
for (int i=0;i<74;i++) { for (int i = 0; i < 74; i++) {
User user = new User(); User user = new User();
user.setAuthority(Authority.CUSTOMER_USER); user.setAuthority(Authority.CUSTOMER_USER);
user.setCustomerId(customerId); user.setCustomerId(customerId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
String email = email1+suffix+ "@thingsboard.org"; String email = email1 + suffix + "@thingsboard.org";
email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase();
user.setEmail(email); user.setEmail(email);
customerUsersEmail1.add(doPost("/api/user", user, User.class)); customerUsersEmail1.add(doPost("/api/user", user, User.class));
} }
String email2 = "testEmail2"; String email2 = "testEmail2";
List<User> customerUsersEmail2 = new ArrayList<>(); List<User> customerUsersEmail2 = new ArrayList<>();
for (int i=0;i<92;i++) { for (int i = 0; i < 92; i++) {
User user = new User(); User user = new User();
user.setAuthority(Authority.CUSTOMER_USER); user.setAuthority(Authority.CUSTOMER_USER);
user.setCustomerId(customerId); user.setCustomerId(customerId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
String email = email2+suffix+ "@thingsboard.org"; String email = email2 + suffix + "@thingsboard.org";
email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase(); email = i % 2 == 0 ? email.toLowerCase() : email.toUpperCase();
user.setEmail(email); user.setEmail(email);
customerUsersEmail2.add(doPost("/api/user", user, User.class)); customerUsersEmail2.add(doPost("/api/user", user, User.class));
} }
List<User> loadedCustomerUsersEmail1 = new ArrayList<>(); List<User> loadedCustomerUsersEmail1 = new ArrayList<>();
TextPageLink pageLink = new TextPageLink(33, email1); TextPageLink pageLink = new TextPageLink(33, email1);
TextPageData<User> pageData = null; TextPageData<User> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
loadedCustomerUsersEmail1.addAll(pageData.getData()); loadedCustomerUsersEmail1.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(customerUsersEmail1, idComparator); Collections.sort(customerUsersEmail1, idComparator);
Collections.sort(loadedCustomerUsersEmail1, idComparator); Collections.sort(loadedCustomerUsersEmail1, idComparator);
Assert.assertEquals(customerUsersEmail1, loadedCustomerUsersEmail1); Assert.assertEquals(customerUsersEmail1, loadedCustomerUsersEmail1);
List<User> loadedCustomerUsersEmail2 = new ArrayList<>(); List<User> loadedCustomerUsersEmail2 = new ArrayList<>();
pageLink = new TextPageLink(16, email2); pageLink = new TextPageLink(16, email2);
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
loadedCustomerUsersEmail2.addAll(pageData.getData()); loadedCustomerUsersEmail2.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageData.getNextPageLink(); pageLink = pageData.getNextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(customerUsersEmail2, idComparator); Collections.sort(customerUsersEmail2, idComparator);
Collections.sort(loadedCustomerUsersEmail2, idComparator); Collections.sort(loadedCustomerUsersEmail2, idComparator);
Assert.assertEquals(customerUsersEmail2, loadedCustomerUsersEmail2); Assert.assertEquals(customerUsersEmail2, loadedCustomerUsersEmail2);
for (User user : loadedCustomerUsersEmail1) { for (User user : loadedCustomerUsersEmail1) {
doDelete("/api/user/"+user.getId().getId().toString()) doDelete("/api/user/" + user.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4, email1); pageLink = new TextPageLink(4, email1);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (User user : loadedCustomerUsersEmail2) { for (User user : loadedCustomerUsersEmail2) {
doDelete("/api/user/"+user.getId().getId().toString()) doDelete("/api/user/" + user.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
pageLink = new TextPageLink(4, email2); pageLink = new TextPageLink(4, email2);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/users?",
new TypeReference<TextPageData<User>>(){}, pageLink); new TypeReference<TextPageData<User>>() {
}, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
doDelete("/api/customer/"+customerId.getId().toString()) doDelete("/api/customer/" + customerId.getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
loginSysAdmin(); loginSysAdmin();
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
} }

14
application/src/test/java/org/thingsboard/server/controller/BaseWidgetTypeControllerTest.java

@ -64,7 +64,6 @@ public abstract class BaseWidgetTypeControllerTest extends AbstractControllerTes
WidgetsBundle widgetsBundle = new WidgetsBundle(); WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTitle("My widgets bundle"); widgetsBundle.setTitle("My widgets bundle");
savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class); savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class);
} }
@After @After
@ -100,6 +99,19 @@ public abstract class BaseWidgetTypeControllerTest extends AbstractControllerTes
Assert.assertEquals(foundWidgetType.getName(), savedWidgetType.getName()); Assert.assertEquals(foundWidgetType.getName(), savedWidgetType.getName());
} }
@Test
public void testUpdateWidgetTypeFromDifferentTenant() throws Exception {
WidgetType widgetType = new WidgetType();
widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
widgetType.setName("Widget Type");
widgetType.setDescriptor(new ObjectMapper().readValue("{ \"someKey\": \"someValue\" }", JsonNode.class));
WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class);
loginDifferentTenant();
doPost("/api/widgetType", savedWidgetType, WidgetType.class, status().isForbidden());
deleteDifferentTenant();
}
@Test @Test
public void testFindWidgetTypeById() throws Exception { public void testFindWidgetTypeById() throws Exception {
WidgetType widgetType = new WidgetType(); WidgetType widgetType = new WidgetType();

11
application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java

@ -88,6 +88,17 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController
Assert.assertEquals(foundWidgetsBundle.getTitle(), savedWidgetsBundle.getTitle()); Assert.assertEquals(foundWidgetsBundle.getTitle(), savedWidgetsBundle.getTitle());
} }
@Test
public void testUpdateWidgetsBundleFromDifferentTenant() throws Exception {
WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTitle("My widgets bundle");
WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class);
loginDifferentTenant();
doPost("/api/widgetsBundle", savedWidgetsBundle, WidgetsBundle.class, status().isForbidden());
deleteDifferentTenant();
}
@Test @Test
public void testFindWidgetsBundleById() throws Exception { public void testFindWidgetsBundleById() throws Exception {
WidgetsBundle widgetsBundle = new WidgetsBundle(); WidgetsBundle widgetsBundle = new WidgetsBundle();

50
application/src/test/java/org/thingsboard/server/rules/RuleEngineNoSqlTestSuite.java

@ -1,50 +0,0 @@
/**
* Copyright © 2016-2020 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.rules;
import org.cassandraunit.dataset.cql.ClassPathCQLDataSet;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.runner.RunWith;
import org.thingsboard.server.dao.CustomCassandraCQLUnit;
import org.thingsboard.server.dao.CustomSqlUnit;
import org.thingsboard.server.queue.memory.InMemoryStorage;
import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.rules.flow.nosql.*Test",
"org.thingsboard.server.rules.lifecycle.nosql.*Test"
})
public class RuleEngineNoSqlTestSuite {
@ClassRule
public static CustomCassandraCQLUnit cassandraUnit =
new CustomCassandraCQLUnit(
Arrays.asList(
new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false),
new ClassPathCQLDataSet("cassandra/schema-entities.cql", false, false),
new ClassPathCQLDataSet("cassandra/system-data.cql", false, false)),
"cassandra-test.yaml", 30000l);
@BeforeClass
public static void cleanupInMemStorage(){
InMemoryStorage.getInstance().cleanup();
}
}

2
common/dao-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/dao-api/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2User.java

@ -28,4 +28,6 @@ public class OAuth2User {
private String email; private String email;
private String firstName; private String firstName;
private String lastName; private String lastName;
private boolean alwaysFullScreen;
private String defaultDashboardName;
} }

2
common/data/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/edge-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/message/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

30
common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java

@ -40,6 +40,7 @@ import java.util.UUID;
public final class TbMsg implements Serializable { public final class TbMsg implements Serializable {
private final UUID id; private final UUID id;
private final long ts;
private final String type; private final String type;
private final EntityId originator; private final EntityId originator;
private final TbMsgMetaData metaData; private final TbMsgMetaData metaData;
@ -51,38 +52,43 @@ public final class TbMsg implements Serializable {
transient private final TbMsgCallback callback; transient private final TbMsgCallback callback;
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) { public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) {
return new TbMsg(UUID.randomUUID(), type, originator, metaData.copy(), TbMsgDataType.JSON, data, null, null, TbMsgCallback.EMPTY); return new TbMsg(UUID.randomUUID(), System.currentTimeMillis(), type, originator, metaData.copy(), TbMsgDataType.JSON, data, null, null, TbMsgCallback.EMPTY);
} }
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(UUID.randomUUID(), type, originator, metaData.copy(), TbMsgDataType.JSON, data, ruleChainId, ruleNodeId, TbMsgCallback.EMPTY); return new TbMsg(UUID.randomUUID(), System.currentTimeMillis(), type, originator, metaData.copy(), TbMsgDataType.JSON, data, ruleChainId, ruleNodeId, TbMsgCallback.EMPTY);
} }
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data) { public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data) {
return new TbMsg(UUID.randomUUID(), type, originator, metaData.copy(), dataType, data, null, null, TbMsgCallback.EMPTY); return new TbMsg(UUID.randomUUID(), System.currentTimeMillis(), type, originator, metaData.copy(), dataType, data, null, null, TbMsgCallback.EMPTY);
} }
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(UUID.randomUUID(), type, originator, metaData.copy(), dataType, data, ruleChainId, ruleNodeId, TbMsgCallback.EMPTY); return new TbMsg(UUID.randomUUID(), System.currentTimeMillis(), type, originator, metaData.copy(), dataType, data, ruleChainId, ruleNodeId, TbMsgCallback.EMPTY);
} }
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data, TbMsgCallback callback) { public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data, TbMsgCallback callback) {
return new TbMsg(UUID.randomUUID(), type, originator, metaData.copy(), TbMsgDataType.JSON, data, null, null, callback); return new TbMsg(UUID.randomUUID(), System.currentTimeMillis(), type, originator, metaData.copy(), TbMsgDataType.JSON, data, null, null, callback);
} }
public static TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) { public static TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data) {
return new TbMsg(origMsg.getId(), type, originator, metaData.copy(), origMsg.getDataType(), return new TbMsg(origMsg.getId(), origMsg.getTs(), type, originator, metaData.copy(), origMsg.getDataType(),
data, origMsg.getRuleChainId(), origMsg.getRuleNodeId(), origMsg.getCallback()); data, origMsg.getRuleChainId(), origMsg.getRuleNodeId(), origMsg.getCallback());
} }
public static TbMsg newMsg(TbMsg tbMsg, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { public static TbMsg newMsg(TbMsg tbMsg, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(UUID.randomUUID(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.getMetaData().copy(), return new TbMsg(UUID.randomUUID(), tbMsg.getTs(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.getMetaData().copy(),
tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, TbMsgCallback.EMPTY); tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, TbMsgCallback.EMPTY);
} }
private TbMsg(UUID id, String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data, private TbMsg(UUID id, long ts, String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data,
RuleChainId ruleChainId, RuleNodeId ruleNodeId, TbMsgCallback callback) { RuleChainId ruleChainId, RuleNodeId ruleNodeId, TbMsgCallback callback) {
this.id = id; this.id = id;
if (ts > 0) {
this.ts = ts;
} else {
this.ts = System.currentTimeMillis();
}
this.type = type; this.type = type;
this.originator = originator; this.originator = originator;
this.metaData = metaData; this.metaData = metaData;
@ -105,6 +111,7 @@ public final class TbMsg implements Serializable {
public static byte[] toByteArray(TbMsg msg) { public static byte[] toByteArray(TbMsg msg) {
MsgProtos.TbMsgProto.Builder builder = MsgProtos.TbMsgProto.newBuilder(); MsgProtos.TbMsgProto.Builder builder = MsgProtos.TbMsgProto.newBuilder();
builder.setId(msg.getId().toString()); builder.setId(msg.getId().toString());
builder.setTs(msg.getTs());
builder.setType(msg.getType()); builder.setType(msg.getType());
builder.setEntityType(msg.getOriginator().getEntityType().name()); builder.setEntityType(msg.getOriginator().getEntityType().name());
builder.setEntityIdMSB(msg.getOriginator().getId().getMostSignificantBits()); builder.setEntityIdMSB(msg.getOriginator().getId().getMostSignificantBits());
@ -124,7 +131,6 @@ public final class TbMsg implements Serializable {
builder.setMetaData(MsgProtos.TbMsgMetaDataProto.newBuilder().putAllData(msg.getMetaData().getData()).build()); builder.setMetaData(MsgProtos.TbMsgMetaDataProto.newBuilder().putAllData(msg.getMetaData().getData()).build());
} }
builder.setDataType(msg.getDataType().ordinal()); builder.setDataType(msg.getDataType().ordinal());
builder.setData(msg.getData()); builder.setData(msg.getData());
return builder.build().toByteArray(); return builder.build().toByteArray();
@ -144,18 +150,18 @@ public final class TbMsg implements Serializable {
ruleNodeId = new RuleNodeId(new UUID(proto.getRuleNodeIdMSB(), proto.getRuleNodeIdLSB())); ruleNodeId = new RuleNodeId(new UUID(proto.getRuleNodeIdMSB(), proto.getRuleNodeIdLSB()));
} }
TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()]; TbMsgDataType dataType = TbMsgDataType.values()[proto.getDataType()];
return new TbMsg(UUID.fromString(proto.getId()), proto.getType(), entityId, metaData, dataType, proto.getData(), ruleChainId, ruleNodeId, callback); return new TbMsg(UUID.fromString(proto.getId()), proto.getTs(), proto.getType(), entityId, metaData, dataType, proto.getData(), ruleChainId, ruleNodeId, callback);
} catch (InvalidProtocolBufferException e) { } catch (InvalidProtocolBufferException e) {
throw new IllegalStateException("Could not parse protobuf for TbMsg", e); throw new IllegalStateException("Could not parse protobuf for TbMsg", e);
} }
} }
public TbMsg copyWithRuleChainId(RuleChainId ruleChainId) { public TbMsg copyWithRuleChainId(RuleChainId ruleChainId) {
return new TbMsg(this.id, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, null, callback); return new TbMsg(this.id, this.ts, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, null, callback);
} }
public TbMsg copyWithRuleNodeId(RuleChainId ruleChainId, RuleNodeId ruleNodeId) { public TbMsg copyWithRuleNodeId(RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(this.id, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, callback); return new TbMsg(this.id, this.ts, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, callback);
} }
public TbMsgCallback getCallback() { public TbMsgCallback getCallback() {

1
common/message/src/main/proto/tbmsg.proto

@ -44,4 +44,5 @@ message TbMsgProto {
int32 dataType = 13; int32 dataType = 13;
string data = 14; string data = 14;
int64 ts = 15;
} }

2
common/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>common</artifactId> <artifactId>common</artifactId>

2
common/queue/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusConsumerTemplate.java

@ -101,7 +101,7 @@ public class TbServiceBusConsumerTemplate<T extends TbQueueMsg> extends Abstract
@Override @Override
protected void doSubscribe(List<String> topicNames) { protected void doSubscribe(List<String> topicNames) {
createReceivers(); createReceivers();
messagesPerQueue = receivers.size() / partitions.size(); messagesPerQueue = receivers.size() / Math.max(partitions.size(), 1);
} }
@Override @Override

7
common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java

@ -85,7 +85,12 @@ public abstract class AbstractTbQueueConsumerTemplate<R, T extends TbQueueMsg> i
subscribed = true; subscribed = true;
} }
List<R> records = doPoll(durationInMillis); List<R> records;
if (partitions.isEmpty()) {
records = Collections.emptyList();
} else {
records = doPoll(durationInMillis);
}
if (!records.isEmpty()) { if (!records.isEmpty()) {
List<T> result = new ArrayList<>(records.size()); List<T> result = new ArrayList<>(records.size());
records.forEach(record -> { records.forEach(record -> {

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

@ -36,6 +36,7 @@ import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -148,6 +149,14 @@ public class HashPartitionService implements PartitionService {
} }
} }
}); });
oldPartitions.forEach((serviceQueueKey, partitions) -> {
if (!myPartitions.containsKey(serviceQueueKey)) {
log.info("[{}] NO MORE PARTITIONS FOR CURRENT KEY", serviceQueueKey);
applicationEventPublisher.publishEvent(new PartitionChangeEvent(this, serviceQueueKey, Collections.emptySet()));
}
});
myPartitions.forEach((serviceQueueKey, partitions) -> { myPartitions.forEach((serviceQueueKey, partitions) -> {
if (!partitions.equals(oldPartitions.get(serviceQueueKey))) { if (!partitions.equals(oldPartitions.get(serviceQueueKey))) {
log.info("[{}] NEW PARTITIONS: {}", serviceQueueKey, partitions); log.info("[{}] NEW PARTITIONS: {}", serviceQueueKey, partitions);

8
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java

@ -71,8 +71,12 @@ public class TbKafkaConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQue
@Override @Override
protected void doSubscribe(List<String> topicNames) { protected void doSubscribe(List<String> topicNames) {
topicNames.forEach(admin::createTopicIfNotExists); if (!topicNames.isEmpty()) {
consumer.subscribe(topicNames); topicNames.forEach(admin::createTopicIfNotExists);
consumer.subscribe(topicNames);
} else {
consumer.unsubscribe();
}
} }
@Override @Override

2
common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java

@ -200,7 +200,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi
consumerBuilder.settings(kafkaSettings); consumerBuilder.settings(kafkaSettings);
consumerBuilder.topic(transportApiSettings.getRequestsTopic()); consumerBuilder.topic(transportApiSettings.getRequestsTopic());
consumerBuilder.clientId("monolith-transport-api-consumer-" + serviceInfoProvider.getServiceId()); consumerBuilder.clientId("monolith-transport-api-consumer-" + serviceInfoProvider.getServiceId());
consumerBuilder.groupId("monolith-transport-api-consumer-" + serviceInfoProvider.getServiceId()); consumerBuilder.groupId("monolith-transport-api-consumer");
consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders()));
consumerBuilder.admin(transportApiAdmin); consumerBuilder.admin(transportApiAdmin);
return consumerBuilder.build(); return consumerBuilder.build();

2
common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java

@ -170,7 +170,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory {
consumerBuilder.settings(kafkaSettings); consumerBuilder.settings(kafkaSettings);
consumerBuilder.topic(transportApiSettings.getRequestsTopic()); consumerBuilder.topic(transportApiSettings.getRequestsTopic());
consumerBuilder.clientId("tb-core-transport-api-consumer-" + serviceInfoProvider.getServiceId()); consumerBuilder.clientId("tb-core-transport-api-consumer-" + serviceInfoProvider.getServiceId());
consumerBuilder.groupId("tb-core-transport-api-consumer-" + serviceInfoProvider.getServiceId()); consumerBuilder.groupId("tb-core-transport-api-consumer");
consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders())); consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders()));
consumerBuilder.admin(transportApiAdmin); consumerBuilder.admin(transportApiAdmin);
return consumerBuilder.build(); return consumerBuilder.build();

2
common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubConsumerTemplate.java

@ -106,7 +106,7 @@ public class TbPubSubConsumerTemplate<T extends TbQueueMsg> extends AbstractPara
subscriptionNames = new LinkedHashSet<>(topicNames); subscriptionNames = new LinkedHashSet<>(topicNames);
subscriptionNames.forEach(admin::createTopicIfNotExists); subscriptionNames.forEach(admin::createTopicIfNotExists);
initNewExecutor(subscriptionNames.size() + 1); initNewExecutor(subscriptionNames.size() + 1);
messagesPerTopic = pubSubSettings.getMaxMessages() / subscriptionNames.size(); messagesPerTopic = pubSubSettings.getMaxMessages() / Math.max(subscriptionNames.size(), 1);
} }
@Override @Override

2
common/transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

2
common/transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>

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

@ -294,7 +294,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
break; break;
} }
} catch (Exception e) { } catch (Exception e) {
log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS); log.warn("[{}] Failed to subscribe to [{}][{}]", sessionId, topic, reqQoS, e);
grantedQoSList.add(FAILURE.value()); grantedQoSList.add(FAILURE.value());
} }
} }

2
common/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

6
common/transport/transport-api/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>transport</artifactId> <artifactId>transport</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common.transport</groupId> <groupId>org.thingsboard.common.transport</groupId>
@ -52,10 +52,6 @@
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>
<artifactId>util</artifactId> <artifactId>util</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.thingsboard.common</groupId>
<artifactId>queue</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>

2
common/util/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>common</artifactId> <artifactId>common</artifactId>
</parent> </parent>
<groupId>org.thingsboard.common</groupId> <groupId>org.thingsboard.common</groupId>

2
dao/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.thingsboard</groupId> <groupId>org.thingsboard</groupId>
<version>2.5.0-SNAPSHOT</version> <version>2.5.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId> <artifactId>thingsboard</artifactId>
</parent> </parent>
<artifactId>dao</artifactId> <artifactId>dao</artifactId>

2
dao/src/main/java/org/thingsboard/server/dao/model/sql/EventEntity.java

@ -112,7 +112,7 @@ public class EventEntity extends BaseSqlEntity<Event> implements BaseEntity<Eve
return event; return event;
} }
private long getTs(UUID uuid) { private static long getTs(UUID uuid) {
return (uuid.timestamp() - EPOCH_DIFF) / 10000; return (uuid.timestamp() - EPOCH_DIFF) / 10000;
} }
} }

2
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java

@ -34,6 +34,8 @@ public class OAuth2ClientMapperConfig {
private String tenantNameStrategy; private String tenantNameStrategy;
private String tenantNamePattern; private String tenantNamePattern;
private String customerNamePattern; private String customerNamePattern;
private boolean alwaysFullScreen;
private String defaultDashboardName;
} }
@Data @Data

2
dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java

@ -90,7 +90,7 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa
} }
private void savePartitionIfNotExist(long ts) { private void savePartitionIfNotExist(long ts) {
if (!tsFormat.equals(SqlTsPartitionDate.INDEFINITE)) { if (!tsFormat.equals(SqlTsPartitionDate.INDEFINITE) && ts >= 0) {
LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC);
LocalDateTime localDateTimeStart = tsFormat.trancateTo(time); LocalDateTime localDateTimeStart = tsFormat.trancateTo(time);
long partitionStartTs = toMills(localDateTimeStart); long partitionStartTs = toMills(localDateTimeStart);

2
dao/src/main/resources/sql/schema-timescale.sql

@ -52,7 +52,7 @@ CREATE TABLE IF NOT EXISTS tb_schema_settings
CONSTRAINT tb_schema_settings_pkey PRIMARY KEY (schema_version) CONSTRAINT tb_schema_settings_pkey PRIMARY KEY (schema_version)
); );
INSERT INTO tb_schema_settings (schema_version) VALUES (2005000) ON CONFLICT (schema_version) DO UPDATE SET schema_version = 2005000; INSERT INTO tb_schema_settings (schema_version) VALUES (2005001) ON CONFLICT (schema_version) DO UPDATE SET schema_version = 2005001;
CREATE OR REPLACE FUNCTION to_uuid(IN entity_id varchar, OUT uuid_id uuid) AS CREATE OR REPLACE FUNCTION to_uuid(IN entity_id varchar, OUT uuid_id uuid) AS
$$ $$

2
dao/src/main/resources/sql/schema-ts-psql.sql

@ -53,7 +53,7 @@ CREATE TABLE IF NOT EXISTS tb_schema_settings
CONSTRAINT tb_schema_settings_pkey PRIMARY KEY (schema_version) CONSTRAINT tb_schema_settings_pkey PRIMARY KEY (schema_version)
); );
INSERT INTO tb_schema_settings (schema_version) VALUES (2005000) ON CONFLICT (schema_version) DO UPDATE SET schema_version = 2005000; INSERT INTO tb_schema_settings (schema_version) VALUES (2005001) ON CONFLICT (schema_version) DO UPDATE SET schema_version = 2005001;
CREATE OR REPLACE PROCEDURE drop_partitions_by_max_ttl(IN partition_type varchar, IN system_ttl bigint, INOUT deleted bigint) CREATE OR REPLACE PROCEDURE drop_partitions_by_max_ttl(IN partition_type varchar, IN system_ttl bigint, INOUT deleted bigint)
LANGUAGE plpgsql AS LANGUAGE plpgsql AS

1
docker/.env

@ -1,3 +1,4 @@
TB_QUEUE_TYPE=kafka
DOCKER_REPO=thingsboard DOCKER_REPO=thingsboard

26
docker/compose-utils.sh

@ -32,6 +32,32 @@ function additionalComposeArgs() {
echo $ADDITIONAL_COMPOSE_ARGS echo $ADDITIONAL_COMPOSE_ARGS
} }
function additionalComposeQueueArgs() {
source .env
ADDITIONAL_COMPOSE_QUEUE_ARGS=""
case $TB_QUEUE_TYPE in
kafka)
ADDITIONAL_COMPOSE_QUEUE_ARGS="-f docker-compose.kafka.yml"
;;
aws-sqs)
ADDITIONAL_COMPOSE_QUEUE_ARGS="-f docker-compose.aws-sqs.yml"
;;
pubsub)
ADDITIONAL_COMPOSE_QUEUE_ARGS="-f docker-compose.pubsub.yml"
;;
rabbitmq)
ADDITIONAL_COMPOSE_QUEUE_ARGS="-f docker-compose.rabbitmq.yml"
;;
service-bus)
ADDITIONAL_COMPOSE_QUEUE_ARGS="-f docker-compose.service-bus.yml"
;;
*)
echo "Unknown Queue service value specified: '${TB_QUEUE_TYPE}'. Should be either kafka or aws-sqs or pubsub or rabbitmq or service-bus." >&2
exit 1
esac
echo $ADDITIONAL_COMPOSE_QUEUE_ARGS
}
function additionalStartupServices() { function additionalStartupServices() {
source .env source .env
ADDITIONAL_STARTUP_SERVICES="" ADDITIONAL_STARTUP_SERVICES=""

71
docker/docker-compose.aws-sqs.yml

@ -0,0 +1,71 @@
#
# Copyright © 2016-2020 The Thingsboard Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
version: '2.2'
services:
tb-js-executor:
env_file:
- queue-aws-sqs.env
tb-core1:
env_file:
- queue-aws-sqs.env
depends_on:
- zookeeper
- redis
tb-core2:
env_file:
- queue-aws-sqs.env
depends_on:
- zookeeper
- redis
tb-rule-engine1:
env_file:
- queue-aws-sqs.env
depends_on:
- zookeeper
- redis
tb-rule-engine2:
env_file:
- queue-aws-sqs.env
depends_on:
- zookeeper
- redis
tb-mqtt-transport1:
env_file:
- queue-aws-sqs.env
depends_on:
- zookeeper
tb-mqtt-transport2:
env_file:
- queue-aws-sqs.env
depends_on:
- zookeeper
tb-http-transport1:
env_file:
- queue-aws-sqs.env
depends_on:
- zookeeper
tb-http-transport2:
env_file:
- queue-aws-sqs.env
depends_on:
- zookeeper
tb-coap-transport:
env_file:
- queue-aws-sqs.env
depends_on:
- zookeeper

8
docker/docker-compose.cassandra.yml

@ -28,27 +28,27 @@ services:
env_file: env_file:
- tb-node.cassandra.env - tb-node.cassandra.env
depends_on: depends_on:
- kafka - zookeeper
- redis - redis
- cassandra - cassandra
tb-core2: tb-core2:
env_file: env_file:
- tb-node.cassandra.env - tb-node.cassandra.env
depends_on: depends_on:
- kafka - zookeeper
- redis - redis
- cassandra - cassandra
tb-rule-engine1: tb-rule-engine1:
env_file: env_file:
- tb-node.cassandra.env - tb-node.cassandra.env
depends_on: depends_on:
- kafka - zookeeper
- redis - redis
- cassandra - cassandra
tb-rule-engine2: tb-rule-engine2:
env_file: env_file:
- tb-node.cassandra.env - tb-node.cassandra.env
depends_on: depends_on:
- kafka - zookeeper
- redis - redis
- cassandra - cassandra

82
docker/docker-compose.kafka.yml

@ -0,0 +1,82 @@
#
# Copyright © 2016-2020 The Thingsboard Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
version: '2.2'
services:
kafka:
restart: always
image: "wurstmeister/kafka:2.12-2.3.0"
ports:
- "9092:9092"
env_file:
- kafka.env
depends_on:
- zookeeper
tb-js-executor:
env_file:
- queue-kafka.env
depends_on:
- kafka
tb-core1:
env_file:
- queue-kafka.env
depends_on:
- kafka
- redis
tb-core2:
env_file:
- queue-kafka.env
depends_on:
- kafka
- redis
tb-rule-engine1:
env_file:
- queue-kafka.env
depends_on:
- kafka
- redis
tb-rule-engine2:
env_file:
- queue-kafka.env
depends_on:
- kafka
- redis
tb-mqtt-transport1:
env_file:
- queue-kafka.env
depends_on:
- kafka
tb-mqtt-transport2:
env_file:
- queue-kafka.env
depends_on:
- kafka
tb-http-transport1:
env_file:
- queue-kafka.env
depends_on:
- kafka
tb-http-transport2:
env_file:
- queue-kafka.env
depends_on:
- kafka
tb-coap-transport:
env_file:
- queue-kafka.env
depends_on:
- kafka

8
docker/docker-compose.postgres.yml

@ -31,27 +31,27 @@ services:
env_file: env_file:
- tb-node.postgres.env - tb-node.postgres.env
depends_on: depends_on:
- kafka - zookeeper
- redis - redis
- postgres - postgres
tb-core2: tb-core2:
env_file: env_file:
- tb-node.postgres.env - tb-node.postgres.env
depends_on: depends_on:
- kafka - zookeeper
- redis - redis
- postgres - postgres
tb-rule-engine1: tb-rule-engine1:
env_file: env_file:
- tb-node.postgres.env - tb-node.postgres.env
depends_on: depends_on:
- kafka - zookeeper
- redis - redis
- postgres - postgres
tb-rule-engine2: tb-rule-engine2:
env_file: env_file:
- tb-node.postgres.env - tb-node.postgres.env
depends_on: depends_on:
- kafka - zookeeper
- redis - redis
- postgres - postgres

71
docker/docker-compose.pubsub.yml

@ -0,0 +1,71 @@
#
# Copyright © 2016-2020 The Thingsboard Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
version: '2.2'
services:
tb-js-executor:
env_file:
- queue-pubsub.env
tb-core1:
env_file:
- queue-pubsub.env
depends_on:
- zookeeper
- redis
tb-core2:
env_file:
- queue-pubsub.env
depends_on:
- zookeeper
- redis
tb-rule-engine1:
env_file:
- queue-pubsub.env
depends_on:
- zookeeper
- redis
tb-rule-engine2:
env_file:
- queue-pubsub.env
depends_on:
- zookeeper
- redis
tb-mqtt-transport1:
env_file:
- queue-pubsub.env
depends_on:
- zookeeper
tb-mqtt-transport2:
env_file:
- queue-pubsub.env
depends_on:
- zookeeper
tb-http-transport1:
env_file:
- queue-pubsub.env
depends_on:
- zookeeper
tb-http-transport2:
env_file:
- queue-pubsub.env
depends_on:
- zookeeper
tb-coap-transport:
env_file:
- queue-pubsub.env
depends_on:
- zookeeper

71
docker/docker-compose.rabbitmq.yml

@ -0,0 +1,71 @@
#
# Copyright © 2016-2020 The Thingsboard Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
version: '2.2'
services:
tb-js-executor:
env_file:
- queue-rabbitmq.env
tb-core1:
env_file:
- queue-rabbitmq.env
depends_on:
- zookeeper
- redis
tb-core2:
env_file:
- queue-rabbitmq.env
depends_on:
- zookeeper
- redis
tb-rule-engine1:
env_file:
- queue-rabbitmq.env
depends_on:
- zookeeper
- redis
tb-rule-engine2:
env_file:
- queue-rabbitmq.env
depends_on:
- zookeeper
- redis
tb-mqtt-transport1:
env_file:
- queue-rabbitmq.env
depends_on:
- zookeeper
tb-mqtt-transport2:
env_file:
- queue-rabbitmq.env
depends_on:
- zookeeper
tb-http-transport1:
env_file:
- queue-rabbitmq.env
depends_on:
- zookeeper
tb-http-transport2:
env_file:
- queue-rabbitmq.env
depends_on:
- zookeeper
tb-coap-transport:
env_file:
- queue-rabbitmq.env
depends_on:
- zookeeper

71
docker/docker-compose.service-bus.yml

@ -0,0 +1,71 @@
#
# Copyright © 2016-2020 The Thingsboard Authors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
version: '2.2'
services:
tb-js-executor:
env_file:
- queue-service-bus.env
tb-core1:
env_file:
- queue-service-bus.env
depends_on:
- zookeeper
- redis
tb-core2:
env_file:
- queue-service-bus.env
depends_on:
- zookeeper
- redis
tb-rule-engine1:
env_file:
- queue-service-bus.env
depends_on:
- zookeeper
- redis
tb-rule-engine2:
env_file:
- queue-service-bus.env
depends_on:
- zookeeper
- redis
tb-mqtt-transport1:
env_file:
- queue-service-bus.env
depends_on:
- zookeeper
tb-mqtt-transport2:
env_file:
- queue-service-bus.env
depends_on:
- zookeeper
tb-http-transport1:
env_file:
- queue-service-bus.env
depends_on:
- zookeeper
tb-http-transport2:
env_file:
- queue-service-bus.env
depends_on:
- zookeeper
tb-coap-transport:
env_file:
- queue-service-bus.env
depends_on:
- zookeeper

29
docker/docker-compose.yml

@ -26,15 +26,6 @@ services:
environment: environment:
ZOO_MY_ID: 1 ZOO_MY_ID: 1
ZOO_SERVERS: server.1=zookeeper:2888:3888;zookeeper:2181 ZOO_SERVERS: server.1=zookeeper:2888:3888;zookeeper:2181
kafka:
restart: always
image: "wurstmeister/kafka:2.12-2.3.0"
ports:
- "9092:9092"
env_file:
- kafka.env
depends_on:
- zookeeper
redis: redis:
restart: always restart: always
image: redis:4.0 image: redis:4.0
@ -46,8 +37,6 @@ services:
scale: 20 scale: 20
env_file: env_file:
- tb-js-executor.env - tb-js-executor.env
depends_on:
- kafka
tb-core1: tb-core1:
restart: always restart: always
image: "${DOCKER_REPO}/${TB_NODE_DOCKER_NAME}:${TB_VERSION}" image: "${DOCKER_REPO}/${TB_NODE_DOCKER_NAME}:${TB_VERSION}"
@ -67,7 +56,7 @@ services:
- ./tb-node/conf:/config - ./tb-node/conf:/config
- ./tb-node/log:/var/log/thingsboard - ./tb-node/log:/var/log/thingsboard
depends_on: depends_on:
- kafka - zookeeper
- redis - redis
- tb-js-executor - tb-js-executor
- tb-rule-engine1 - tb-rule-engine1
@ -91,7 +80,7 @@ services:
- ./tb-node/conf:/config - ./tb-node/conf:/config
- ./tb-node/log:/var/log/thingsboard - ./tb-node/log:/var/log/thingsboard
depends_on: depends_on:
- kafka - zookeeper
- redis - redis
- tb-js-executor - tb-js-executor
- tb-rule-engine1 - tb-rule-engine1
@ -115,7 +104,7 @@ services:
- ./tb-node/conf:/config - ./tb-node/conf:/config
- ./tb-node/log:/var/log/thingsboard - ./tb-node/log:/var/log/thingsboard
depends_on: depends_on:
- kafka - zookeeper
- redis - redis
- tb-js-executor - tb-js-executor
tb-rule-engine2: tb-rule-engine2:
@ -137,7 +126,7 @@ services:
- ./tb-node/conf:/config - ./tb-node/conf:/config
- ./tb-node/log:/var/log/thingsboard - ./tb-node/log:/var/log/thingsboard
depends_on: depends_on:
- kafka - zookeeper
- redis - redis
- tb-js-executor - tb-js-executor
tb-mqtt-transport1: tb-mqtt-transport1:
@ -153,7 +142,7 @@ services:
- ./tb-transports/mqtt/conf:/config - ./tb-transports/mqtt/conf:/config
- ./tb-transports/mqtt/log:/var/log/tb-mqtt-transport - ./tb-transports/mqtt/log:/var/log/tb-mqtt-transport
depends_on: depends_on:
- kafka - zookeeper
tb-mqtt-transport2: tb-mqtt-transport2:
restart: always restart: always
image: "${DOCKER_REPO}/${MQTT_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" image: "${DOCKER_REPO}/${MQTT_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
@ -167,7 +156,7 @@ services:
- ./tb-transports/mqtt/conf:/config - ./tb-transports/mqtt/conf:/config
- ./tb-transports/mqtt/log:/var/log/tb-mqtt-transport - ./tb-transports/mqtt/log:/var/log/tb-mqtt-transport
depends_on: depends_on:
- kafka - zookeeper
tb-http-transport1: tb-http-transport1:
restart: always restart: always
image: "${DOCKER_REPO}/${HTTP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" image: "${DOCKER_REPO}/${HTTP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
@ -181,7 +170,7 @@ services:
- ./tb-transports/http/conf:/config - ./tb-transports/http/conf:/config
- ./tb-transports/http/log:/var/log/tb-http-transport - ./tb-transports/http/log:/var/log/tb-http-transport
depends_on: depends_on:
- kafka - zookeeper
tb-http-transport2: tb-http-transport2:
restart: always restart: always
image: "${DOCKER_REPO}/${HTTP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" image: "${DOCKER_REPO}/${HTTP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
@ -195,7 +184,7 @@ services:
- ./tb-transports/http/conf:/config - ./tb-transports/http/conf:/config
- ./tb-transports/http/log:/var/log/tb-http-transport - ./tb-transports/http/log:/var/log/tb-http-transport
depends_on: depends_on:
- kafka - zookeeper
tb-coap-transport: tb-coap-transport:
restart: always restart: always
image: "${DOCKER_REPO}/${COAP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" image: "${DOCKER_REPO}/${COAP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}"
@ -209,7 +198,7 @@ services:
- ./tb-transports/coap/conf:/config - ./tb-transports/coap/conf:/config
- ./tb-transports/coap/log:/var/log/tb-coap-transport - ./tb-transports/coap/log:/var/log/tb-coap-transport
depends_on: depends_on:
- kafka - zookeeper
tb-web-ui1: tb-web-ui1:
restart: always restart: always
image: "${DOCKER_REPO}/${WEB_UI_DOCKER_NAME}:${TB_VERSION}" image: "${DOCKER_REPO}/${WEB_UI_DOCKER_NAME}:${TB_VERSION}"

6
docker/docker-install-tb.sh

@ -41,14 +41,16 @@ set -e
source compose-utils.sh source compose-utils.sh
ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $?
ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $?
ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $? ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $?
if [ ! -z "${ADDITIONAL_STARTUP_SERVICES// }" ]; then if [ ! -z "${ADDITIONAL_STARTUP_SERVICES// }" ]; then
docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS up -d redis $ADDITIONAL_STARTUP_SERVICES docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS up -d redis $ADDITIONAL_STARTUP_SERVICES
fi fi
docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS run --no-deps --rm -e INSTALL_TB=true -e LOAD_DEMO=${loadDemo} tb-core1 docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS run --no-deps --rm -e INSTALL_TB=true -e LOAD_DEMO=${loadDemo} tb-core1

4
docker/docker-remove-services.sh

@ -19,6 +19,8 @@ set -e
source compose-utils.sh source compose-utils.sh
ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $?
ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $?
docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS down -v docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS down -v

4
docker/docker-start-services.sh

@ -19,6 +19,8 @@ set -e
source compose-utils.sh source compose-utils.sh
ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $?
ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $?
docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS up -d docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS up -d

4
docker/docker-stop-services.sh

@ -19,6 +19,8 @@ set -e
source compose-utils.sh source compose-utils.sh
ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $?
ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $?
docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS stop docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS stop

6
docker/docker-update-service.sh

@ -19,7 +19,9 @@ set -e
source compose-utils.sh source compose-utils.sh
ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $?
ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $?
docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS pull $@ docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS pull $@
docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS up -d --no-deps --build $@ docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS up -d --no-deps --build $@

8
docker/docker-upgrade-tb.sh

@ -40,12 +40,14 @@ set -e
source compose-utils.sh source compose-utils.sh
ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $?
ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $?
ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $? ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $?
docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS pull tb-core1 docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS pull tb-core1
docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS up -d redis $ADDITIONAL_STARTUP_SERVICES docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS up -d redis $ADDITIONAL_STARTUP_SERVICES
docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS run --no-deps --rm -e UPGRADE_TB=true -e FROM_VERSION=${fromVersion} tb-core1 docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS run --no-deps --rm -e UPGRADE_TB=true -e FROM_VERSION=${fromVersion} tb-core1

4
docker/queue-aws-sqs.env

@ -0,0 +1,4 @@
TB_QUEUE_TYPE=aws-sqs
TB_QUEUE_AWS_SQS_ACCESS_KEY_ID=YOUR_KEY
TB_QUEUE_AWS_SQS_SECRET_ACCESS_KEY=YOUR_SECRET
TB_QUEUE_AWS_SQS_REGION=YOUR_REGION

2
docker/queue-kafka.env

@ -0,0 +1,2 @@
TB_QUEUE_TYPE=kafka
TB_KAFKA_SERVERS=kafka:9092

3
docker/queue-pubsub.env

@ -0,0 +1,3 @@
TB_QUEUE_TYPE=pubsub
TB_QUEUE_PUBSUB_PROJECT_ID=YOUR_PROJECT_ID
TB_QUEUE_PUBSUB_SERVICE_ACCOUNT=YOUR_SERVICE_ACCOUNT

5
docker/queue-rabbitmq.env

@ -0,0 +1,5 @@
TB_QUEUE_TYPE=rabbitmq
TB_QUEUE_RABBIT_MQ_HOST=localhost
TB_QUEUE_RABBIT_MQ_PORT=5672
TB_QUEUE_RABBIT_MQ_USERNAME=YOUR_USERNAME
TB_QUEUE_RABBIT_MQ_PASSWORD=YOUR_PASSWORD

4
docker/queue-service-bus.env

@ -0,0 +1,4 @@
TB_QUEUE_TYPE=service-bus
TB_QUEUE_SERVICE_BUS_NAMESPACE_NAME=YOUR_NAMESPACE_NAME
TB_QUEUE_SERVICE_BUS_SAS_KEY_NAME=YOUR_SAS_KEY_NAME
TB_QUEUE_SERVICE_BUS_SAS_KEY=YOUR_SAS_KEY

3
docker/tb-coap-transport.env

@ -4,6 +4,3 @@ ZOOKEEPER_URL=zookeeper:2181
COAP_BIND_ADDRESS=0.0.0.0 COAP_BIND_ADDRESS=0.0.0.0
COAP_BIND_PORT=5683 COAP_BIND_PORT=5683
COAP_TIMEOUT=10000 COAP_TIMEOUT=10000
TB_QUEUE_TYPE=kafka
TB_KAFKA_SERVERS=kafka:9092

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

Loading…
Cancel
Save