diff --git a/application/src/main/java/org/thingsboard/server/service/iot_hub/DefaultIotHubService.java b/application/src/main/java/org/thingsboard/server/service/iot_hub/DefaultIotHubService.java index fbd7e4160b..e88d812f5b 100644 --- a/application/src/main/java/org/thingsboard/server/service/iot_hub/DefaultIotHubService.java +++ b/application/src/main/java/org/thingsboard/server/service/iot_hub/DefaultIotHubService.java @@ -178,7 +178,22 @@ public class DefaultIotHubService implements IotHubService { installedItem.setItemType(itemType); installedItem.setVersion(version); installedItem.setDescriptor(descriptor); - installedItem = iotHubInstalledItemService.save(tenantId, installedItem); + try { + installedItem = iotHubInstalledItemService.save(tenantId, installedItem); + } catch (Exception e) { + // Tracking save failed after the entity was already created locally — compensate by + // deleting the orphaned entity so the user is not left with an installed item that + // IoT Hub update/delete flows cannot see. + log.error("[{}] Failed to save IoT Hub installed-item tracking row for {} ({}); rolling back created entity", + tenantId, itemName, itemType, e); + try { + deleteEntityForDescriptor(user, tenantId, descriptor, null); + } catch (Exception rb) { + log.error("[{}] Compensating delete after failed tracking save threw — entity may remain orphaned", + tenantId, rb); + } + throw e; + } try { iotHubRestClient.reportVersionInstalled(versionId); @@ -620,9 +635,26 @@ public class DefaultIotHubService implements IotHubService { installedItem.setItemType("DEVICE"); installedItem.setVersion(version); installedItem.setDescriptor(descriptor); - iotHubInstalledItemService.save(tenantId, installedItem); + try { + iotHubInstalledItemService.save(tenantId, installedItem); + } catch (Exception e) { + log.error("[{}] Failed to save IoT Hub installed-item tracking row for device package {} (version {}); rolling back created entities", + tenantId, itemName, version, e); + try { + deleteEntityForDescriptor(user, tenantId, descriptor, null); + } catch (Exception rb) { + log.error("[{}] Compensating delete after failed tracking save threw — device package entities may remain orphaned", + tenantId, rb); + } + throw e; + } - iotHubRestClient.reportVersionInstalled(versionId); + try { + iotHubRestClient.reportVersionInstalled(versionId); + } catch (Exception e) { + // Counter ping is best-effort — do not fail the install if it errors. + log.warn("[{}] Failed to report install counter for version {}: {}", tenantId, versionId, e.getMessage()); + } log.info("[{}] Registered device package install: {} (version {})", tenantId, itemName, version); return InstallItemVersionResult.success(descriptor); @@ -677,7 +709,20 @@ public class DefaultIotHubService implements IotHubService { throw new IllegalArgumentException("Installed item not found"); } - IotHubInstalledItemDescriptor descriptor = installedItem.getDescriptor(); + deleteEntityForDescriptor(user, tenantId, installedItem.getDescriptor(), installedItemId); + + iotHubInstalledItemService.deleteById(tenantId, installedItemId); + log.info("[{}] Deleted installed IoT Hub item: {}", tenantId, installedItem.getItemName()); + } + + /** + * Per-type dispatcher that removes the local entities a marketplace install created. Shared by + * {@link #deleteInstalledItem} and by the tracking-row compensation path in + * {@link #doInstallVersion} so a failed tracking save does not leave the entity orphaned. + */ + private void deleteEntityForDescriptor(SecurityUser user, TenantId tenantId, + IotHubInstalledItemDescriptor descriptor, + IotHubInstalledItemId installedItemId) { if (descriptor instanceof WidgetInstalledItemDescriptor wd) { WidgetTypeDetails widgetType = widgetTypeService.findWidgetTypeDetailsById(tenantId, wd.getWidgetTypeId()); if (widgetType != null) { @@ -709,9 +754,6 @@ public class DefaultIotHubService implements IotHubService { log.error("[{}] Failed to delete solution for installed item {}", tenantId, installedItemId, e); } } - - iotHubInstalledItemService.deleteById(tenantId, installedItemId); - log.info("[{}] Deleted installed IoT Hub item: {}", tenantId, installedItem.getItemName()); } @Override