created data limits for resources and otaPackages, added url for the otaPackage

This commit is contained in:
YevhenBondarenko 2021-06-03 18:34:39 +03:00
parent b978f0f7ab
commit 43b4f4461d
42 changed files with 491 additions and 189 deletions

View File

@ -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),

View File

@ -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;

View File

@ -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();

View File

@ -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);
}

View File

@ -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,21 @@ 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 (StringUtils.isEmpty(otaPackage.getUrl())) {
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)));
} else {
List<String> attrToRemove = new ArrayList<>();
attrToRemove.add(getAttributeKey(otaPackageType, SIZE));
attrToRemove.add(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM));
attrToRemove.add(getAttributeKey(otaPackageType, CHECKSUM));
remove(device, otaPackageType, attrToRemove);
attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, URL), otaPackage.getUrl())));
}
telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() {
@Override
@ -299,20 +312,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);
}
});
}

View File

@ -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)) {

View File

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

View File

@ -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());

View File

@ -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();

View File

@ -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);
}

View File

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

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -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";

View File

@ -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);

View File

@ -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);

View File

@ -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 (StringUtils.isEmpty(otaPackage.getUrl())) {
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);
}
}
};

View File

@ -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);
}

View File

@ -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);

View File

@ -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())) {

View File

@ -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);

View File

@ -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();

View File

@ -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());
}
}

View File

@ -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)));
}

View File

@ -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 * " +

View File

@ -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);
}

View File

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

View File

@ -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);
}

View File

@ -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),

View File

@ -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),

View File

@ -24,10 +24,10 @@ import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClassnameFilters({
"org.thingsboard.server.dao.service.sql.*SqlTest",
"org.thingsboard.server.dao.service.attributes.sql.*SqlTest",
"org.thingsboard.server.dao.service.event.sql.*SqlTest",
"org.thingsboard.server.dao.service.timeseries.sql.*SqlTest"
"org.thingsboard.server.dao.service.sql.OtaPackageServiceSqlTest",
// "org.thingsboard.server.dao.service.attributes.sql.*SqlTest",
// "org.thingsboard.server.dao.service.event.sql.*SqlTest",
// "org.thingsboard.server.dao.service.timeseries.sql.*SqlTest"
})
public class SqlDaoServiceTestSuite {

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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));
}

View File

@ -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"

View File

@ -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, []],

View File

@ -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,

View File

@ -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.",