committed by
GitHub
41 changed files with 1181 additions and 71 deletions
@ -0,0 +1,30 @@ |
|||
# |
|||
# Copyright © 2016-2025 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: '3.0' |
|||
|
|||
services: |
|||
tb-edqs-1: |
|||
volumes: |
|||
- tb-edqs-log-volume:/var/log/edqs |
|||
tb-edqs-2: |
|||
volumes: |
|||
- tb-edqs-log-volume:/var/log/edqs |
|||
|
|||
volumes: |
|||
tb-edqs-log-volume: |
|||
external: |
|||
name: ${TB_EDQS_LOG_VOLUME} |
|||
@ -0,0 +1,57 @@ |
|||
# |
|||
# Copyright © 2016-2025 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: '3.0' |
|||
|
|||
services: |
|||
tb-core1: |
|||
env_file: |
|||
- tb-core-edqs.env |
|||
tb-core2: |
|||
env_file: |
|||
- tb-core-edqs.env |
|||
tb-rule-engine1: |
|||
env_file: |
|||
- tb-rule-engine-edqs.env |
|||
tb-rule-engine2: |
|||
env_file: |
|||
- tb-rule-engine-edqs.env |
|||
tb-edqs-1: |
|||
restart: always |
|||
image: "${DOCKER_REPO}/${EDQS_DOCKER_NAME}:${TB_VERSION}" |
|||
env_file: |
|||
- edqs.env |
|||
volumes: |
|||
- ./edqs/conf:/usr/share/edqs/conf |
|||
- ./edqs/log:/var/log/edqs |
|||
ports: |
|||
- "8080" |
|||
depends_on: |
|||
- zookeeper |
|||
- kafka |
|||
tb-edqs-2: |
|||
restart: always |
|||
image: "${DOCKER_REPO}/${EDQS_DOCKER_NAME}:${TB_VERSION}" |
|||
env_file: |
|||
- edqs.env |
|||
volumes: |
|||
- ./edqs/conf:/usr/share/edqs/conf |
|||
- ./edqs/log:/var/log/edqs |
|||
ports: |
|||
- "8080" |
|||
depends_on: |
|||
- zookeeper |
|||
- kafka |
|||
@ -0,0 +1,7 @@ |
|||
ZOOKEEPER_ENABLED=true |
|||
ZOOKEEPER_URL=zookeeper:2181 |
|||
TB_KAFKA_SERVERS=kafka:9092 |
|||
HTTP_BIND_PORT=8080 |
|||
|
|||
METRICS_ENABLED=true |
|||
METRICS_ENDPOINTS_EXPOSE=prometheus |
|||
@ -0,0 +1,22 @@ |
|||
# |
|||
# Copyright © 2016-2025 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. |
|||
# |
|||
|
|||
export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/edqs/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M" |
|||
export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError" |
|||
export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" |
|||
export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" |
|||
export LOG_FILENAME=tb-edqs.out |
|||
export LOADER_PATH=/usr/share/edqs/conf |
|||
@ -0,0 +1,52 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<!-- |
|||
|
|||
Copyright © 2016-2025 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. |
|||
|
|||
--> |
|||
<!DOCTYPE configuration> |
|||
<configuration scan="true" scanPeriod="10 seconds"> |
|||
|
|||
<appender name="fileLogAppender" |
|||
class="ch.qos.logback.core.rolling.RollingFileAppender"> |
|||
<file>/var/log/edqs/${TB_SERVICE_ID}/tb-edqs.log</file> |
|||
<rollingPolicy |
|||
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> |
|||
<fileNamePattern>/var/log/edqs/tb-edqs.%d{yyyy-MM-dd}.%i.log</fileNamePattern> |
|||
<maxFileSize>100MB</maxFileSize> |
|||
<maxHistory>30</maxHistory> |
|||
<totalSizeCap>3GB</totalSizeCap> |
|||
</rollingPolicy> |
|||
<encoder> |
|||
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> |
|||
</encoder> |
|||
</appender> |
|||
|
|||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> |
|||
<encoder> |
|||
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> |
|||
</encoder> |
|||
</appender> |
|||
|
|||
<logger name="org.thingsboard.server" level="INFO" /> |
|||
<logger name="org.thingsboard.server.edqs" level="TRACE" /> |
|||
<logger name="org.apache.kafka.clients" level="WARN"/> |
|||
|
|||
<root level="INFO"> |
|||
<appender-ref ref="fileLogAppender"/> |
|||
<appender-ref ref="STDOUT"/> |
|||
</root> |
|||
|
|||
</configuration> |
|||
@ -0,0 +1,161 @@ |
|||
{ |
|||
"annotations": { |
|||
"list": [ |
|||
{ |
|||
"builtIn": 1, |
|||
"datasource": "-- Grafana --", |
|||
"enable": true, |
|||
"hide": true, |
|||
"iconColor": "rgba(0, 211, 255, 1)", |
|||
"name": "Annotations & Alerts", |
|||
"target": { |
|||
"limit": 100, |
|||
"matchAny": false, |
|||
"tags": [], |
|||
"type": "dashboard" |
|||
}, |
|||
"type": "dashboard" |
|||
} |
|||
] |
|||
}, |
|||
"editable": true, |
|||
"fiscalYearStartMonth": 0, |
|||
"graphTooltip": 0, |
|||
"id": 6, |
|||
"iteration": 1737564772936, |
|||
"links": [], |
|||
"liveNow": false, |
|||
"panels": [ |
|||
{ |
|||
"fieldConfig": { |
|||
"defaults": { |
|||
"color": { |
|||
"mode": "palette-classic" |
|||
}, |
|||
"custom": { |
|||
"axisLabel": "", |
|||
"axisPlacement": "auto", |
|||
"barAlignment": 0, |
|||
"drawStyle": "line", |
|||
"fillOpacity": 0, |
|||
"gradientMode": "none", |
|||
"hideFrom": { |
|||
"legend": false, |
|||
"tooltip": false, |
|||
"viz": false |
|||
}, |
|||
"lineInterpolation": "smooth", |
|||
"lineWidth": 1, |
|||
"pointSize": 1, |
|||
"scaleDistribution": { |
|||
"type": "linear" |
|||
}, |
|||
"showPoints": "auto", |
|||
"spanNulls": false, |
|||
"stacking": { |
|||
"group": "A", |
|||
"mode": "none" |
|||
}, |
|||
"thresholdsStyle": { |
|||
"mode": "off" |
|||
} |
|||
}, |
|||
"decimals": 0, |
|||
"mappings": [], |
|||
"thresholds": { |
|||
"mode": "absolute", |
|||
"steps": [ |
|||
{ |
|||
"color": "green", |
|||
"value": null |
|||
}, |
|||
{ |
|||
"color": "red", |
|||
"value": 80 |
|||
} |
|||
] |
|||
} |
|||
}, |
|||
"overrides": [] |
|||
}, |
|||
"gridPos": { |
|||
"h": 10, |
|||
"w": 12, |
|||
"x": 0, |
|||
"y": 0 |
|||
}, |
|||
"id": 2, |
|||
"options": { |
|||
"legend": { |
|||
"calcs": [], |
|||
"displayMode": "list", |
|||
"placement": "bottom" |
|||
}, |
|||
"tooltip": { |
|||
"mode": "single", |
|||
"sort": "none" |
|||
} |
|||
}, |
|||
"targets": [ |
|||
{ |
|||
"datasource": { |
|||
"type": "prometheus", |
|||
"uid": "9BonzvTSz" |
|||
}, |
|||
"exemplar": true, |
|||
"expr": "sum by (objectType) (edqs_object_count{tenantId=~\"$tenantId\"})", |
|||
"interval": "", |
|||
"legendFormat": "{{objectType}}", |
|||
"refId": "A" |
|||
} |
|||
], |
|||
"title": "EDQS object count", |
|||
"type": "timeseries" |
|||
} |
|||
], |
|||
"refresh": "", |
|||
"schemaVersion": 35, |
|||
"style": "dark", |
|||
"tags": [], |
|||
"templating": { |
|||
"list": [ |
|||
{ |
|||
"current": { |
|||
"selected": true, |
|||
"text": [ |
|||
"All" |
|||
], |
|||
"value": [ |
|||
"$__all" |
|||
] |
|||
}, |
|||
"definition": "label_values(edqs_object_count, tenantId)", |
|||
"hide": 0, |
|||
"includeAll": true, |
|||
"label": "Tenant", |
|||
"multi": true, |
|||
"name": "tenantId", |
|||
"options": [], |
|||
"query": { |
|||
"query": "label_values(edqs_object_count, tenantId)", |
|||
"refId": "StandardVariableQuery" |
|||
}, |
|||
"refresh": 1, |
|||
"regex": "", |
|||
"skipUrlSync": false, |
|||
"sort": 0, |
|||
"type": "query" |
|||
} |
|||
] |
|||
}, |
|||
"time": { |
|||
"from": "now-15m", |
|||
"to": "now" |
|||
}, |
|||
"timepicker": {}, |
|||
"timezone": "", |
|||
"title": "EDQS", |
|||
"uid": "mK5A_DdHk", |
|||
"version": 9, |
|||
"weekStart": "" |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
# ThingsBoard server configuration with enabled EDQS synchronization |
|||
|
|||
TB_EDQS_MODE=remote |
|||
TB_EDQS_SYNC_ENABLED=true |
|||
TB_EDQS_API_ENABLED=true |
|||
@ -0,0 +1,3 @@ |
|||
# ThingsBoard server configuration with enabled EDQS synchronization |
|||
|
|||
TB_EDQS_SYNC_ENABLED=true |
|||
@ -0,0 +1,22 @@ |
|||
# |
|||
# Copyright © 2016-2025 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. |
|||
# |
|||
|
|||
export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=@pkg.logFolder@/gc.log:time,uptime,level,tags:filecount=10,filesize=10M" |
|||
export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError" |
|||
export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" |
|||
export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" |
|||
export LOG_FILENAME=${pkg.name}.out |
|||
export LOADER_PATH=${pkg.installFolder}/conf |
|||
@ -0,0 +1,49 @@ |
|||
<?xml version="1.0" encoding="UTF-8" ?> |
|||
<!-- |
|||
|
|||
Copyright © 2016-2025 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. |
|||
|
|||
--> |
|||
<!DOCTYPE configuration> |
|||
<configuration> |
|||
|
|||
<appender name="fileLogAppender" |
|||
class="ch.qos.logback.core.rolling.RollingFileAppender"> |
|||
<file>${pkg.logFolder}/${pkg.name}.log</file> |
|||
<rollingPolicy |
|||
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> |
|||
<fileNamePattern>${pkg.logFolder}/${pkg.name}.%d{yyyy-MM-dd}.%i.log</fileNamePattern> |
|||
<maxFileSize>100MB</maxFileSize> |
|||
<maxHistory>30</maxHistory> |
|||
<totalSizeCap>3GB</totalSizeCap> |
|||
</rollingPolicy> |
|||
<encoder> |
|||
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> |
|||
</encoder> |
|||
</appender> |
|||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> |
|||
<encoder> |
|||
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern> |
|||
</encoder> |
|||
</appender> |
|||
|
|||
<logger name="org.thingsboard.server" level="INFO" /> |
|||
|
|||
<root level="INFO"> |
|||
<appender-ref ref="fileLogAppender"/> |
|||
<appender-ref ref="STDOUT"/> |
|||
</root> |
|||
|
|||
</configuration> |
|||
@ -0,0 +1,214 @@ |
|||
/** |
|||
* Copyright © 2016-2025 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.msa.edqs; |
|||
|
|||
import com.fasterxml.jackson.databind.node.ObjectNode; |
|||
import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; |
|||
import org.testng.annotations.AfterClass; |
|||
import org.testng.annotations.BeforeClass; |
|||
import org.testng.annotations.BeforeMethod; |
|||
import org.testng.annotations.Test; |
|||
import org.thingsboard.server.common.data.Device; |
|||
import org.thingsboard.server.common.data.EntityType; |
|||
import org.thingsboard.server.common.data.StringUtils; |
|||
import org.thingsboard.server.common.data.id.CustomerId; |
|||
import org.thingsboard.server.common.data.id.TenantId; |
|||
import org.thingsboard.server.common.data.id.UserId; |
|||
import org.thingsboard.server.common.data.page.PageData; |
|||
import org.thingsboard.server.common.data.query.DeviceTypeFilter; |
|||
import org.thingsboard.server.common.data.query.EntityCountQuery; |
|||
import org.thingsboard.server.common.data.query.EntityData; |
|||
import org.thingsboard.server.common.data.query.EntityDataPageLink; |
|||
import org.thingsboard.server.common.data.query.EntityDataQuery; |
|||
import org.thingsboard.server.common.data.query.EntityDataSortOrder; |
|||
import org.thingsboard.server.common.data.query.EntityKey; |
|||
import org.thingsboard.server.common.data.query.EntityKeyType; |
|||
import org.thingsboard.server.common.data.query.EntityTypeFilter; |
|||
import org.thingsboard.server.common.data.query.TsValue; |
|||
import org.thingsboard.server.msa.AbstractContainerTest; |
|||
import org.thingsboard.server.msa.DisableUIListeners; |
|||
import org.thingsboard.server.msa.ui.utils.EntityPrototypes; |
|||
|
|||
import java.util.ArrayList; |
|||
import java.util.Collections; |
|||
import java.util.List; |
|||
import java.util.Map; |
|||
import java.util.concurrent.TimeUnit; |
|||
|
|||
import static org.assertj.core.api.Assertions.assertThat; |
|||
import static org.awaitility.Awaitility.await; |
|||
import static org.thingsboard.server.msa.ui.utils.EntityPrototypes.defaultCustomer; |
|||
import static org.thingsboard.server.msa.ui.utils.EntityPrototypes.defaultCustomerAdmin; |
|||
import static org.thingsboard.server.msa.ui.utils.EntityPrototypes.defaultDeviceProfile; |
|||
import static org.thingsboard.server.msa.ui.utils.EntityPrototypes.defaultTenantAdmin; |
|||
|
|||
@DisableUIListeners |
|||
public class EdqsEntityDataQueryTest extends AbstractContainerTest { |
|||
|
|||
private TenantId tenantId; |
|||
private CustomerId customerId; |
|||
private TenantId tenantId2; |
|||
private CustomerId customerId2; |
|||
private UserId tenantAdminId; |
|||
private UserId customerUserId; |
|||
private UserId tenant2AdminId; |
|||
private UserId customer2UserId; |
|||
private final List<Device> tenantDevices = new ArrayList<>(); |
|||
private final List<Device> tenant2Devices = new ArrayList<>(); |
|||
private final String deviceProfile = "LoRa-" + RandomStringUtils.randomAlphabetic(10); |
|||
|
|||
@BeforeClass |
|||
public void beforeClass() throws Exception { |
|||
testRestClient.login("sysadmin@thingsboard.org", "sysadmin"); |
|||
await().atMost(60, TimeUnit.SECONDS).until(() -> testRestClient.isEdqsApiEnabled()); |
|||
|
|||
tenantId = testRestClient.postTenant(EntityPrototypes.defaultTenantPrototype("Tenant")).getId(); |
|||
tenantAdminId = testRestClient.createUserAndLogin(defaultTenantAdmin(tenantId, "tenantAdmin@thingsboard.org"), "tenant"); |
|||
testRestClient.postDeviceProfile(defaultDeviceProfile(deviceProfile)); |
|||
createDevices(deviceProfile, tenantDevices, 97); |
|||
customerId = testRestClient.postCustomer(defaultCustomer(tenantId, "Customer")).getId(); |
|||
customerUserId = testRestClient.postUser(defaultCustomerAdmin(tenantId, customerId, "customerUser@thingsboard.org")).getId(); |
|||
assignDevicesToCustomer(customerId, tenantDevices, 12); |
|||
|
|||
testRestClient.login("sysadmin@thingsboard.org", "sysadmin"); |
|||
tenantId2 = testRestClient.postTenant(EntityPrototypes.defaultTenantPrototype("Tenant")).getId(); |
|||
tenant2AdminId = testRestClient.createUserAndLogin(defaultTenantAdmin(tenantId2, "tenant2Admin@thingsboard.org"), "tenant"); |
|||
testRestClient.postDeviceProfile(defaultDeviceProfile(deviceProfile)); |
|||
createDevices(deviceProfile, tenant2Devices, 97); |
|||
customerId2 = testRestClient.postCustomer(defaultCustomer(tenantId2, "Customer")).getId(); |
|||
customer2UserId = testRestClient.postUser(defaultCustomerAdmin(tenantId2, customerId2, "customer2User@thingsboard.org")).getId(); |
|||
assignDevicesToCustomer(customerId2, tenant2Devices, 12); |
|||
} |
|||
|
|||
@BeforeMethod |
|||
public void beforeMethod() { |
|||
testRestClient.login("sysadmin@thingsboard.org", "sysadmin"); |
|||
} |
|||
|
|||
@AfterClass |
|||
public void afterClass() { |
|||
testRestClient.resetToken(); |
|||
testRestClient.login("sysadmin@thingsboard.org", "sysadmin"); |
|||
testRestClient.deleteTenant(tenantId); |
|||
testRestClient.deleteTenant(tenantId2); |
|||
} |
|||
|
|||
@Test |
|||
public void testSysAdminCountEntitiesByQuery() { |
|||
EntityTypeFilter allDeviceFilter = new EntityTypeFilter(); |
|||
allDeviceFilter.setEntityType(EntityType.DEVICE); |
|||
EntityCountQuery query = new EntityCountQuery(allDeviceFilter); |
|||
await("Waiting for total device count") |
|||
.atMost(30, TimeUnit.SECONDS) |
|||
.until(() -> testRestClient.postCountDataQuery(query).compareTo(97L * 2) >= 0); |
|||
|
|||
testRestClient.getAndSetUserToken(tenantAdminId); |
|||
await("Waiting for total device count") |
|||
.atMost(30, TimeUnit.SECONDS) |
|||
.until(() -> testRestClient.postCountDataQuery(query).equals(97L)); |
|||
|
|||
testRestClient.resetToken(); |
|||
testRestClient.login("sysadmin@thingsboard.org", "sysadmin"); |
|||
testRestClient.getAndSetUserToken(tenant2AdminId); |
|||
await("Waiting for total device count") |
|||
.atMost(30, TimeUnit.SECONDS) |
|||
.until(() -> testRestClient.postCountDataQuery(query).equals(97L)); |
|||
} |
|||
|
|||
@Test |
|||
public void testRetrieveTenantDevicesByDeviceTypeFilter() { |
|||
// login tenant admin
|
|||
testRestClient.getAndSetUserToken(tenantAdminId); |
|||
checkUserDevices(tenantDevices); |
|||
|
|||
// login customer user
|
|||
testRestClient.getAndSetUserToken(customerUserId); |
|||
checkUserDevices(tenantDevices.subList(0, 12)); |
|||
|
|||
// login other tenant admin
|
|||
testRestClient.resetToken(); |
|||
testRestClient.login("sysadmin@thingsboard.org", "sysadmin"); |
|||
testRestClient.getAndSetUserToken(tenant2AdminId); |
|||
checkUserDevices(tenant2Devices); |
|||
} |
|||
|
|||
private void checkUserDevices(List<Device> devices) { |
|||
DeviceTypeFilter filter = new DeviceTypeFilter(); |
|||
filter.setDeviceTypes(List.of(deviceProfile)); |
|||
filter.setDeviceNameFilter(""); |
|||
|
|||
EntityDataSortOrder sortOrder = new EntityDataSortOrder(new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC); |
|||
EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder); |
|||
List<EntityKey> entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name")); |
|||
List<EntityKey> latestFields = Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")); |
|||
EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestFields, null); |
|||
|
|||
EntityTypeFilter allDeviceFilter = new EntityTypeFilter(); |
|||
allDeviceFilter.setEntityType(EntityType.DEVICE); |
|||
EntityCountQuery countQuery = new EntityCountQuery(allDeviceFilter); |
|||
await("Waiting for total device count") |
|||
.atMost(30, TimeUnit.SECONDS) |
|||
.until(() -> testRestClient.postCountDataQuery(countQuery).intValue() == devices.size()); |
|||
|
|||
PageData<EntityData> result = testRestClient.postEntityDataQuery(query); |
|||
assertThat(result.getTotalElements()).isEqualTo(devices.size()); |
|||
List<EntityData> retrievedDevices = result.getData(); |
|||
|
|||
assertThat(retrievedDevices).hasSize(10); |
|||
List<String> retrievedDeviceNames = retrievedDevices.stream().map(entityData -> entityData.getLatest().get(EntityKeyType.ENTITY_FIELD).get("name").getValue()).toList(); |
|||
assertThat(retrievedDeviceNames).containsExactlyInAnyOrderElementsOf(devices.stream().map(Device::getName).toList().subList(0, 10)); |
|||
|
|||
//check temperature
|
|||
for (int i = 0; i < 10; i++) { |
|||
Map<EntityKeyType, Map<String, TsValue>> latest = retrievedDevices.get(i).getLatest(); |
|||
String name = latest.get(EntityKeyType.ENTITY_FIELD).get("name").getValue(); |
|||
assertThat(latest.get(EntityKeyType.TIME_SERIES).get("temperature").getValue()).isEqualTo(name.substring(name.length() - 1)); |
|||
} |
|||
} |
|||
|
|||
private String createDevices(String deviceType, List<Device> tenantDevices, int deviceCount) throws InterruptedException { |
|||
String prefix = StringUtils.randomAlphabetic(5); |
|||
for (int i = 0; i < deviceCount; i++) { |
|||
Device device = new Device(); |
|||
device.setName(prefix + "Device" + i); |
|||
device.setType(deviceType); |
|||
device.setLabel("testLabel" + (int) (Math.random() * 1000)); |
|||
//TO make sure devices have different created time
|
|||
Thread.sleep(1); |
|||
String token = RandomStringUtils.randomAlphabetic(10); |
|||
Device saved = testRestClient.postDevice(token, device); |
|||
tenantDevices.add(saved); |
|||
|
|||
// save timeseries data
|
|||
testRestClient.postTelemetry(token, createDeviceTelemetry(i)); |
|||
} |
|||
return deviceType; |
|||
} |
|||
|
|||
private void assignDevicesToCustomer(CustomerId customerId, List<Device> devices, int deviceCount) { |
|||
for (int i = 0; i < deviceCount; i++) { |
|||
Device device = devices.get(i); |
|||
testRestClient.assignDeviceToCustomer(customerId, device.getId()); |
|||
} |
|||
} |
|||
|
|||
protected ObjectNode createDeviceTelemetry(int temperature) { |
|||
ObjectNode objectNode = mapper.createObjectNode(); |
|||
objectNode.put("temperature", temperature); |
|||
return objectNode; |
|||
} |
|||
|
|||
} |
|||
@ -0,0 +1,31 @@ |
|||
# |
|||
# Copyright © 2016-2025 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. |
|||
# |
|||
|
|||
FROM thingsboard/openjdk17:bookworm-slim |
|||
|
|||
COPY start-tb-edqs.sh ${pkg.name}.deb /tmp/ |
|||
|
|||
RUN chmod a+x /tmp/*.sh \ |
|||
&& mv /tmp/start-tb-edqs.sh /usr/bin && \ |
|||
(yes | dpkg -i /tmp/${pkg.name}.deb) && \ |
|||
rm /tmp/${pkg.name}.deb && \ |
|||
(systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || :) && \ |
|||
chown -R ${pkg.user}:${pkg.user} /tmp && \ |
|||
chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar |
|||
|
|||
USER ${pkg.user} |
|||
|
|||
CMD ["start-tb-edqs.sh"] |
|||
@ -0,0 +1,31 @@ |
|||
#!/bin/bash |
|||
# |
|||
# Copyright © 2016-2025 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. |
|||
# |
|||
|
|||
CONF_FOLDER=${pkg.installFolder}/conf |
|||
jarfile=${pkg.installFolder}/bin/${pkg.name}.jar |
|||
configfile=${pkg.name}.conf |
|||
|
|||
source "${CONF_FOLDER}/${configfile}" |
|||
|
|||
echo "Starting '${project.name}' ..." |
|||
|
|||
cd ${pkg.installFolder}/bin |
|||
|
|||
exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.edqs.ThingsboardEdqsApplication \ |
|||
-Dspring.jpa.hibernate.ddl-auto=none \ |
|||
-Dlogging.config=$CONF_FOLDER/logback.xml \ |
|||
org.springframework.boot.loader.launch.PropertiesLauncher |
|||
@ -0,0 +1,190 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2025 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. |
|||
|
|||
--> |
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
|||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
|||
<modelVersion>4.0.0</modelVersion> |
|||
<parent> |
|||
<groupId>org.thingsboard</groupId> |
|||
<version>4.0.0-SNAPSHOT</version> |
|||
<artifactId>msa</artifactId> |
|||
</parent> |
|||
<groupId>org.thingsboard.msa</groupId> |
|||
<artifactId>edqs</artifactId> |
|||
<packaging>pom</packaging> |
|||
|
|||
<name>ThingsBoard Entity Data Query Microservice</name> |
|||
<url>https://thingsboard.io</url> |
|||
<description>ThingsBoard Entity Data Query Microservice</description> |
|||
|
|||
<properties> |
|||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
|||
<main.dir>${basedir}/../..</main.dir> |
|||
<pkg.name>edqs</pkg.name> |
|||
<docker.name>tb-edqs</docker.name> |
|||
<pkg.logFolder>/var/log/${pkg.name}</pkg.logFolder> |
|||
<pkg.installFolder>/usr/share/${pkg.name}</pkg.installFolder> |
|||
<docker.push-arm-amd-image.phase>pre-integration-test</docker.push-arm-amd-image.phase> |
|||
</properties> |
|||
|
|||
<dependencies> |
|||
<dependency> |
|||
<groupId>org.thingsboard</groupId> |
|||
<artifactId>edqs</artifactId> |
|||
<version>${project.version}</version> |
|||
<classifier>deb</classifier> |
|||
<type>deb</type> |
|||
<scope>provided</scope> |
|||
</dependency> |
|||
</dependencies> |
|||
|
|||
<build> |
|||
<plugins> |
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-dependency-plugin</artifactId> |
|||
<executions> |
|||
<execution> |
|||
<id>copy-edqs</id> |
|||
<phase>package</phase> |
|||
<goals> |
|||
<goal>copy</goal> |
|||
</goals> |
|||
<configuration> |
|||
<artifactItems> |
|||
<artifactItem> |
|||
<groupId>org.thingsboard</groupId> |
|||
<artifactId>edqs</artifactId> |
|||
<classifier>deb</classifier> |
|||
<type>deb</type> |
|||
<destFileName>${pkg.name}.deb</destFileName> |
|||
<outputDirectory>${project.build.directory}</outputDirectory> |
|||
</artifactItem> |
|||
</artifactItems> |
|||
</configuration> |
|||
</execution> |
|||
</executions> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>org.apache.maven.plugins</groupId> |
|||
<artifactId>maven-resources-plugin</artifactId> |
|||
<executions> |
|||
<execution> |
|||
<id>copy-docker-config</id> |
|||
<phase>process-resources</phase> |
|||
<goals> |
|||
<goal>copy-resources</goal> |
|||
</goals> |
|||
<configuration> |
|||
<outputDirectory>${project.build.directory}</outputDirectory> |
|||
<resources> |
|||
<resource> |
|||
<directory>docker</directory> |
|||
<filtering>true</filtering> |
|||
</resource> |
|||
</resources> |
|||
</configuration> |
|||
</execution> |
|||
</executions> |
|||
</plugin> |
|||
<plugin> |
|||
<groupId>com.spotify</groupId> |
|||
<artifactId>dockerfile-maven-plugin</artifactId> |
|||
<executions> |
|||
<execution> |
|||
<id>build-docker-image</id> |
|||
<phase>pre-integration-test</phase> |
|||
<goals> |
|||
<goal>build</goal> |
|||
</goals> |
|||
<configuration> |
|||
<skip>${dockerfile.skip}</skip> |
|||
<repository>${docker.repo}/${docker.name}</repository> |
|||
<verbose>true</verbose> |
|||
<googleContainerRegistryEnabled>false</googleContainerRegistryEnabled> |
|||
<contextDirectory>${project.build.directory}</contextDirectory> |
|||
</configuration> |
|||
</execution> |
|||
<execution> |
|||
<id>tag-docker-image</id> |
|||
<phase>pre-integration-test</phase> |
|||
<goals> |
|||
<goal>tag</goal> |
|||
</goals> |
|||
<configuration> |
|||
<skip>${dockerfile.skip}</skip> |
|||
<repository>${docker.repo}/${docker.name}</repository> |
|||
<tag>${project.version}</tag> |
|||
</configuration> |
|||
</execution> |
|||
</executions> |
|||
</plugin> |
|||
</plugins> |
|||
</build> |
|||
<profiles> |
|||
<profile> |
|||
<id>push-docker-image</id> |
|||
<activation> |
|||
<property> |
|||
<name>push-docker-image</name> |
|||
</property> |
|||
</activation> |
|||
<build> |
|||
<plugins> |
|||
<plugin> |
|||
<groupId>com.spotify</groupId> |
|||
<artifactId>dockerfile-maven-plugin</artifactId> |
|||
<executions> |
|||
<execution> |
|||
<id>push-latest-docker-image</id> |
|||
<phase>pre-integration-test</phase> |
|||
<goals> |
|||
<goal>push</goal> |
|||
</goals> |
|||
<configuration> |
|||
<tag>latest</tag> |
|||
<repository>${docker.repo}/${docker.name}</repository> |
|||
</configuration> |
|||
</execution> |
|||
<execution> |
|||
<id>push-version-docker-image</id> |
|||
<phase>pre-integration-test</phase> |
|||
<goals> |
|||
<goal>push</goal> |
|||
</goals> |
|||
<configuration> |
|||
<tag>${project.version}</tag> |
|||
<repository>${docker.repo}/${docker.name}</repository> |
|||
</configuration> |
|||
</execution> |
|||
</executions> |
|||
</plugin> |
|||
</plugins> |
|||
</build> |
|||
</profile> |
|||
</profiles> |
|||
<repositories> |
|||
<repository> |
|||
<id>jenkins</id> |
|||
<name>Jenkins Repository</name> |
|||
<url>https://repo.jenkins-ci.org/releases</url> |
|||
<snapshots> |
|||
<enabled>false</enabled> |
|||
</snapshots> |
|||
</repository> |
|||
</repositories> |
|||
</project> |
|||
Loading…
Reference in new issue