diff --git a/storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorageFactory.java b/storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorageFactory.java index 57456c18d98..5769362d074 100644 --- a/storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorageFactory.java +++ b/storage/src/main/java/org/zstack/storage/addon/primary/ExternalPrimaryStorageFactory.java @@ -320,7 +320,21 @@ public PrimaryStorageInventory createPrimaryStorage(PrimaryStorageVO vo, APIAddP dbf.persist(lvo); dbf.persist(ref); - saveControllerIfNeed(lvo); + try { + saveControllerIfNeed(lvo); + } catch (Throwable t) { + logger.warn(String.format("failed to build controller for primary storage[uuid:%s, identity:%s], rolling back persisted records", + lvo.getUuid(), identity), t); + try { + dbf.remove(ref); + dbf.remove(lvo); + } catch (Throwable cleanupEx) { + logger.warn(String.format("failed to roll back persisted records for primary storage[uuid:%s]", lvo.getUuid()), cleanupEx); + } + controllers.remove(lvo.getUuid()); + nodes.remove(lvo.getUuid()); + throw t; + } return lvo.toInventory(); } diff --git a/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsPrimaryStorageCase.groovy b/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsPrimaryStorageCase.groovy index f07add523d3..1b4cf83e027 100644 --- a/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsPrimaryStorageCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/zbs/ZbsPrimaryStorageCase.groovy @@ -179,6 +179,7 @@ class ZbsPrimaryStorageCase extends SubCase { testMdsPing() testCheckHostStorageConnection() testNegativeScenario() + testAddExternalPrimaryStorageWithMalformedJsonShouldRollback() testDataVolumeNegativeScenario() testDecodeMdsUriWithSpecialPassword() testMdsReconnectAfterMaximumPingFailures() @@ -725,6 +726,35 @@ class ZbsPrimaryStorageCase extends SubCase { } } + // AddExternalPrimaryStorage 收到非法 JSON config 导致 buildControllerSvc 抛异常时, + // ExternalPrimaryStorageVO/PrimaryStorageVO/PrimaryStorageOutputProtocolRefVO 不应残留在 DB 中。 + // 否则 buildPsController 会被脏 VO 持续打挂,QueryPrimaryStorage 永久 503。 + void testAddExternalPrimaryStorageWithMalformedJsonShouldRollback() { + long psCountBefore = Q.New(ExternalPrimaryStorageVO.class).count() + + // malformed JSON — JSONObjectUtil.toObject(config, Config.class) 会抛 RuntimeException + expect(AssertionError.class) { + addExternalPrimaryStorage { + zoneUuid = zone.uuid + name = "zbs-bad-json" + identity = "zbs" + defaultOutputProtocol = "CBD" + config = "{this is not valid json" + url = "" + } + } + + // 失败后不应在 DB 中遗留任何 zbs-bad-json 相关记录 + assert Q.New(ExternalPrimaryStorageVO.class).count() == psCountBefore + assert !Q.New(ExternalPrimaryStorageVO.class) + .eq(ExternalPrimaryStorageVO_.name, "zbs-bad-json") + .isExists() + + // 失败之后 QueryPrimaryStorage 仍可正常返回(不被脏数据打挂) + def psList = queryPrimaryStorage {} as List + assert psList != null + } + void testDataVolumeNegativeScenario() { env.simulator(ZbsStorageController.CREATE_VOLUME_PATH) { HttpEntity e, EnvSpec spec -> def rsp = new ZbsStorageController.CreateVolumeRsp()