Browse Source

Merge branch 'lts-4.2' into feature/iot-hub

pull/15539/head
Igor Kulikov 3 months ago
parent
commit
e211e15fbb
  1. 2
      application/src/main/resources/thingsboard.yml
  2. 9
      application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java
  3. 18
      application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java
  4. 7
      application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java
  5. 9
      application/src/test/java/org/thingsboard/server/service/resource/DefaultResourceDataCacheTest.java
  6. 5
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/StringDataPoint.java
  7. 2
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java
  8. 14
      dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java
  9. 47
      dao/src/test/java/org/thingsboard/server/dao/TbTimescaleDBContainerProvider.java
  10. 1
      dao/src/test/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider
  11. 2
      dao/src/test/resources/nosql-test.properties
  12. 2
      dao/src/test/resources/sql-test.properties
  13. 2
      dao/src/test/resources/timescale-test.properties
  14. 57
      edqs/src/test/java/org/thingsboard/server/edqs/repo/DeviceTypeFilterTest.java
  15. 4
      pom.xml

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

@ -1542,7 +1542,7 @@ device:
pem_cert_file: "${DEVICE_CONNECTIVITY_COAPS_CA_ROOT_CERT:cafile.pem}"
gateway:
# The docker tag for thingsboard/tb-gateway image used in docker-compose file for gateway launch
image_version: "${DEVICE_CONNECTIVITY_GATEWAY_IMAGE_VERSION:3.7-stable}"
image_version: "${DEVICE_CONNECTIVITY_GATEWAY_IMAGE_VERSION:3.8-stable}"
# Edges parameters
edges:

9
application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java

@ -95,7 +95,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
private DeviceProfileId mqttDeviceProfileId;
private DeviceProfileId coapDeviceProfileId;
@Value("${device.connectivity.gateway.image_version:3.7-stable}")
@Value("${device.connectivity.gateway.image_version:3.8-stable}")
private String gatewayImageVersion;
@Before
@ -321,9 +321,10 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
"\n" +
" # Environment variables\n" +
" environment:\n" +
" - host=host.docker.internal\n" +
" - port=1883\n" +
" - accessToken=" + credentials.getCredentialsId() + "\n" +
" - TB_GW_HOST=host.docker.internal\n" +
" - TB_GW_PORT=1883\n" +
" - TB_GW_SECURITY_TYPE=accessToken\n" +
" - TB_GW_ACCESS_TOKEN=" + credentials.getCredentialsId() + "\n" +
"\n" +
" # Volumes bind\n" +
" volumes:\n" +

18
application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java

@ -54,11 +54,13 @@ import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.device.DeviceProfileDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DaoSqlTest;
import org.awaitility.Awaitility;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
@ -1028,11 +1030,17 @@ public class DeviceProfileControllerTest extends AbstractControllerTest {
MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration, false);
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration);
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/deviceProfile", deviceProfile)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(errorMsg)));
// The request may hit a transient TenantNotFoundException right after the @Before tenant creation
// if the tenant profile cache is not yet warmed up for the newly created tenant. Retry until the
// request returns the expected 400 Bad Request for the invalid schema. Mockito.reset is inside the
// retry loop so the subsequent verify* assertions see only the invocations from the last attempt.
Awaitility.await().atMost(10, TimeUnit.SECONDS).pollInterval(500, TimeUnit.MILLISECONDS)
.ignoreExceptions().untilAsserted(() -> {
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/deviceProfile", deviceProfile)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(errorMsg)));
});
testNotifyEntityEqualsOneTimeServiceNeverError(deviceProfile, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(errorMsg));

7
application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java

@ -57,7 +57,12 @@ public class UserEdgeTest extends AbstractEdgeTest {
User savedTenantAdmin = createUser(newTenantAdmin, "tenant");
Assert.assertTrue(edgeImitator.waitForMessages()); // wait 3 messages - x1 user update msg and x2 user credentials update msgs (create + authenticate user)
Assert.assertEquals(1, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size());
Assert.assertEquals(2, edgeImitator.findAllMessagesByType(UserCredentialsUpdateMsg.class).size());
// The initial USER ADDED edge event may bundle a UserCredentialsUpdateMsg when
// user activation completes before the event is processed, in addition to the 2
// messages from the CREDENTIALS_UPDATED events fired during activation. Accept 2 or 3.
int credMsgCount = edgeImitator.findAllMessagesByType(UserCredentialsUpdateMsg.class).size();
Assert.assertTrue("Expected 2 or 3 UserCredentialsUpdateMsg (ADDED/activation race), got " + credMsgCount,
credMsgCount == 2 || credMsgCount == 3);
Optional<UserUpdateMsg> userUpdateMsgOpt = edgeImitator.findMessageByType(UserUpdateMsg.class);
Assert.assertTrue(userUpdateMsgOpt.isPresent());
UserUpdateMsg userUpdateMsg = userUpdateMsgOpt.get();

9
application/src/test/java/org/thingsboard/server/service/resource/DefaultResourceDataCacheTest.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.service.resource;
import org.awaitility.Awaitility;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
@ -29,6 +30,8 @@ import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.resource.TbResourceDataCache;
import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.timeout;
@ -61,6 +64,8 @@ public class DefaultResourceDataCacheTest extends AbstractControllerTest {
TbResourceInfo savedResource = tbResourceService.save(resource);
verify(resourceDataCache, timeout(2000).times(1)).evictResourceData(tenantId, savedResource.getId());
Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAsserted(() ->
assertThat(resourceDataCache.getResourceDataInfoAsync(tenantId, savedResource.getId()).get()).isNotNull());
TbResourceDataInfo cachedData = resourceDataCache.getResourceDataInfoAsync(tenantId, savedResource.getId()).get();
assertThat(cachedData.getData()).isEqualTo(data);
assertThat(JacksonUtil.treeToValue(cachedData.getDescriptor(), GeneralFileDescriptor.class)).isEqualTo(descriptor);
@ -76,8 +81,8 @@ public class DefaultResourceDataCacheTest extends AbstractControllerTest {
TbResource resourceById = resourceService.findResourceById(tenantId, savedResource.getId());
tbResourceService.delete(resourceById, true, null);
verify(resourceDataCache, timeout(2000).times(2)).evictResourceData(tenantId, savedResource.getId());
TbResourceDataInfo cachedDataAfterDeletion = resourceDataCache.getResourceDataInfoAsync(tenantId, savedResource.getId()).get();
assertThat(cachedDataAfterDeletion).isEqualTo(null);
Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAsserted(() ->
assertThat(resourceDataCache.getResourceDataInfoAsync(tenantId, savedResource.getId()).get()).isNull());
}
}

5
common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/StringDataPoint.java

@ -33,6 +33,11 @@ public class StringDataPoint extends AbstractDataPoint {
this.value = deduplicate ? TbStringPool.intern(value) : value;
}
@Override
public boolean getBool() {
return Boolean.parseBoolean(value);
}
@Override
public double getDouble() {
return Double.parseDouble(value);

2
dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java

@ -86,7 +86,7 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService
private String mqttsPemCertFile;
@Value("${device.connectivity.coaps.pem_cert_file:}")
private String coapsPemCertFile;
@Value("${device.connectivity.gateway.image_version:3.7-stable}")
@Value("${device.connectivity.gateway.image_version:3.8-stable}")
private String gatewayImageVersion;
@Override

14
dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java

@ -124,24 +124,26 @@ public class DeviceConnectivityUtil {
dockerComposeBuilder.append("\n");
dockerComposeBuilder.append(" # Environment variables\n");
dockerComposeBuilder.append(" environment:\n");
dockerComposeBuilder.append(" - host=").append(isLocalhost(host) ? HOST_DOCKER_INTERNAL : host).append("\n");
dockerComposeBuilder.append(" - port=1883\n");
dockerComposeBuilder.append(" - TB_GW_HOST=").append(isLocalhost(host) ? HOST_DOCKER_INTERNAL : host).append("\n");
dockerComposeBuilder.append(" - TB_GW_PORT=1883\n");
switch (deviceCredentials.getCredentialsType()) {
case ACCESS_TOKEN:
dockerComposeBuilder.append(" - accessToken=").append(deviceCredentials.getCredentialsId()).append("\n");
dockerComposeBuilder.append(" - TB_GW_SECURITY_TYPE=accessToken\n");
dockerComposeBuilder.append(" - TB_GW_ACCESS_TOKEN=").append(deviceCredentials.getCredentialsId()).append("\n");
break;
case MQTT_BASIC:
dockerComposeBuilder.append(" - TB_GW_SECURITY_TYPE=usernamePassword\n");
BasicMqttCredentials credentials = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(),
BasicMqttCredentials.class);
if (credentials != null) {
if (StringUtils.isNotEmpty(credentials.getClientId())) {
dockerComposeBuilder.append(" - clientId=").append(credentials.getClientId()).append("\n");
dockerComposeBuilder.append(" - TB_GW_CLIENT_ID=").append(credentials.getClientId()).append("\n");
}
if (StringUtils.isNotEmpty(credentials.getUserName())) {
dockerComposeBuilder.append(" - username=").append(credentials.getUserName()).append("\n");
dockerComposeBuilder.append(" - TB_GW_USERNAME=").append(credentials.getUserName()).append("\n");
}
if (StringUtils.isNotEmpty(credentials.getPassword())) {
dockerComposeBuilder.append(" - password=").append(credentials.getPassword()).append("\n");
dockerComposeBuilder.append(" - TB_GW_PASSWORD=").append(credentials.getPassword()).append("\n");
}
}
break;

47
dao/src/test/java/org/thingsboard/server/dao/TbTimescaleDBContainerProvider.java

@ -0,0 +1,47 @@
/**
* Copyright © 2016-2026 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.dao;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.containers.TimescaleDBContainerProvider;
/**
* Extends the upstream {@link TimescaleDBContainerProvider} to disable the
* timescaledb-tune entrypoint script via NO_TS_TUNE=true.
*
* Works around a shell bug in /docker-entrypoint-initdb.d/001_timescaledb_tune.sh
* that crashes the container entrypoint on cgroup v2 hosts (including CI agents)
* when the kernel reports the 64-bit max for memory.max.
*
* Activated by the jdbc:tc:tbtimescaledb:&lt;tag&gt;:///... URL prefix
* registered via META-INF/services.
*/
public class TbTimescaleDBContainerProvider extends TimescaleDBContainerProvider {
private static final String NAME = "tbtimescaledb";
@Override
public boolean supports(String databaseType) {
return NAME.equals(databaseType);
}
@Override
public JdbcDatabaseContainer newInstance(String tag) {
JdbcDatabaseContainer container = super.newInstance(tag);
container.withEnv("NO_TS_TUNE", "true");
return container;
}
}

1
dao/src/test/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider

@ -0,0 +1 @@
org.thingsboard.server.dao.TbTimescaleDBContainerProvider

2
dao/src/test/resources/nosql-test.properties

@ -13,6 +13,6 @@ spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=none
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.url=jdbc:tc:postgresql:16.6:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.PostgreSqlInitializer::initDb
spring.datasource.url=jdbc:tc:postgresql:18:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.PostgreSqlInitializer::initDb
spring.datasource.driverClassName=org.testcontainers.jdbc.ContainerDatabaseDriver
spring.datasource.hikari.maximumPoolSize=16

2
dao/src/test/resources/sql-test.properties

@ -14,7 +14,7 @@ spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=none
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.url=jdbc:tc:postgresql:16.6:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.PostgreSqlInitializer::initDb
spring.datasource.url=jdbc:tc:postgresql:18:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.PostgreSqlInitializer::initDb
spring.datasource.driverClassName=org.testcontainers.jdbc.ContainerDatabaseDriver
spring.datasource.hikari.maximumPoolSize=16

2
dao/src/test/resources/timescale-test.properties

@ -13,6 +13,6 @@ spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=none
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.url=jdbc:tc:timescaledb:latest-pg12:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.TimescaleSqlInitializer::initDb
spring.datasource.url=jdbc:tc:tbtimescaledb:latest-pg18:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.TimescaleSqlInitializer::initDb
spring.datasource.driverClassName=org.testcontainers.jdbc.ContainerDatabaseDriver
spring.datasource.hikari.maximumPoolSize = 50

57
edqs/src/test/java/org/thingsboard/server/edqs/repo/DeviceTypeFilterTest.java

@ -19,15 +19,20 @@ import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.edqs.AttributeKv;
import org.thingsboard.server.common.data.edqs.LatestTsKv;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.DeviceTypeFilter;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
@ -39,8 +44,10 @@ import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.StringFilterPredicate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
public class DeviceTypeFilterTest extends AbstractEDQTest {
@ -119,7 +126,50 @@ public class DeviceTypeFilterTest extends AbstractEDQTest {
Assert.assertEquals("42", first.getLatest().get(EntityKeyType.ENTITY_FIELD).get("createdTime").getValue());
}
@Test
public void testFindDeviceByBooleanAttributeWithMixedTypes() {
DeviceId device1Id = createLoraDevice("LoRa-1");
DeviceId device2Id = createLoraDevice("LoRa-2");
DeviceId device3Id = createLoraDevice("LoRa-3");
long ts = System.currentTimeMillis();
addOrUpdate(new AttributeKv(device1Id, AttributeScope.SERVER_SCOPE,
new BaseAttributeKvEntry(new BooleanDataEntry("active", true), ts), 1L));
addOrUpdate(new AttributeKv(device2Id, AttributeScope.SERVER_SCOPE,
new BaseAttributeKvEntry(new BooleanDataEntry("active", false), ts), 1L));
addOrUpdate(new AttributeKv(device3Id, AttributeScope.SERVER_SCOPE,
new BaseAttributeKvEntry(new StringDataEntry("active", "true"), ts), 1L));
KeyFilter activeFilter = new KeyFilter();
activeFilter.setKey(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, "active"));
activeFilter.setValueType(EntityKeyValueType.BOOLEAN);
BooleanFilterPredicate predicate = new BooleanFilterPredicate();
predicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
predicate.setValue(FilterPredicateValue.fromBoolean(true));
activeFilter.setPredicate(predicate);
var result = repository.countEntitiesByQuery(tenantId, null,
getDeviceTypeQuery("LoRa", List.of(activeFilter)), false);
Assert.assertEquals(2, result);
}
private DeviceId createLoraDevice(String name) {
DeviceId deviceId = new DeviceId(UUID.randomUUID());
Device device = new Device();
device.setId(deviceId);
device.setTenantId(tenantId);
device.setDeviceProfileId(loraProfileId);
device.setName(name);
device.setCreatedTime(42L);
addOrUpdate(EntityType.DEVICE, device);
return deviceId;
}
private static EntityDataQuery getDeviceTypeQuery(String deviceType) {
return getDeviceTypeQuery(deviceType, null);
}
private static EntityDataQuery getDeviceTypeQuery(String deviceType, List<KeyFilter> extraFilters) {
DeviceTypeFilter filter = new DeviceTypeFilter();
filter.setDeviceTypes(Collections.singletonList(deviceType));
var pageLink = new EntityDataPageLink(20, 0, null, new EntityDataSortOrder(new EntityKey(EntityKeyType.TIME_SERIES, "state"), EntityDataSortOrder.Direction.DESC), false);
@ -135,7 +185,12 @@ public class DeviceTypeFilterTest extends AbstractEDQTest {
nameFilter.setPredicate(predicate);
nameFilter.setValueType(EntityKeyValueType.STRING);
return new EntityDataQuery(filter, pageLink, entityFields, latestValues, Arrays.asList(nameFilter));
List<KeyFilter> keyFilters = new ArrayList<>();
keyFilters.add(nameFilter);
if (extraFilters != null) {
keyFilters.addAll(extraFilters);
}
return new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
}
}

4
pom.xml

@ -619,7 +619,6 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<skipAssembly>${pkg.skip.zip}</skipAssembly>
<finalName>${pkg.name}</finalName>
<descriptors>
<descriptor>${main.dir}/packaging/${pkg.type}/assembly/windows.xml</descriptor>
@ -632,6 +631,9 @@
<goals>
<goal>single</goal>
</goals>
<configuration>
<skipAssembly>${pkg.skip.zip}</skipAssembly>
</configuration>
</execution>
</executions>
</plugin>

Loading…
Cancel
Save