Merge pull request #6781 from volodymyr-babak/feature/edge-add-ota-updates

[3.4] Edge OTA support
This commit is contained in:
Andrew Shvayka 2022-06-22 12:47:51 +03:00 committed by GitHub
commit 4c6f817e05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 409 additions and 44 deletions

View File

@ -137,6 +137,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService {
case ENTITY_VIEW:
case DASHBOARD:
case RULE_CHAIN:
case OTA_PACKAGE:
future = entityProcessor.processEntityNotification(tenantId, edgeNotificationMsg);
break;
case CUSTOMER:

View File

@ -26,6 +26,7 @@ import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceProfileService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.user.UserService;
@ -41,6 +42,7 @@ import org.thingsboard.server.service.edge.rpc.processor.DeviceEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.DeviceProfileEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.EntityEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.EntityViewEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.OtaPackageEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.RuleChainEdgeProcessor;
import org.thingsboard.server.service.edge.rpc.processor.TelemetryEdgeProcessor;
@ -93,6 +95,9 @@ public class EdgeContextComponent {
@Autowired
private EdgeRequestsService edgeRequestsService;
@Autowired
private OtaPackageService otaPackageService;
@Autowired
private AlarmEdgeProcessor alarmProcessor;
@ -138,6 +143,9 @@ public class EdgeContextComponent {
@Autowired
private AdminSettingsEdgeProcessor adminSettingsProcessor;
@Autowired
private OtaPackageEdgeProcessor otaPackageEdgeProcessor;
@Autowired
private EdgeEventStorageSettings edgeEventStorageSettings;

View File

@ -536,6 +536,8 @@ public final class EdgeGrpcSession implements Closeable {
return ctx.getWidgetTypeProcessor().processWidgetTypeToEdge(edgeEvent, msgType, action);
case ADMIN_SETTINGS:
return ctx.getAdminSettingsProcessor().processAdminSettingsToEdge(edgeEvent);
case OTA_PACKAGE:
return ctx.getOtaPackageEdgeProcessor().processOtaPackageToEdge(edgeEvent, msgType, action);
default:
log.warn("Unsupported edge event type [{}]", edgeEvent);
return null;

View File

@ -25,6 +25,7 @@ import org.thingsboard.server.service.edge.rpc.fetch.CustomerUsersEdgeEventFetch
import org.thingsboard.server.service.edge.rpc.fetch.DashboardsEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.DeviceProfilesEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.EdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.OtaPackagesEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.RuleChainsEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.SystemWidgetsBundlesEdgeEventFetcher;
import org.thingsboard.server.service.edge.rpc.fetch.TenantAdminUsersEdgeEventFetcher;
@ -53,6 +54,7 @@ public class EdgeSyncCursor {
fetchers.add(new SystemWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService()));
fetchers.add(new TenantWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService()));
fetchers.add(new DashboardsEdgeEventFetcher(ctx.getDashboardService()));
fetchers.add(new OtaPackagesEdgeEventFetcher(ctx.getOtaPackageService()));
}
public boolean hasNext() {

View File

@ -0,0 +1,77 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.constructor;
import com.google.protobuf.ByteString;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@Component
@TbCoreComponent
public class OtaPackageMsgConstructor {
public OtaPackageUpdateMsg constructOtaPackageUpdatedMsg(UpdateMsgType msgType, OtaPackage otaPackage) {
OtaPackageUpdateMsg.Builder builder = OtaPackageUpdateMsg.newBuilder()
.setMsgType(msgType)
.setIdMSB(otaPackage.getId().getId().getMostSignificantBits())
.setIdLSB(otaPackage.getId().getId().getLeastSignificantBits())
.setDeviceProfileIdMSB(otaPackage.getDeviceProfileId().getId().getMostSignificantBits())
.setDeviceProfileIdLSB(otaPackage.getDeviceProfileId().getId().getLeastSignificantBits())
.setType(otaPackage.getType().name())
.setTitle(otaPackage.getTitle())
.setVersion(otaPackage.getVersion())
.setTag(otaPackage.getTag());
if (otaPackage.getUrl() != null) {
builder.setUrl(otaPackage.getUrl());
}
if (otaPackage.getAdditionalInfo() != null) {
builder.setAdditionalInfo(JacksonUtil.toString(otaPackage.getAdditionalInfo()));
}
if (otaPackage.getFileName() != null) {
builder.setFileName(otaPackage.getFileName());
}
if (otaPackage.getContentType() != null) {
builder.setContentType(otaPackage.getContentType());
}
if (otaPackage.getChecksumAlgorithm() != null) {
builder.setChecksumAlgorithm(otaPackage.getChecksumAlgorithm().name());
}
if (otaPackage.getChecksum() != null) {
builder.setChecksum(otaPackage.getChecksum());
}
if (otaPackage.getDataSize() != null) {
builder.setDataSize(otaPackage.getDataSize());
}
if (otaPackage.getData() != null) {
builder.setData(ByteString.copyFrom(otaPackage.getData().array()));
}
return builder.build();
}
public OtaPackageUpdateMsg constructOtaPackageDeleteMsg(OtaPackageId otaPackageId) {
return OtaPackageUpdateMsg.newBuilder()
.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE)
.setIdMSB(otaPackageId.getId().getMostSignificantBits())
.setIdLSB(otaPackageId.getId().getLeastSignificantBits()).build();
}
}

View File

@ -0,0 +1,47 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.fetch;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.edge.EdgeEventType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.ota.OtaPackageService;
@AllArgsConstructor
@Slf4j
public class OtaPackagesEdgeEventFetcher extends BasePageableEdgeEventFetcher<OtaPackageInfo> {
private final OtaPackageService otaPackageService;
@Override
PageData<OtaPackageInfo> fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) {
return otaPackageService.findTenantOtaPackagesByTenantId(tenantId, pageLink);
}
@Override
EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, OtaPackageInfo otaPackageInfo) {
return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.OTA_PACKAGE,
EdgeEventActionType.ADDED, otaPackageInfo.getId(), null);
}
}

View File

@ -46,6 +46,7 @@ import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.edge.EdgeEventService;
import org.thingsboard.server.dao.edge.EdgeService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.ota.OtaPackageService;
import org.thingsboard.server.dao.relation.RelationService;
import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.service.DataValidator;
@ -61,6 +62,7 @@ import org.thingsboard.server.service.edge.rpc.constructor.DeviceMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.DeviceProfileMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.EntityDataMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.EntityViewMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.OtaPackageMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.RelationMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.RuleChainMsgConstructor;
import org.thingsboard.server.service.edge.rpc.constructor.UserMsgConstructor;
@ -137,6 +139,9 @@ public abstract class BaseEdgeProcessor {
@Autowired
protected WidgetTypeService widgetTypeService;
@Autowired
protected OtaPackageService otaPackageService;
@Autowired
protected DataValidator<Device> deviceValidator;
@ -182,6 +187,9 @@ public abstract class BaseEdgeProcessor {
@Autowired
protected AdminSettingsMsgConstructor adminSettingsMsgConstructor;
@Autowired
protected OtaPackageMsgConstructor otaPackageMsgConstructor;
@Autowired
protected DbCallbackExecutorService dbCallbackExecutorService;

View File

@ -0,0 +1,63 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.processor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.EdgeUtils;
import org.thingsboard.server.common.data.OtaPackage;
import org.thingsboard.server.common.data.edge.EdgeEvent;
import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.gen.edge.v1.DownlinkMsg;
import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg;
import org.thingsboard.server.gen.edge.v1.UpdateMsgType;
import org.thingsboard.server.queue.util.TbCoreComponent;
@Component
@Slf4j
@TbCoreComponent
public class OtaPackageEdgeProcessor extends BaseEdgeProcessor {
public DownlinkMsg processOtaPackageToEdge(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) {
OtaPackageId otaPackageId = new OtaPackageId(edgeEvent.getEntityId());
DownlinkMsg downlinkMsg = null;
switch (action) {
case ADDED:
case UPDATED:
OtaPackage otaPackage = otaPackageService.findOtaPackageById(edgeEvent.getTenantId(), otaPackageId);
if (otaPackage != null) {
OtaPackageUpdateMsg otaPackageUpdateMsg =
otaPackageMsgConstructor.constructOtaPackageUpdatedMsg(msgType, otaPackage);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addOtaPackageUpdateMsg(otaPackageUpdateMsg)
.build();
}
break;
case DELETED:
OtaPackageUpdateMsg otaPackageUpdateMsg =
otaPackageMsgConstructor.constructOtaPackageDeleteMsg(otaPackageId);
downlinkMsg = DownlinkMsg.newBuilder()
.setDownlinkMsgId(EdgeUtils.nextPositiveInt())
.addOtaPackageUpdateMsg(otaPackageUpdateMsg)
.build();
break;
}
return downlinkMsg;
}
}

View File

@ -45,8 +45,10 @@ public class DefaultTbOtaPackageService extends AbstractTbEntityService implemen
ActionType actionType = saveOtaPackageInfoRequest.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
try {
OtaPackageInfo savedOtaPackageInfo = otaPackageService.saveOtaPackageInfo(new OtaPackageInfo(saveOtaPackageInfoRequest), saveOtaPackageInfoRequest.isUsesUrl());
notificationEntityService.notifyEntity(tenantId, savedOtaPackageInfo.getId(), savedOtaPackageInfo, null,
actionType, user, null);
boolean sendToEdge = savedOtaPackageInfo.hasUrl() || savedOtaPackageInfo.isHasData();
notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedOtaPackageInfo.getId(), savedOtaPackageInfo, user, actionType, sendToEdge, null);
return savedOtaPackageInfo;
} catch (Exception e) {
notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.OTA_PACKAGE), saveOtaPackageInfoRequest, null,
@ -61,8 +63,11 @@ public class DefaultTbOtaPackageService extends AbstractTbEntityService implemen
OtaPackageId otaPackageId = otaPackageInfo.getId();
try {
otaPackageService.deleteOtaPackage(tenantId, otaPackageId);
notificationEntityService.notifyEntity(tenantId, otaPackageId, otaPackageInfo, null,
ActionType.DELETED, user, null, otaPackageInfo.getId().toString());
// notificationEntityService.notifyEntity(tenantId, otaPackageId, otaPackageInfo, null,
// ActionType.DELETED, user, null, otaPackageInfo.getId().toString());
notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, otaPackageId, otaPackageInfo,
user, ActionType.DELETED, true, null, otaPackageInfo.getId().toString());
} catch (Exception e) {
notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.OTA_PACKAGE), null, null,
ActionType.DELETED, user, e, otaPackageInfo.getId().toString());
@ -95,8 +100,8 @@ public class DefaultTbOtaPackageService extends AbstractTbEntityService implemen
otaPackage.setData(ByteBuffer.wrap(data));
otaPackage.setDataSize((long) data.length);
OtaPackageInfo savedOtaPackage = otaPackageService.saveOtaPackage(otaPackage);
notificationEntityService.notifyEntity(tenantId, savedOtaPackage.getId(), savedOtaPackage, null,
ActionType.UPDATED, user, null);
notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedOtaPackage.getId(), savedOtaPackage, user, ActionType.UPDATED, true, null);
return savedOtaPackage;
} catch (Exception e) {
notificationEntityService.notifyEntity(tenantId, emptyId(EntityType.OTA_PACKAGE), null, null,

View File

@ -23,6 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.gson.JsonObject;
import com.google.protobuf.AbstractMessage;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.MessageLite;
import org.apache.commons.lang3.RandomStringUtils;
@ -32,7 +33,10 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.Customer;
@ -42,6 +46,8 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.User;
@ -70,6 +76,8 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.ota.ChecksumAlgorithm;
import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.EntityKeyValueType;
@ -107,6 +115,7 @@ import org.thingsboard.server.gen.edge.v1.EdgeConfiguration;
import org.thingsboard.server.gen.edge.v1.EntityDataProto;
import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg;
import org.thingsboard.server.gen.edge.v1.EntityViewsRequestMsg;
import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RelationRequestMsg;
import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RpcResponseMsg;
@ -123,6 +132,7 @@ import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg;
import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@ -133,18 +143,21 @@ import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
@TestPropertySource(properties = {
"edges.enabled=true",
})
abstract public class BaseEdgeTest extends AbstractControllerTest {
private static final String CUSTOM_DEVICE_PROFILE_NAME = "Thermostat";
private static final String THERMOSTAT_DEVICE_PROFILE_NAME = "Thermostat";
private Tenant savedTenant;
private TenantId tenantId;
private User tenantAdmin;
private DeviceProfile thermostatDeviceProfile;
private EdgeImitator edgeImitator;
private Edge edge;
@ -204,12 +217,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
MqttDeviceProfileTransportConfiguration transportConfiguration = new MqttDeviceProfileTransportConfiguration();
transportConfiguration.setTransportPayloadTypeConfiguration(new JsonTransportPayloadConfiguration());
DeviceProfile deviceProfile = this.createDeviceProfile(CUSTOM_DEVICE_PROFILE_NAME, transportConfiguration);
thermostatDeviceProfile = this.createDeviceProfile(THERMOSTAT_DEVICE_PROFILE_NAME, transportConfiguration);
extendDeviceProfileData(deviceProfile);
doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class);
extendDeviceProfileData(thermostatDeviceProfile);
thermostatDeviceProfile = doPost("/api/deviceProfile", thermostatDeviceProfile, DeviceProfile.class);
Device savedDevice = saveDevice("Edge Device 1", CUSTOM_DEVICE_PROFILE_NAME);
Device savedDevice = saveDevice("Edge Device 1", THERMOSTAT_DEVICE_PROFILE_NAME);
doPost("/api/edge/" + edge.getUuidId()
+ "/device/" + savedDevice.getUuidId(), Device.class);
@ -269,7 +282,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
List<DeviceProfileUpdateMsg> deviceProfileUpdateMsgList = edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class);
Assert.assertEquals(3, deviceProfileUpdateMsgList.size());
Optional<DeviceProfileUpdateMsg> deviceProfileUpdateMsgOpt =
deviceProfileUpdateMsgList.stream().filter(dfum -> CUSTOM_DEVICE_PROFILE_NAME.equals(dfum.getName())).findAny();
deviceProfileUpdateMsgList.stream().filter(dfum -> THERMOSTAT_DEVICE_PROFILE_NAME.equals(dfum.getName())).findAny();
Assert.assertTrue(deviceProfileUpdateMsgOpt.isPresent());
DeviceProfileUpdateMsg deviceProfileUpdateMsg = deviceProfileUpdateMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType());
@ -1643,6 +1656,110 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertTrue("Expected key and value must be found", found);
}
@Test
public void testOtaPackages_usesUrl() throws Exception {
// 1
SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest();
firmwareInfo.setDeviceProfileId(thermostatDeviceProfile.getId());
firmwareInfo.setType(FIRMWARE);
firmwareInfo.setTitle("My firmware #1");
firmwareInfo.setVersion("v1.0");
firmwareInfo.setTag("My firmware #1 v1.0");
firmwareInfo.setUsesUrl(true);
firmwareInfo.setUrl("http://localhost:8080/v1/package");
firmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode());
edgeImitator.expectMessageAmount(1);
OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class);
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof OtaPackageUpdateMsg);
OtaPackageUpdateMsg otaPackageUpdateMsg = (OtaPackageUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, otaPackageUpdateMsg.getMsgType());
Assert.assertEquals(savedFirmwareInfo.getUuidId().getMostSignificantBits(), otaPackageUpdateMsg.getIdMSB());
Assert.assertEquals(savedFirmwareInfo.getUuidId().getLeastSignificantBits(), otaPackageUpdateMsg.getIdLSB());
Assert.assertEquals(thermostatDeviceProfile.getUuidId().getMostSignificantBits(), otaPackageUpdateMsg.getDeviceProfileIdMSB());
Assert.assertEquals(thermostatDeviceProfile.getUuidId().getLeastSignificantBits(), otaPackageUpdateMsg.getDeviceProfileIdLSB());
Assert.assertEquals(FIRMWARE, OtaPackageType.valueOf(otaPackageUpdateMsg.getType()));
Assert.assertEquals("My firmware #1", otaPackageUpdateMsg.getTitle());
Assert.assertEquals("v1.0", otaPackageUpdateMsg.getVersion());
Assert.assertEquals("My firmware #1 v1.0", otaPackageUpdateMsg.getTag());
Assert.assertEquals("http://localhost:8080/v1/package", otaPackageUpdateMsg.getUrl());
Assert.assertFalse(otaPackageUpdateMsg.hasData());
Assert.assertFalse(otaPackageUpdateMsg.hasFileName());
Assert.assertFalse(otaPackageUpdateMsg.hasContentType());
Assert.assertFalse(otaPackageUpdateMsg.hasChecksumAlgorithm());
Assert.assertFalse(otaPackageUpdateMsg.hasChecksum());
Assert.assertFalse(otaPackageUpdateMsg.hasDataSize());
// 2
edgeImitator.expectMessageAmount(1);
doDelete("/api/otaPackage/" + savedFirmwareInfo.getUuidId())
.andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof OtaPackageUpdateMsg);
otaPackageUpdateMsg = (OtaPackageUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, otaPackageUpdateMsg.getMsgType());
Assert.assertEquals(otaPackageUpdateMsg.getIdMSB(), savedFirmwareInfo.getUuidId().getMostSignificantBits());
Assert.assertEquals(otaPackageUpdateMsg.getIdLSB(), savedFirmwareInfo.getUuidId().getLeastSignificantBits());
}
@Test
public void testOtaPackages_hasData() throws Exception {
// 1
SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest();
firmwareInfo.setDeviceProfileId(thermostatDeviceProfile.getId());
firmwareInfo.setType(FIRMWARE);
firmwareInfo.setTitle("My firmware #2");
firmwareInfo.setVersion("v2.0");
firmwareInfo.setTag("My firmware #2 v2.0");
firmwareInfo.setUsesUrl(false);
firmwareInfo.setHasData(false);
firmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode());
edgeImitator.expectMessageAmount(1);
OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class);
MockMultipartFile testData = new MockMultipartFile("file", "firmware.bin", "image/png", ByteBuffer.wrap(new byte[]{1, 3, 5}).array());
savedFirmwareInfo = saveData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksumAlgorithm={checksumAlgorithm}", testData, ChecksumAlgorithm.SHA256.name());
Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof OtaPackageUpdateMsg);
OtaPackageUpdateMsg otaPackageUpdateMsg = (OtaPackageUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, otaPackageUpdateMsg.getMsgType());
Assert.assertEquals(savedFirmwareInfo.getUuidId().getMostSignificantBits(), otaPackageUpdateMsg.getIdMSB());
Assert.assertEquals(savedFirmwareInfo.getUuidId().getLeastSignificantBits(), otaPackageUpdateMsg.getIdLSB());
Assert.assertEquals(thermostatDeviceProfile.getUuidId().getMostSignificantBits(), otaPackageUpdateMsg.getDeviceProfileIdMSB());
Assert.assertEquals(thermostatDeviceProfile.getUuidId().getLeastSignificantBits(), otaPackageUpdateMsg.getDeviceProfileIdLSB());
Assert.assertEquals(FIRMWARE, OtaPackageType.valueOf(otaPackageUpdateMsg.getType()));
Assert.assertEquals("My firmware #2", otaPackageUpdateMsg.getTitle());
Assert.assertEquals("v2.0", otaPackageUpdateMsg.getVersion());
Assert.assertEquals("My firmware #2 v2.0", otaPackageUpdateMsg.getTag());
Assert.assertFalse(otaPackageUpdateMsg.hasUrl());
Assert.assertEquals("firmware.bin", otaPackageUpdateMsg.getFileName());
Assert.assertEquals("image/png", otaPackageUpdateMsg.getContentType());
Assert.assertEquals(ChecksumAlgorithm.SHA256.name(), otaPackageUpdateMsg.getChecksumAlgorithm());
Assert.assertEquals("62467691cf583d4fa78b18fafaf9801f505e0ef03baf0603fd4b0cd004cd1e75", otaPackageUpdateMsg.getChecksum());
Assert.assertEquals(3L, otaPackageUpdateMsg.getDataSize());
Assert.assertEquals(ByteString.copyFrom(new byte[]{1, 3, 5}), otaPackageUpdateMsg.getData());
// 2
edgeImitator.expectMessageAmount(1);
doDelete("/api/otaPackage/" + savedFirmwareInfo.getUuidId())
.andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages());
latestMessage = edgeImitator.getLatestMessage();
Assert.assertTrue(latestMessage instanceof OtaPackageUpdateMsg);
otaPackageUpdateMsg = (OtaPackageUpdateMsg) latestMessage;
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, otaPackageUpdateMsg.getMsgType());
Assert.assertEquals(otaPackageUpdateMsg.getIdMSB(), savedFirmwareInfo.getUuidId().getMostSignificantBits());
Assert.assertEquals(otaPackageUpdateMsg.getIdLSB(), savedFirmwareInfo.getUuidId().getLeastSignificantBits());
}
// Utility methods
private Device saveDeviceOnCloudAndVerifyDeliveryToEdge() throws Exception {
@ -1723,4 +1840,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest {
Assert.assertEquals(source, target);
Assert.assertEquals(source.hashCode(), target.hashCode());
}
private OtaPackageInfo saveData(String urlTemplate, MockMultipartFile content, String... params) throws Exception {
MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params);
postRequest.file(content);
setJwtToken(postRequest);
return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackageInfo.class);
}
}

View File

@ -41,6 +41,7 @@ import org.thingsboard.server.gen.edge.v1.DownlinkResponseMsg;
import org.thingsboard.server.gen.edge.v1.EdgeConfiguration;
import org.thingsboard.server.gen.edge.v1.EntityDataProto;
import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg;
import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg;
import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg;
@ -277,6 +278,11 @@ public class EdgeImitator {
result.add(saveDownlinkMsg(deviceCredentialsRequestMsg));
}
}
if (downlinkMsg.getOtaPackageUpdateMsgCount() > 0) {
for (OtaPackageUpdateMsg otaPackageUpdateMsg : downlinkMsg.getOtaPackageUpdateMsgList()) {
result.add(saveDownlinkMsg(otaPackageUpdateMsg));
}
}
return Futures.allAsList(result);
}

View File

@ -60,6 +60,8 @@ public final class EdgeUtils {
return EdgeEventType.WIDGETS_BUNDLE;
case WIDGET_TYPE:
return EdgeEventType.WIDGET_TYPE;
case OTA_PACKAGE:
return EdgeEventType.OTA_PACKAGE;
default:
log.warn("Unsupported entity type [{}]", entityType);
return null;

View File

@ -31,5 +31,6 @@ public enum EdgeEventType {
TENANT,
WIDGETS_BUNDLE,
WIDGET_TYPE,
ADMIN_SETTINGS
ADMIN_SETTINGS,
OTA_PACKAGE
}

View File

@ -107,6 +107,8 @@ public class EntityIdFactory {
return new WidgetsBundleId(uuid);
case WIDGET_TYPE:
return new WidgetTypeId(uuid);
case OTA_PACKAGE:
return new OtaPackageId(uuid);
case EDGE:
return new EdgeId(uuid);
}

View File

@ -421,6 +421,26 @@ enum EdgeEntityType {
ASSET = 1;
}
message OtaPackageUpdateMsg {
UpdateMsgType msgType = 1;
int64 idMSB = 2;
int64 idLSB = 3;
int64 deviceProfileIdMSB = 4;
int64 deviceProfileIdLSB = 5;
string type = 6;
string title = 7;
string version = 8;
string tag = 9;
optional string url = 10;
optional string fileName = 11;
optional string contentType = 12;
optional string checksumAlgorithm = 13;
optional string checksum = 14;
optional int64 dataSize = 15;
optional bytes data = 16;
optional string additionalInfo = 17;
}
/**
* Main Messages;
*/
@ -477,5 +497,6 @@ message DownlinkMsg {
repeated WidgetTypeUpdateMsg widgetTypeUpdateMsg = 19;
repeated AdminSettingsUpdateMsg adminSettingsUpdateMsg = 20;
repeated DeviceRpcCallMsg deviceRpcCallMsg = 21;
repeated OtaPackageUpdateMsg otaPackageUpdateMsg = 22;
}

View File

@ -397,40 +397,35 @@ public class EdgeServiceImpl extends AbstractCachedEntityService<EdgeCacheKey, E
@Override
public PageData<EdgeId> findRelatedEdgeIdsByEntityId(TenantId tenantId, EntityId entityId, PageLink pageLink) {
log.trace("[{}] Executing findRelatedEdgeIdsByEntityId [{}] [{}]", tenantId, entityId, pageLink);
if (EntityType.TENANT.equals(entityId.getEntityType()) ||
EntityType.CUSTOMER.equals(entityId.getEntityType()) ||
EntityType.DEVICE_PROFILE.equals(entityId.getEntityType())) {
if (EntityType.TENANT.equals(entityId.getEntityType()) ||
EntityType.DEVICE_PROFILE.equals(entityId.getEntityType())) {
switch (entityId.getEntityType()) {
case TENANT:
case DEVICE_PROFILE:
case OTA_PACKAGE:
return convertToEdgeIds(findEdgesByTenantId(tenantId, pageLink));
} else {
case CUSTOMER:
return convertToEdgeIds(findEdgesByTenantIdAndCustomerId(tenantId, new CustomerId(entityId.getId()), pageLink));
}
} else {
switch (entityId.getEntityType()) {
case EDGE:
List<EdgeId> edgeIds = Collections.singletonList(new EdgeId(entityId.getId()));
return new PageData<>(edgeIds, 1, 1, false);
case DEVICE:
case ASSET:
case ENTITY_VIEW:
case DASHBOARD:
case RULE_CHAIN:
return convertToEdgeIds(findEdgesByTenantIdAndEntityId(tenantId, entityId, pageLink));
case USER:
User userById = userService.findUserById(tenantId, new UserId(entityId.getId()));
if (userById == null) {
return createEmptyEdgeIdPageData();
}
if (userById.getCustomerId() == null || userById.getCustomerId().isNullUid()) {
return convertToEdgeIds(findEdgesByTenantId(tenantId, pageLink));
} else {
return convertToEdgeIds(findEdgesByTenantIdAndCustomerId(tenantId, userById.getCustomerId(), pageLink));
}
default:
log.warn("[{}] Unsupported entity type {}", tenantId, entityId.getEntityType());
case EDGE:
List<EdgeId> edgeIds = Collections.singletonList(new EdgeId(entityId.getId()));
return new PageData<>(edgeIds, 1, 1, false);
case DEVICE:
case ASSET:
case ENTITY_VIEW:
case DASHBOARD:
case RULE_CHAIN:
return convertToEdgeIds(findEdgesByTenantIdAndEntityId(tenantId, entityId, pageLink));
case USER:
User userById = userService.findUserById(tenantId, new UserId(entityId.getId()));
if (userById == null) {
return createEmptyEdgeIdPageData();
}
}
if (userById.getCustomerId() == null || userById.getCustomerId().isNullUid()) {
return convertToEdgeIds(findEdgesByTenantId(tenantId, pageLink));
} else {
return convertToEdgeIds(findEdgesByTenantIdAndCustomerId(tenantId, userById.getCustomerId(), pageLink));
}
default:
log.warn("[{}] Unsupported entity type {}", tenantId, entityId.getEntityType());
return createEmptyEdgeIdPageData();
}
}