Browse Source

fix(iot-hub): compensate orphaned entities when installed-item tracking save fails

If iotHubInstalledItemService.save throws after the local entity was already created, the entity was orphaned — visible in the tenant but invisible to IoT Hub update/delete. Extracted the per-type entity-deletion dispatch from deleteInstalledItem into a deleteEntityForDescriptor helper and call it from doInstallVersion and registerDeviceInstall on tracking-save failure. registerDeviceInstall now also wraps reportVersionInstalled best-effort to match doInstallVersion.
pull/15193/head
Igor Kulikov 1 month ago
parent
commit
77c74d7ccd
  1. 56
      application/src/main/java/org/thingsboard/server/service/iot_hub/DefaultIotHubService.java

56
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

Loading…
Cancel
Save