Browse Source

Merge pull request #4681 from YevhenBondarenko/feature/ota-limits

created data limits for resources and otaPackages, added url for the otaPackage
pull/4687/head
Andrew Shvayka 5 years ago
committed by GitHub
parent
commit
ecb2efdabd
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      application/src/main/data/upgrade/3.2.2/schema_update.sql
  2. 10
      application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java
  3. 12
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  4. 9
      application/src/main/java/org/thingsboard/server/controller/OtaPackageController.java
  5. 57
      application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java
  6. 5
      application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java
  7. 1
      application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java
  8. 3
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
  9. 33
      application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java
  10. 65
      application/src/test/java/org/thingsboard/server/service/resource/BaseTbResourceServiceTest.java
  11. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/ota/OtaPackageService.java
  12. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java
  13. 6
      common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java
  14. 7
      common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java
  15. 2
      common/data/src/main/java/org/thingsboard/server/common/data/ota/OtaPackageKey.java
  16. 2
      common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
  17. 23
      dao/src/main/java/org/thingsboard/server/dao/TenantEntityWithDataDao.java
  18. 1
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  19. 6
      dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageEntity.java
  20. 10
      dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageInfoEntity.java
  21. 77
      dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java
  22. 7
      dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageDao.java
  23. 2
      dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageInfoDao.java
  24. 24
      dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java
  25. 3
      dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceDao.java
  26. 15
      dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java
  27. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageDao.java
  28. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageInfoDao.java
  29. 9
      dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageInfoRepository.java
  30. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageRepository.java
  31. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java
  32. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java
  33. 1
      dao/src/main/resources/sql/schema-entities-hsql.sql
  34. 1
      dao/src/main/resources/sql/schema-entities.sql
  35. 5
      dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
  36. 253
      dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java
  37. 6
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java
  38. 2
      ui-ngx/src/app/core/http/ota-package.service.ts
  39. 24
      ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html
  40. 2
      ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts
  41. 5
      ui-ngx/src/app/shared/models/tenant.model.ts
  42. 6
      ui-ngx/src/assets/locale/locale.constant-en_US.json

1
application/src/main/data/upgrade/3.2.2/schema_update.sql

@ -67,6 +67,7 @@ CREATE TABLE IF NOT EXISTS ota_package (
type varchar(32) NOT NULL,
title varchar(255) NOT NULL,
version varchar(255) NOT NULL,
url varchar(255),
file_name varchar(255),
content_type varchar(255),
checksum_algorithm varchar(32),

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

@ -60,7 +60,9 @@ import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.event.EventService;
import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.rule.RuleNodeStateService;
import org.thingsboard.server.dao.tenant.TenantProfileService;
@ -311,6 +313,14 @@ public class ActorSystemContext {
@Autowired(required = false)
@Getter private EdgeRpcService edgeRpcService;
@Lazy
@Autowired(required = false)
@Getter private ResourceService resourceService;
@Lazy
@Autowired(required = false)
@Getter private OtaPackageService otaPackageService;
@Value("${actors.session.max_concurrent_sessions_per_device:1}")
@Getter
private long maxConcurrentSessionsPerDevice;

12
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -69,7 +69,9 @@ import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.nosql.CassandraStatementTask;
import org.thingsboard.server.dao.nosql.TbResultSetFuture;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
@ -486,6 +488,16 @@ class DefaultTbContext implements TbContext {
return mainCtx.getEntityViewService();
}
@Override
public ResourceService getResourceService() {
return mainCtx.getResourceService();
}
@Override
public OtaPackageService getOtaPackageService() {
return mainCtx.getOtaPackageService();
}
@Override
public RuleEngineDeviceProfileCache getDeviceProfileCache() {
return mainCtx.getDeviceProfileCache();

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

@ -64,6 +64,10 @@ public class OtaPackageController extends BaseController {
OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId));
OtaPackage otaPackage = checkOtaPackageId(otaPackageId, Operation.READ);
if (otaPackage.hasUrl()) {
return ResponseEntity.badRequest().build();
}
ByteArrayResource resource = new ByteArrayResource(otaPackage.getData().array());
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + otaPackage.getFileName())
@ -182,11 +186,10 @@ public class OtaPackageController extends BaseController {
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/otaPackages/{deviceProfileId}/{type}/{hasData}", method = RequestMethod.GET)
@RequestMapping(value = "/otaPackages/{deviceProfileId}/{type}", method = RequestMethod.GET)
@ResponseBody
public PageData<OtaPackageInfo> getOtaPackages(@PathVariable("deviceProfileId") String strDeviceProfileId,
@PathVariable("type") String strType,
@PathVariable("hasData") boolean hasData,
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@ -197,7 +200,7 @@ public class OtaPackageController extends BaseController {
try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
return checkNotNull(otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(getTenantId(),
new DeviceProfileId(toUUID(strDeviceProfileId)), OtaPackageType.valueOf(strType), hasData, pageLink));
new DeviceProfileId(toUUID(strDeviceProfileId)), OtaPackageType.valueOf(strType), pageLink));
} catch (Exception e) {
throw handleException(e);
}

57
application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.common.data.id.TenantId;
@ -65,6 +66,7 @@ import static org.thingsboard.server.common.data.ota.OtaPackageKey.SIZE;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.TITLE;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.TS;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.URL;
import static org.thingsboard.server.common.data.ota.OtaPackageKey.VERSION;
import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE;
@ -261,11 +263,12 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
}
private void update(Device device, OtaPackageInfo firmware, long ts) {
private void update(Device device, OtaPackageInfo otaPackage, long ts) {
TenantId tenantId = device.getTenantId();
DeviceId deviceId = device.getId();
OtaPackageType otaPackageType = otaPackage.getType();
BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.INITIATED.name()));
BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(otaPackageType, STATE), OtaPackageUpdateStatus.INITIATED.name()));
telemetryService.saveAndNotify(tenantId, deviceId, Collections.singletonList(status), new FutureCallback<>() {
@Override
@ -280,11 +283,37 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
});
List<AttributeKvEntry> attributes = new ArrayList<>();
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), TITLE), firmware.getTitle())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), VERSION), firmware.getVersion())));
attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(firmware.getType(), SIZE), firmware.getDataSize())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM_ALGORITHM), firmware.getChecksumAlgorithm().name())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM), firmware.getChecksum())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TITLE), otaPackage.getTitle())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, VERSION), otaPackage.getVersion())));
if (otaPackage.hasUrl()) {
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, URL), otaPackage.getUrl())));
List<String> attrToRemove = new ArrayList<>();
if (otaPackage.getDataSize() == null) {
attrToRemove.add(getAttributeKey(otaPackageType, SIZE));
} else {
attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(otaPackageType, SIZE), otaPackage.getDataSize())));
}
if (otaPackage.getChecksumAlgorithm() != null) {
attrToRemove.add(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM));
} else {
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM), otaPackage.getChecksumAlgorithm().name())));
}
if (StringUtils.isEmpty(otaPackage.getChecksum())) {
attrToRemove.add(getAttributeKey(otaPackageType, CHECKSUM));
} else {
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM), otaPackage.getChecksum())));
}
remove(device, otaPackageType, attrToRemove);
} else {
attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(otaPackageType, SIZE), otaPackage.getDataSize())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM), otaPackage.getChecksumAlgorithm().name())));
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM), otaPackage.getChecksum())));
remove(device, otaPackageType, Collections.singletonList(getAttributeKey(otaPackageType, URL)));
}
telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() {
@Override
@ -299,20 +328,24 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
});
}
private void remove(Device device, OtaPackageType firmwareType) {
telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, OtaPackageUtil.getAttributeKeys(firmwareType),
private void remove(Device device, OtaPackageType otaPackageType) {
remove(device, otaPackageType, OtaPackageUtil.getAttributeKeys(otaPackageType));
}
private void remove(Device device, OtaPackageType otaPackageType, List<String> attributesKeys) {
telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, attributesKeys,
new FutureCallback<>() {
@Override
public void onSuccess(@Nullable Void tmp) {
log.trace("[{}] Success remove target firmware attributes!", device.getId());
log.trace("[{}] Success remove target {} attributes!", device.getId(), otaPackageType);
Set<AttributeKey> keysToNotify = new HashSet<>();
OtaPackageUtil.ALL_FW_ATTRIBUTE_KEYS.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key)));
attributesKeys.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key)));
tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), device.getId(), keysToNotify), null);
}
@Override
public void onFailure(Throwable t) {
log.error("[{}] Failed to remove target firmware attributes!", device.getId(), t);
log.error("[{}] Failed to remove target {} attributes!", device.getId(), otaPackageType, t);
}
});
}

5
application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java

@ -157,6 +157,11 @@ public class DefaultTbResourceService implements TbResourceService {
resourceService.deleteResourcesByTenantId(tenantId);
}
@Override
public long sumDataSizeByTenantId(TenantId tenantId) {
return resourceService.sumDataSizeByTenantId(tenantId);
}
private Comparator<? super LwM2mObject> getComparator(String sortProperty, String sortOrder) {
Comparator<LwM2mObject> comparator;
if ("name".equals(sortProperty)) {

1
application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java

@ -55,4 +55,5 @@ public interface TbResourceService {
void deleteResourcesByTenantId(TenantId tenantId);
long sumDataSizeByTenantId(TenantId tenantId);
}

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

@ -536,6 +536,9 @@ public class DefaultTransportApiService implements TransportApiService {
if (otaPackageInfo == null) {
builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND);
} else if (otaPackageInfo.hasUrl()) {
builder.setResponseStatus(TransportProtos.ResponseStatus.FAILURE);
log.trace("[{}] Can`t send OtaPackage with URL data!", otaPackageInfo.getId());
} else {
builder.setResponseStatus(TransportProtos.ResponseStatus.SUCCESS);
builder.setOtaPackageIdMSB(otaPackageId.getId().getMostSignificantBits());

33
application/src/test/java/org/thingsboard/server/controller/BaseOtaPackageControllerTest.java

@ -50,7 +50,7 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
private static final String FILE_NAME = "filename.txt";
private static final String VERSION = "v1.0";
private static final String CONTENT_TYPE = "text/plain";
private static final String CHECKSUM_ALGORITHM = "sha256";
private static final String CHECKSUM_ALGORITHM = "SHA256";
private static final String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a";
private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{1});
@ -257,7 +257,7 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
@Test
public void testFindTenantFirmwaresByHasData() throws Exception {
List<OtaPackageInfo> otaPackagesWithData = new ArrayList<>();
List<OtaPackageInfo> otaPackagesWithoutData = new ArrayList<>();
List<OtaPackageInfo> allOtaPackages = new ArrayList<>();
for (int i = 0; i < 165; i++) {
OtaPackageInfo firmwareInfo = new OtaPackageInfo();
@ -272,44 +272,45 @@ public abstract class BaseOtaPackageControllerTest extends AbstractControllerTes
MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array());
OtaPackage savedFirmware = savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM);
otaPackagesWithData.add(new OtaPackageInfo(savedFirmware));
} else {
otaPackagesWithoutData.add(savedFirmwareInfo);
savedFirmwareInfo = new OtaPackageInfo(savedFirmware);
otaPackagesWithData.add(savedFirmwareInfo);
}
allOtaPackages.add(savedFirmwareInfo);
}
List<OtaPackageInfo> loadedFirmwaresWithData = new ArrayList<>();
List<OtaPackageInfo> loadedOtaPackagesWithData = new ArrayList<>();
PageLink pageLink = new PageLink(24);
PageData<OtaPackageInfo> pageData;
do {
pageData = doGetTypedWithPageLink("/api/otaPackages/" + deviceProfileId.toString() + "/FIRMWARE/true?",
pageData = doGetTypedWithPageLink("/api/otaPackages/" + deviceProfileId.toString() + "/FIRMWARE?",
new TypeReference<>() {
}, pageLink);
loadedFirmwaresWithData.addAll(pageData.getData());
loadedOtaPackagesWithData.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
List<OtaPackageInfo> loadedFirmwaresWithoutData = new ArrayList<>();
List<OtaPackageInfo> allLoadedOtaPackages = new ArrayList<>();
pageLink = new PageLink(24);
do {
pageData = doGetTypedWithPageLink("/api/otaPackages/" + deviceProfileId.toString() + "/FIRMWARE/false?",
pageData = doGetTypedWithPageLink("/api/otaPackages?",
new TypeReference<>() {
}, pageLink);
loadedFirmwaresWithoutData.addAll(pageData.getData());
allLoadedOtaPackages.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(otaPackagesWithData, idComparator);
Collections.sort(otaPackagesWithoutData, idComparator);
Collections.sort(loadedFirmwaresWithData, idComparator);
Collections.sort(loadedFirmwaresWithoutData, idComparator);
Collections.sort(allOtaPackages, idComparator);
Collections.sort(loadedOtaPackagesWithData, idComparator);
Collections.sort(allLoadedOtaPackages, idComparator);
Assert.assertEquals(otaPackagesWithData, loadedFirmwaresWithData);
Assert.assertEquals(otaPackagesWithoutData, loadedFirmwaresWithoutData);
Assert.assertEquals(otaPackagesWithData, loadedOtaPackagesWithData);
Assert.assertEquals(allOtaPackages, allLoadedOtaPackages);
}

65
application/src/test/java/org/thingsboard/server/service/resource/BaseTbResourceServiceTest.java

@ -19,17 +19,24 @@ import com.datastax.oss.driver.api.core.uuid.Uuids;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.EntityInfo;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
import org.thingsboard.server.common.data.TbResourceInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DaoSqlTest;
@ -109,6 +116,64 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
.andExpect(status().isOk());
}
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testSaveResourceWithMaxSumDataSizeOutOfLimit() throws Exception {
loginSysAdmin();
long limit = 1;
EntityInfo defaultTenantProfileInfo = doGet("/api/tenantProfileInfo/default", EntityInfo.class);
TenantProfile defaultTenantProfile = doGet("/api/tenantProfile/" + defaultTenantProfileInfo.getId().getId().toString(), TenantProfile.class);
defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxResourcesInBytes(limit).build());
doPost("/api/tenantProfile", defaultTenantProfile, TenantProfile.class);
loginTenantAdmin();
Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId));
createResource("test", DEFAULT_FILE_NAME);
Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
try {
thrown.expect(DataValidationException.class);
thrown.expectMessage(String.format("Failed to create the tb resource, files size limit is exhausted %d bytes!", limit));
createResource("test1", 1 + DEFAULT_FILE_NAME);
} finally {
defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxResourcesInBytes(0).build());
loginSysAdmin();
doPost("/api/tenantProfile", defaultTenantProfile, TenantProfile.class);
}
}
@Test
public void sumDataSizeByTenantId() throws ThingsboardException {
Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId));
createResource("test", DEFAULT_FILE_NAME);
Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
int maxSumDataSize = 8;
for (int i = 2; i <= maxSumDataSize; i++) {
createResource("test" + i, i + DEFAULT_FILE_NAME);
Assert.assertEquals(i, resourceService.sumDataSizeByTenantId(tenantId));
}
Assert.assertEquals(maxSumDataSize, resourceService.sumDataSizeByTenantId(tenantId));
}
private TbResource createResource(String title, String filename) throws ThingsboardException {
TbResource resource = new TbResource();
resource.setTenantId(tenantId);
resource.setTitle(title);
resource.setResourceType(ResourceType.JKS);
resource.setFileName(filename);
resource.setData("1");
return resourceService.saveResource(resource);
}
@Test
public void testSaveTbResource() throws Exception {
TbResource resource = new TbResource();

4
common/dao-api/src/main/java/org/thingsboard/server/dao/ota/OtaPackageService.java

@ -44,9 +44,11 @@ public interface OtaPackageService {
PageData<OtaPackageInfo> findTenantOtaPackagesByTenantId(TenantId tenantId, PageLink pageLink);
PageData<OtaPackageInfo> findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, boolean hasData, PageLink pageLink);
PageData<OtaPackageInfo> findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, PageLink pageLink);
void deleteOtaPackage(TenantId tenantId, OtaPackageId otaPackageId);
void deleteOtaPackagesByTenantId(TenantId tenantId);
long sumDataSizeByTenantId(TenantId tenantId);
}

2
common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java

@ -49,5 +49,5 @@ public interface ResourceService {
void deleteResourcesByTenantId(TenantId tenantId);
long sumDataSizeByTenantId(TenantId tenantId);
}

6
common/data/src/main/java/org/thingsboard/server/common/data/OtaPackage.java

@ -37,8 +37,8 @@ public class OtaPackage extends OtaPackageInfo {
super(id);
}
public OtaPackage(OtaPackage firmware) {
super(firmware);
this.data = firmware.getData();
public OtaPackage(OtaPackage otaPackage) {
super(otaPackage);
this.data = otaPackage.getData();
}
}

7
common/data/src/main/java/org/thingsboard/server/common/data/OtaPackageInfo.java

@ -37,6 +37,7 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
private OtaPackageType type;
private String title;
private String version;
private String url;
private boolean hasData;
private String fileName;
private String contentType;
@ -60,6 +61,7 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
this.type = otaPackageInfo.getType();
this.title = otaPackageInfo.getTitle();
this.version = otaPackageInfo.getVersion();
this.url = otaPackageInfo.getUrl();
this.hasData = otaPackageInfo.isHasData();
this.fileName = otaPackageInfo.getFileName();
this.contentType = otaPackageInfo.getContentType();
@ -78,4 +80,9 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
public String getName() {
return title;
}
@JsonIgnore
public boolean hasUrl() {
return StringUtils.isNotEmpty(url);
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/ota/OtaPackageKey.java

@ -19,7 +19,7 @@ import lombok.Getter;
public enum OtaPackageKey {
TITLE("title"), VERSION("version"), TS("ts"), STATE("state"), SIZE("size"), CHECKSUM("checksum"), CHECKSUM_ALGORITHM("checksum_algorithm");
TITLE("title"), VERSION("version"), TS("ts"), STATE("state"), SIZE("size"), CHECKSUM("checksum"), CHECKSUM_ALGORITHM("checksum_algorithm"), URL("url");
@Getter
private final String value;

2
common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java

@ -34,6 +34,8 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
private long maxUsers;
private long maxDashboards;
private long maxRuleChains;
private long maxResourcesInBytes;
private long maxOtaPackagesInBytes;
private String transportTenantMsgRateLimit;
private String transportTenantTelemetryMsgRateLimit;

23
dao/src/main/java/org/thingsboard/server/dao/TenantEntityWithDataDao.java

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2021 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.thingsboard.server.common.data.id.TenantId;
public interface TenantEntityWithDataDao {
Long sumDataSizeByTenantId(TenantId tenantId);
}

1
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -487,6 +487,7 @@ public class ModelConstants {
public static final String OTA_PACKAGE_TYPE_COLUMN = "type";
public static final String OTA_PACKAGE_TILE_COLUMN = TITLE_PROPERTY;
public static final String OTA_PACKAGE_VERSION_COLUMN = "version";
public static final String OTA_PACKAGE_URL_COLUMN = "url";
public static final String OTA_PACKAGE_FILE_NAME_COLUMN = "file_name";
public static final String OTA_PACKAGE_CONTENT_TYPE_COLUMN = "content_type";
public static final String OTA_PACKAGE_CHECKSUM_ALGORITHM_COLUMN = "checksum_algorithm";

6
dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageEntity.java

@ -51,6 +51,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TABLE_
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TENANT_ID_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TILE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TYPE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_URL_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_VERSION_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
@ -77,6 +78,9 @@ public class OtaPackageEntity extends BaseSqlEntity<OtaPackage> implements Searc
@Column(name = OTA_PACKAGE_VERSION_COLUMN)
private String version;
@Column(name = OTA_PACKAGE_URL_COLUMN)
private String url;
@Column(name = OTA_PACKAGE_FILE_NAME_COLUMN)
private String fileName;
@ -118,6 +122,7 @@ public class OtaPackageEntity extends BaseSqlEntity<OtaPackage> implements Searc
this.type = firmware.getType();
this.title = firmware.getTitle();
this.version = firmware.getVersion();
this.url = firmware.getUrl();
this.fileName = firmware.getFileName();
this.contentType = firmware.getContentType();
this.checksumAlgorithm = firmware.getChecksumAlgorithm();
@ -148,6 +153,7 @@ public class OtaPackageEntity extends BaseSqlEntity<OtaPackage> implements Searc
firmware.setType(type);
firmware.setTitle(title);
firmware.setVersion(version);
firmware.setUrl(url);
firmware.setFileName(fileName);
firmware.setContentType(contentType);
firmware.setChecksumAlgorithm(checksumAlgorithm);

10
dao/src/main/java/org/thingsboard/server/dao/model/sql/OtaPackageInfoEntity.java

@ -22,6 +22,7 @@ import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.id.DeviceProfileId;
@ -50,6 +51,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TABLE_
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TENANT_ID_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TILE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TYPE_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_URL_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_VERSION_COLUMN;
import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
@ -76,6 +78,9 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
@Column(name = OTA_PACKAGE_VERSION_COLUMN)
private String version;
@Column(name = OTA_PACKAGE_URL_COLUMN)
private String url;
@Column(name = OTA_PACKAGE_FILE_NAME_COLUMN)
private String fileName;
@ -116,6 +121,7 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
}
this.title = firmware.getTitle();
this.version = firmware.getVersion();
this.url = firmware.getUrl();
this.fileName = firmware.getFileName();
this.contentType = firmware.getContentType();
this.checksumAlgorithm = firmware.getChecksumAlgorithm();
@ -125,7 +131,7 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
}
public OtaPackageInfoEntity(UUID id, long createdTime, UUID tenantId, UUID deviceProfileId, OtaPackageType type, String title, String version,
String fileName, String contentType, ChecksumAlgorithm checksumAlgorithm, String checksum, Long dataSize,
String url, String fileName, String contentType, ChecksumAlgorithm checksumAlgorithm, String checksum, Long dataSize,
Object additionalInfo, boolean hasData) {
this.id = id;
this.createdTime = createdTime;
@ -134,6 +140,7 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
this.type = type;
this.title = title;
this.version = version;
this.url = url;
this.fileName = fileName;
this.contentType = contentType;
this.checksumAlgorithm = checksumAlgorithm;
@ -164,6 +171,7 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
firmware.setType(type);
firmware.setTitle(title);
firmware.setVersion(version);
firmware.setUrl(url);
firmware.setFileName(fileName);
firmware.setContentType(contentType);
firmware.setChecksumAlgorithm(checksumAlgorithm);

77
dao/src/main/java/org/thingsboard/server/dao/ota/BaseOtaPackageService.java

@ -20,28 +20,32 @@ import com.google.common.hash.Hashing;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.server.cache.ota.OtaPackageDataCache;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.device.DeviceProfileDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import java.nio.ByteBuffer;
@ -50,6 +54,7 @@ import java.util.List;
import java.util.Optional;
import static org.thingsboard.server.common.data.CacheConstants.OTA_PACKAGE_CACHE;
import static org.thingsboard.server.common.data.EntityType.OTA_PACKAGE;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;
@ -67,6 +72,10 @@ public class BaseOtaPackageService implements OtaPackageService {
private final CacheManager cacheManager;
private final OtaPackageDataCache otaPackageDataCache;
@Autowired
@Lazy
private TbTenantProfileCache tenantProfileCache;
@Override
public OtaPackageInfo saveOtaPackageInfo(OtaPackageInfo otaPackageInfo) {
log.trace("Executing saveOtaPackageInfo [{}]", otaPackageInfo);
@ -172,11 +181,11 @@ public class BaseOtaPackageService implements OtaPackageService {
}
@Override
public PageData<OtaPackageInfo> findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, boolean hasData, PageLink pageLink) {
log.trace("Executing findTenantOtaPackagesByTenantIdAndHasData, tenantId [{}], hasData [{}] pageLink [{}]", tenantId, hasData, pageLink);
public PageData<OtaPackageInfo> findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, PageLink pageLink) {
log.trace("Executing findTenantOtaPackagesByTenantIdAndHasData, tenantId [{}], pageLink [{}]", tenantId, pageLink);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validatePageLink(pageLink);
return otaPackageInfoDao.findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, otaPackageType, hasData, pageLink);
return otaPackageInfoDao.findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, otaPackageType, pageLink);
}
@Override
@ -204,6 +213,11 @@ public class BaseOtaPackageService implements OtaPackageService {
}
}
@Override
public long sumDataSizeByTenantId(TenantId tenantId) {
return otaPackageDao.sumDataSizeByTenantId(tenantId);
}
@Override
public void deleteOtaPackagesByTenantId(TenantId tenantId) {
log.trace("Executing deleteOtaPackagesByTenantId, tenantId [{}]", tenantId);
@ -227,31 +241,43 @@ public class BaseOtaPackageService implements OtaPackageService {
private DataValidator<OtaPackage> otaPackageValidator = new DataValidator<>() {
@Override
protected void validateCreate(TenantId tenantId, OtaPackage otaPackage) {
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxOtaPackagesInBytes = profileConfiguration.getMaxOtaPackagesInBytes();
validateMaxSumDataSizePerTenant(tenantId, otaPackageDao, maxOtaPackagesInBytes, otaPackage.getDataSize(), OTA_PACKAGE);
}
@Override
protected void validateDataImpl(TenantId tenantId, OtaPackage otaPackage) {
validateImpl(otaPackage);
if (StringUtils.isEmpty(otaPackage.getFileName())) {
throw new DataValidationException("OtaPackage file name should be specified!");
}
if (!otaPackage.hasUrl()) {
if (StringUtils.isEmpty(otaPackage.getFileName())) {
throw new DataValidationException("OtaPackage file name should be specified!");
}
if (StringUtils.isEmpty(otaPackage.getContentType())) {
throw new DataValidationException("OtaPackage content type should be specified!");
}
if (StringUtils.isEmpty(otaPackage.getContentType())) {
throw new DataValidationException("OtaPackage content type should be specified!");
}
if (otaPackage.getChecksumAlgorithm() == null) {
throw new DataValidationException("OtaPackage checksum algorithm should be specified!");
}
if (StringUtils.isEmpty(otaPackage.getChecksum())) {
throw new DataValidationException("OtaPackage checksum should be specified!");
}
if (otaPackage.getChecksumAlgorithm() == null) {
throw new DataValidationException("OtaPackage checksum algorithm should be specified!");
}
if (StringUtils.isEmpty(otaPackage.getChecksum())) {
throw new DataValidationException("OtaPackage checksum should be specified!");
}
String currentChecksum;
String currentChecksum;
currentChecksum = generateChecksum(otaPackage.getChecksumAlgorithm(), otaPackage.getData());
currentChecksum = generateChecksum(otaPackage.getChecksumAlgorithm(), otaPackage.getData());
if (!currentChecksum.equals(otaPackage.getChecksum())) {
throw new DataValidationException("Wrong otaPackage file!");
if (!currentChecksum.equals(otaPackage.getChecksum())) {
throw new DataValidationException("Wrong otaPackage file!");
}
} else {
//TODO: validate url
}
}
@ -264,6 +290,13 @@ public class BaseOtaPackageService implements OtaPackageService {
if (otaPackageOld.getData() != null && !otaPackageOld.getData().equals(otaPackage.getData())) {
throw new DataValidationException("Updating otaPackage data is prohibited!");
}
if (otaPackageOld.getData() == null && otaPackage.getData() != null) {
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxOtaPackagesInBytes = profileConfiguration.getMaxOtaPackagesInBytes();
validateMaxSumDataSizePerTenant(tenantId, otaPackageDao, maxOtaPackagesInBytes, otaPackage.getDataSize(), OTA_PACKAGE);
}
}
};

7
dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageDao.java

@ -16,8 +16,11 @@
package org.thingsboard.server.dao.ota;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.TenantEntityDao;
import org.thingsboard.server.dao.TenantEntityWithDataDao;
public interface OtaPackageDao extends Dao<OtaPackage> {
public interface OtaPackageDao extends Dao<OtaPackage>, TenantEntityWithDataDao {
Long sumDataSizeByTenantId(TenantId tenantId);
}

2
dao/src/main/java/org/thingsboard/server/dao/ota/OtaPackageInfoDao.java

@ -28,7 +28,7 @@ public interface OtaPackageInfoDao extends Dao<OtaPackageInfo> {
PageData<OtaPackageInfo> findOtaPackageInfoByTenantId(TenantId tenantId, PageLink pageLink);
PageData<OtaPackageInfo> findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, boolean hasData, PageLink pageLink);
PageData<OtaPackageInfo> findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, PageLink pageLink);
boolean isOtaPackageUsed(OtaPackageId otaPackageId, OtaPackageType otaPackageType, DeviceProfileId deviceProfileId);

24
dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java

@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.ResourceType;
import org.thingsboard.server.common.data.TbResource;
@ -28,16 +29,19 @@ import org.thingsboard.server.common.data.id.TbResourceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao;
import java.util.List;
import java.util.Optional;
import static org.thingsboard.server.common.data.EntityType.TB_RESOURCE;
import static org.thingsboard.server.dao.device.DeviceServiceImpl.INCORRECT_TENANT_ID;
import static org.thingsboard.server.dao.service.Validator.validateId;
@ -49,12 +53,13 @@ public class BaseResourceService implements ResourceService {
private final TbResourceDao resourceDao;
private final TbResourceInfoDao resourceInfoDao;
private final TenantDao tenantDao;
private final TbTenantProfileCache tenantProfileCache;
public BaseResourceService(TbResourceDao resourceDao, TbResourceInfoDao resourceInfoDao, TenantDao tenantDao) {
public BaseResourceService(TbResourceDao resourceDao, TbResourceInfoDao resourceInfoDao, TenantDao tenantDao, @Lazy TbTenantProfileCache tenantProfileCache) {
this.resourceDao = resourceDao;
this.resourceInfoDao = resourceInfoDao;
this.tenantDao = tenantDao;
this.tenantProfileCache = tenantProfileCache;
}
@Override
@ -143,8 +148,23 @@ public class BaseResourceService implements ResourceService {
tenantResourcesRemover.removeEntities(tenantId, tenantId);
}
@Override
public long sumDataSizeByTenantId(TenantId tenantId) {
return resourceDao.sumDataSizeByTenantId(tenantId);
}
private DataValidator<TbResource> resourceValidator = new DataValidator<>() {
@Override
protected void validateCreate(TenantId tenantId, TbResource resource) {
if (tenantId != null && !TenantId.SYS_TENANT_ID.equals(tenantId) ) {
DefaultTenantProfileConfiguration profileConfiguration =
(DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
long maxSumResourcesDataInBytes = profileConfiguration.getMaxResourcesInBytes();
validateMaxSumDataSizePerTenant(tenantId, resourceDao, maxSumResourcesDataInBytes, resource.getData().length(), TB_RESOURCE);
}
}
@Override
protected void validateDataImpl(TenantId tenantId, TbResource resource) {
if (StringUtils.isEmpty(resource.getTitle())) {

3
dao/src/main/java/org/thingsboard/server/dao/resource/TbResourceDao.java

@ -21,10 +21,11 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.TenantEntityWithDataDao;
import java.util.List;
public interface TbResourceDao extends Dao<TbResource> {
public interface TbResourceDao extends Dao<TbResource>, TenantEntityWithDataDao {
TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId);

15
dao/src/main/java/org/thingsboard/server/dao/service/DataValidator.java

@ -23,8 +23,10 @@ import org.hibernate.validator.cfg.ConstraintMapping;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.validation.NoXss;
import org.thingsboard.server.dao.TenantEntityDao;
import org.thingsboard.server.dao.TenantEntityWithDataDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import javax.validation.ConstraintViolation;
@ -123,6 +125,19 @@ public abstract class DataValidator<D extends BaseData<?>> {
}
}
protected void validateMaxSumDataSizePerTenant(TenantId tenantId,
TenantEntityWithDataDao dataDao,
long maxSumDataSize,
long currentDataSize,
EntityType entityType) {
if (maxSumDataSize > 0) {
if (dataDao.sumDataSizeByTenantId(tenantId) + currentDataSize > maxSumDataSize) {
throw new DataValidationException(String.format("Failed to create the %s, files size limit is exhausted %d bytes!",
entityType.name().toLowerCase().replaceAll("_", " "), maxSumDataSize));
}
}
}
protected static void validateJsonStructure(JsonNode expectedNode, JsonNode actualNode) {
Set<String> expectedFields = new HashSet<>();
Iterator<String> fieldsIterator = expectedNode.fieldNames();

5
dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageDao.java

@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.ota.OtaPackageDao;
import org.thingsboard.server.dao.model.sql.OtaPackageEntity;
import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
@ -43,4 +44,8 @@ public class JpaOtaPackageDao extends JpaAbstractSearchTextDao<OtaPackageEntity,
return otaPackageRepository;
}
@Override
public Long sumDataSizeByTenantId(TenantId tenantId) {
return otaPackageRepository.sumDataSizeByTenantId(tenantId.getId());
}
}

3
dao/src/main/java/org/thingsboard/server/dao/sql/ota/JpaOtaPackageInfoDao.java

@ -76,13 +76,12 @@ public class JpaOtaPackageInfoDao extends JpaAbstractSearchTextDao<OtaPackageInf
}
@Override
public PageData<OtaPackageInfo> findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, boolean hasData, PageLink pageLink) {
public PageData<OtaPackageInfo> findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, PageLink pageLink) {
return DaoUtil.toPageData(otaPackageInfoRepository
.findAllByTenantIdAndTypeAndDeviceProfileIdAndHasData(
tenantId.getId(),
deviceProfileId.getId(),
otaPackageType,
hasData,
Objects.toString(pageLink.getTextSearch(), ""),
DaoUtil.toPageable(pageLink)));
}

9
dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageInfoRepository.java

@ -26,27 +26,26 @@ import org.thingsboard.server.dao.model.sql.OtaPackageInfoEntity;
import java.util.UUID;
public interface OtaPackageInfoRepository extends CrudRepository<OtaPackageInfoEntity, UUID> {
@Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM OtaPackageEntity f WHERE " +
@Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.url, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, CASE WHEN (f.data IS NOT NULL OR f.url IS NOT NULL) THEN true ELSE false END) FROM OtaPackageEntity f WHERE " +
"f.tenantId = :tenantId " +
"AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
Page<OtaPackageInfoEntity> findAllByTenantId(@Param("tenantId") UUID tenantId,
@Param("searchText") String searchText,
Pageable pageable);
@Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM OtaPackageEntity f WHERE " +
@Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.url, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, true) FROM OtaPackageEntity f WHERE " +
"f.tenantId = :tenantId " +
"AND f.deviceProfileId = :deviceProfileId " +
"AND f.type = :type " +
"AND ((f.data IS NOT NULL AND :hasData = true) OR (f.data IS NULL AND :hasData = false ))" +
"AND (f.data IS NOT NULL OR f.url IS NOT NULL) " +
"AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
Page<OtaPackageInfoEntity> findAllByTenantIdAndTypeAndDeviceProfileIdAndHasData(@Param("tenantId") UUID tenantId,
@Param("deviceProfileId") UUID deviceProfileId,
@Param("type") OtaPackageType type,
@Param("hasData") boolean hasData,
@Param("searchText") String searchText,
Pageable pageable);
@Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM OtaPackageEntity f WHERE f.id = :id")
@Query("SELECT new OtaPackageInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.url, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, CASE WHEN (f.data IS NOT NULL OR f.url IS NOT NULL) THEN true ELSE false END) FROM OtaPackageEntity f WHERE f.id = :id")
OtaPackageInfoEntity findOtaPackageInfoById(@Param("id") UUID id);
@Query(value = "SELECT exists(SELECT * " +

5
dao/src/main/java/org/thingsboard/server/dao/sql/ota/OtaPackageRepository.java

@ -15,10 +15,15 @@
*/
package org.thingsboard.server.dao.sql.ota;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.dao.model.sql.OtaPackageEntity;
import org.thingsboard.server.dao.model.sql.OtaPackageInfoEntity;
import java.util.UUID;
public interface OtaPackageRepository extends CrudRepository<OtaPackageEntity, UUID> {
@Query(value = "SELECT COALESCE(SUM(ota.data_size), 0) FROM ota_package ota WHERE ota.tenant_id = :tenantId AND ota.data IS NOT NULL", nativeQuery = true)
Long sumDataSizeByTenantId(@Param("tenantId") UUID tenantId);
}

4
dao/src/main/java/org/thingsboard/server/dao/sql/resource/JpaTbResourceDao.java

@ -92,4 +92,8 @@ public class JpaTbResourceDao extends JpaAbstractSearchTextDao<TbResourceEntity,
resourceType.name(), objectIds));
}
@Override
public Long sumDataSizeByTenantId(TenantId tenantId) {
return resourceRepository.sumDataSizeByTenantId(tenantId.getId());
}
}

3
dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java

@ -77,4 +77,7 @@ public interface TbResourceRepository extends CrudRepository<TbResourceEntity, U
@Param("systemAdminId") UUID sysAdminId,
@Param("resourceType") String resourceType,
@Param("resourceIds") String[] objectIds);
@Query(value = "SELECT COALESCE(SUM(LENGTH(r.data)), 0) FROM resource r WHERE r.tenant_id = :tenantId", nativeQuery = true)
Long sumDataSizeByTenantId(@Param("tenantId") UUID tenantId);
}

1
dao/src/main/resources/sql/schema-entities-hsql.sql

@ -168,6 +168,7 @@ CREATE TABLE IF NOT EXISTS ota_package (
type varchar(32) NOT NULL,
title varchar(255) NOT NULL,
version varchar(255) NOT NULL,
url varchar(255),
file_name varchar(255),
content_type varchar(255),
checksum_algorithm varchar(32),

1
dao/src/main/resources/sql/schema-entities.sql

@ -186,6 +186,7 @@ CREATE TABLE IF NOT EXISTS ota_package (
type varchar(32) NOT NULL,
title varchar(255) NOT NULL,
version varchar(255) NOT NULL,
url varchar(255),
file_name varchar(255),
content_type varchar(255),
checksum_algorithm varchar(32),

5
dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java

@ -59,6 +59,7 @@ import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.tenant.DefaultTbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
@ -158,10 +159,12 @@ public abstract class AbstractServiceTest {
@Autowired
protected ResourceService resourceService;
@Autowired
protected OtaPackageService otaPackageService;
@Autowired
protected DefaultTbTenantProfileCache tenantProfileCache;
public class IdComparator<D extends HasId> implements Comparator<D> {
@Override
public int compare(D o1, D o2) {

253
dao/src/test/java/org/thingsboard/server/dao/service/BaseOtaPackageServiceTest.java

@ -28,11 +28,13 @@ import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.nio.ByteBuffer;
@ -50,7 +52,9 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
private static final String CONTENT_TYPE = "text/plain";
private static final ChecksumAlgorithm CHECKSUM_ALGORITHM = ChecksumAlgorithm.SHA256;
private static final String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a";
private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{1});
private static final long DATA_SIZE = 1L;
private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{(int) DATA_SIZE});
private static final String URL = "http://firmware.test.org";
private IdComparator<OtaPackageInfo> idComparator = new IdComparator<>();
@ -78,6 +82,41 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
@After
public void after() {
tenantService.deleteTenant(tenantId);
tenantProfileService.deleteTenantProfiles(tenantId);
}
@Test
public void testSaveOtaPackageWithMaxSumDataSizeOutOfLimit() {
TenantProfile defaultTenantProfile = tenantProfileService.findDefaultTenantProfile(tenantId);
defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxOtaPackagesInBytes(DATA_SIZE).build());
tenantProfileService.saveTenantProfile(tenantId, defaultTenantProfile);
Assert.assertEquals(0, otaPackageService.sumDataSizeByTenantId(tenantId));
createFirmware(tenantId, "1");
Assert.assertEquals(1, otaPackageService.sumDataSizeByTenantId(tenantId));
thrown.expect(DataValidationException.class);
thrown.expectMessage(String.format("Failed to create the ota package, files size limit is exhausted %d bytes!", DATA_SIZE));
createFirmware(tenantId, "2");
}
@Test
public void sumDataSizeByTenantId() {
Assert.assertEquals(0, otaPackageService.sumDataSizeByTenantId(tenantId));
createFirmware(tenantId, "0.1");
Assert.assertEquals(1, otaPackageService.sumDataSizeByTenantId(tenantId));
int maxSumDataSize = 8;
List<OtaPackage> packages = new ArrayList<>(maxSumDataSize);
for (int i = 2; i <= maxSumDataSize; i++) {
packages.add(createFirmware(tenantId, "0." + i));
Assert.assertEquals(i, otaPackageService.sumDataSizeByTenantId(tenantId));
}
Assert.assertEquals(maxSumDataSize, otaPackageService.sumDataSizeByTenantId(tenantId));
}
@Test
@ -93,6 +132,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
firmware.setDataSize(DATA_SIZE);
OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware);
Assert.assertNotNull(savedFirmware);
@ -113,6 +153,35 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
otaPackageService.deleteOtaPackage(tenantId, savedFirmware.getId());
}
@Test
public void testSaveFirmwareWithUrl() {
OtaPackageInfo firmware = new OtaPackageInfo();
firmware.setTenantId(tenantId);
firmware.setDeviceProfileId(deviceProfileId);
firmware.setType(FIRMWARE);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setUrl(URL);
firmware.setDataSize(0L);
OtaPackageInfo savedFirmware = otaPackageService.saveOtaPackageInfo(firmware);
Assert.assertNotNull(savedFirmware);
Assert.assertNotNull(savedFirmware.getId());
Assert.assertTrue(savedFirmware.getCreatedTime() > 0);
Assert.assertEquals(firmware.getTenantId(), savedFirmware.getTenantId());
Assert.assertEquals(firmware.getTitle(), savedFirmware.getTitle());
Assert.assertEquals(firmware.getFileName(), savedFirmware.getFileName());
Assert.assertEquals(firmware.getContentType(), savedFirmware.getContentType());
savedFirmware.setAdditionalInfo(JacksonUtil.newObjectNode());
otaPackageService.saveOtaPackageInfo(savedFirmware);
OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId());
Assert.assertEquals(foundFirmware.getTitle(), savedFirmware.getTitle());
otaPackageService.deleteOtaPackage(tenantId, savedFirmware.getId());
}
@Test
public void testSaveFirmwareInfoAndUpdateWithData() {
OtaPackageInfo firmwareInfo = new OtaPackageInfo();
@ -141,6 +210,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
firmware.setDataSize(DATA_SIZE);
otaPackageService.saveOtaPackage(firmware);
@ -345,50 +415,15 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
@Test
public void testSaveFirmwareWithExistingTitleAndVersion() {
OtaPackage firmware = new OtaPackage();
firmware.setTenantId(tenantId);
firmware.setDeviceProfileId(deviceProfileId);
firmware.setType(FIRMWARE);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
otaPackageService.saveOtaPackage(firmware);
OtaPackage newFirmware = new OtaPackage();
newFirmware.setTenantId(tenantId);
newFirmware.setDeviceProfileId(deviceProfileId);
newFirmware.setType(FIRMWARE);
newFirmware.setTitle(TITLE);
newFirmware.setVersion(VERSION);
newFirmware.setFileName(FILE_NAME);
newFirmware.setContentType(CONTENT_TYPE);
newFirmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
newFirmware.setChecksum(CHECKSUM);
newFirmware.setData(DATA);
createFirmware(tenantId, VERSION);
thrown.expect(DataValidationException.class);
thrown.expectMessage("OtaPackage with such title and version already exists!");
otaPackageService.saveOtaPackage(newFirmware);
createFirmware(tenantId, VERSION);
}
@Test
public void testDeleteFirmwareWithReferenceByDevice() {
OtaPackage firmware = new OtaPackage();
firmware.setTenantId(tenantId);
firmware.setDeviceProfileId(deviceProfileId);
firmware.setType(FIRMWARE);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware);
OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
Device device = new Device();
device.setTenantId(tenantId);
@ -409,18 +444,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
@Test
public void testUpdateDeviceProfileId() {
OtaPackage firmware = new OtaPackage();
firmware.setTenantId(tenantId);
firmware.setDeviceProfileId(deviceProfileId);
firmware.setType(FIRMWARE);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware);
OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
try {
thrown.expect(DataValidationException.class);
@ -448,6 +472,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
firmware.setDataSize(DATA_SIZE);
OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware);
savedDeviceProfile.setFirmwareId(savedFirmware.getId());
@ -465,18 +490,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
@Test
public void testFindFirmwareById() {
OtaPackage firmware = new OtaPackage();
firmware.setTenantId(tenantId);
firmware.setDeviceProfileId(deviceProfileId);
firmware.setType(FIRMWARE);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware);
OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId());
Assert.assertNotNull(foundFirmware);
@ -502,18 +516,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
@Test
public void testDeleteFirmware() {
OtaPackage firmware = new OtaPackage();
firmware.setTenantId(tenantId);
firmware.setDeviceProfileId(deviceProfileId);
firmware.setType(FIRMWARE);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware);
OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId());
Assert.assertNotNull(foundFirmware);
@ -526,23 +529,25 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
public void testFindTenantFirmwaresByTenantId() {
List<OtaPackageInfo> firmwares = new ArrayList<>();
for (int i = 0; i < 165; i++) {
OtaPackage firmware = new OtaPackage();
firmware.setTenantId(tenantId);
firmware.setDeviceProfileId(deviceProfileId);
firmware.setType(FIRMWARE);
firmware.setTitle(TITLE);
firmware.setVersion(VERSION + i);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
OtaPackageInfo info = new OtaPackageInfo(otaPackageService.saveOtaPackage(firmware));
OtaPackageInfo info = new OtaPackageInfo(createFirmware(tenantId, VERSION + i));
info.setHasData(true);
firmwares.add(info);
}
OtaPackageInfo firmwareWithUrl = new OtaPackageInfo();
firmwareWithUrl.setTenantId(tenantId);
firmwareWithUrl.setDeviceProfileId(deviceProfileId);
firmwareWithUrl.setType(FIRMWARE);
firmwareWithUrl.setTitle(TITLE);
firmwareWithUrl.setVersion(VERSION);
firmwareWithUrl.setUrl(URL);
firmwareWithUrl.setDataSize(0L);
OtaPackageInfo savedFwWithUrl = otaPackageService.saveOtaPackageInfo(firmwareWithUrl);
savedFwWithUrl.setHasData(true);
firmwares.add(savedFwWithUrl);
List<OtaPackageInfo> loadedFirmwares = new ArrayList<>();
PageLink pageLink = new PageLink(16);
PageData<OtaPackageInfo> pageData;
@ -571,58 +576,38 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
public void testFindTenantFirmwaresByTenantIdAndHasData() {
List<OtaPackageInfo> firmwares = new ArrayList<>();
for (int i = 0; i < 165; i++) {
OtaPackageInfo firmwareInfo = new OtaPackageInfo();
firmwareInfo.setTenantId(tenantId);
firmwareInfo.setDeviceProfileId(deviceProfileId);
firmwareInfo.setType(FIRMWARE);
firmwareInfo.setTitle(TITLE);
firmwareInfo.setVersion(VERSION + i);
firmwareInfo.setFileName(FILE_NAME);
firmwareInfo.setContentType(CONTENT_TYPE);
firmwareInfo.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmwareInfo.setChecksum(CHECKSUM);
firmwareInfo.setDataSize((long) DATA.array().length);
firmwares.add(otaPackageService.saveOtaPackageInfo(firmwareInfo));
firmwares.add(new OtaPackageInfo(otaPackageService.saveOtaPackage(createFirmware(tenantId, VERSION + i))));
}
OtaPackageInfo firmwareWithUrl = new OtaPackageInfo();
firmwareWithUrl.setTenantId(tenantId);
firmwareWithUrl.setDeviceProfileId(deviceProfileId);
firmwareWithUrl.setType(FIRMWARE);
firmwareWithUrl.setTitle(TITLE);
firmwareWithUrl.setVersion(VERSION);
firmwareWithUrl.setUrl(URL);
firmwareWithUrl.setDataSize(0L);
OtaPackageInfo savedFwWithUrl = otaPackageService.saveOtaPackageInfo(firmwareWithUrl);
savedFwWithUrl.setHasData(true);
firmwares.add(savedFwWithUrl);
List<OtaPackageInfo> loadedFirmwares = new ArrayList<>();
PageLink pageLink = new PageLink(16);
PageData<OtaPackageInfo> pageData;
do {
pageData = otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, FIRMWARE, false, pageLink);
pageData = otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, FIRMWARE, pageLink);
loadedFirmwares.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
}
} while (pageData.hasNext());
Collections.sort(firmwares, idComparator);
Collections.sort(loadedFirmwares, idComparator);
Assert.assertEquals(firmwares, loadedFirmwares);
firmwares.forEach(f -> {
OtaPackage firmware = new OtaPackage(f.getId());
firmware.setCreatedTime(f.getCreatedTime());
firmware.setTenantId(f.getTenantId());
firmware.setDeviceProfileId(deviceProfileId);
firmware.setType(FIRMWARE);
firmware.setTitle(f.getTitle());
firmware.setVersion(f.getVersion());
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
firmware.setDataSize((long) DATA.array().length);
otaPackageService.saveOtaPackage(firmware);
f.setHasData(true);
});
loadedFirmwares = new ArrayList<>();
pageLink = new PageLink(16);
do {
pageData = otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, FIRMWARE, true, pageLink);
pageData = otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, FIRMWARE, pageLink);
loadedFirmwares.addAll(pageData.getData());
if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink();
@ -642,4 +627,20 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
Assert.assertTrue(pageData.getData().isEmpty());
}
private OtaPackage createFirmware(TenantId tenantId, String version) {
OtaPackage firmware = new OtaPackage();
firmware.setTenantId(tenantId);
firmware.setDeviceProfileId(deviceProfileId);
firmware.setType(FIRMWARE);
firmware.setTitle(TITLE);
firmware.setVersion(version);
firmware.setFileName(FILE_NAME);
firmware.setContentType(CONTENT_TYPE);
firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
firmware.setChecksum(CHECKSUM);
firmware.setData(DATA);
firmware.setDataSize(DATA_SIZE);
return otaPackageService.saveOtaPackage(firmware);
}
}

6
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java

@ -47,7 +47,9 @@ import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.nosql.CassandraStatementTask;
import org.thingsboard.server.dao.nosql.TbResultSetFuture;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
@ -202,6 +204,10 @@ public interface TbContext {
EntityViewService getEntityViewService();
ResourceService getResourceService();
OtaPackageService getOtaPackageService();
RuleEngineDeviceProfileCache getDeviceProfileCache();
EdgeService getEdgeService();

2
ui-ngx/src/app/core/http/ota-package.service.ts

@ -40,7 +40,7 @@ export class OtaPackageService {
public getOtaPackagesInfoByDeviceProfileId(pageLink: PageLink, deviceProfileId: string, type: OtaUpdateType,
hasData = true, config?: RequestConfig): Observable<PageData<OtaPackageInfo>> {
const url = `/api/otaPackages/${deviceProfileId}/${type}/${hasData}${pageLink.toQuery()}`;
const url = `/api/otaPackages/${deviceProfileId}/${type}${pageLink.toQuery()}`;
return this.http.get<PageData<OtaPackageInfo>>(url, defaultHttpOptionsFromConfig(config));
}

24
ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html

@ -88,6 +88,30 @@
{{ 'tenant-profile.maximum-rule-chains-range' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>tenant-profile.maximum-resources-sum-data-size</mat-label>
<input matInput required min="0" step="1"
formControlName="maxResourcesInBytes"
type="number">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxResourcesInBytes').hasError('required')">
{{ 'tenant-profile.maximum-resources-sum-data-size-required' | translate}}
</mat-error>
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxResourcesInBytes').hasError('min')">
{{ 'tenant-profile.maximum-resources-sum-data-size-range' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>tenant-profile.maximum-ota-packages-sum-data-size</mat-label>
<input matInput required min="0" step="1"
formControlName="maxOtaPackagesInBytes"
type="number">
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxOtaPackagesInBytes').hasError('required')">
{{ 'tenant-profile.maximum-ota-packages-sum-data-size-required' | translate}}
</mat-error>
<mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxOtaPackagesInBytes').hasError('min')">
{{ 'tenant-profile.maximum-ota-packages-sum-data-size-range' | translate}}
</mat-error>
</mat-form-field>
<mat-form-field class="mat-block">
<mat-label translate>tenant-profile.max-transport-messages</mat-label>
<input matInput required min="0" step="1"

2
ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts

@ -59,6 +59,8 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA
maxUsers: [null, [Validators.required, Validators.min(0)]],
maxDashboards: [null, [Validators.required, Validators.min(0)]],
maxRuleChains: [null, [Validators.required, Validators.min(0)]],
maxResourcesInBytes: [null, [Validators.required, Validators.min(0)]],
maxOtaPackagesInBytes: [null, [Validators.required, Validators.min(0)]],
transportTenantMsgRateLimit: [null, []],
transportTenantTelemetryMsgRateLimit: [null, []],
transportTenantTelemetryDataPointsRateLimit: [null, []],

5
ui-ngx/src/app/shared/models/tenant.model.ts

@ -18,6 +18,7 @@ import { ContactBased } from '@shared/models/contact-based.model';
import { TenantId } from './id/tenant-id';
import { TenantProfileId } from '@shared/models/id/tenant-profile-id';
import { BaseData } from '@shared/models/base-data';
import {Validators} from "@angular/forms";
export enum TenantProfileType {
DEFAULT = 'DEFAULT'
@ -30,6 +31,8 @@ export interface DefaultTenantProfileConfiguration {
maxUsers: number;
maxDashboards: number;
maxRuleChains: number;
maxResourcesInBytes: number;
maxOtaPackagesInBytes: number;
transportTenantMsgRateLimit?: string;
transportTenantTelemetryMsgRateLimit?: string;
@ -68,6 +71,8 @@ export function createTenantProfileConfiguration(type: TenantProfileType): Tenan
maxUsers: 0,
maxDashboards: 0,
maxRuleChains: 0,
maxResourcesInBytes: 0,
maxOtaPackagesInBytes: 0,
maxTransportMessages: 0,
maxTransportDataPoints: 0,
maxREExecutions: 0,

6
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2497,6 +2497,12 @@
"maximum-rule-chains": "Maximum number of rule chains (0 - unlimited)",
"maximum-rule-chains-required": "Maximum number of rule chains is required.",
"maximum-rule-chains-range": "Maximum number of rule chains can't be negative",
"maximum-resources-sum-data-size": "Maximum sum of resource files size in bytes (0 - unlimited)",
"maximum-resources-sum-data-size-required": "Maximum sum of resource files size is required.",
"maximum-resources-sum-data-size-range": "Maximum sum of resource files size can`t be negative",
"maximum-ota-packages-sum-data-size": "Maximum sum of ota package files size in bytes (0 - unlimited)",
"maximum-ota-package-sum-data-size-required": "Maximum sum of ota package files size is required.",
"maximum-ota-package-sum-data-size-range": "Maximum sum of ota package files size can`t be negative",
"transport-tenant-msg-rate-limit": "Transport tenant messages rate limit.",
"transport-tenant-telemetry-msg-rate-limit": "Transport tenant telemetry messages rate limit.",
"transport-tenant-telemetry-data-points-rate-limit": "Transport tenant telemetry data points rate limit.",

Loading…
Cancel
Save