Commit 43b4f4461d22784ffd19a4fda38523c44dc926c7

Authored by YevhenBondarenko
1 parent b978f0f7

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

Showing 42 changed files with 491 additions and 189 deletions
... ... @@ -67,6 +67,7 @@ CREATE TABLE IF NOT EXISTS ota_package (
67 67 type varchar(32) NOT NULL,
68 68 title varchar(255) NOT NULL,
69 69 version varchar(255) NOT NULL,
  70 + url varchar(255),
70 71 file_name varchar(255),
71 72 content_type varchar(255),
72 73 checksum_algorithm varchar(32),
... ...
... ... @@ -60,7 +60,9 @@ import org.thingsboard.server.dao.edge.EdgeService;
60 60 import org.thingsboard.server.dao.entityview.EntityViewService;
61 61 import org.thingsboard.server.dao.event.EventService;
62 62 import org.thingsboard.server.dao.nosql.CassandraBufferedRateExecutor;
  63 +import org.thingsboard.server.dao.ota.OtaPackageService;
63 64 import org.thingsboard.server.dao.relation.RelationService;
  65 +import org.thingsboard.server.dao.resource.ResourceService;
64 66 import org.thingsboard.server.dao.rule.RuleChainService;
65 67 import org.thingsboard.server.dao.rule.RuleNodeStateService;
66 68 import org.thingsboard.server.dao.tenant.TenantProfileService;
... ... @@ -311,6 +313,14 @@ public class ActorSystemContext {
311 313 @Autowired(required = false)
312 314 @Getter private EdgeRpcService edgeRpcService;
313 315
  316 + @Lazy
  317 + @Autowired(required = false)
  318 + @Getter private ResourceService resourceService;
  319 +
  320 + @Lazy
  321 + @Autowired(required = false)
  322 + @Getter private OtaPackageService otaPackageService;
  323 +
314 324 @Value("${actors.session.max_concurrent_sessions_per_device:1}")
315 325 @Getter
316 326 private long maxConcurrentSessionsPerDevice;
... ...
... ... @@ -69,7 +69,9 @@ import org.thingsboard.server.dao.edge.EdgeService;
69 69 import org.thingsboard.server.dao.entityview.EntityViewService;
70 70 import org.thingsboard.server.dao.nosql.CassandraStatementTask;
71 71 import org.thingsboard.server.dao.nosql.TbResultSetFuture;
  72 +import org.thingsboard.server.dao.ota.OtaPackageService;
72 73 import org.thingsboard.server.dao.relation.RelationService;
  74 +import org.thingsboard.server.dao.resource.ResourceService;
73 75 import org.thingsboard.server.dao.rule.RuleChainService;
74 76 import org.thingsboard.server.dao.tenant.TenantService;
75 77 import org.thingsboard.server.dao.timeseries.TimeseriesService;
... ... @@ -487,6 +489,16 @@ class DefaultTbContext implements TbContext {
487 489 }
488 490
489 491 @Override
  492 + public ResourceService getResourceService() {
  493 + return mainCtx.getResourceService();
  494 + }
  495 +
  496 + @Override
  497 + public OtaPackageService getOtaPackageService() {
  498 + return mainCtx.getOtaPackageService();
  499 + }
  500 +
  501 + @Override
490 502 public RuleEngineDeviceProfileCache getDeviceProfileCache() {
491 503 return mainCtx.getDeviceProfileCache();
492 504 }
... ...
... ... @@ -64,6 +64,10 @@ public class OtaPackageController extends BaseController {
64 64 OtaPackageId otaPackageId = new OtaPackageId(toUUID(strOtaPackageId));
65 65 OtaPackage otaPackage = checkOtaPackageId(otaPackageId, Operation.READ);
66 66
  67 + if (otaPackage.hasUrl()) {
  68 + return ResponseEntity.badRequest().build();
  69 + }
  70 +
67 71 ByteArrayResource resource = new ByteArrayResource(otaPackage.getData().array());
68 72 return ResponseEntity.ok()
69 73 .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + otaPackage.getFileName())
... ... @@ -182,11 +186,10 @@ public class OtaPackageController extends BaseController {
182 186 }
183 187
184 188 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
185   - @RequestMapping(value = "/otaPackages/{deviceProfileId}/{type}/{hasData}", method = RequestMethod.GET)
  189 + @RequestMapping(value = "/otaPackages/{deviceProfileId}/{type}", method = RequestMethod.GET)
186 190 @ResponseBody
187 191 public PageData<OtaPackageInfo> getOtaPackages(@PathVariable("deviceProfileId") String strDeviceProfileId,
188 192 @PathVariable("type") String strType,
189   - @PathVariable("hasData") boolean hasData,
190 193 @RequestParam int pageSize,
191 194 @RequestParam int page,
192 195 @RequestParam(required = false) String textSearch,
... ... @@ -197,7 +200,7 @@ public class OtaPackageController extends BaseController {
197 200 try {
198 201 PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
199 202 return checkNotNull(otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(getTenantId(),
200   - new DeviceProfileId(toUUID(strDeviceProfileId)), OtaPackageType.valueOf(strType), hasData, pageLink));
  203 + new DeviceProfileId(toUUID(strDeviceProfileId)), OtaPackageType.valueOf(strType), pageLink));
201 204 } catch (Exception e) {
202 205 throw handleException(e);
203 206 }
... ...
... ... @@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.DataConstants;
24 24 import org.thingsboard.server.common.data.Device;
25 25 import org.thingsboard.server.common.data.DeviceProfile;
26 26 import org.thingsboard.server.common.data.OtaPackageInfo;
  27 +import org.thingsboard.server.common.data.StringUtils;
27 28 import org.thingsboard.server.common.data.id.DeviceId;
28 29 import org.thingsboard.server.common.data.id.OtaPackageId;
29 30 import org.thingsboard.server.common.data.id.TenantId;
... ... @@ -65,6 +66,7 @@ import static org.thingsboard.server.common.data.ota.OtaPackageKey.SIZE;
65 66 import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE;
66 67 import static org.thingsboard.server.common.data.ota.OtaPackageKey.TITLE;
67 68 import static org.thingsboard.server.common.data.ota.OtaPackageKey.TS;
  69 +import static org.thingsboard.server.common.data.ota.OtaPackageKey.URL;
68 70 import static org.thingsboard.server.common.data.ota.OtaPackageKey.VERSION;
69 71 import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
70 72 import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE;
... ... @@ -261,11 +263,12 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
261 263 }
262 264
263 265
264   - private void update(Device device, OtaPackageInfo firmware, long ts) {
  266 + private void update(Device device, OtaPackageInfo otaPackage, long ts) {
265 267 TenantId tenantId = device.getTenantId();
266 268 DeviceId deviceId = device.getId();
  269 + OtaPackageType otaPackageType = otaPackage.getType();
267 270
268   - BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(firmware.getType(), STATE), OtaPackageUpdateStatus.INITIATED.name()));
  271 + BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(getTelemetryKey(otaPackageType, STATE), OtaPackageUpdateStatus.INITIATED.name()));
269 272
270 273 telemetryService.saveAndNotify(tenantId, deviceId, Collections.singletonList(status), new FutureCallback<>() {
271 274 @Override
... ... @@ -280,11 +283,21 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
280 283 });
281 284
282 285 List<AttributeKvEntry> attributes = new ArrayList<>();
283   - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), TITLE), firmware.getTitle())));
284   - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), VERSION), firmware.getVersion())));
285   - attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(firmware.getType(), SIZE), firmware.getDataSize())));
286   - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM_ALGORITHM), firmware.getChecksumAlgorithm().name())));
287   - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(firmware.getType(), CHECKSUM), firmware.getChecksum())));
  286 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, TITLE), otaPackage.getTitle())));
  287 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, VERSION), otaPackage.getVersion())));
  288 + if (StringUtils.isEmpty(otaPackage.getUrl())) {
  289 + attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(getAttributeKey(otaPackageType, SIZE), otaPackage.getDataSize())));
  290 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM), otaPackage.getChecksumAlgorithm().name())));
  291 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, CHECKSUM), otaPackage.getChecksum())));
  292 + remove(device, otaPackageType, Collections.singletonList(getAttributeKey(otaPackageType, URL)));
  293 + } else {
  294 + List<String> attrToRemove = new ArrayList<>();
  295 + attrToRemove.add(getAttributeKey(otaPackageType, SIZE));
  296 + attrToRemove.add(getAttributeKey(otaPackageType, CHECKSUM_ALGORITHM));
  297 + attrToRemove.add(getAttributeKey(otaPackageType, CHECKSUM));
  298 + remove(device, otaPackageType, attrToRemove);
  299 + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(getAttributeKey(otaPackageType, URL), otaPackage.getUrl())));
  300 + }
288 301
289 302 telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() {
290 303 @Override
... ... @@ -299,20 +312,24 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService {
299 312 });
300 313 }
301 314
302   - private void remove(Device device, OtaPackageType firmwareType) {
303   - telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, OtaPackageUtil.getAttributeKeys(firmwareType),
  315 + private void remove(Device device, OtaPackageType otaPackageType) {
  316 + remove(device, otaPackageType, OtaPackageUtil.getAttributeKeys(otaPackageType));
  317 + }
  318 +
  319 + private void remove(Device device, OtaPackageType otaPackageType, List<String> attributesKeys) {
  320 + telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, attributesKeys,
304 321 new FutureCallback<>() {
305 322 @Override
306 323 public void onSuccess(@Nullable Void tmp) {
307   - log.trace("[{}] Success remove target firmware attributes!", device.getId());
  324 + log.trace("[{}] Success remove target {} attributes!", device.getId(), otaPackageType);
308 325 Set<AttributeKey> keysToNotify = new HashSet<>();
309   - OtaPackageUtil.ALL_FW_ATTRIBUTE_KEYS.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key)));
  326 + attributesKeys.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key)));
310 327 tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), device.getId(), keysToNotify), null);
311 328 }
312 329
313 330 @Override
314 331 public void onFailure(Throwable t) {
315   - log.error("[{}] Failed to remove target firmware attributes!", device.getId(), t);
  332 + log.error("[{}] Failed to remove target {} attributes!", device.getId(), otaPackageType, t);
316 333 }
317 334 });
318 335 }
... ...
... ... @@ -157,6 +157,11 @@ public class DefaultTbResourceService implements TbResourceService {
157 157 resourceService.deleteResourcesByTenantId(tenantId);
158 158 }
159 159
  160 + @Override
  161 + public long sumDataSizeByTenantId(TenantId tenantId) {
  162 + return resourceService.sumDataSizeByTenantId(tenantId);
  163 + }
  164 +
160 165 private Comparator<? super LwM2mObject> getComparator(String sortProperty, String sortOrder) {
161 166 Comparator<LwM2mObject> comparator;
162 167 if ("name".equals(sortProperty)) {
... ...
... ... @@ -55,4 +55,5 @@ public interface TbResourceService {
55 55
56 56 void deleteResourcesByTenantId(TenantId tenantId);
57 57
  58 + long sumDataSizeByTenantId(TenantId tenantId);
58 59 }
... ...
... ... @@ -536,6 +536,9 @@ public class DefaultTransportApiService implements TransportApiService {
536 536
537 537 if (otaPackageInfo == null) {
538 538 builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND);
  539 + } else if (otaPackageInfo.hasUrl()) {
  540 + builder.setResponseStatus(TransportProtos.ResponseStatus.FAILURE);
  541 + log.trace("[{}] Can`t send OtaPackage with URL data!", otaPackageInfo.getId());
539 542 } else {
540 543 builder.setResponseStatus(TransportProtos.ResponseStatus.SUCCESS);
541 544 builder.setOtaPackageIdMSB(otaPackageId.getId().getMostSignificantBits());
... ...
... ... @@ -19,17 +19,24 @@ import com.datastax.oss.driver.api.core.uuid.Uuids;
19 19 import org.junit.After;
20 20 import org.junit.Assert;
21 21 import org.junit.Before;
  22 +import org.junit.Rule;
22 23 import org.junit.Test;
  24 +import org.junit.rules.ExpectedException;
23 25 import org.springframework.beans.factory.annotation.Autowired;
  26 +import org.thingsboard.server.common.data.EntityInfo;
  27 +import org.thingsboard.server.common.data.OtaPackage;
24 28 import org.thingsboard.server.common.data.ResourceType;
25 29 import org.thingsboard.server.common.data.TbResource;
26 30 import org.thingsboard.server.common.data.TbResourceInfo;
27 31 import org.thingsboard.server.common.data.Tenant;
  32 +import org.thingsboard.server.common.data.TenantProfile;
28 33 import org.thingsboard.server.common.data.User;
  34 +import org.thingsboard.server.common.data.exception.ThingsboardException;
29 35 import org.thingsboard.server.common.data.id.TenantId;
30 36 import org.thingsboard.server.common.data.page.PageData;
31 37 import org.thingsboard.server.common.data.page.PageLink;
32 38 import org.thingsboard.server.common.data.security.Authority;
  39 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
33 40 import org.thingsboard.server.controller.AbstractControllerTest;
34 41 import org.thingsboard.server.dao.exception.DataValidationException;
35 42 import org.thingsboard.server.dao.service.DaoSqlTest;
... ... @@ -109,6 +116,64 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
109 116 .andExpect(status().isOk());
110 117 }
111 118
  119 + @Rule
  120 + public ExpectedException thrown = ExpectedException.none();
  121 +
  122 + @Test
  123 + public void testSaveResourceWithMaxSumDataSizeOutOfLimit() throws Exception {
  124 + loginSysAdmin();
  125 + long limit = 1;
  126 + EntityInfo defaultTenantProfileInfo = doGet("/api/tenantProfileInfo/default", EntityInfo.class);
  127 + TenantProfile defaultTenantProfile = doGet("/api/tenantProfile/" + defaultTenantProfileInfo.getId().getId().toString(), TenantProfile.class);
  128 + defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxResourcesInBytes(limit).build());
  129 + doPost("/api/tenantProfile", defaultTenantProfile, TenantProfile.class);
  130 +
  131 + loginTenantAdmin();
  132 +
  133 + Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId));
  134 +
  135 + createResource("test", DEFAULT_FILE_NAME);
  136 +
  137 + Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
  138 +
  139 + try {
  140 + thrown.expect(DataValidationException.class);
  141 + thrown.expectMessage(String.format("Failed to create the tb resource, files size limit is exhausted %d bytes!", limit));
  142 + createResource("test1", 1 + DEFAULT_FILE_NAME);
  143 + } finally {
  144 + defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxResourcesInBytes(0).build());
  145 + loginSysAdmin();
  146 + doPost("/api/tenantProfile", defaultTenantProfile, TenantProfile.class);
  147 + }
  148 + }
  149 +
  150 + @Test
  151 + public void sumDataSizeByTenantId() throws ThingsboardException {
  152 + Assert.assertEquals(0, resourceService.sumDataSizeByTenantId(tenantId));
  153 +
  154 + createResource("test", DEFAULT_FILE_NAME);
  155 + Assert.assertEquals(1, resourceService.sumDataSizeByTenantId(tenantId));
  156 +
  157 + int maxSumDataSize = 8;
  158 +
  159 + for (int i = 2; i <= maxSumDataSize; i++) {
  160 + createResource("test" + i, i + DEFAULT_FILE_NAME);
  161 + Assert.assertEquals(i, resourceService.sumDataSizeByTenantId(tenantId));
  162 + }
  163 +
  164 + Assert.assertEquals(maxSumDataSize, resourceService.sumDataSizeByTenantId(tenantId));
  165 + }
  166 +
  167 + private TbResource createResource(String title, String filename) throws ThingsboardException {
  168 + TbResource resource = new TbResource();
  169 + resource.setTenantId(tenantId);
  170 + resource.setTitle(title);
  171 + resource.setResourceType(ResourceType.JKS);
  172 + resource.setFileName(filename);
  173 + resource.setData("1");
  174 + return resourceService.saveResource(resource);
  175 + }
  176 +
112 177 @Test
113 178 public void testSaveTbResource() throws Exception {
114 179 TbResource resource = new TbResource();
... ...
... ... @@ -44,9 +44,11 @@ public interface OtaPackageService {
44 44
45 45 PageData<OtaPackageInfo> findTenantOtaPackagesByTenantId(TenantId tenantId, PageLink pageLink);
46 46
47   - PageData<OtaPackageInfo> findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, boolean hasData, PageLink pageLink);
  47 + PageData<OtaPackageInfo> findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, PageLink pageLink);
48 48
49 49 void deleteOtaPackage(TenantId tenantId, OtaPackageId otaPackageId);
50 50
51 51 void deleteOtaPackagesByTenantId(TenantId tenantId);
  52 +
  53 + long sumDataSizeByTenantId(TenantId tenantId);
52 54 }
... ...
... ... @@ -49,5 +49,5 @@ public interface ResourceService {
49 49
50 50 void deleteResourcesByTenantId(TenantId tenantId);
51 51
52   -
  52 + long sumDataSizeByTenantId(TenantId tenantId);
53 53 }
... ...
... ... @@ -37,8 +37,8 @@ public class OtaPackage extends OtaPackageInfo {
37 37 super(id);
38 38 }
39 39
40   - public OtaPackage(OtaPackage firmware) {
41   - super(firmware);
42   - this.data = firmware.getData();
  40 + public OtaPackage(OtaPackage otaPackage) {
  41 + super(otaPackage);
  42 + this.data = otaPackage.getData();
43 43 }
44 44 }
... ...
... ... @@ -37,6 +37,7 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
37 37 private OtaPackageType type;
38 38 private String title;
39 39 private String version;
  40 + private String url;
40 41 private boolean hasData;
41 42 private String fileName;
42 43 private String contentType;
... ... @@ -60,6 +61,7 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
60 61 this.type = otaPackageInfo.getType();
61 62 this.title = otaPackageInfo.getTitle();
62 63 this.version = otaPackageInfo.getVersion();
  64 + this.url = otaPackageInfo.getUrl();
63 65 this.hasData = otaPackageInfo.isHasData();
64 66 this.fileName = otaPackageInfo.getFileName();
65 67 this.contentType = otaPackageInfo.getContentType();
... ... @@ -78,4 +80,9 @@ public class OtaPackageInfo extends SearchTextBasedWithAdditionalInfo<OtaPackage
78 80 public String getName() {
79 81 return title;
80 82 }
  83 +
  84 + @JsonIgnore
  85 + public boolean hasUrl() {
  86 + return StringUtils.isNotEmpty(url);
  87 + }
81 88 }
... ...
... ... @@ -19,7 +19,7 @@ import lombok.Getter;
19 19
20 20 public enum OtaPackageKey {
21 21
22   - TITLE("title"), VERSION("version"), TS("ts"), STATE("state"), SIZE("size"), CHECKSUM("checksum"), CHECKSUM_ALGORITHM("checksum_algorithm");
  22 + TITLE("title"), VERSION("version"), TS("ts"), STATE("state"), SIZE("size"), CHECKSUM("checksum"), CHECKSUM_ALGORITHM("checksum_algorithm"), URL("url");
23 23
24 24 @Getter
25 25 private final String value;
... ...
... ... @@ -34,6 +34,8 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
34 34 private long maxUsers;
35 35 private long maxDashboards;
36 36 private long maxRuleChains;
  37 + private long maxResourcesInBytes;
  38 + private long maxOtaPackagesInBytes;
37 39
38 40 private String transportTenantMsgRateLimit;
39 41 private String transportTenantTelemetryMsgRateLimit;
... ...
  1 +/**
  2 + * Copyright © 2016-2021 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao;
  17 +
  18 +import org.thingsboard.server.common.data.id.TenantId;
  19 +
  20 +public interface TenantEntityWithDataDao {
  21 +
  22 + Long sumDataSizeByTenantId(TenantId tenantId);
  23 +}
... ...
... ... @@ -487,6 +487,7 @@ public class ModelConstants {
487 487 public static final String OTA_PACKAGE_TYPE_COLUMN = "type";
488 488 public static final String OTA_PACKAGE_TILE_COLUMN = TITLE_PROPERTY;
489 489 public static final String OTA_PACKAGE_VERSION_COLUMN = "version";
  490 + public static final String OTA_PACKAGE_URL_COLUMN = "url";
490 491 public static final String OTA_PACKAGE_FILE_NAME_COLUMN = "file_name";
491 492 public static final String OTA_PACKAGE_CONTENT_TYPE_COLUMN = "content_type";
492 493 public static final String OTA_PACKAGE_CHECKSUM_ALGORITHM_COLUMN = "checksum_algorithm";
... ...
... ... @@ -51,6 +51,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TABLE_
51 51 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TENANT_ID_COLUMN;
52 52 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TILE_COLUMN;
53 53 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TYPE_COLUMN;
  54 +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_URL_COLUMN;
54 55 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_VERSION_COLUMN;
55 56 import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
56 57
... ... @@ -77,6 +78,9 @@ public class OtaPackageEntity extends BaseSqlEntity<OtaPackage> implements Searc
77 78 @Column(name = OTA_PACKAGE_VERSION_COLUMN)
78 79 private String version;
79 80
  81 + @Column(name = OTA_PACKAGE_URL_COLUMN)
  82 + private String url;
  83 +
80 84 @Column(name = OTA_PACKAGE_FILE_NAME_COLUMN)
81 85 private String fileName;
82 86
... ... @@ -118,6 +122,7 @@ public class OtaPackageEntity extends BaseSqlEntity<OtaPackage> implements Searc
118 122 this.type = firmware.getType();
119 123 this.title = firmware.getTitle();
120 124 this.version = firmware.getVersion();
  125 + this.url = firmware.getUrl();
121 126 this.fileName = firmware.getFileName();
122 127 this.contentType = firmware.getContentType();
123 128 this.checksumAlgorithm = firmware.getChecksumAlgorithm();
... ... @@ -148,6 +153,7 @@ public class OtaPackageEntity extends BaseSqlEntity<OtaPackage> implements Searc
148 153 firmware.setType(type);
149 154 firmware.setTitle(title);
150 155 firmware.setVersion(version);
  156 + firmware.setUrl(url);
151 157 firmware.setFileName(fileName);
152 158 firmware.setContentType(contentType);
153 159 firmware.setChecksumAlgorithm(checksumAlgorithm);
... ...
... ... @@ -22,6 +22,7 @@ import org.hibernate.annotations.Type;
22 22 import org.hibernate.annotations.TypeDef;
23 23 import org.thingsboard.common.util.JacksonUtil;
24 24 import org.thingsboard.server.common.data.OtaPackageInfo;
  25 +import org.thingsboard.server.common.data.StringUtils;
25 26 import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
26 27 import org.thingsboard.server.common.data.ota.OtaPackageType;
27 28 import org.thingsboard.server.common.data.id.DeviceProfileId;
... ... @@ -50,6 +51,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TABLE_
50 51 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TENANT_ID_COLUMN;
51 52 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TILE_COLUMN;
52 53 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_TYPE_COLUMN;
  54 +import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_URL_COLUMN;
53 55 import static org.thingsboard.server.dao.model.ModelConstants.OTA_PACKAGE_VERSION_COLUMN;
54 56 import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
55 57
... ... @@ -76,6 +78,9 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
76 78 @Column(name = OTA_PACKAGE_VERSION_COLUMN)
77 79 private String version;
78 80
  81 + @Column(name = OTA_PACKAGE_URL_COLUMN)
  82 + private String url;
  83 +
79 84 @Column(name = OTA_PACKAGE_FILE_NAME_COLUMN)
80 85 private String fileName;
81 86
... ... @@ -116,6 +121,7 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
116 121 }
117 122 this.title = firmware.getTitle();
118 123 this.version = firmware.getVersion();
  124 + this.url = firmware.getUrl();
119 125 this.fileName = firmware.getFileName();
120 126 this.contentType = firmware.getContentType();
121 127 this.checksumAlgorithm = firmware.getChecksumAlgorithm();
... ... @@ -125,7 +131,7 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
125 131 }
126 132
127 133 public OtaPackageInfoEntity(UUID id, long createdTime, UUID tenantId, UUID deviceProfileId, OtaPackageType type, String title, String version,
128   - String fileName, String contentType, ChecksumAlgorithm checksumAlgorithm, String checksum, Long dataSize,
  134 + String url, String fileName, String contentType, ChecksumAlgorithm checksumAlgorithm, String checksum, Long dataSize,
129 135 Object additionalInfo, boolean hasData) {
130 136 this.id = id;
131 137 this.createdTime = createdTime;
... ... @@ -134,6 +140,7 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
134 140 this.type = type;
135 141 this.title = title;
136 142 this.version = version;
  143 + this.url = url;
137 144 this.fileName = fileName;
138 145 this.contentType = contentType;
139 146 this.checksumAlgorithm = checksumAlgorithm;
... ... @@ -164,6 +171,7 @@ public class OtaPackageInfoEntity extends BaseSqlEntity<OtaPackageInfo> implemen
164 171 firmware.setType(type);
165 172 firmware.setTitle(title);
166 173 firmware.setVersion(version);
  174 + firmware.setUrl(url);
167 175 firmware.setFileName(fileName);
168 176 firmware.setContentType(contentType);
169 177 firmware.setChecksumAlgorithm(checksumAlgorithm);
... ...
... ... @@ -20,28 +20,32 @@ import com.google.common.hash.Hashing;
20 20 import com.google.common.util.concurrent.ListenableFuture;
21 21 import lombok.RequiredArgsConstructor;
22 22 import lombok.extern.slf4j.Slf4j;
23   -import org.apache.commons.lang3.StringUtils;
24 23 import org.hibernate.exception.ConstraintViolationException;
  24 +import org.springframework.beans.factory.annotation.Autowired;
25 25 import org.springframework.cache.Cache;
26 26 import org.springframework.cache.CacheManager;
27 27 import org.springframework.cache.annotation.Cacheable;
  28 +import org.springframework.context.annotation.Lazy;
28 29 import org.springframework.stereotype.Service;
29 30 import org.thingsboard.server.cache.ota.OtaPackageDataCache;
30 31 import org.thingsboard.server.common.data.DeviceProfile;
31 32 import org.thingsboard.server.common.data.OtaPackage;
32 33 import org.thingsboard.server.common.data.OtaPackageInfo;
  34 +import org.thingsboard.server.common.data.StringUtils;
33 35 import org.thingsboard.server.common.data.Tenant;
34   -import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
35   -import org.thingsboard.server.common.data.ota.OtaPackageType;
36 36 import org.thingsboard.server.common.data.id.DeviceProfileId;
37 37 import org.thingsboard.server.common.data.id.OtaPackageId;
38 38 import org.thingsboard.server.common.data.id.TenantId;
  39 +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
  40 +import org.thingsboard.server.common.data.ota.OtaPackageType;
39 41 import org.thingsboard.server.common.data.page.PageData;
40 42 import org.thingsboard.server.common.data.page.PageLink;
  43 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
41 44 import org.thingsboard.server.dao.device.DeviceProfileDao;
42 45 import org.thingsboard.server.dao.exception.DataValidationException;
43 46 import org.thingsboard.server.dao.service.DataValidator;
44 47 import org.thingsboard.server.dao.service.PaginatedRemover;
  48 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
45 49 import org.thingsboard.server.dao.tenant.TenantDao;
46 50
47 51 import java.nio.ByteBuffer;
... ... @@ -50,6 +54,7 @@ import java.util.List;
50 54 import java.util.Optional;
51 55
52 56 import static org.thingsboard.server.common.data.CacheConstants.OTA_PACKAGE_CACHE;
  57 +import static org.thingsboard.server.common.data.EntityType.OTA_PACKAGE;
53 58 import static org.thingsboard.server.dao.service.Validator.validateId;
54 59 import static org.thingsboard.server.dao.service.Validator.validatePageLink;
55 60
... ... @@ -67,6 +72,10 @@ public class BaseOtaPackageService implements OtaPackageService {
67 72 private final CacheManager cacheManager;
68 73 private final OtaPackageDataCache otaPackageDataCache;
69 74
  75 + @Autowired
  76 + @Lazy
  77 + private TbTenantProfileCache tenantProfileCache;
  78 +
70 79 @Override
71 80 public OtaPackageInfo saveOtaPackageInfo(OtaPackageInfo otaPackageInfo) {
72 81 log.trace("Executing saveOtaPackageInfo [{}]", otaPackageInfo);
... ... @@ -172,11 +181,11 @@ public class BaseOtaPackageService implements OtaPackageService {
172 181 }
173 182
174 183 @Override
175   - public PageData<OtaPackageInfo> findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, boolean hasData, PageLink pageLink) {
176   - log.trace("Executing findTenantOtaPackagesByTenantIdAndHasData, tenantId [{}], hasData [{}] pageLink [{}]", tenantId, hasData, pageLink);
  184 + public PageData<OtaPackageInfo> findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, PageLink pageLink) {
  185 + log.trace("Executing findTenantOtaPackagesByTenantIdAndHasData, tenantId [{}], pageLink [{}]", tenantId, pageLink);
177 186 validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
178 187 validatePageLink(pageLink);
179   - return otaPackageInfoDao.findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, otaPackageType, hasData, pageLink);
  188 + return otaPackageInfoDao.findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, otaPackageType, pageLink);
180 189 }
181 190
182 191 @Override
... ... @@ -205,6 +214,11 @@ public class BaseOtaPackageService implements OtaPackageService {
205 214 }
206 215
207 216 @Override
  217 + public long sumDataSizeByTenantId(TenantId tenantId) {
  218 + return otaPackageDao.sumDataSizeByTenantId(tenantId);
  219 + }
  220 +
  221 + @Override
208 222 public void deleteOtaPackagesByTenantId(TenantId tenantId) {
209 223 log.trace("Executing deleteOtaPackagesByTenantId, tenantId [{}]", tenantId);
210 224 validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
... ... @@ -228,30 +242,42 @@ public class BaseOtaPackageService implements OtaPackageService {
228 242 private DataValidator<OtaPackage> otaPackageValidator = new DataValidator<>() {
229 243
230 244 @Override
  245 + protected void validateCreate(TenantId tenantId, OtaPackage otaPackage) {
  246 + DefaultTenantProfileConfiguration profileConfiguration =
  247 + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
  248 + long maxOtaPackagesInBytes = profileConfiguration.getMaxOtaPackagesInBytes();
  249 + validateMaxSumDataSizePerTenant(tenantId, otaPackageDao, maxOtaPackagesInBytes, otaPackage.getDataSize(), OTA_PACKAGE);
  250 + }
  251 +
  252 + @Override
231 253 protected void validateDataImpl(TenantId tenantId, OtaPackage otaPackage) {
232 254 validateImpl(otaPackage);
233 255
234   - if (StringUtils.isEmpty(otaPackage.getFileName())) {
235   - throw new DataValidationException("OtaPackage file name should be specified!");
236   - }
  256 + if (StringUtils.isEmpty(otaPackage.getUrl())) {
  257 + if (StringUtils.isEmpty(otaPackage.getFileName())) {
  258 + throw new DataValidationException("OtaPackage file name should be specified!");
  259 + }
237 260
238   - if (StringUtils.isEmpty(otaPackage.getContentType())) {
239   - throw new DataValidationException("OtaPackage content type should be specified!");
240   - }
  261 + if (StringUtils.isEmpty(otaPackage.getContentType())) {
  262 + throw new DataValidationException("OtaPackage content type should be specified!");
  263 + }
241 264
242   - if (otaPackage.getChecksumAlgorithm() == null) {
243   - throw new DataValidationException("OtaPackage checksum algorithm should be specified!");
244   - }
245   - if (StringUtils.isEmpty(otaPackage.getChecksum())) {
246   - throw new DataValidationException("OtaPackage checksum should be specified!");
247   - }
  265 + if (otaPackage.getChecksumAlgorithm() == null) {
  266 + throw new DataValidationException("OtaPackage checksum algorithm should be specified!");
  267 + }
  268 + if (StringUtils.isEmpty(otaPackage.getChecksum())) {
  269 + throw new DataValidationException("OtaPackage checksum should be specified!");
  270 + }
248 271
249   - String currentChecksum;
  272 + String currentChecksum;
250 273
251   - currentChecksum = generateChecksum(otaPackage.getChecksumAlgorithm(), otaPackage.getData());
  274 + currentChecksum = generateChecksum(otaPackage.getChecksumAlgorithm(), otaPackage.getData());
252 275
253   - if (!currentChecksum.equals(otaPackage.getChecksum())) {
254   - throw new DataValidationException("Wrong otaPackage file!");
  276 + if (!currentChecksum.equals(otaPackage.getChecksum())) {
  277 + throw new DataValidationException("Wrong otaPackage file!");
  278 + }
  279 + } else {
  280 + //TODO: validate url
255 281 }
256 282 }
257 283
... ... @@ -264,6 +290,13 @@ public class BaseOtaPackageService implements OtaPackageService {
264 290 if (otaPackageOld.getData() != null && !otaPackageOld.getData().equals(otaPackage.getData())) {
265 291 throw new DataValidationException("Updating otaPackage data is prohibited!");
266 292 }
  293 +
  294 + if (otaPackageOld.getData() == null && otaPackage.getData() != null) {
  295 + DefaultTenantProfileConfiguration profileConfiguration =
  296 + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
  297 + long maxOtaPackagesInBytes = profileConfiguration.getMaxOtaPackagesInBytes();
  298 + validateMaxSumDataSizePerTenant(tenantId, otaPackageDao, maxOtaPackagesInBytes, otaPackage.getDataSize(), OTA_PACKAGE);
  299 + }
267 300 }
268 301 };
269 302
... ...
... ... @@ -16,8 +16,11 @@
16 16 package org.thingsboard.server.dao.ota;
17 17
18 18 import org.thingsboard.server.common.data.OtaPackage;
  19 +import org.thingsboard.server.common.data.id.TenantId;
19 20 import org.thingsboard.server.dao.Dao;
  21 +import org.thingsboard.server.dao.TenantEntityDao;
  22 +import org.thingsboard.server.dao.TenantEntityWithDataDao;
20 23
21   -public interface OtaPackageDao extends Dao<OtaPackage> {
22   -
  24 +public interface OtaPackageDao extends Dao<OtaPackage>, TenantEntityWithDataDao {
  25 + Long sumDataSizeByTenantId(TenantId tenantId);
23 26 }
... ...
... ... @@ -28,7 +28,7 @@ public interface OtaPackageInfoDao extends Dao<OtaPackageInfo> {
28 28
29 29 PageData<OtaPackageInfo> findOtaPackageInfoByTenantId(TenantId tenantId, PageLink pageLink);
30 30
31   - PageData<OtaPackageInfo> findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, boolean hasData, PageLink pageLink);
  31 + PageData<OtaPackageInfo> findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, PageLink pageLink);
32 32
33 33 boolean isOtaPackageUsed(OtaPackageId otaPackageId, OtaPackageType otaPackageType, DeviceProfileId deviceProfileId);
34 34
... ...
... ... @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
19 19 import lombok.extern.slf4j.Slf4j;
20 20 import org.apache.commons.lang3.StringUtils;
21 21 import org.hibernate.exception.ConstraintViolationException;
  22 +import org.springframework.context.annotation.Lazy;
22 23 import org.springframework.stereotype.Service;
23 24 import org.thingsboard.server.common.data.ResourceType;
24 25 import org.thingsboard.server.common.data.TbResource;
... ... @@ -28,16 +29,19 @@ import org.thingsboard.server.common.data.id.TbResourceId;
28 29 import org.thingsboard.server.common.data.id.TenantId;
29 30 import org.thingsboard.server.common.data.page.PageData;
30 31 import org.thingsboard.server.common.data.page.PageLink;
  32 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
31 33 import org.thingsboard.server.dao.exception.DataValidationException;
32 34 import org.thingsboard.server.dao.model.ModelConstants;
33 35 import org.thingsboard.server.dao.service.DataValidator;
34 36 import org.thingsboard.server.dao.service.PaginatedRemover;
35 37 import org.thingsboard.server.dao.service.Validator;
  38 +import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
36 39 import org.thingsboard.server.dao.tenant.TenantDao;
37 40
38 41 import java.util.List;
39 42 import java.util.Optional;
40 43
  44 +import static org.thingsboard.server.common.data.EntityType.TB_RESOURCE;
41 45 import static org.thingsboard.server.dao.device.DeviceServiceImpl.INCORRECT_TENANT_ID;
42 46 import static org.thingsboard.server.dao.service.Validator.validateId;
43 47
... ... @@ -49,12 +53,13 @@ public class BaseResourceService implements ResourceService {
49 53 private final TbResourceDao resourceDao;
50 54 private final TbResourceInfoDao resourceInfoDao;
51 55 private final TenantDao tenantDao;
  56 + private final TbTenantProfileCache tenantProfileCache;
52 57
53   -
54   - public BaseResourceService(TbResourceDao resourceDao, TbResourceInfoDao resourceInfoDao, TenantDao tenantDao) {
  58 + public BaseResourceService(TbResourceDao resourceDao, TbResourceInfoDao resourceInfoDao, TenantDao tenantDao, @Lazy TbTenantProfileCache tenantProfileCache) {
55 59 this.resourceDao = resourceDao;
56 60 this.resourceInfoDao = resourceInfoDao;
57 61 this.tenantDao = tenantDao;
  62 + this.tenantProfileCache = tenantProfileCache;
58 63 }
59 64
60 65 @Override
... ... @@ -143,9 +148,24 @@ public class BaseResourceService implements ResourceService {
143 148 tenantResourcesRemover.removeEntities(tenantId, tenantId);
144 149 }
145 150
  151 + @Override
  152 + public long sumDataSizeByTenantId(TenantId tenantId) {
  153 + return resourceDao.sumDataSizeByTenantId(tenantId);
  154 + }
  155 +
146 156 private DataValidator<TbResource> resourceValidator = new DataValidator<>() {
147 157
148 158 @Override
  159 + protected void validateCreate(TenantId tenantId, TbResource resource) {
  160 + if (tenantId != null && !TenantId.SYS_TENANT_ID.equals(tenantId) ) {
  161 + DefaultTenantProfileConfiguration profileConfiguration =
  162 + (DefaultTenantProfileConfiguration) tenantProfileCache.get(tenantId).getProfileData().getConfiguration();
  163 + long maxSumResourcesDataInBytes = profileConfiguration.getMaxResourcesInBytes();
  164 + validateMaxSumDataSizePerTenant(tenantId, resourceDao, maxSumResourcesDataInBytes, resource.getData().length(), TB_RESOURCE);
  165 + }
  166 + }
  167 +
  168 + @Override
149 169 protected void validateDataImpl(TenantId tenantId, TbResource resource) {
150 170 if (StringUtils.isEmpty(resource.getTitle())) {
151 171 throw new DataValidationException("Resource title should be specified!");
... ...
... ... @@ -21,10 +21,11 @@ import org.thingsboard.server.common.data.id.TenantId;
21 21 import org.thingsboard.server.common.data.page.PageData;
22 22 import org.thingsboard.server.common.data.page.PageLink;
23 23 import org.thingsboard.server.dao.Dao;
  24 +import org.thingsboard.server.dao.TenantEntityWithDataDao;
24 25
25 26 import java.util.List;
26 27
27   -public interface TbResourceDao extends Dao<TbResource> {
  28 +public interface TbResourceDao extends Dao<TbResource>, TenantEntityWithDataDao {
28 29
29 30 TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId);
30 31
... ...
... ... @@ -23,8 +23,10 @@ import org.hibernate.validator.cfg.ConstraintMapping;
23 23 import org.thingsboard.server.common.data.BaseData;
24 24 import org.thingsboard.server.common.data.EntityType;
25 25 import org.thingsboard.server.common.data.id.TenantId;
  26 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
26 27 import org.thingsboard.server.common.data.validation.NoXss;
27 28 import org.thingsboard.server.dao.TenantEntityDao;
  29 +import org.thingsboard.server.dao.TenantEntityWithDataDao;
28 30 import org.thingsboard.server.dao.exception.DataValidationException;
29 31
30 32 import javax.validation.ConstraintViolation;
... ... @@ -123,6 +125,19 @@ public abstract class DataValidator<D extends BaseData<?>> {
123 125 }
124 126 }
125 127
  128 + protected void validateMaxSumDataSizePerTenant(TenantId tenantId,
  129 + TenantEntityWithDataDao dataDao,
  130 + long maxSumDataSize,
  131 + long currentDataSize,
  132 + EntityType entityType) {
  133 + if (maxSumDataSize > 0) {
  134 + if (dataDao.sumDataSizeByTenantId(tenantId) + currentDataSize > maxSumDataSize) {
  135 + throw new DataValidationException(String.format("Failed to create the %s, files size limit is exhausted %d bytes!",
  136 + entityType.name().toLowerCase().replaceAll("_", " "), maxSumDataSize));
  137 + }
  138 + }
  139 + }
  140 +
126 141 protected static void validateJsonStructure(JsonNode expectedNode, JsonNode actualNode) {
127 142 Set<String> expectedFields = new HashSet<>();
128 143 Iterator<String> fieldsIterator = expectedNode.fieldNames();
... ...
... ... @@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired;
20 20 import org.springframework.data.repository.CrudRepository;
21 21 import org.springframework.stereotype.Component;
22 22 import org.thingsboard.server.common.data.OtaPackage;
  23 +import org.thingsboard.server.common.data.id.TenantId;
23 24 import org.thingsboard.server.dao.ota.OtaPackageDao;
24 25 import org.thingsboard.server.dao.model.sql.OtaPackageEntity;
25 26 import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao;
... ... @@ -43,4 +44,8 @@ public class JpaOtaPackageDao extends JpaAbstractSearchTextDao<OtaPackageEntity,
43 44 return otaPackageRepository;
44 45 }
45 46
  47 + @Override
  48 + public Long sumDataSizeByTenantId(TenantId tenantId) {
  49 + return otaPackageRepository.sumDataSizeByTenantId(tenantId.getId());
  50 + }
46 51 }
... ...
... ... @@ -76,13 +76,12 @@ public class JpaOtaPackageInfoDao extends JpaAbstractSearchTextDao<OtaPackageInf
76 76 }
77 77
78 78 @Override
79   - public PageData<OtaPackageInfo> findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, boolean hasData, PageLink pageLink) {
  79 + public PageData<OtaPackageInfo> findOtaPackageInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, OtaPackageType otaPackageType, PageLink pageLink) {
80 80 return DaoUtil.toPageData(otaPackageInfoRepository
81 81 .findAllByTenantIdAndTypeAndDeviceProfileIdAndHasData(
82 82 tenantId.getId(),
83 83 deviceProfileId.getId(),
84 84 otaPackageType,
85   - hasData,
86 85 Objects.toString(pageLink.getTextSearch(), ""),
87 86 DaoUtil.toPageable(pageLink)));
88 87 }
... ...
... ... @@ -26,27 +26,26 @@ import org.thingsboard.server.dao.model.sql.OtaPackageInfoEntity;
26 26 import java.util.UUID;
27 27
28 28 public interface OtaPackageInfoRepository extends CrudRepository<OtaPackageInfoEntity, UUID> {
29   - @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 " +
  29 + @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 " +
30 30 "f.tenantId = :tenantId " +
31 31 "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
32 32 Page<OtaPackageInfoEntity> findAllByTenantId(@Param("tenantId") UUID tenantId,
33 33 @Param("searchText") String searchText,
34 34 Pageable pageable);
35 35
36   - @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 " +
  36 + @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 " +
37 37 "f.tenantId = :tenantId " +
38 38 "AND f.deviceProfileId = :deviceProfileId " +
39 39 "AND f.type = :type " +
40   - "AND ((f.data IS NOT NULL AND :hasData = true) OR (f.data IS NULL AND :hasData = false ))" +
  40 + "AND (f.data IS NOT NULL OR f.url IS NOT NULL) " +
41 41 "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
42 42 Page<OtaPackageInfoEntity> findAllByTenantIdAndTypeAndDeviceProfileIdAndHasData(@Param("tenantId") UUID tenantId,
43 43 @Param("deviceProfileId") UUID deviceProfileId,
44 44 @Param("type") OtaPackageType type,
45   - @Param("hasData") boolean hasData,
46 45 @Param("searchText") String searchText,
47 46 Pageable pageable);
48 47
49   - @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")
  48 + @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")
50 49 OtaPackageInfoEntity findOtaPackageInfoById(@Param("id") UUID id);
51 50
52 51 @Query(value = "SELECT exists(SELECT * " +
... ...
... ... @@ -15,10 +15,15 @@
15 15 */
16 16 package org.thingsboard.server.dao.sql.ota;
17 17
  18 +import org.springframework.data.jpa.repository.Query;
18 19 import org.springframework.data.repository.CrudRepository;
  20 +import org.springframework.data.repository.query.Param;
19 21 import org.thingsboard.server.dao.model.sql.OtaPackageEntity;
  22 +import org.thingsboard.server.dao.model.sql.OtaPackageInfoEntity;
20 23
21 24 import java.util.UUID;
22 25
23 26 public interface OtaPackageRepository extends CrudRepository<OtaPackageEntity, UUID> {
  27 + @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)
  28 + Long sumDataSizeByTenantId(@Param("tenantId") UUID tenantId);
24 29 }
... ...
... ... @@ -92,4 +92,8 @@ public class JpaTbResourceDao extends JpaAbstractSearchTextDao<TbResourceEntity,
92 92 resourceType.name(), objectIds));
93 93 }
94 94
  95 + @Override
  96 + public Long sumDataSizeByTenantId(TenantId tenantId) {
  97 + return resourceRepository.sumDataSizeByTenantId(tenantId.getId());
  98 + }
95 99 }
... ...
... ... @@ -77,4 +77,7 @@ public interface TbResourceRepository extends CrudRepository<TbResourceEntity, U
77 77 @Param("systemAdminId") UUID sysAdminId,
78 78 @Param("resourceType") String resourceType,
79 79 @Param("resourceIds") String[] objectIds);
  80 +
  81 + @Query(value = "SELECT COALESCE(SUM(LENGTH(r.data)), 0) FROM resource r WHERE r.tenant_id = :tenantId", nativeQuery = true)
  82 + Long sumDataSizeByTenantId(@Param("tenantId") UUID tenantId);
80 83 }
... ...
... ... @@ -168,6 +168,7 @@ CREATE TABLE IF NOT EXISTS ota_package (
168 168 type varchar(32) NOT NULL,
169 169 title varchar(255) NOT NULL,
170 170 version varchar(255) NOT NULL,
  171 + url varchar(255),
171 172 file_name varchar(255),
172 173 content_type varchar(255),
173 174 checksum_algorithm varchar(32),
... ...
... ... @@ -186,6 +186,7 @@ CREATE TABLE IF NOT EXISTS ota_package (
186 186 type varchar(32) NOT NULL,
187 187 title varchar(255) NOT NULL,
188 188 version varchar(255) NOT NULL,
  189 + url varchar(255),
189 190 file_name varchar(255),
190 191 content_type varchar(255),
191 192 checksum_algorithm varchar(32),
... ...
... ... @@ -24,10 +24,10 @@ import java.util.Arrays;
24 24
25 25 @RunWith(ClasspathSuite.class)
26 26 @ClassnameFilters({
27   - "org.thingsboard.server.dao.service.sql.*SqlTest",
28   - "org.thingsboard.server.dao.service.attributes.sql.*SqlTest",
29   - "org.thingsboard.server.dao.service.event.sql.*SqlTest",
30   - "org.thingsboard.server.dao.service.timeseries.sql.*SqlTest"
  27 + "org.thingsboard.server.dao.service.sql.OtaPackageServiceSqlTest",
  28 +// "org.thingsboard.server.dao.service.attributes.sql.*SqlTest",
  29 +// "org.thingsboard.server.dao.service.event.sql.*SqlTest",
  30 +// "org.thingsboard.server.dao.service.timeseries.sql.*SqlTest"
31 31
32 32 })
33 33 public class SqlDaoServiceTestSuite {
... ...
... ... @@ -59,6 +59,7 @@ import org.thingsboard.server.dao.relation.RelationService;
59 59 import org.thingsboard.server.dao.resource.ResourceService;
60 60 import org.thingsboard.server.dao.rule.RuleChainService;
61 61 import org.thingsboard.server.dao.settings.AdminSettingsService;
  62 +import org.thingsboard.server.dao.tenant.DefaultTbTenantProfileCache;
62 63 import org.thingsboard.server.dao.tenant.TenantProfileService;
63 64 import org.thingsboard.server.dao.tenant.TenantService;
64 65 import org.thingsboard.server.dao.timeseries.TimeseriesService;
... ... @@ -158,10 +159,12 @@ public abstract class AbstractServiceTest {
158 159 @Autowired
159 160 protected ResourceService resourceService;
160 161
161   -
162 162 @Autowired
163 163 protected OtaPackageService otaPackageService;
164 164
  165 + @Autowired
  166 + protected DefaultTbTenantProfileCache tenantProfileCache;
  167 +
165 168 public class IdComparator<D extends HasId> implements Comparator<D> {
166 169 @Override
167 170 public int compare(D o1, D o2) {
... ...
... ... @@ -28,11 +28,13 @@ import org.thingsboard.server.common.data.DeviceProfile;
28 28 import org.thingsboard.server.common.data.OtaPackage;
29 29 import org.thingsboard.server.common.data.OtaPackageInfo;
30 30 import org.thingsboard.server.common.data.Tenant;
31   -import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
  31 +import org.thingsboard.server.common.data.TenantProfile;
32 32 import org.thingsboard.server.common.data.id.DeviceProfileId;
33 33 import org.thingsboard.server.common.data.id.TenantId;
  34 +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
34 35 import org.thingsboard.server.common.data.page.PageData;
35 36 import org.thingsboard.server.common.data.page.PageLink;
  37 +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
36 38 import org.thingsboard.server.dao.exception.DataValidationException;
37 39
38 40 import java.nio.ByteBuffer;
... ... @@ -50,7 +52,9 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
50 52 private static final String CONTENT_TYPE = "text/plain";
51 53 private static final ChecksumAlgorithm CHECKSUM_ALGORITHM = ChecksumAlgorithm.SHA256;
52 54 private static final String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a";
53   - private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{1});
  55 + private static final long DATA_SIZE = 1L;
  56 + private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{(int) DATA_SIZE});
  57 + private static final String URL = "http://firmware.test.org";
54 58
55 59 private IdComparator<OtaPackageInfo> idComparator = new IdComparator<>();
56 60
... ... @@ -78,6 +82,41 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
78 82 @After
79 83 public void after() {
80 84 tenantService.deleteTenant(tenantId);
  85 + tenantProfileService.deleteTenantProfiles(tenantId);
  86 + }
  87 +
  88 + @Test
  89 + public void testSaveOtaPackageWithMaxSumDataSizeOutOfLimit() {
  90 + TenantProfile defaultTenantProfile = tenantProfileService.findDefaultTenantProfile(tenantId);
  91 + defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxOtaPackagesInBytes(DATA_SIZE).build());
  92 + tenantProfileService.saveTenantProfile(tenantId, defaultTenantProfile);
  93 +
  94 + Assert.assertEquals(0, otaPackageService.sumDataSizeByTenantId(tenantId));
  95 +
  96 + createFirmware(tenantId, "1");
  97 + Assert.assertEquals(1, otaPackageService.sumDataSizeByTenantId(tenantId));
  98 +
  99 + thrown.expect(DataValidationException.class);
  100 + thrown.expectMessage(String.format("Failed to create the ota package, files size limit is exhausted %d bytes!", DATA_SIZE));
  101 + createFirmware(tenantId, "2");
  102 + }
  103 +
  104 + @Test
  105 + public void sumDataSizeByTenantId() {
  106 + Assert.assertEquals(0, otaPackageService.sumDataSizeByTenantId(tenantId));
  107 +
  108 + createFirmware(tenantId, "0.1");
  109 + Assert.assertEquals(1, otaPackageService.sumDataSizeByTenantId(tenantId));
  110 +
  111 + int maxSumDataSize = 8;
  112 + List<OtaPackage> packages = new ArrayList<>(maxSumDataSize);
  113 +
  114 + for (int i = 2; i <= maxSumDataSize; i++) {
  115 + packages.add(createFirmware(tenantId, "0." + i));
  116 + Assert.assertEquals(i, otaPackageService.sumDataSizeByTenantId(tenantId));
  117 + }
  118 +
  119 + Assert.assertEquals(maxSumDataSize, otaPackageService.sumDataSizeByTenantId(tenantId));
81 120 }
82 121
83 122 @Test
... ... @@ -93,6 +132,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
93 132 firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
94 133 firmware.setChecksum(CHECKSUM);
95 134 firmware.setData(DATA);
  135 + firmware.setDataSize(DATA_SIZE);
96 136 OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware);
97 137
98 138 Assert.assertNotNull(savedFirmware);
... ... @@ -114,6 +154,35 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
114 154 }
115 155
116 156 @Test
  157 + public void testSaveFirmwareWithUrl() {
  158 + OtaPackageInfo firmware = new OtaPackageInfo();
  159 + firmware.setTenantId(tenantId);
  160 + firmware.setDeviceProfileId(deviceProfileId);
  161 + firmware.setType(FIRMWARE);
  162 + firmware.setTitle(TITLE);
  163 + firmware.setVersion(VERSION);
  164 + firmware.setUrl(URL);
  165 + firmware.setDataSize(0L);
  166 + OtaPackageInfo savedFirmware = otaPackageService.saveOtaPackageInfo(firmware);
  167 +
  168 + Assert.assertNotNull(savedFirmware);
  169 + Assert.assertNotNull(savedFirmware.getId());
  170 + Assert.assertTrue(savedFirmware.getCreatedTime() > 0);
  171 + Assert.assertEquals(firmware.getTenantId(), savedFirmware.getTenantId());
  172 + Assert.assertEquals(firmware.getTitle(), savedFirmware.getTitle());
  173 + Assert.assertEquals(firmware.getFileName(), savedFirmware.getFileName());
  174 + Assert.assertEquals(firmware.getContentType(), savedFirmware.getContentType());
  175 +
  176 + savedFirmware.setAdditionalInfo(JacksonUtil.newObjectNode());
  177 + otaPackageService.saveOtaPackageInfo(savedFirmware);
  178 +
  179 + OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId());
  180 + Assert.assertEquals(foundFirmware.getTitle(), savedFirmware.getTitle());
  181 +
  182 + otaPackageService.deleteOtaPackage(tenantId, savedFirmware.getId());
  183 + }
  184 +
  185 + @Test
117 186 public void testSaveFirmwareInfoAndUpdateWithData() {
118 187 OtaPackageInfo firmwareInfo = new OtaPackageInfo();
119 188 firmwareInfo.setTenantId(tenantId);
... ... @@ -141,6 +210,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
141 210 firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
142 211 firmware.setChecksum(CHECKSUM);
143 212 firmware.setData(DATA);
  213 + firmware.setDataSize(DATA_SIZE);
144 214
145 215 otaPackageService.saveOtaPackage(firmware);
146 216
... ... @@ -345,50 +415,15 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
345 415
346 416 @Test
347 417 public void testSaveFirmwareWithExistingTitleAndVersion() {
348   - OtaPackage firmware = new OtaPackage();
349   - firmware.setTenantId(tenantId);
350   - firmware.setDeviceProfileId(deviceProfileId);
351   - firmware.setType(FIRMWARE);
352   - firmware.setTitle(TITLE);
353   - firmware.setVersion(VERSION);
354   - firmware.setFileName(FILE_NAME);
355   - firmware.setContentType(CONTENT_TYPE);
356   - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
357   - firmware.setChecksum(CHECKSUM);
358   - firmware.setData(DATA);
359   - otaPackageService.saveOtaPackage(firmware);
360   -
361   - OtaPackage newFirmware = new OtaPackage();
362   - newFirmware.setTenantId(tenantId);
363   - newFirmware.setDeviceProfileId(deviceProfileId);
364   - newFirmware.setType(FIRMWARE);
365   - newFirmware.setTitle(TITLE);
366   - newFirmware.setVersion(VERSION);
367   - newFirmware.setFileName(FILE_NAME);
368   - newFirmware.setContentType(CONTENT_TYPE);
369   - newFirmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
370   - newFirmware.setChecksum(CHECKSUM);
371   - newFirmware.setData(DATA);
372   -
  418 + createFirmware(tenantId, VERSION);
373 419 thrown.expect(DataValidationException.class);
374 420 thrown.expectMessage("OtaPackage with such title and version already exists!");
375   - otaPackageService.saveOtaPackage(newFirmware);
  421 + createFirmware(tenantId, VERSION);
376 422 }
377 423
378 424 @Test
379 425 public void testDeleteFirmwareWithReferenceByDevice() {
380   - OtaPackage firmware = new OtaPackage();
381   - firmware.setTenantId(tenantId);
382   - firmware.setDeviceProfileId(deviceProfileId);
383   - firmware.setType(FIRMWARE);
384   - firmware.setTitle(TITLE);
385   - firmware.setVersion(VERSION);
386   - firmware.setFileName(FILE_NAME);
387   - firmware.setContentType(CONTENT_TYPE);
388   - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
389   - firmware.setChecksum(CHECKSUM);
390   - firmware.setData(DATA);
391   - OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware);
  426 + OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
392 427
393 428 Device device = new Device();
394 429 device.setTenantId(tenantId);
... ... @@ -409,18 +444,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
409 444
410 445 @Test
411 446 public void testUpdateDeviceProfileId() {
412   - OtaPackage firmware = new OtaPackage();
413   - firmware.setTenantId(tenantId);
414   - firmware.setDeviceProfileId(deviceProfileId);
415   - firmware.setType(FIRMWARE);
416   - firmware.setTitle(TITLE);
417   - firmware.setVersion(VERSION);
418   - firmware.setFileName(FILE_NAME);
419   - firmware.setContentType(CONTENT_TYPE);
420   - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
421   - firmware.setChecksum(CHECKSUM);
422   - firmware.setData(DATA);
423   - OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware);
  447 + OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
424 448
425 449 try {
426 450 thrown.expect(DataValidationException.class);
... ... @@ -448,6 +472,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
448 472 firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
449 473 firmware.setChecksum(CHECKSUM);
450 474 firmware.setData(DATA);
  475 + firmware.setDataSize(DATA_SIZE);
451 476 OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware);
452 477
453 478 savedDeviceProfile.setFirmwareId(savedFirmware.getId());
... ... @@ -465,18 +490,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
465 490
466 491 @Test
467 492 public void testFindFirmwareById() {
468   - OtaPackage firmware = new OtaPackage();
469   - firmware.setTenantId(tenantId);
470   - firmware.setDeviceProfileId(deviceProfileId);
471   - firmware.setType(FIRMWARE);
472   - firmware.setTitle(TITLE);
473   - firmware.setVersion(VERSION);
474   - firmware.setFileName(FILE_NAME);
475   - firmware.setContentType(CONTENT_TYPE);
476   - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
477   - firmware.setChecksum(CHECKSUM);
478   - firmware.setData(DATA);
479   - OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware);
  493 + OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
480 494
481 495 OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId());
482 496 Assert.assertNotNull(foundFirmware);
... ... @@ -502,18 +516,7 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
502 516
503 517 @Test
504 518 public void testDeleteFirmware() {
505   - OtaPackage firmware = new OtaPackage();
506   - firmware.setTenantId(tenantId);
507   - firmware.setDeviceProfileId(deviceProfileId);
508   - firmware.setType(FIRMWARE);
509   - firmware.setTitle(TITLE);
510   - firmware.setVersion(VERSION);
511   - firmware.setFileName(FILE_NAME);
512   - firmware.setContentType(CONTENT_TYPE);
513   - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
514   - firmware.setChecksum(CHECKSUM);
515   - firmware.setData(DATA);
516   - OtaPackage savedFirmware = otaPackageService.saveOtaPackage(firmware);
  519 + OtaPackage savedFirmware = createFirmware(tenantId, VERSION);
517 520
518 521 OtaPackage foundFirmware = otaPackageService.findOtaPackageById(tenantId, savedFirmware.getId());
519 522 Assert.assertNotNull(foundFirmware);
... ... @@ -526,23 +529,25 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
526 529 public void testFindTenantFirmwaresByTenantId() {
527 530 List<OtaPackageInfo> firmwares = new ArrayList<>();
528 531 for (int i = 0; i < 165; i++) {
529   - OtaPackage firmware = new OtaPackage();
530   - firmware.setTenantId(tenantId);
531   - firmware.setDeviceProfileId(deviceProfileId);
532   - firmware.setType(FIRMWARE);
533   - firmware.setTitle(TITLE);
534   - firmware.setVersion(VERSION + i);
535   - firmware.setFileName(FILE_NAME);
536   - firmware.setContentType(CONTENT_TYPE);
537   - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
538   - firmware.setChecksum(CHECKSUM);
539   - firmware.setData(DATA);
540   -
541   - OtaPackageInfo info = new OtaPackageInfo(otaPackageService.saveOtaPackage(firmware));
  532 + OtaPackageInfo info = new OtaPackageInfo(createFirmware(tenantId, VERSION + i));
542 533 info.setHasData(true);
543 534 firmwares.add(info);
544 535 }
545 536
  537 + OtaPackageInfo firmwareWithUrl = new OtaPackageInfo();
  538 + firmwareWithUrl.setTenantId(tenantId);
  539 + firmwareWithUrl.setDeviceProfileId(deviceProfileId);
  540 + firmwareWithUrl.setType(FIRMWARE);
  541 + firmwareWithUrl.setTitle(TITLE);
  542 + firmwareWithUrl.setVersion(VERSION);
  543 + firmwareWithUrl.setUrl(URL);
  544 + firmwareWithUrl.setDataSize(0L);
  545 +
  546 + OtaPackageInfo savedFwWithUrl = otaPackageService.saveOtaPackageInfo(firmwareWithUrl);
  547 + savedFwWithUrl.setHasData(true);
  548 +
  549 + firmwares.add(savedFwWithUrl);
  550 +
546 551 List<OtaPackageInfo> loadedFirmwares = new ArrayList<>();
547 552 PageLink pageLink = new PageLink(16);
548 553 PageData<OtaPackageInfo> pageData;
... ... @@ -571,58 +576,38 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
571 576 public void testFindTenantFirmwaresByTenantIdAndHasData() {
572 577 List<OtaPackageInfo> firmwares = new ArrayList<>();
573 578 for (int i = 0; i < 165; i++) {
574   - OtaPackageInfo firmwareInfo = new OtaPackageInfo();
575   - firmwareInfo.setTenantId(tenantId);
576   - firmwareInfo.setDeviceProfileId(deviceProfileId);
577   - firmwareInfo.setType(FIRMWARE);
578   - firmwareInfo.setTitle(TITLE);
579   - firmwareInfo.setVersion(VERSION + i);
580   - firmwareInfo.setFileName(FILE_NAME);
581   - firmwareInfo.setContentType(CONTENT_TYPE);
582   - firmwareInfo.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
583   - firmwareInfo.setChecksum(CHECKSUM);
584   - firmwareInfo.setDataSize((long) DATA.array().length);
585   - firmwares.add(otaPackageService.saveOtaPackageInfo(firmwareInfo));
  579 + firmwares.add(new OtaPackageInfo(otaPackageService.saveOtaPackage(createFirmware(tenantId, VERSION + i))));
586 580 }
587 581
  582 + OtaPackageInfo firmwareWithUrl = new OtaPackageInfo();
  583 + firmwareWithUrl.setTenantId(tenantId);
  584 + firmwareWithUrl.setDeviceProfileId(deviceProfileId);
  585 + firmwareWithUrl.setType(FIRMWARE);
  586 + firmwareWithUrl.setTitle(TITLE);
  587 + firmwareWithUrl.setVersion(VERSION);
  588 + firmwareWithUrl.setUrl(URL);
  589 + firmwareWithUrl.setDataSize(0L);
  590 +
  591 + OtaPackageInfo savedFwWithUrl = otaPackageService.saveOtaPackageInfo(firmwareWithUrl);
  592 + savedFwWithUrl.setHasData(true);
  593 +
  594 + firmwares.add(savedFwWithUrl);
  595 +
588 596 List<OtaPackageInfo> loadedFirmwares = new ArrayList<>();
589 597 PageLink pageLink = new PageLink(16);
590 598 PageData<OtaPackageInfo> pageData;
591 599 do {
592   - pageData = otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, FIRMWARE, false, pageLink);
  600 + pageData = otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, FIRMWARE, pageLink);
593 601 loadedFirmwares.addAll(pageData.getData());
594 602 if (pageData.hasNext()) {
595 603 pageLink = pageLink.nextPageLink();
596 604 }
597 605 } while (pageData.hasNext());
598 606
599   - Collections.sort(firmwares, idComparator);
600   - Collections.sort(loadedFirmwares, idComparator);
601   -
602   - Assert.assertEquals(firmwares, loadedFirmwares);
603   -
604   - firmwares.forEach(f -> {
605   - OtaPackage firmware = new OtaPackage(f.getId());
606   - firmware.setCreatedTime(f.getCreatedTime());
607   - firmware.setTenantId(f.getTenantId());
608   - firmware.setDeviceProfileId(deviceProfileId);
609   - firmware.setType(FIRMWARE);
610   - firmware.setTitle(f.getTitle());
611   - firmware.setVersion(f.getVersion());
612   - firmware.setFileName(FILE_NAME);
613   - firmware.setContentType(CONTENT_TYPE);
614   - firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
615   - firmware.setChecksum(CHECKSUM);
616   - firmware.setData(DATA);
617   - firmware.setDataSize((long) DATA.array().length);
618   - otaPackageService.saveOtaPackage(firmware);
619   - f.setHasData(true);
620   - });
621   -
622 607 loadedFirmwares = new ArrayList<>();
623 608 pageLink = new PageLink(16);
624 609 do {
625   - pageData = otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, FIRMWARE, true, pageLink);
  610 + pageData = otaPackageService.findTenantOtaPackagesByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, FIRMWARE, pageLink);
626 611 loadedFirmwares.addAll(pageData.getData());
627 612 if (pageData.hasNext()) {
628 613 pageLink = pageLink.nextPageLink();
... ... @@ -642,4 +627,20 @@ public abstract class BaseOtaPackageServiceTest extends AbstractServiceTest {
642 627 Assert.assertTrue(pageData.getData().isEmpty());
643 628 }
644 629
  630 + private OtaPackage createFirmware(TenantId tenantId, String version) {
  631 + OtaPackage firmware = new OtaPackage();
  632 + firmware.setTenantId(tenantId);
  633 + firmware.setDeviceProfileId(deviceProfileId);
  634 + firmware.setType(FIRMWARE);
  635 + firmware.setTitle(TITLE);
  636 + firmware.setVersion(version);
  637 + firmware.setFileName(FILE_NAME);
  638 + firmware.setContentType(CONTENT_TYPE);
  639 + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM);
  640 + firmware.setChecksum(CHECKSUM);
  641 + firmware.setData(DATA);
  642 + firmware.setDataSize(DATA_SIZE);
  643 + return otaPackageService.saveOtaPackage(firmware);
  644 + }
  645 +
645 646 }
... ...
... ... @@ -47,7 +47,9 @@ import org.thingsboard.server.dao.edge.EdgeService;
47 47 import org.thingsboard.server.dao.entityview.EntityViewService;
48 48 import org.thingsboard.server.dao.nosql.CassandraStatementTask;
49 49 import org.thingsboard.server.dao.nosql.TbResultSetFuture;
  50 +import org.thingsboard.server.dao.ota.OtaPackageService;
50 51 import org.thingsboard.server.dao.relation.RelationService;
  52 +import org.thingsboard.server.dao.resource.ResourceService;
51 53 import org.thingsboard.server.dao.rule.RuleChainService;
52 54 import org.thingsboard.server.dao.tenant.TenantService;
53 55 import org.thingsboard.server.dao.timeseries.TimeseriesService;
... ... @@ -202,6 +204,10 @@ public interface TbContext {
202 204
203 205 EntityViewService getEntityViewService();
204 206
  207 + ResourceService getResourceService();
  208 +
  209 + OtaPackageService getOtaPackageService();
  210 +
205 211 RuleEngineDeviceProfileCache getDeviceProfileCache();
206 212
207 213 EdgeService getEdgeService();
... ...
... ... @@ -40,7 +40,7 @@ export class OtaPackageService {
40 40
41 41 public getOtaPackagesInfoByDeviceProfileId(pageLink: PageLink, deviceProfileId: string, type: OtaUpdateType,
42 42 hasData = true, config?: RequestConfig): Observable<PageData<OtaPackageInfo>> {
43   - const url = `/api/otaPackages/${deviceProfileId}/${type}/${hasData}${pageLink.toQuery()}`;
  43 + const url = `/api/otaPackages/${deviceProfileId}/${type}${pageLink.toQuery()}`;
44 44 return this.http.get<PageData<OtaPackageInfo>>(url, defaultHttpOptionsFromConfig(config));
45 45 }
46 46
... ...
... ... @@ -89,6 +89,30 @@
89 89 </mat-error>
90 90 </mat-form-field>
91 91 <mat-form-field class="mat-block">
  92 + <mat-label translate>tenant-profile.maximum-resources-sum-data-size</mat-label>
  93 + <input matInput required min="0" step="1"
  94 + formControlName="maxResourcesInBytes"
  95 + type="number">
  96 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxResourcesInBytes').hasError('required')">
  97 + {{ 'tenant-profile.maximum-resources-sum-data-size-required' | translate}}
  98 + </mat-error>
  99 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxResourcesInBytes').hasError('min')">
  100 + {{ 'tenant-profile.maximum-resources-sum-data-size-range' | translate}}
  101 + </mat-error>
  102 + </mat-form-field>
  103 + <mat-form-field class="mat-block">
  104 + <mat-label translate>tenant-profile.maximum-ota-packages-sum-data-size</mat-label>
  105 + <input matInput required min="0" step="1"
  106 + formControlName="maxOtaPackagesInBytes"
  107 + type="number">
  108 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxOtaPackagesInBytes').hasError('required')">
  109 + {{ 'tenant-profile.maximum-ota-packages-sum-data-size-required' | translate}}
  110 + </mat-error>
  111 + <mat-error *ngIf="defaultTenantProfileConfigurationFormGroup.get('maxOtaPackagesInBytes').hasError('min')">
  112 + {{ 'tenant-profile.maximum-ota-packages-sum-data-size-range' | translate}}
  113 + </mat-error>
  114 + </mat-form-field>
  115 + <mat-form-field class="mat-block">
92 116 <mat-label translate>tenant-profile.max-transport-messages</mat-label>
93 117 <input matInput required min="0" step="1"
94 118 formControlName="maxTransportMessages"
... ...
... ... @@ -59,6 +59,8 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA
59 59 maxUsers: [null, [Validators.required, Validators.min(0)]],
60 60 maxDashboards: [null, [Validators.required, Validators.min(0)]],
61 61 maxRuleChains: [null, [Validators.required, Validators.min(0)]],
  62 + maxResourcesInBytes: [null, [Validators.required, Validators.min(0)]],
  63 + maxOtaPackagesInBytes: [null, [Validators.required, Validators.min(0)]],
62 64 transportTenantMsgRateLimit: [null, []],
63 65 transportTenantTelemetryMsgRateLimit: [null, []],
64 66 transportTenantTelemetryDataPointsRateLimit: [null, []],
... ...
... ... @@ -18,6 +18,7 @@ import { ContactBased } from '@shared/models/contact-based.model';
18 18 import { TenantId } from './id/tenant-id';
19 19 import { TenantProfileId } from '@shared/models/id/tenant-profile-id';
20 20 import { BaseData } from '@shared/models/base-data';
  21 +import {Validators} from "@angular/forms";
21 22
22 23 export enum TenantProfileType {
23 24 DEFAULT = 'DEFAULT'
... ... @@ -30,6 +31,8 @@ export interface DefaultTenantProfileConfiguration {
30 31 maxUsers: number;
31 32 maxDashboards: number;
32 33 maxRuleChains: number;
  34 + maxResourcesInBytes: number;
  35 + maxOtaPackagesInBytes: number;
33 36
34 37 transportTenantMsgRateLimit?: string;
35 38 transportTenantTelemetryMsgRateLimit?: string;
... ... @@ -68,6 +71,8 @@ export function createTenantProfileConfiguration(type: TenantProfileType): Tenan
68 71 maxUsers: 0,
69 72 maxDashboards: 0,
70 73 maxRuleChains: 0,
  74 + maxResourcesInBytes: 0,
  75 + maxOtaPackagesInBytes: 0,
71 76 maxTransportMessages: 0,
72 77 maxTransportDataPoints: 0,
73 78 maxREExecutions: 0,
... ...
... ... @@ -2497,6 +2497,12 @@
2497 2497 "maximum-rule-chains": "Maximum number of rule chains (0 - unlimited)",
2498 2498 "maximum-rule-chains-required": "Maximum number of rule chains is required.",
2499 2499 "maximum-rule-chains-range": "Maximum number of rule chains can't be negative",
  2500 + "maximum-resources-sum-data-size": "Maximum sum of resource files size in bytes (0 - unlimited)",
  2501 + "maximum-resources-sum-data-size-required": "Maximum sum of resource files size is required.",
  2502 + "maximum-resources-sum-data-size-range": "Maximum sum of resource files size can`t be negative",
  2503 + "maximum-ota-packages-sum-data-size": "Maximum sum of ota package files size in bytes (0 - unlimited)",
  2504 + "maximum-ota-package-sum-data-size-required": "Maximum sum of ota package files size is required.",
  2505 + "maximum-ota-package-sum-data-size-range": "Maximum sum of ota package files size can`t be negative",
2500 2506 "transport-tenant-msg-rate-limit": "Transport tenant messages rate limit.",
2501 2507 "transport-tenant-telemetry-msg-rate-limit": "Transport tenant telemetry messages rate limit.",
2502 2508 "transport-tenant-telemetry-data-points-rate-limit": "Transport tenant telemetry data points rate limit.",
... ...