Commit 43b4f4461d22784ffd19a4fda38523c44dc926c7
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)) { | ... | ... |
... | ... | @@ -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 | } | ... | ... |
... | ... | @@ -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.", | ... | ... |