From fd36fcd1d66bc16d4339c25b03aa90b473ff88ce Mon Sep 17 00:00:00 2001 From: J M Date: Mon, 1 Jun 2026 17:35:12 +0800 Subject: [PATCH 01/54] [zbs]: fix linked-clone parent volume residual stats()/flatten() returned parentUri, and batchStats() returned the volume install path, as the agent cbd path while the rest of the code expects zbs:// paths. The snapshot-reference GC fed the cbd parentUri back into stats(); prepareCmd() converted it again and the backing chain query failed with ZBS_10026, rolling back the cleanup and leaving the parent volume and snapshot on storage. batchStats() set the VolumeStats install path to the cbd key, so BatchSyncVolumeSize could not map actual size back to the volume uuid. Normalize agent-returned cbd paths to zbs:// so they stay in one path namespace. Resolves: ZSTAC-85707 Change-Id: I1c3dbe96086b681b83a4a627bdce37e76575564f --- .../org/zstack/storage/zbs/ZbsHelper.java | 4 + .../storage/zbs/ZbsStorageController.java | 6 +- .../addon/zbs/ZbsPrimaryStorageCase.groovy | 75 +++++++++++++++++++ 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsHelper.java b/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsHelper.java index 3484be39c20..6808a02bb1e 100644 --- a/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsHelper.java +++ b/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsHelper.java @@ -19,6 +19,10 @@ public static String convertCbdPathToZbsPath(String cbdPath) { return cbdPath.replaceFirst(".+?/", "zbs://"); } + public static String normalizeToZbsPath(String path) { + return path != null && path.startsWith("cbd:") ? convertCbdPathToZbsPath(path) : path; + } + public static String convertZbsPathToCbdPath(String zbsPath, Function physicalPoolGetter) { String logicalPool = getPoolFromVolumePath(zbsPath); String physicalPool = physicalPoolGetter.apply(logicalPool); diff --git a/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java b/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java index 1a7342fc4a4..27810c6bb45 100644 --- a/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java +++ b/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsStorageController.java @@ -1022,7 +1022,7 @@ public void success(FlattenVolumeRsp returnValue) { stats.setSize(returnValue.getSize()); stats.setActualSize(returnValue.getActualSize()); stats.setFormat(VolumeConstant.VOLUME_FORMAT_RAW); - stats.setParentUri(returnValue.getParentUri()); + stats.setParentUri(ZbsHelper.normalizeToZbsPath(returnValue.getParentUri())); comp.success(stats); } @@ -1046,7 +1046,7 @@ public void success(QueryVolumeRsp returnValue) { stats.setSize(returnValue.getSize()); stats.setActualSize(returnValue.getActualSize()); stats.setFormat(VolumeConstant.VOLUME_FORMAT_RAW); - stats.setParentUri(returnValue.getParentUri()); + stats.setParentUri(ZbsHelper.normalizeToZbsPath(returnValue.getParentUri())); comp.success(stats); } @@ -1069,7 +1069,7 @@ public void batchStats(Collection installPaths, ReturnValueCompletion
  • stats = returnValue.getVolumes().entrySet().stream().map(v -> { VolumeStats s = new VolumeStats(); - s.setInstallPath(v.getKey()); + s.setInstallPath(ZbsHelper.normalizeToZbsPath(v.getKey())); s.setSize(v.getValue().get("length")); s.setActualSize(v.getValue().get("usedSize")); s.setFormat(VolumeConstant.VOLUME_FORMAT_RAW); 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 d938f1da8fc..089a8ac2c1c 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 @@ -1,10 +1,17 @@ package org.zstack.test.integration.storage.primary.addon.zbs import org.springframework.http.HttpEntity +import org.zstack.core.cloudbus.CloudBus import org.zstack.core.cloudbus.EventCallback import org.zstack.core.cloudbus.EventFacade import org.zstack.core.db.DatabaseFacade import org.zstack.core.db.Q +import org.zstack.header.message.MessageReply +import org.zstack.header.storage.primary.GetVolumeBackingChainFromPrimaryStorageMsg +import org.zstack.header.storage.primary.GetVolumeBackingChainFromPrimaryStorageReply +import org.zstack.header.storage.primary.PrimaryStorageConstant +import org.zstack.header.volume.BatchSyncVolumeSizeOnPrimaryStorageMsg +import org.zstack.header.volume.BatchSyncVolumeSizeOnPrimaryStorageReply import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageVO import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageSpaceVO import org.zstack.header.storage.addon.primary.ExternalPrimaryStorageVO_ @@ -191,6 +198,8 @@ class ZbsPrimaryStorageCase extends SubCase { testDataVolumeNegativeScenario() testDecodeMdsUriWithSpecialPassword() testMdsReconnectAfterMaximumPingFailures() + testGetBackingChainNormalizesCbdParentUri() + testBatchStatsNormalizesCbdInstallPath() } } @@ -1095,6 +1104,72 @@ class ZbsPrimaryStorageCase extends SubCase { env.cleanAfterSimulatorHandlers() } + void testGetBackingChainNormalizesCbdParentUri() { + String childSnapPath = "zbs://lpool1/volume_child@snapshot_s1" + String parentZbsPath = "zbs://lpool1/volume_parent" + String parentCbdPath = "cbd:pool1/lpool1/volume_parent" + + env.simulator(ZbsStorageController.QUERY_VOLUME_PATH) { HttpEntity e, EnvSpec spec -> + def cmd = JSONObjectUtil.toObject(e.body, ZbsStorageController.QueryVolumeCmd.class) + def rsp = new ZbsStorageController.QueryVolumeRsp() + rsp.size = SizeUnit.GIGABYTE.toByte(8) + rsp.actualSize = SizeUnit.MEGABYTE.toByte(1) + if (cmd.path.contains("volume_child")) { + rsp.parentUri = parentCbdPath + } else { + rsp.parentUri = null + } + return rsp + } + + CloudBus bus = bean(CloudBus.class) + GetVolumeBackingChainFromPrimaryStorageMsg msg = new GetVolumeBackingChainFromPrimaryStorageMsg() + msg.setPrimaryStorageUuid(ps.uuid) + msg.setVolumeUuid(childSnapPath) + msg.setRootInstallPaths([childSnapPath]) + bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, ps.uuid) + MessageReply reply = bus.call(msg) + + assert reply.isSuccess() : "backing chain walk must not fail on cbd-form parentUri: ${reply.error}" + GetVolumeBackingChainFromPrimaryStorageReply r = reply as GetVolumeBackingChainFromPrimaryStorageReply + List chain = r.getBackingChainInstallPath(childSnapPath) + assert chain != null && chain.contains(parentZbsPath) : + "parent must be resolved as a zbs:// path, got ${chain}" + + env.cleanSimulatorHandlers() + } + + void testBatchStatsNormalizesCbdInstallPath() { + String volUuid = "vol85707batch" + String zbsPath = "zbs://lpool1/volume_batch" + long usedSize = SizeUnit.MEGABYTE.toByte(7) + + env.simulator(ZbsStorageController.BATCH_QUERY_VOLUME_PATH) { HttpEntity e, EnvSpec spec -> + def cmd = JSONObjectUtil.toObject(e.body, ZbsStorageController.BatchQueryVolumeCmd.class) + def rsp = new ZbsStorageController.BatchQueryVolumeRsp() + Map> volumes = new HashMap<>() + cmd.installPaths.each { cbd -> + volumes.put(cbd, ["length": SizeUnit.GIGABYTE.toByte(8), "usedSize": usedSize]) + } + rsp.setVolumes(volumes) + return rsp + } + + CloudBus bus = bean(CloudBus.class) + BatchSyncVolumeSizeOnPrimaryStorageMsg msg = new BatchSyncVolumeSizeOnPrimaryStorageMsg() + msg.setPrimaryStorageUuid(ps.uuid) + msg.setVolumeUuidInstallPaths([(volUuid): zbsPath]) + bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, ps.uuid) + MessageReply reply = bus.call(msg) + + assert reply.isSuccess() : "batch size sync must not fail on cbd-form install path: ${reply.error}" + BatchSyncVolumeSizeOnPrimaryStorageReply r = reply as BatchSyncVolumeSizeOnPrimaryStorageReply + assert r.actualSizes.get(volUuid) == usedSize : + "actualSize must map back to uuid via zbs:// install path, got ${r.actualSizes}" + + env.cleanSimulatorHandlers() + } + void deleteVolume(String volUuid) { deleteDataVolume { uuid = volUuid From 9877fbb59f963cabe642c331af253589f34ecc28 Mon Sep 17 00:00:00 2001 From: "dejing.liu" Date: Tue, 2 Jun 2026 14:09:51 +0800 Subject: [PATCH 02/54] [eip]: fix EIP attach release race Persist vmNicUuid/guestIp before backend apply so VM release can see in-flight EIP bindings. Roll back only when the failed attach still matches the same EIP/NIC/IP tuple. Test: real Flat EIP flow and AttachEipRaceCase JIRA: ZSTAC-83309 Change-Id: Ie8795d5bd1c3e90aa10e18cfef19d4968c73cfee --- .../network/service/eip/EipManagerImpl.java | 26 +- .../flat/eip/AttachEipRaceCase.groovy | 258 ++++++++++++++++++ 2 files changed, 279 insertions(+), 5 deletions(-) create mode 100644 test/src/test/groovy/org/zstack/test/integration/networkservice/provider/flat/eip/AttachEipRaceCase.groovy diff --git a/plugin/eip/src/main/java/org/zstack/network/service/eip/EipManagerImpl.java b/plugin/eip/src/main/java/org/zstack/network/service/eip/EipManagerImpl.java index dae4a3e10bf..6311ed4be99 100755 --- a/plugin/eip/src/main/java/org/zstack/network/service/eip/EipManagerImpl.java +++ b/plugin/eip/src/main/java/org/zstack/network/service/eip/EipManagerImpl.java @@ -587,7 +587,13 @@ private void handle(final APIAttachEipMsg msg) { return; } - EipInventory eip = EipInventory.valueOf(vo); + final String attachedVmNicUuid = nicInventory.getUuid(); + final String attachedGuestIp = guestIp.getIp(); + vo.setVmNicUuid(attachedVmNicUuid); + vo.setGuestIp(attachedGuestIp); + final EipVO updated = dbf.updateAndRefresh(vo); + + EipInventory eip = EipInventory.valueOf(updated); String l3NetworkUuid = getEipL3Network(nicInventory, eip); NetworkServiceProviderType providerType = nwServiceMgr.getTypeOfNetworkServiceProviderForService( l3NetworkUuid, EipConstant.EIP_TYPE); @@ -596,15 +602,25 @@ private void handle(final APIAttachEipMsg msg) { attachEip(struct, providerType.toString(), new Completion(msg) { @Override public void success() { - vo.setVmNicUuid(nicInventory.getUuid()); - vo.setGuestIp(guestIp.getIp()); - EipVO evo = dbf.updateAndRefresh(vo); - evt.setInventory(EipInventory.valueOf(evo)); + evt.setInventory(EipInventory.valueOf(dbf.reload(updated))); bus.publish(evt); } @Override public void fail(ErrorCode errorCode) { + int affectedRows = SQL.New(EipVO.class) + .eq(EipVO_.uuid, msg.getEipUuid()) + .eq(EipVO_.vmNicUuid, attachedVmNicUuid) + .eq(EipVO_.guestIp, attachedGuestIp) + .set(EipVO_.vmNicUuid, null) + .set(EipVO_.guestIp, null) + .update(); + if (affectedRows == 0) { + logger.warn(String.format( + "failed to roll back EIP[uuid:%s] attach binding[vmNicUuid:%s, guestIp:%s], " + + "the binding may have been changed by another flow", + msg.getEipUuid(), attachedVmNicUuid, attachedGuestIp)); + } evt.setError(errorCode); bus.publish(evt); } diff --git a/test/src/test/groovy/org/zstack/test/integration/networkservice/provider/flat/eip/AttachEipRaceCase.groovy b/test/src/test/groovy/org/zstack/test/integration/networkservice/provider/flat/eip/AttachEipRaceCase.groovy new file mode 100644 index 00000000000..27123046860 --- /dev/null +++ b/test/src/test/groovy/org/zstack/test/integration/networkservice/provider/flat/eip/AttachEipRaceCase.groovy @@ -0,0 +1,258 @@ +package org.zstack.test.integration.networkservice.provider.flat.eip + +import org.springframework.http.HttpEntity +import org.zstack.header.network.service.NetworkServiceType +import org.zstack.network.service.eip.EipConstant +import org.zstack.network.service.eip.EipVO +import org.zstack.network.service.flat.FlatEipBackend +import org.zstack.network.service.flat.FlatNetworkServiceConstant +import org.zstack.network.service.userdata.UserdataConstant +import org.zstack.sdk.AttachEipAction +import org.zstack.sdk.EipInventory +import org.zstack.sdk.StopVmInstanceAction +import org.zstack.sdk.VmInstanceInventory +import org.zstack.test.integration.networkservice.provider.NetworkServiceProviderTest +import org.zstack.testlib.EnvSpec +import org.zstack.testlib.HttpError +import org.zstack.testlib.SubCase +import org.zstack.utils.data.SizeUnit + +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +class AttachEipRaceCase extends SubCase { + EnvSpec env + + @Override + void setup() { + useSpring(NetworkServiceProviderTest.springSpec) + } + + @Override + void environment() { + env = env { + instanceOffering { + name = "instanceOffering" + memory = SizeUnit.GIGABYTE.toByte(8) + cpu = 4 + } + + sftpBackupStorage { + name = "sftp" + url = "/sftp" + username = "root" + password = "password" + hostname = "localhost" + + image { + name = "image" + url = "http://zstack.org/download/test.qcow2" + } + } + + zone { + name = "zone" + description = "test" + + cluster { + name = "cluster" + hypervisorType = "KVM" + + kvm { + name = "kvm" + managementIp = "localhost" + username = "root" + password = "password" + } + + attachPrimaryStorage("local") + attachL2Network("l2") + attachL2Network("l2-pub") + } + + localPrimaryStorage { + name = "local" + url = "/local_ps" + } + + l2NoVlanNetwork { + name = "l2" + physicalInterface = "eth0" + + l3Network { + name = "l3" + + service { + provider = FlatNetworkServiceConstant.FLAT_NETWORK_SERVICE_TYPE_STRING + types = [NetworkServiceType.DHCP.toString(), EipConstant.EIP_NETWORK_SERVICE_TYPE, UserdataConstant.USERDATA_TYPE_STRING] + } + + ip { + startIp = "192.168.100.10" + endIp = "192.168.100.100" + netmask = "255.255.255.0" + gateway = "192.168.100.1" + } + } + } + + l2NoVlanNetwork { + name = "l2-pub" + physicalInterface = "eth1" + + l3Network { + name = "pubL3" + + ip { + startIp = "11.168.100.10" + endIp = "11.168.100.100" + netmask = "255.255.255.0" + gateway = "11.168.100.1" + } + } + } + + attachBackupStorage("sftp") + + eip { + name = "eip-race" + useVip("pubL3") + } + + eip { + name = "eip-fail" + useVip("pubL3") + } + } + + vm { + name = "vm-race" + useImage("image") + useL3Networks("l3") + useInstanceOffering("instanceOffering") + } + + vm { + name = "vm-fail" + useImage("image") + useL3Networks("l3") + useInstanceOffering("instanceOffering") + } + } + } + + @Override + void test() { + env.create { + testStopVmDuringAttachReleasesEip() + testAttachFailureRollsBackEarlyBinding() + } + } + + void testStopVmDuringAttachReleasesEip() { + EipInventory eip = env.inventoryByName("eip-race") as EipInventory + VmInstanceInventory vm = env.inventoryByName("vm-race") as VmInstanceInventory + String nicUuid = vm.vmNics[0].uuid + String guestIp = vm.vmNics[0].ip + String sessionId = adminSession() + + CountDownLatch applyEntered = new CountDownLatch(1) + CountDownLatch releaseApply = new CountDownLatch(1) + CountDownLatch deleteCalled = new CountDownLatch(1) + FlatEipBackend.ApplyEipCmd applyCmd = null + FlatEipBackend.DeleteEipCmd deleteCmd = null + + env.afterSimulator(FlatEipBackend.APPLY_EIP_PATH) { rsp, HttpEntity entity -> + FlatEipBackend.ApplyEipCmd cmd = json(entity.getBody(), FlatEipBackend.ApplyEipCmd.class) + if (cmd.eip.eipUuid == eip.uuid) { + applyCmd = cmd + applyEntered.countDown() + assert releaseApply.await(30, TimeUnit.SECONDS) + } + return rsp + } + + env.afterSimulator(FlatEipBackend.DELETE_EIP_PATH) { rsp, HttpEntity entity -> + FlatEipBackend.DeleteEipCmd cmd = json(entity.getBody(), FlatEipBackend.DeleteEipCmd.class) + if (cmd.eip.eipUuid == eip.uuid) { + deleteCmd = cmd + deleteCalled.countDown() + } + return rsp + } + + Throwable attachFailure = null + Thread attachThread = Thread.start { + try { + AttachEipAction action = new AttachEipAction() + action.eipUuid = eip.uuid + action.vmNicUuid = nicUuid + action.sessionId = sessionId + def result = action.call() + if (result.error != null) { + attachFailure = new AssertionError(result.error.toString()) + } + } catch (Throwable t) { + attachFailure = t + } + } + + assert applyEntered.await(30, TimeUnit.SECONDS) + try { + retryInSecs { + EipVO vo = dbFindByUuid(eip.uuid, EipVO.class) + assert vo.vmNicUuid == nicUuid + assert vo.guestIp == guestIp + } + } finally { + releaseApply.countDown() + } + + attachThread.join(30000) + + assert !attachThread.isAlive() + assert attachFailure == null + assert applyCmd != null + + StopVmInstanceAction action = new StopVmInstanceAction() + action.uuid = vm.uuid + action.sessionId = sessionId + def result = action.call() + + assert result.error == null + assert deleteCalled.await(30, TimeUnit.SECONDS) + assert deleteCmd != null + assert deleteCmd.eip.eipUuid == eip.uuid + } + + void testAttachFailureRollsBackEarlyBinding() { + EipInventory eip = env.inventoryByName("eip-fail") as EipInventory + VmInstanceInventory vm = env.inventoryByName("vm-fail") as VmInstanceInventory + + env.afterSimulator(FlatEipBackend.APPLY_EIP_PATH) { rsp, HttpEntity entity -> + FlatEipBackend.ApplyEipCmd cmd = json(entity.getBody(), FlatEipBackend.ApplyEipCmd.class) + if (cmd.eip.eipUuid == eip.uuid) { + throw new HttpError(500, "on purpose") + } + return rsp + } + + AttachEipAction action = new AttachEipAction() + action.eipUuid = eip.uuid + action.vmNicUuid = vm.vmNics[0].uuid + action.sessionId = adminSession() + def result = action.call() + + assert result.error != null + retryInSecs { + EipVO vo = dbFindByUuid(eip.uuid, EipVO.class) + assert vo.vmNicUuid == null + assert vo.guestIp == null + } + } + + @Override + void clean() { + env.delete() + } +} From 3d42dfb11d95044df70db44bdeec67df31f53cb0 Mon Sep 17 00:00:00 2001 From: "pengchao.liu" Date: Tue, 2 Jun 2026 16:31:13 +0800 Subject: [PATCH 03/54] [portForwarding]: serialize rule creation Concurrent creates could pass interceptor validation before any rule was persisted. Recheck VIP port conflicts inside the VIP sync chain with closed-range boundary handling. Resolves: ZSTAC-77673 Change-Id: I7666646b7567696e6e7568736666766b61617766 --- .../PortForwardingManagerImpl.java | 66 ++++++- ...currentCreatePortForwardingRuleCase.groovy | 162 ++++++++++++++++++ 2 files changed, 223 insertions(+), 5 deletions(-) create mode 100644 test/src/test/groovy/org/zstack/test/integration/networkservice/provider/virtualrouter/portforwarding/ConcurrentCreatePortForwardingRuleCase.groovy diff --git a/plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/PortForwardingManagerImpl.java b/plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/PortForwardingManagerImpl.java index 80aa7597ff1..87dd4c1539c 100755 --- a/plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/PortForwardingManagerImpl.java +++ b/plugin/portForwarding/src/main/java/org/zstack/network/service/portforwarding/PortForwardingManagerImpl.java @@ -55,6 +55,7 @@ import java.util.stream.Collectors; import static java.util.Arrays.asList; +import static org.zstack.core.Platform.argerr; import static org.zstack.core.Platform.err; import static org.zstack.core.Platform.operr; import static org.zstack.utils.CollectionDSL.list; @@ -690,11 +691,60 @@ public void fail(ErrorCode errorCode) { private void handle(APICreatePortForwardingRuleMsg msg) { + thdf.chainSubmit(new ChainTask(msg) { + @Override + public String getSyncSignature() { + return String.format("portforwardingrule-vip-%s", msg.getVipUuid()); + } + + @Override + public void run(SyncTaskChain chain) { + doCreatePortForwardingRule(msg, chain); + } + + @Override + public String getName() { + return String.format("api-create-portforwardingrule-vip-%s", msg.getVipUuid()); + } + }); + } + + private ErrorCode validateVipPortConflict(String vipUuid, String protocolType, int vipPortStart, int vipPortEnd) { + List vos = Q.New(PortForwardingRuleVO.class) + .eq(PortForwardingRuleVO_.vipUuid, vipUuid) + .eq(PortForwardingRuleVO_.protocolType, PortForwardingProtocolType.valueOf(protocolType)) + .list(); + PortForwardingRuleVO vo = vos.stream() + .filter(it -> { + int existingStart = it.getVipPortStart(); + int existingEnd = it.getVipPortEnd(); + return existingStart <= vipPortEnd && vipPortStart <= existingEnd; + }) + .findFirst() + .orElse(null); + if (vo == null) { + return null; + } + + return argerr(ORG_ZSTACK_NETWORK_SERVICE_PORTFORWARDING_10017, + "vip port range[vipStartPort:%s, vipEndPort:%s] overlaps with rule[uuid:%s, vipStartPort:%s, vipEndPort:%s]", + vipPortStart, vipPortEnd, vo.getUuid(), vo.getVipPortStart(), vo.getVipPortEnd()); + } + + private void doCreatePortForwardingRule(APICreatePortForwardingRuleMsg msg, SyncTaskChain syncChain) { final APICreatePortForwardingRuleEvent evt = new APICreatePortForwardingRuleEvent(msg.getId()); int vipPortEnd = msg.getVipPortEnd() == null ? msg.getVipPortStart() : msg.getVipPortEnd(); int privatePortEnd = msg.getPrivatePortEnd() == null ? msg.getPrivatePortStart() : msg.getPrivatePortEnd(); + ErrorCode errCode = validateVipPortConflict(msg.getVipUuid(), msg.getProtocolType(), msg.getVipPortStart(), vipPortEnd); + if (errCode != null) { + evt.setError(errCode); + bus.publish(evt); + syncChain.next(); + return; + } + VipVO vip = dbf.findByUuid(msg.getVipUuid(), VipVO.class); final PortForwardingRuleVO vo = new PortForwardingRuleVO(); if (msg.getResourceUuid() != null) { @@ -723,8 +773,8 @@ protected void scripts() { } }.execute(); - FlowChain chain = FlowChainBuilder.newShareFlowChain(); - chain.setName("create-portforwading"); + FlowChain flowChain = FlowChainBuilder.newShareFlowChain(); + flowChain.setName("create-portforwading"); VipInventory vipInventory = VipInventory.valueOf(vip); if (msg.getVmNicUuid() == null) { ModifyVipAttributesStruct struct = new ModifyVipAttributesStruct(); @@ -737,6 +787,7 @@ protected void scripts() { public void success() { evt.setInventory(PortForwardingRuleInventory.valueOf(vo)); bus.publish(evt); + syncChain.next(); } @Override @@ -744,6 +795,7 @@ public void fail(ErrorCode errorCode) { dbf.remove(vo); evt.setError(errorCode); bus.publish(evt); + syncChain.next(); } }); @@ -767,6 +819,7 @@ public void fail(ErrorCode errorCode) { public void success() { evt.setInventory(PortForwardingRuleInventory.valueOf(vo)); bus.publish(evt); + syncChain.next(); } @Override @@ -774,13 +827,14 @@ public void fail(ErrorCode errorCode) { dbf.remove(vo); evt.setError(errorCode); bus.publish(evt); + syncChain.next(); } }); return; } - chain.then(new ShareFlow() { + flowChain.then(new ShareFlow() { @Override public void setup() { vo.setVmNicUuid(vmNic.getUuid()); @@ -828,7 +882,7 @@ public void run(AttachPortForwardingRuleExtensionPoint extp) { public void run(FlowTrigger trigger, Map data) { NetworkServiceProviderType providerType = (NetworkServiceProviderType)data.get("providerType"); final PortForwardingStruct struct = makePortForwardingStruct(ruleInv); - attachPortForwardingRule(struct, providerType.toString(), new Completion(msg) { + doAttachPortForwardingRule(struct, providerType.toString(), new Completion(msg) { @Override public void success() { CollectionUtils.safeForEach(attachRuleExts, new ForEachFunction() { @@ -863,11 +917,12 @@ public void fail(ErrorCode errorCode) { }); - chain.done(new FlowDoneHandler(msg) { + flowChain.done(new FlowDoneHandler(msg) { @Override public void handle(Map data) { evt.setInventory(PortForwardingRuleInventory.valueOf(dbf.reload(vo))); bus.publish(evt); + syncChain.next(); } }).error(new FlowErrorHandler(msg) { @Override @@ -875,6 +930,7 @@ public void handle(ErrorCode errCode, Map data) { dbf.remove(vo); evt.setError(errCode); bus.publish(evt); + syncChain.next(); } }).start(); } diff --git a/test/src/test/groovy/org/zstack/test/integration/networkservice/provider/virtualrouter/portforwarding/ConcurrentCreatePortForwardingRuleCase.groovy b/test/src/test/groovy/org/zstack/test/integration/networkservice/provider/virtualrouter/portforwarding/ConcurrentCreatePortForwardingRuleCase.groovy new file mode 100644 index 00000000000..b024da29b1f --- /dev/null +++ b/test/src/test/groovy/org/zstack/test/integration/networkservice/provider/virtualrouter/portforwarding/ConcurrentCreatePortForwardingRuleCase.groovy @@ -0,0 +1,162 @@ +package org.zstack.test.integration.networkservice.provider.virtualrouter.portforwarding + +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_MEDIATOR_10007 +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_NETWORK_SERVICE_PORTFORWARDING_10017 + +import org.zstack.core.db.Q +import org.zstack.network.service.portforwarding.PortForwardingProtocolType +import org.zstack.network.service.portforwarding.PortForwardingRuleVO +import org.zstack.network.service.portforwarding.PortForwardingRuleVO_ +import org.zstack.sdk.CreatePortForwardingRuleAction +import org.zstack.sdk.L3NetworkInventory +import org.zstack.sdk.VipInventory +import org.zstack.test.integration.networkservice.provider.NetworkServiceProviderTest +import org.zstack.testlib.EnvSpec +import org.zstack.testlib.SubCase + +import java.util.concurrent.ConcurrentLinkedQueue +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +class ConcurrentCreatePortForwardingRuleCase extends SubCase { + EnvSpec env + + @Override + void setup() { + useSpring(NetworkServiceProviderTest.springSpec) + } + + @Override + void environment() { + env = env { + zone { + name = "zone" + + l2NoVlanNetwork { + name = "l2" + physicalInterface = "eth0" + + l3Network { + name = "pubL3" + + ip { + startIp = "11.168.100.10" + endIp = "11.168.100.100" + netmask = "255.255.255.0" + gateway = "11.168.100.1" + } + } + } + } + } + } + + @Override + void test() { + env.create { + testConcurrentCreateSamePortForwardingRule() + } + } + + void testConcurrentCreateSamePortForwardingRule() { + L3NetworkInventory pubL3 = env.inventoryByName("pubL3") as L3NetworkInventory + VipInventory vip = createVip { + name = "vip" + l3NetworkUuid = pubL3.uuid + } as VipInventory + + int requestCount = 50 + String sessionId = adminSession() + Closure createRule = { String name, int vipPortStart, int vipPortEnd -> + CreatePortForwardingRuleAction action = new CreatePortForwardingRuleAction() + action.name = name + action.vipUuid = vip.uuid + action.vipPortStart = vipPortStart + action.vipPortEnd = vipPortEnd + action.privatePortStart = vipPortStart + action.privatePortEnd = vipPortEnd + action.protocolType = PortForwardingProtocolType.TCP.toString() + action.sessionId = sessionId + return action.call() + } + CountDownLatch ready = new CountDownLatch(requestCount) + CountDownLatch start = new CountDownLatch(1) + CountDownLatch done = new CountDownLatch(requestCount) + def results = new ConcurrentLinkedQueue() + def errors = new ConcurrentLinkedQueue() + + (0.. + Thread.start("create-same-port-forwarding-rule-${index}") { + try { + ready.countDown() + if (!start.await(10, TimeUnit.SECONDS)) { + throw new AssertionError("not all createPortForwardingRule workers became ready within 10 seconds") + } + + results.add(createRule("pf-${index}", 8080, 8080)) + } catch (Throwable t) { + errors.add(t) + } finally { + done.countDown() + } + } + } + + assert ready.await(10, TimeUnit.SECONDS) : + "createPortForwardingRule workers did not all become ready: expected=${requestCount}" + start.countDown() + assert done.await(60, TimeUnit.SECONDS) : + "concurrent createPortForwardingRule requests did not finish within 60 seconds" + assert errors.isEmpty() : + "worker thread threw unexpected errors: ${errors.collect { it.message }.join('; ')}" + + def successes = results.findAll { it.error == null } + def failures = results.findAll { it.error != null } + assert successes.size() == 1 : + "same VIP/protocol/port create must succeed once: expectedSuccess=1, actualSuccess=${successes.size()}, results=${results.size()}" + assert failures.size() == requestCount - 1 : + "duplicate same VIP/protocol/port creates must fail: expectedFailures=${requestCount - 1}, actualFailures=${failures.size()}" + Set conflictErrorCodes = [ + ORG_ZSTACK_NETWORK_SERVICE_PORTFORWARDING_10017, + ORG_ZSTACK_MEDIATOR_10007 + ] as Set + assert failures.every { conflictErrorCodes.contains(it.error.globalErrorCode) } : + "duplicate failures should be VIP port conflict errors: errors=${failures.collect { it.error.globalErrorCode }}" + + def lowerBoundaryConflict = createRule("pf-lower-boundary-conflict", 8079, 8080) + assert lowerBoundaryConflict.error != null : + "closed VIP port range should conflict when new end touches existing start" + assert conflictErrorCodes.contains(lowerBoundaryConflict.error.globalErrorCode) : + "lower boundary conflict should report VIP port conflict error: error=${lowerBoundaryConflict.error}" + + def upperBoundaryConflict = createRule("pf-upper-boundary-conflict", 8080, 8081) + assert upperBoundaryConflict.error != null : + "closed VIP port range should conflict when new start touches existing end" + assert conflictErrorCodes.contains(upperBoundaryConflict.error.globalErrorCode) : + "upper boundary conflict should report VIP port conflict error: error=${upperBoundaryConflict.error}" + + def lowerAdjacent = createRule("pf-lower-adjacent", 8079, 8079) + assert lowerAdjacent.error == null : + "adjacent lower VIP port should not conflict with existing port 8080: error=${lowerAdjacent.error}" + + def upperAdjacent = createRule("pf-upper-adjacent", 8081, 8081) + assert upperAdjacent.error == null : + "adjacent upper VIP port should not conflict with existing port 8080: error=${upperAdjacent.error}" + + long sameRules = Q.New(PortForwardingRuleVO.class) + .eq(PortForwardingRuleVO_.vipUuid, vip.uuid) + .eq(PortForwardingRuleVO_.protocolType, PortForwardingProtocolType.TCP) + .eq(PortForwardingRuleVO_.vipPortStart, 8080) + .eq(PortForwardingRuleVO_.vipPortEnd, 8080) + .eq(PortForwardingRuleVO_.privatePortStart, 8080) + .eq(PortForwardingRuleVO_.privatePortEnd, 8080) + .count() + assert sameRules == 1L : + "database must not contain duplicate same VIP/protocol/port rules: expected=1, actual=${sameRules}, vipUuid=${vip.uuid}" + } + + @Override + void clean() { + env.delete() + } +} From 275bd286954e400eb3d32a84f3ce5c66824fa1d1 Mon Sep 17 00:00:00 2001 From: J M Date: Fri, 29 May 2026 17:09:23 +0800 Subject: [PATCH 04/54] [ceph]: cap & parallelize host conn check Per-host ceph connectivity check shared one serial chain per primary storage. A stuck in-flight check (riding the default 1800s http timeout) blocked every other host's check on the same PS for ~30min. - raise check chain syncLevel 1 -> 10 so hosts run concurrently - add KVMAgentHttpTimeoutExtensionPoint: a module declares which agent paths are short-lived. Their requested timeout is the agent.connectivityCheck.timeout global config (300s default). KVMHost.Http always sends min(requested, inherited total) so a per-call timeout never exceeds the remaining overall timeout. ceph registers the check path, no per-call setTimeout. Resolves: ZSTAC-85421 Change-Id: I717a746f676c6f776168646f6f776c64636e6870 --- conf/springConfigXml/ceph.xml | 1 + .../ceph/primary/CephKvmExtension.java | 12 +- .../ceph/primary/CephPrimaryStorageBase.java | 5 + .../KVMAgentHttpTimeoutExtensionPoint.java | 7 + .../java/org/zstack/kvm/KVMGlobalConfig.java | 4 + .../src/main/java/org/zstack/kvm/KVMHost.java | 11 +- .../java/org/zstack/kvm/KVMHostFactory.java | 15 ++ .../ceph/CephHostStorageCheckCase.groovy | 189 ++++++++++++++++++ 8 files changed, 242 insertions(+), 2 deletions(-) create mode 100644 plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentHttpTimeoutExtensionPoint.java create mode 100644 test/src/test/groovy/org/zstack/test/integration/storage/ceph/CephHostStorageCheckCase.groovy diff --git a/conf/springConfigXml/ceph.xml b/conf/springConfigXml/ceph.xml index c4489de344c..9b3f658033f 100755 --- a/conf/springConfigXml/ceph.xml +++ b/conf/springConfigXml/ceph.xml @@ -64,6 +64,7 @@ + diff --git a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephKvmExtension.java b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephKvmExtension.java index 461e841f69f..b126d49e46c 100755 --- a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephKvmExtension.java +++ b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephKvmExtension.java @@ -20,6 +20,7 @@ import org.zstack.header.host.HypervisorType; import org.zstack.header.message.MessageReply; import org.zstack.header.storage.primary.PrimaryStorageConstant; +import org.zstack.kvm.KVMAgentHttpTimeoutExtensionPoint; import org.zstack.kvm.KVMConstant; import org.zstack.kvm.KVMHostConnectExtensionPoint; import org.zstack.kvm.KVMHostConnectedContext; @@ -30,15 +31,24 @@ import org.zstack.utils.function.Function; import javax.persistence.TypedQuery; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; import static org.zstack.utils.CollectionDSL.list; /** * Created by frank on 8/17/2015. */ -public class CephKvmExtension implements KVMHostConnectExtensionPoint, HostConnectionReestablishExtensionPoint { +public class CephKvmExtension implements KVMHostConnectExtensionPoint, HostConnectionReestablishExtensionPoint, + KVMAgentHttpTimeoutExtensionPoint { + + @Override + public Set agentHttpPathsWithShortTimeout() { + return Collections.singleton(CephPrimaryStorageBase.CHECK_HOST_STORAGE_CONNECTION_PATH); + } + @Autowired private CloudBus bus; @Autowired diff --git a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java index fab8dd0b583..969db15411c 100755 --- a/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java +++ b/plugin/ceph/src/main/java/org/zstack/storage/ceph/primary/CephPrimaryStorageBase.java @@ -4900,6 +4900,11 @@ public String getSyncSignature() { return String.format("check-storage-%s-host-connection", msg.getPrimaryStorageUuid()); } + @Override + protected int getSyncLevel() { + return 10; + } + @Override public void run(SyncTaskChain chain) { CheckHostStorageConnectionReply reply = new CheckHostStorageConnectionReply(); diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentHttpTimeoutExtensionPoint.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentHttpTimeoutExtensionPoint.java new file mode 100644 index 00000000000..511f57e13db --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentHttpTimeoutExtensionPoint.java @@ -0,0 +1,7 @@ +package org.zstack.kvm; + +import java.util.Set; + +public interface KVMAgentHttpTimeoutExtensionPoint { + Set agentHttpPathsWithShortTimeout(); +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java index ee765aad40f..d2b2063b635 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java @@ -143,6 +143,10 @@ public class KVMGlobalConfig { @GlobalConfigDef(defaultValue = "true", type = Boolean.class, description = "enable TLS encryption for libvirt remote connections (migration)") public static GlobalConfig LIBVIRT_TLS_ENABLED = new GlobalConfig(CATEGORY, "libvirt.tls.enabled"); + @GlobalConfigValidation + @GlobalConfigDef(defaultValue = "300", type = Long.class, description = "timeout in seconds for KVM agent connectivity-check commands that opt out of the default command timeout") + public static GlobalConfig AGENT_CONNECTIVITY_CHECK_TIMEOUT = new GlobalConfig(CATEGORY, "agent.connectivityCheck.timeout"); + @GlobalConfigValidation public static GlobalConfig KVMAGENT_PHYSICAL_MEMORY_USAGE_ALARM_THRESHOLD = new GlobalConfig(CATEGORY, "kvmagent.physicalmemory.usage.alarm.threshold"); diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java index 0c3ec6d2d23..f480874e526 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java @@ -488,6 +488,12 @@ class Http { Class responseClass; String commandStr; String commandName; + long timeout = -1; + + Http timeout(long timeout) { + this.timeout = timeout; + return this; + } public Http(String path, String cmd, String commandName, Class rspClz) { this.path = path; @@ -518,6 +524,8 @@ void call(String resourceUuid, ReturnValueCompletion completion) { header.put(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID, resourceUuid == null ? self.getUuid() : resourceUuid); runBeforeAsyncJsonPostExts(header); if (commandStr != null) { + long overall = timeoutManager.getTimeout(); + long effectiveTimeout = timeout > 0 ? Math.min(timeout, overall) : overall; restf.asyncJsonPost(path, commandStr, header, new JsonAsyncRESTCallback(completion) { @Override public void fail(ErrorCode err) { @@ -537,7 +545,7 @@ public void success(T ret) { public Class getReturnClass() { return responseClass; } - }, TimeUnit.MILLISECONDS, timeoutManager.getTimeout()); + }, TimeUnit.MILLISECONDS, effectiveTimeout); } else { restf.asyncJsonPost(path, cmd, header, new JsonAsyncRESTCallback(completion) { @Override @@ -2782,6 +2790,7 @@ private void executeAsyncHttpCall(final KVMHostAsyncHttpCallMsg msg, final NoErr String url = buildUrl(msg.getPath()); MessageCommandRecorder.record(msg.getCommandClassName()); new Http<>(url, msg.getCommand(), msg.getCommandClassName(), LinkedHashMap.class) + .timeout(msg.getTimeout() > 0 ? msg.getTimeout() : factory.getAgentHttpShortTimeout(msg.getPath())) .call(new ReturnValueCompletion(msg, completion) { @Override public void success(LinkedHashMap ret) { diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java index 513add98a83..0bbb01f3148 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java @@ -104,6 +104,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -138,6 +139,7 @@ public class KVMHostFactory extends AbstractService implements HypervisorFactory public static final VolumeFormat VMDK_FORMAT = new VolumeFormat(VolumeConstant.VOLUME_FORMAT_VMDK, hypervisorType); private List connectExtensions = new ArrayList<>(); private final Map completeNicInfoExtensions = new HashMap<>(); + private final Set agentHttpPathsWithShortTimeout = new HashSet<>(); private int maxDataVolumeNum; private final Map socketTimeoutMap = new ConcurrentHashMap<>(); @@ -376,6 +378,19 @@ protected void populateExtensions() { } completeNicInfoExtensions.put(ext.getL2NetworkTypeVmNicOn(), ext); } + for (KVMAgentHttpTimeoutExtensionPoint ext : pluginRgty.getExtensionList(KVMAgentHttpTimeoutExtensionPoint.class)) { + Set paths = ext.agentHttpPathsWithShortTimeout(); + if (paths != null) { + agentHttpPathsWithShortTimeout.addAll(paths); + } + } + } + + public long getAgentHttpShortTimeout(String path) { + if (agentHttpPathsWithShortTimeout.contains(path)) { + return TimeUnit.SECONDS.toMillis(KVMGlobalConfig.AGENT_CONNECTIVITY_CHECK_TIMEOUT.value(Long.class)); + } + return -1; } public KVMCompleteNicInformationExtensionPoint getCompleteNicInfoExtension(L2NetworkType type) { diff --git a/test/src/test/groovy/org/zstack/test/integration/storage/ceph/CephHostStorageCheckCase.groovy b/test/src/test/groovy/org/zstack/test/integration/storage/ceph/CephHostStorageCheckCase.groovy new file mode 100644 index 00000000000..b416787f9d0 --- /dev/null +++ b/test/src/test/groovy/org/zstack/test/integration/storage/ceph/CephHostStorageCheckCase.groovy @@ -0,0 +1,189 @@ +package org.zstack.test.integration.storage.ceph + +import org.springframework.http.HttpEntity +import org.zstack.core.cloudbus.CloudBus +import org.zstack.core.cloudbus.CloudBusCallBack +import org.zstack.header.host.HostConstant +import org.zstack.header.message.MessageReply +import org.zstack.header.storage.primary.PrimaryStorageConstant +import org.zstack.kvm.KVMAgentCommands +import org.zstack.kvm.KVMHostAsyncHttpCallMsg +import org.zstack.kvm.KVMHostFactory +import org.zstack.sdk.HostInventory +import org.zstack.sdk.PrimaryStorageInventory +import org.zstack.storage.ceph.primary.CephPrimaryStorageBase +import org.zstack.storage.primary.CheckHostStorageConnectionMsg +import org.zstack.test.integration.storage.StorageTest +import org.zstack.testlib.EnvSpec +import org.zstack.testlib.SubCase +import org.zstack.utils.data.SizeUnit +import org.zstack.utils.gson.JSONObjectUtil + +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicReference + +class CephHostStorageCheckCase extends SubCase { + EnvSpec env + CloudBus bus + + @Override + void setup() { + useSpring(StorageTest.springSpec) + } + + @Override + void environment() { + env = makeEnv { + zone { + name = "zone" + cluster { + name = "test-cluster" + hypervisorType = "KVM" + + kvm { + name = "host1" + managementIp = "127.0.0.1" + username = "root" + password = "password" + usedMem = 1000 + totalCpu = 10 + } + + kvm { + name = "host2" + managementIp = "127.0.0.2" + username = "root" + password = "password" + usedMem = 1000 + totalCpu = 10 + } + + attachPrimaryStorage("ceph-pri") + } + + cephPrimaryStorage { + name = "ceph-pri" + description = "Test" + totalCapacity = SizeUnit.GIGABYTE.toByte(100) + availableCapacity = SizeUnit.GIGABYTE.toByte(100) + url = "ceph://pri" + fsid = "7ff218d9-f525-435f-8a40-3618d1772a64" + monUrls = ["root:password@localhost/?monPort=7777"] + } + } + } + } + + @Override + void test() { + env.create { + bus = bean(CloudBus.class) + testCheckNotSerializedAcrossHosts() + testPerMessageTimeoutHonored() + } + } + + @Override + void clean() { + env.delete() + } + + // ZSTAC-85421: a stuck per-host check must not block other hosts' check on the + // same primary storage. The chain syncLevel is raised from 1 to 10 so the second + // host's check runs concurrently instead of queueing behind the stuck one. + void testCheckNotSerializedAcrossHosts() { + def ps = env.inventoryByName("ceph-pri") as PrimaryStorageInventory + def host1 = env.inventoryByName("host1") as HostInventory + def host2 = env.inventoryByName("host2") as HostInventory + + CountDownLatch host1Entered = new CountDownLatch(1) + CountDownLatch release = new CountDownLatch(1) + + // ceph registers the check path with a short timeout via + // KVMAgentHttpTimeoutExtensionPoint; the value comes from the + // agent.connectivityCheck.timeout global config (default 300s). + // When sending, KVMHost clamps it down to the inherited total timeout. + assert bean(KVMHostFactory.class).getAgentHttpShortTimeout(CephPrimaryStorageBase.CHECK_HOST_STORAGE_CONNECTION_PATH) == TimeUnit.MINUTES.toMillis(5) + + env.simulator(CephPrimaryStorageBase.CHECK_HOST_STORAGE_CONNECTION_PATH) { HttpEntity e -> + def cmd = JSONObjectUtil.toObject(e.body, CephPrimaryStorageBase.CheckHostStorageConnectionCmd) + if (cmd.hostUuid == host1.uuid) { + host1Entered.countDown() + release.await(60, TimeUnit.SECONDS) + } + return new KVMAgentCommands.AgentResponse() + } + + CountDownLatch reply1Done = new CountDownLatch(1) + sendCheckMsg(ps.uuid, host1.uuid, { MessageReply r -> reply1Done.countDown() }) + assert host1Entered.await(10, TimeUnit.SECONDS) + + AtomicReference reply2 = new AtomicReference<>() + CountDownLatch reply2Done = new CountDownLatch(1) + sendCheckMsg(ps.uuid, host2.uuid, { MessageReply r -> reply2.set(r); reply2Done.countDown() }) + + assert reply2Done.await(15, TimeUnit.SECONDS) + assert reply2.get().isSuccess() + assert reply1Done.getCount() == 1 + + release.countDown() + assert reply1Done.await(15, TimeUnit.SECONDS) + } + + // ZSTAC-85421: a KVMHostAsyncHttpCallMsg carrying an explicit timeout must fail at + // that timeout instead of riding the default 1800s. The ceph check relies on this to + // limit its blast radius to 5 minutes. + void testPerMessageTimeoutHonored() { + def host1 = env.inventoryByName("host1") as HostInventory + def stuckPath = "/test/zstac85421/stuck" + CountDownLatch release = new CountDownLatch(1) + + env.simulator(stuckPath) { HttpEntity e -> + release.await(60, TimeUnit.SECONDS) + return new KVMAgentCommands.AgentResponse() + } + + KVMHostAsyncHttpCallMsg msg = new KVMHostAsyncHttpCallMsg() + msg.setHostUuid(host1.uuid) + msg.setPath(stuckPath) + msg.setCommand(new KVMAgentCommands.AgentCommand()) + msg.setNoStatusCheck(true) + msg.setTimeout(TimeUnit.SECONDS.toMillis(3)) + bus.makeTargetServiceIdByResourceUuid(msg, HostConstant.SERVICE_ID, host1.uuid) + + AtomicReference reply = new AtomicReference<>() + CountDownLatch done = new CountDownLatch(1) + long start = System.currentTimeMillis() + bus.send(msg, new CloudBusCallBack(null) { + @Override + void run(MessageReply r) { + reply.set(r) + done.countDown() + } + }) + + assert done.await(20, TimeUnit.SECONDS) + long elapsed = System.currentTimeMillis() - start + assert !reply.get().isSuccess() + assert elapsed >= 2000 + assert elapsed < 20000 + + release.countDown() + } + + private void sendCheckMsg(String psUuid, String hostUuid, Closure cb) { + CheckHostStorageConnectionMsg msg = new CheckHostStorageConnectionMsg() + msg.setPrimaryStorageUuid(psUuid) + msg.setHostUuids([hostUuid]) + bus.makeTargetServiceIdByResourceUuid(msg, PrimaryStorageConstant.SERVICE_ID, psUuid) + bus.send(msg, new CloudBusCallBack(null) { + @Override + void run(MessageReply r) { + if (cb != null) { + cb(r) + } + } + }) + } +} From e986a62445c04d10d140ba333aa9db3eea77cacb Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Thu, 4 Jun 2026 19:28:44 +0900 Subject: [PATCH 05/54] [network]: add IPv6 host and bridge switches Add host-level IPv6 deployment control. Add bridge multicast querier config and agent field. Keep default bridge multicast querier enabled. Resolves: ZSTAC-85691 Change-Id: I2c4b968e7b3509d974b4f00df45fc3147d3fc5e5 --- conf/globalConfig/l2Network.xml | 9 ++++++++- .../org/zstack/network/l2/L2NetworkGlobalConfig.java | 4 ++++ .../main/java/org/zstack/kvm/KVMAgentCommands.java | 11 +++++++++++ .../src/main/java/org/zstack/kvm/KVMGlobalConfig.java | 5 +++++ plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java | 1 + .../java/org/zstack/kvm/KVMHostDeployArguments.java | 10 ++++++++++ .../zstack/kvm/KVMRealizeL2NoVlanNetworkBackend.java | 1 + .../zstack/kvm/KVMRealizeL2VlanNetworkBackend.java | 4 ++++ .../KVMRealizeL2VxlanNetworkBackend.java | 1 + .../KVMRealizeL2VxlanNetworkPoolBackend.java | 1 + .../vxlan/vxlanNetworkPool/VxlanKvmAgentCommands.java | 11 +++++++++++ 11 files changed, 57 insertions(+), 1 deletion(-) diff --git a/conf/globalConfig/l2Network.xml b/conf/globalConfig/l2Network.xml index f6032411257..3c20b72b044 100644 --- a/conf/globalConfig/l2Network.xml +++ b/conf/globalConfig/l2Network.xml @@ -28,4 +28,11 @@ l2Network java.lang.Integer - \ No newline at end of file + + bridge.multicastQuerier + Enable multicast querier and snooping on linux bridge when creating l2 network bridge + true + l2Network + java.lang.Boolean + + diff --git a/network/src/main/java/org/zstack/network/l2/L2NetworkGlobalConfig.java b/network/src/main/java/org/zstack/network/l2/L2NetworkGlobalConfig.java index 953219f6a30..65a600ae248 100755 --- a/network/src/main/java/org/zstack/network/l2/L2NetworkGlobalConfig.java +++ b/network/src/main/java/org/zstack/network/l2/L2NetworkGlobalConfig.java @@ -21,4 +21,8 @@ public class L2NetworkGlobalConfig { @GlobalConfigValidation public static GlobalConfig MLDVersion = new GlobalConfig(CATEGORY, "mld.version"); + + @GlobalConfigValidation(validValues = {"true", "false"}) + public static GlobalConfig BridgeMulticastQuerier = new GlobalConfig(CATEGORY, "bridge.multicastQuerier"); + } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java index 5011d661659..a1a807604ae 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java @@ -965,6 +965,8 @@ public static class CreateBridgeCmd extends AgentCommand { private Integer igmpVersion; @GrayVersion(value = "5.3.0") private Integer mldVersion; + @GrayVersion(value = "5.5.28") + private Boolean enableMulticastQuerier; public String getL2NetworkUuid() { return l2NetworkUuid; @@ -1037,6 +1039,15 @@ public Integer getMldVersion() { public void setMldVersion(Integer mldVersion) { this.mldVersion = mldVersion; } + + public Boolean getEnableMulticastQuerier() { + return enableMulticastQuerier; + } + + public void setEnableMulticastQuerier(Boolean enableMulticastQuerier) { + this.enableMulticastQuerier = enableMulticastQuerier; + } + } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java index d2b2063b635..6f39de8302d 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java @@ -134,6 +134,11 @@ public class KVMGlobalConfig { @BindResourceConfig({HostVO.class}) public static GlobalConfig HOST_KSM = new GlobalConfig(CATEGORY, "host.ksm"); + @GlobalConfigValidation(validValues = {"true", "false"}) + @GlobalConfigDef(defaultValue = "true", type = Boolean.class, description = "enable host IPv6") + @BindResourceConfig({HostVO.class, ClusterVO.class, ZoneVO.class}) + public static GlobalConfig HOST_IPV6 = new GlobalConfig(CATEGORY, "host.ipv6.enable"); + @GlobalConfigValidation(validValues = {"true", "false"}) @GlobalConfigDef(defaultValue = "false", description = "restart kvm host libvirtd service or not") @BindResourceConfig({HostVO.class, ClusterVO.class}) diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java index f480874e526..58ef62dcdc4 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java @@ -5989,6 +5989,7 @@ public void run(final FlowTrigger trigger, Map data) { if (NetworkGlobalProperty.SKIP_IPV6 || NetworkGlobalProperty.BRIDGE_DISABLE_IP6TABLES) { deployArguments.setDisableIp6Tables("true"); } + deployArguments.setEnableIpv6(rcf.getResourceConfigValue(KVMGlobalConfig.HOST_IPV6, self.getUuid(), String.class)); for (KvmHostAgentDeploymentExtensionPoint ext : pluginRegistry.getExtensionList(KvmHostAgentDeploymentExtensionPoint.class)) { if (logger.isTraceEnabled()) { diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostDeployArguments.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostDeployArguments.java index f2bb79c110b..7c6e54404a4 100644 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostDeployArguments.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostDeployArguments.java @@ -17,6 +17,8 @@ public class KVMHostDeployArguments extends SyncTimeRequestedDeployArguments { private String skipIpv6; @SerializedName("disableIp6Tables") private String disableIp6Tables; + @SerializedName("enableIpv6") + private String enableIpv6; @SerializedName("isBareMetal2Gateway") private String isBareMetal2Gateway; @SerializedName("bridgeDisableIptables") @@ -68,6 +70,14 @@ public void setDisableIp6Tables(String disableIp6Tables) { this.disableIp6Tables = disableIp6Tables; } + public String getEnableIpv6() { + return enableIpv6; + } + + public void setEnableIpv6(String enableIpv6) { + this.enableIpv6 = enableIpv6; + } + public String getIsBareMetal2Gateway() { return isBareMetal2Gateway; } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2NoVlanNetworkBackend.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2NoVlanNetworkBackend.java index 3ec9dd0780e..9c030aca303 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2NoVlanNetworkBackend.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2NoVlanNetworkBackend.java @@ -60,6 +60,7 @@ public void realize(final L2NetworkInventory l2Network, final String hostUuid, b cmd.setIsolated(l2Network.getIsolated()); cmd.setIgmpVersion(L2NetworkGlobalConfig.IGMPVersion.value(Integer.class)); cmd.setMldVersion(L2NetworkGlobalConfig.MLDVersion.value(Integer.class)); + cmd.setEnableMulticastQuerier(L2NetworkGlobalConfig.BridgeMulticastQuerier.value(Boolean.class)); KVMHostAsyncHttpCallMsg msg = new KVMHostAsyncHttpCallMsg(); msg.setCommand(cmd); diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2VlanNetworkBackend.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2VlanNetworkBackend.java index e486fddff98..5555a813ab7 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2VlanNetworkBackend.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMRealizeL2VlanNetworkBackend.java @@ -20,6 +20,7 @@ import org.zstack.kvm.KVMAgentCommands.DeleteVlanBridgeCmd; import org.zstack.kvm.KVMAgentCommands.DeleteVlanBridgeResponse; import org.zstack.kvm.KVMAgentCommands.NicTO; +import org.zstack.network.l2.L2NetworkGlobalConfig; import org.zstack.network.l3.NetworkGlobalProperty; import org.zstack.network.service.MtuGetter; import org.zstack.tag.SystemTagCreator; @@ -64,6 +65,9 @@ public void realize(final L2NetworkInventory l2Network, final String hostUuid, b cmd.setMtu(new MtuGetter().getL2Mtu(l2Network)); cmd.setIsolated(l2vlan.getIsolated()); cmd.setPvlan(l2vlan.getPvlan()); + cmd.setIgmpVersion(L2NetworkGlobalConfig.IGMPVersion.value(Integer.class)); + cmd.setMldVersion(L2NetworkGlobalConfig.MLDVersion.value(Integer.class)); + cmd.setEnableMulticastQuerier(L2NetworkGlobalConfig.BridgeMulticastQuerier.value(Boolean.class)); KVMHostAsyncHttpCallMsg msg = new KVMHostAsyncHttpCallMsg(); msg.setHostUuid(hostUuid); diff --git a/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/KVMRealizeL2VxlanNetworkBackend.java b/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/KVMRealizeL2VxlanNetworkBackend.java index 43c4ddef021..ffe76b77329 100755 --- a/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/KVMRealizeL2VxlanNetworkBackend.java +++ b/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/KVMRealizeL2VxlanNetworkBackend.java @@ -128,6 +128,7 @@ public void realize(final L2NetworkInventory l2Network, final String hostUuid, b cmd.setMtu(new MtuGetter().getL2Mtu(l2Network)); cmd.setIgmpVersion(L2NetworkGlobalConfig.IGMPVersion.value(Integer.class)); cmd.setMldVersion(L2NetworkGlobalConfig.MLDVersion.value(Integer.class)); + cmd.setEnableMulticastQuerier(L2NetworkGlobalConfig.BridgeMulticastQuerier.value(Boolean.class)); KVMHostAsyncHttpCallMsg msg = new KVMHostAsyncHttpCallMsg(); msg.setHostUuid(hostUuid); diff --git a/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/KVMRealizeL2VxlanNetworkPoolBackend.java b/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/KVMRealizeL2VxlanNetworkPoolBackend.java index f52acab7dab..376a84ee53a 100755 --- a/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/KVMRealizeL2VxlanNetworkPoolBackend.java +++ b/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/KVMRealizeL2VxlanNetworkPoolBackend.java @@ -243,6 +243,7 @@ public void run(FlowTrigger trigger, Map data) { bridgeCmd.setMtu(new MtuGetter().getL2Mtu(L2VxlanNetworkInventory.valueOf(vo))); bridgeCmd.setIgmpVersion(L2NetworkGlobalConfig.IGMPVersion.value(Integer.class)); bridgeCmd.setMldVersion(L2NetworkGlobalConfig.MLDVersion.value(Integer.class)); + bridgeCmd.setEnableMulticastQuerier(L2NetworkGlobalConfig.BridgeMulticastQuerier.value(Boolean.class)); cmd.getBridgeCmds().add(bridgeCmd); } diff --git a/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/VxlanKvmAgentCommands.java b/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/VxlanKvmAgentCommands.java index 00d623a0e2d..5dce29998b2 100644 --- a/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/VxlanKvmAgentCommands.java +++ b/plugin/vxlan/src/main/java/org/zstack/network/l2/vxlan/vxlanNetworkPool/VxlanKvmAgentCommands.java @@ -122,6 +122,8 @@ public static class CreateVxlanBridgeCmd extends AgentCommand { private Integer igmpVersion; @GrayVersion(value = "5.3.0") private Integer mldVersion; + @GrayVersion(value = "5.5.28") + private Boolean enableMulticastQuerier; public Integer getDstport() { return dstport; @@ -194,6 +196,15 @@ public Integer getMldVersion() { public void setMldVersion(Integer mldVersion) { this.mldVersion = mldVersion; } + + public Boolean getEnableMulticastQuerier() { + return enableMulticastQuerier; + } + + public void setEnableMulticastQuerier(Boolean enableMulticastQuerier) { + this.enableMulticastQuerier = enableMulticastQuerier; + } + } public static class CreateVxlanBridgeResponse extends CreateBridgeResponse { From da6789ad9db39b11a6ad8f24b63ce60c15b7edd4 Mon Sep 17 00:00:00 2001 From: "haidong.pang" Date: Sun, 24 May 2026 16:57:47 +0800 Subject: [PATCH 06/54] [kvm]: add iothread vq mapping data path Resolves: ZSTAC-82672 Change-Id: Ib43a7a02a0528cc40cd8aa370b93b7fba7dfc051 --- .../java/org/zstack/kvm/KVMAgentCommands.java | 10 ++++ .../main/java/org/zstack/kvm/KVMConstant.java | 2 + .../src/main/java/org/zstack/kvm/KVMHost.java | 51 ++++++++++++++++++- .../java/org/zstack/kvm/KVMSystemTags.java | 4 ++ .../main/java/org/zstack/kvm/VolumeTO.java | 11 ++++ .../main/java/org/zstack/sdk/VolumeTO.java | 8 +++ .../integration/kvm/host/AddHostCase.groovy | 39 ++++++++++++++ 7 files changed, 124 insertions(+), 1 deletion(-) diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java index e963aa9e2ad..6886c94beb5 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java @@ -505,6 +505,8 @@ public static class HostFactResponse extends AgentResponse { private String osRelease; @GrayVersion(value = "5.0.0") private String qemuImgVersion; + @GrayVersion(value = "5.5.28") + private String qemuKvmPackageVersion; @GrayVersion(value = "5.0.0") private String libvirtVersion; @GrayVersion(value = "5.0.0") @@ -621,6 +623,14 @@ public void setQemuImgVersion(String qemuImgVersion) { this.qemuImgVersion = qemuImgVersion; } + public String getQemuKvmPackageVersion() { + return qemuKvmPackageVersion; + } + + public void setQemuKvmPackageVersion(String qemuKvmPackageVersion) { + this.qemuKvmPackageVersion = qemuKvmPackageVersion; + } + public List getIpAddresses() { return ipAddresses; } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java index 96c4d05b200..5fa4635d3fe 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java @@ -113,6 +113,8 @@ public interface KVMConstant { String MIN_QEMU_LIVESNAPSHOT_VERSION = "1.3.0"; String MIN_LIBVIRT_LIVE_BLOCK_COMMIT_VERSION = "1.2.7"; String MIN_LIBVIRT_VIRTIO_SCSI_VERSION = "1.0.4"; + String MIN_QEMU_KVM_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION = "6.2.0-451"; + String MIN_LIBVIRT_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION = "8.0.0-163"; String KVM_REPORT_VM_STATE = "/kvm/reportvmstate"; String KVM_RECONNECT_ME = "/kvm/reconnectme"; diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java index 7a5a0bcf99f..73725c4ce7a 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java @@ -3,6 +3,7 @@ import okhttp3.Response; import org.apache.commons.lang.StringUtils; +import org.apache.maven.artifact.versioning.ComparableVersion; import org.apache.logging.log4j.util.Strings; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -116,6 +117,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.zstack.core.Platform.*; @@ -131,6 +134,38 @@ public class KVMHost extends HostBase implements Host { private static final ZTester tester = Utils.getTester(); protected static OperationChecker allowedOperations = new OperationChecker(true); protected static OperationChecker skipOperations = new OperationChecker(true); + private static final Pattern NUMERIC_VERSION_TOKEN = Pattern.compile("\\d+(?:\\.\\d+)*"); + + private static boolean isIothreadVqMappingSupported(String qemuKvmPackageVersion, String libvirtPackageVersion) { + return versionAtLeast(qemuKvmPackageVersion, KVMConstant.MIN_QEMU_KVM_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION) + && versionAtLeast(libvirtPackageVersion, KVMConstant.MIN_LIBVIRT_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION); + } + + private static boolean versionAtLeast(String current, String required) { + String normalized = normalizePackageVersion(current); + if (normalized == null) { + return false; + } + + return new ComparableVersion(normalized).compareTo(new ComparableVersion(required)) >= 0; + } + + private static String normalizePackageVersion(String version) { + if (StringUtils.isBlank(version)) { + return null; + } + + String normalized = Arrays.stream(version.trim().split("\\s+")[0].split("-")) + .map(KVMHost::numericPrefix) + .filter(StringUtils::isNotBlank) + .collect(Collectors.joining("-")); + return StringUtils.isBlank(normalized) ? null : normalized; + } + + private static String numericPrefix(String versionSegment) { + Matcher matcher = NUMERIC_VERSION_TOKEN.matcher(versionSegment); + return matcher.lookingAt() ? matcher.group() : null; + } public static Set parseSanIps(String sanOutput) { Set sanIps = new HashSet<>(); @@ -6498,8 +6533,22 @@ private boolean checkVirtualizationEnabled(HostFactResponse response) { private void saveKvmHostRelatedFacts(HostFactResponse ret) { updateHostOsInformation(ret.getOsDistribution(), ret.getOsRelease(), ret.getOsVersion()); - if (ret.getLibvirtPackageVersion() != null) { + if (StringUtils.isNotBlank(ret.getLibvirtPackageVersion())) { createTagWithoutNonValue(KVMSystemTags.LIBVIRT_PACKAGE_VERSION, KVMSystemTags.LIBVIRT_PACKAGE_VERSION_TOKEN, ret.getLibvirtPackageVersion().trim(), false); + } else { + KVMSystemTags.LIBVIRT_PACKAGE_VERSION.delete(self.getUuid()); + } + + if (StringUtils.isNotBlank(ret.getQemuKvmPackageVersion())) { + createTagWithoutNonValue(KVMSystemTags.QEMU_KVM_PACKAGE_VERSION, KVMSystemTags.QEMU_KVM_PACKAGE_VERSION_TOKEN, ret.getQemuKvmPackageVersion().trim(), false); + } else { + KVMSystemTags.QEMU_KVM_PACKAGE_VERSION.delete(self.getUuid()); + } + + if (isIothreadVqMappingSupported(ret.getQemuKvmPackageVersion(), ret.getLibvirtPackageVersion())) { + recreateNonInherentTag(KVMSystemTags.IOTHREAD_VQ_MAPPING); + } else { + KVMSystemTags.IOTHREAD_VQ_MAPPING.delete(self.getUuid()); } createTagWithoutNonValue(KVMSystemTags.QEMU_IMG_VERSION, KVMSystemTags.QEMU_IMG_VERSION_TOKEN, ret.getQemuImgVersion(), false); diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMSystemTags.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMSystemTags.java index e5c14fb9d57..b0e4cc3fdeb 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMSystemTags.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMSystemTags.java @@ -18,6 +18,9 @@ public class KVMSystemTags { public static final String QEMU_IMG_VERSION_TOKEN = "version"; public static PatternedSystemTag QEMU_IMG_VERSION = new PatternedSystemTag(String.format("qemu-img::version::{%s}", QEMU_IMG_VERSION_TOKEN), HostVO.class); + public static final String QEMU_KVM_PACKAGE_VERSION_TOKEN = "version"; + public static PatternedSystemTag QEMU_KVM_PACKAGE_VERSION = new PatternedSystemTag(String.format("qemu-kvm::package::version::{%s}", QEMU_KVM_PACKAGE_VERSION_TOKEN), HostVO.class); + public static final String LIBVIRT_VERSION_TOKEN = "version"; public static PatternedSystemTag LIBVIRT_VERSION = new PatternedSystemTag(String.format("libvirt::version::{%s}", LIBVIRT_VERSION_TOKEN), HostVO.class); @@ -42,6 +45,7 @@ public class KVMSystemTags { public static PatternedSystemTag CHECK_CLUSTER_CPU_MODEL = new PatternedSystemTag(String.format("check::cluster::cpu::model::{%s}", CHECK_CLUSTER_CPU_MODEL_TOKEN), ClusterVO.class); public static SystemTag VIRTIO_SCSI = new SystemTag("capability:virtio-scsi", HostVO.class); + public static SystemTag IOTHREAD_VQ_MAPPING = new SystemTag("capability::iothread-vq-mapping", HostVO.class); public static final String L2_BRIDGE_NAME_TOKEN = "name"; public static PatternedSystemTag L2_BRIDGE_NAME = new PatternedSystemTag(String.format("kvm::bridge::{%s}", L2_BRIDGE_NAME_TOKEN), L2NetworkVO.class); diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java b/plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java index d6695b93208..e40dfe40513 100644 --- a/plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java @@ -54,6 +54,8 @@ public class VolumeTO extends BaseVirtualDeviceTO { private String multiQueues; private int ioThreadId; private String ioThreadPin; + // 0 means automatic IOThread VQ Mapping is disabled. + private int ioThreads; private int controllerIndex; private CacheTO cache; @@ -86,6 +88,7 @@ public VolumeTO(VolumeTO other) { this.multiQueues = other.multiQueues; this.ioThreadId = other.ioThreadId; this.ioThreadPin = other.ioThreadPin; + this.ioThreads = other.ioThreads; this.controllerIndex = other.controllerIndex; this.cache = other.cache; } @@ -316,6 +319,14 @@ public String getIoThreadPin() { return ioThreadPin; } + public int getIoThreads() { + return ioThreads; + } + + public void setIoThreads(int ioThreads) { + this.ioThreads = ioThreads; + } + public int getControllerIndex() { return controllerIndex; } diff --git a/sdk/src/main/java/org/zstack/sdk/VolumeTO.java b/sdk/src/main/java/org/zstack/sdk/VolumeTO.java index adaa44f8fe7..3ecf4636c1c 100644 --- a/sdk/src/main/java/org/zstack/sdk/VolumeTO.java +++ b/sdk/src/main/java/org/zstack/sdk/VolumeTO.java @@ -156,6 +156,14 @@ public java.lang.String getIoThreadPin() { return this.ioThreadPin; } + public int ioThreads; + public void setIoThreads(int ioThreads) { + this.ioThreads = ioThreads; + } + public int getIoThreads() { + return this.ioThreads; + } + public int controllerIndex; public void setControllerIndex(int controllerIndex) { this.controllerIndex = controllerIndex; diff --git a/test/src/test/groovy/org/zstack/test/integration/kvm/host/AddHostCase.groovy b/test/src/test/groovy/org/zstack/test/integration/kvm/host/AddHostCase.groovy index 1a26995ba59..12ab7899dbd 100644 --- a/test/src/test/groovy/org/zstack/test/integration/kvm/host/AddHostCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/kvm/host/AddHostCase.groovy @@ -12,6 +12,7 @@ import org.zstack.header.host.HostVO import org.zstack.kvm.APIAddKVMHostMsg import org.zstack.kvm.AddKVMHostMsg import org.zstack.kvm.KVMHostInventory +import org.zstack.kvm.KVMSystemTags import org.zstack.sdk.AddKVMHostAction import org.zstack.sdk.ClusterInventory import org.zstack.sdk.GetHypervisorTypesResult @@ -56,6 +57,7 @@ class AddHostCase extends SubCase { testCheckHostManagementFailure() testInnerAddHostMsg() testGetHypervisorTypes() + testPackageVersionTagsClearedWhenFactMissing() testAddHostFailureRollback() testAddHostViaLongJob() testLongJobAddHostFailure() @@ -256,6 +258,43 @@ class AddHostCase extends SubCase { assert (reply.inventory as KVMHostInventory).osDistribution == distribution } + void testPackageVersionTagsClearedWhenFactMissing() { + String qemuKvmPackageVersion = "6.2.0-451.g623f2a5caf.el8" + String libvirtPackageVersion = "8.0.0-163.gd30ff15b84.el8" + + env.afterSimulator(KVM_HOST_FACT_PATH) { HostFactResponse rsp -> + rsp.qemuKvmPackageVersion = qemuKvmPackageVersion + rsp.libvirtPackageVersion = libvirtPackageVersion + return rsp + } + + org.zstack.sdk.KVMHostInventory host = addKVMHost { + resourceUuid = Platform.uuid + sessionId = adminSession() + clusterUuid = cluster.uuid + name = "kvm-package-version" + managementIp = "127.0.0.6" + username = "root" + password = "password" + } as org.zstack.sdk.KVMHostInventory + + assert KVMSystemTags.QEMU_KVM_PACKAGE_VERSION.getTokenByResourceUuid(host.uuid, KVMSystemTags.QEMU_KVM_PACKAGE_VERSION_TOKEN) == qemuKvmPackageVersion + assert KVMSystemTags.LIBVIRT_PACKAGE_VERSION.getTokenByResourceUuid(host.uuid, KVMSystemTags.LIBVIRT_PACKAGE_VERSION_TOKEN) == libvirtPackageVersion + + env.afterSimulator(KVM_HOST_FACT_PATH) { HostFactResponse rsp -> + rsp.qemuKvmPackageVersion = null + rsp.libvirtPackageVersion = " " + return rsp + } + + reconnectHost { + uuid = host.uuid + } + + assert KVMSystemTags.QEMU_KVM_PACKAGE_VERSION.getTokenByResourceUuid(host.uuid, KVMSystemTags.QEMU_KVM_PACKAGE_VERSION_TOKEN) == null + assert KVMSystemTags.LIBVIRT_PACKAGE_VERSION.getTokenByResourceUuid(host.uuid, KVMSystemTags.LIBVIRT_PACKAGE_VERSION_TOKEN) == null + } + void testGetHypervisorTypes() { GetHypervisorTypesResult result = getHypervisorTypes { sessionId = adminSession() From 4ada5a52ee58d17b238ea1e22abb3609d6d47104 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Thu, 4 Jun 2026 14:59:56 +0800 Subject: [PATCH 07/54] [virtualrouter]: configure dnsmasq query limits Pass dns-forward-max and cache-size through VR DNS commands. Render both options in zstack-vyos dnsmasq config. Resolves: ZSTAC-85197 Change-Id: Ib78504edc564a2ab7dbd39bcb2e0ee396d019240 --- conf/globalConfig/virutalRouter.xml | 14 ++++++++++++++ .../virtualrouter/VirtualRouterGlobalConfig.java | 6 ++++++ 2 files changed, 20 insertions(+) diff --git a/conf/globalConfig/virutalRouter.xml b/conf/globalConfig/virutalRouter.xml index 36e3b30ef2c..e084e3f925b 100755 --- a/conf/globalConfig/virutalRouter.xml +++ b/conf/globalConfig/virutalRouter.xml @@ -21,6 +21,20 @@ virtualRouter java.lang.Integer + + dnsmasq.dnsForwardMax + The maximum number of concurrent DNS queries forwarded by dnsmasq on a virtual router. + 1000 + virtualRouter + java.lang.Integer + + + dnsmasq.cacheSize + The DNS cache size used by dnsmasq on a virtual router. + 10000 + virtualRouter + java.lang.Integer + ping.interval The interval management nodes ping the virtual router agents running virtual router VMs, in seconds diff --git a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterGlobalConfig.java b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterGlobalConfig.java index 3b8d87bb8f0..7b38e6cc723 100755 --- a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterGlobalConfig.java +++ b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterGlobalConfig.java @@ -18,6 +18,12 @@ public class VirtualRouterGlobalConfig { public static GlobalConfig COMMANDS_PARALELLISM_DEGREE = new GlobalConfig(CATEGORY, "command.parallelismDegree"); @GlobalConfigValidation public static GlobalConfig RESTART_DNSMASQ_COUNT = new GlobalConfig(CATEGORY, "dnsmasq.restartAfterNumberOfSIGUSER1"); + @GlobalConfigValidation(numberGreaterThan = 0) + @BindResourceConfig({VmInstanceVO.class}) + public static GlobalConfig DNSMASQ_DNS_FORWARD_MAX = new GlobalConfig(CATEGORY, "dnsmasq.dnsForwardMax"); + @GlobalConfigValidation(numberGreaterThan = 0) + @BindResourceConfig({VmInstanceVO.class}) + public static GlobalConfig DNSMASQ_CACHE_SIZE = new GlobalConfig(CATEGORY, "dnsmasq.cacheSize"); @GlobalConfigValidation(numberGreaterThan = 1) public static GlobalConfig PING_INTERVAL = new GlobalConfig(CATEGORY, "ping.interval"); @GlobalConfigValidation From 4af8c5bb17f3ca993c4df38cff7f9063f4addcd0 Mon Sep 17 00:00:00 2001 From: "xinhao.huang" Date: Wed, 29 Apr 2026 15:09:08 +0800 Subject: [PATCH 08/54] [upgrade]: compare incoming version in updateAgentVersion UpgradeChecker.updateAgentVersion early-returned when DB.expectVersion equals DB.currentVersion, ignoring the currentVersion reported by ping. Once a host record reached the stable current==expect state, any subsequent ping carrying a different current version was silently dropped, breaking grayscale upgrade tracking and causing GrayscaleUpgradeCase to fail intermittently. Compare the persisted currentVersion with the incoming currentVersion instead. Existing records still keep their original expectVersion semantics; this fix only updates currentVersion when the agent reports a changed version. Resolves: ZSTAC-84886 Change-Id: I63636b6f696b6463756f656c627367617a6d6a79 --- .../zstack/core/upgrade/UpgradeChecker.java | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/zstack/core/upgrade/UpgradeChecker.java b/core/src/main/java/org/zstack/core/upgrade/UpgradeChecker.java index 90639caf24c..d0f4c73156a 100644 --- a/core/src/main/java/org/zstack/core/upgrade/UpgradeChecker.java +++ b/core/src/main/java/org/zstack/core/upgrade/UpgradeChecker.java @@ -327,24 +327,22 @@ public void updateAgentVersion(String agentUuid, String agentType, String expect return; } - if (Objects.equals(agentVersionVO.getExpectVersion(), agentVersionVO.getCurrentVersion())) { + if (Objects.equals(agentVersionVO.getCurrentVersion(), currentVersion)) { logger.trace(String.format("Agent[uuid: %s] version expected version: %s, current version: %s, not changed", agentUuid, agentVersionVO.getExpectVersion(), agentVersionVO.getCurrentVersion())); return; } - if (!Objects.equals(agentVersionVO.getCurrentVersion(), currentVersion)) { - String originCurrentVersion = agentVersionVO.getCurrentVersion(); - agentVersionVO.setCurrentVersion(currentVersion); - logger.trace(String.format("Update agent[uuid: %s] version\n" + - "From:\n" + - "expected version: %s, current version: %s\n" + - "To:\n" + - "expected version: %s, current version: %s\n", - agentUuid, - agentVersionVO.getExpectVersion(), originCurrentVersion, - agentVersionVO.getExpectVersion(), agentVersionVO.getCurrentVersion())); - dbf.update(agentVersionVO); - } + String originCurrentVersion = agentVersionVO.getCurrentVersion(); + agentVersionVO.setCurrentVersion(currentVersion); + logger.trace(String.format("Update agent[uuid: %s] version\n" + + "From:\n" + + "expected version: %s, current version: %s\n" + + "To:\n" + + "expected version: %s, current version: %s\n", + agentUuid, + agentVersionVO.getExpectVersion(), originCurrentVersion, + agentVersionVO.getExpectVersion(), agentVersionVO.getCurrentVersion())); + dbf.update(agentVersionVO); } /** From 037dc313f377c04fd77d010c379c3b142f5c7dee Mon Sep 17 00:00:00 2001 From: "ye.zou" Date: Mon, 8 Jun 2026 09:32:35 +0800 Subject: [PATCH 09/54] [kvm]: cache hypervisor metadata lookup Cache KVM hypervisor metadata in the MN-local repository lookup so resource matching does not depend on HostOsCategory/KvmHostHypervisorMetadata joins in the hot path. Keep compatibility metadata tables refreshed for QueryHostOsCategory and wire the metadata store into the KVM test Spring context. Constraint: Existing query APIs still read the compatibility tables. Confidence: high Scope-risk: moderate Tested: PR docker ./runMavenProfile premium; targeted PremiumKvmVirtualizerTest HypervisorAlarmCase and HypervisorMetadataCase passed Resolves: ZSTAC-65322 Change-Id: I4266c8df0770255ddd10c00fdeb708074e1757fa --- conf/springConfigXml/Kvm.xml | 2 + .../HostExpectedHypervisorMetadata.java | 52 ++++++ .../hypervisor/KvmHypervisorInfoHelper.java | 74 +++------ .../KvmHypervisorInfoManagerImpl.java | 100 ++++-------- .../KvmHypervisorMetadataStore.java | 17 ++ .../KvmHypervisorMetadataStoreImpl.java | 149 ++++++++++++++++++ .../datatype/ResourceHypervisorInfo.java | 52 +++--- .../test/resources/springConfigXml/Kvm.xml | 2 + 8 files changed, 306 insertions(+), 142 deletions(-) create mode 100644 plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/HostExpectedHypervisorMetadata.java create mode 100644 plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorMetadataStore.java create mode 100644 plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorMetadataStoreImpl.java diff --git a/conf/springConfigXml/Kvm.xml b/conf/springConfigXml/Kvm.xml index cdb2f667c15..a96965fc01b 100755 --- a/conf/springConfigXml/Kvm.xml +++ b/conf/springConfigXml/Kvm.xml @@ -214,6 +214,8 @@ + + diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/HostExpectedHypervisorMetadata.java b/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/HostExpectedHypervisorMetadata.java new file mode 100644 index 00000000000..d4a06d9bedd --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/HostExpectedHypervisorMetadata.java @@ -0,0 +1,52 @@ +package org.zstack.kvm.hypervisor; + +/** + * Expected host hypervisor version resolved from the local MN repository. + */ +public class HostExpectedHypervisorMetadata { + private String uuid; + private String architecture; + private String osReleaseVersion; + private String hypervisor; + private String version; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getArchitecture() { + return architecture; + } + + public void setArchitecture(String architecture) { + this.architecture = architecture; + } + + public String getOsReleaseVersion() { + return osReleaseVersion; + } + + public void setOsReleaseVersion(String osReleaseVersion) { + this.osReleaseVersion = osReleaseVersion; + } + + public String getHypervisor() { + return hypervisor; + } + + public void setHypervisor(String hypervisor) { + this.hypervisor = hypervisor; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorInfoHelper.java b/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorInfoHelper.java index 9ae81bb114c..cee9771f010 100644 --- a/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorInfoHelper.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorInfoHelper.java @@ -1,21 +1,15 @@ package org.zstack.kvm.hypervisor; -import org.zstack.core.Platform; -import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.Q; import org.zstack.header.host.HostVO_; import org.zstack.header.host.HostOperationSystem; import org.zstack.kvm.KVMConstant; import org.zstack.kvm.KVMHostVO; import org.zstack.kvm.KVMHostVO_; -import org.zstack.kvm.hypervisor.datatype.HostOsCategoryVO; -import org.zstack.kvm.hypervisor.datatype.HostOsCategoryVO_; import org.zstack.kvm.hypervisor.datatype.HypervisorVersionState; -import org.zstack.utils.data.Pair; import javax.persistence.Tuple; import java.util.*; -import java.util.stream.Collectors; /** @@ -25,66 +19,48 @@ public class KvmHypervisorInfoHelper { /** * @return map * key: host uuid - * value: HostOsCategoryVO, may be null + * value: expected hypervisor metadata, may be null */ - public static Map collectExpectedHypervisorInfoForHosts( - Collection hostUuidList) { + public static Map collectExpectedHypervisorInfoForHosts( + Collection hostUuidList, String hypervisor) { + if (hostUuidList == null || hostUuidList.isEmpty()) { + return Collections.emptyMap(); + } + List tuples = Q.New(KVMHostVO.class) .select(KVMHostVO_.uuid, KVMHostVO_.architecture, KVMHostVO_.osDistribution, KVMHostVO_.osRelease, KVMHostVO_.osVersion) .notNull(HostVO_.architecture) .in(KVMHostVO_.uuid, hostUuidList) .listTuple(); - Map hostArchMap = tuples.stream() - .collect(Collectors.toMap(tuple -> tuple.get(0, String.class), tuple -> tuple.get(1, String.class))); - Map hostOsMap = tuples.stream() - .collect(Collectors.toMap(tuple -> tuple.get(0, String.class), - tuple -> HostOperationSystem.of( - tuple.get(2, String.class), tuple.get(3, String.class), tuple.get(4, String.class)))); - final Map, HostOsCategoryVO> caches = new HashMap<>(); - final Map results = new HashMap<>(); - DatabaseFacade dbf = Platform.getComponentLoader().getComponent(DatabaseFacade.class); - for (String hostUuid : hostUuidList) { - String architecture = hostArchMap.get(hostUuid); - if (architecture == null) { + KvmHypervisorMetadataStore metadataStore = org.zstack.core.Platform.getComponentLoader() + .getComponent(KvmHypervisorMetadataStore.class); + Map results = new HashMap<>(); + for (Tuple tuple : tuples) { + String hostUuid = tuple.get(0, String.class); + String architecture = tuple.get(1, String.class); + HostOperationSystem os = HostOperationSystem.of( + tuple.get(2, String.class), + tuple.get(3, String.class), + tuple.get(4, String.class)); + if (os == null || os.distribution == null) { results.put(hostUuid, null); continue; } - - HostOperationSystem os = hostOsMap.get(hostUuid); String osReleaseVersion = String.format("%s %s", os.distribution, normalizeOsVersion(os.version)); - - Pair key = new Pair<>(architecture, osReleaseVersion); - HostOsCategoryVO vo = caches.get(key); - if (vo != null) { - results.put(hostUuid, vo); - continue; - } - - String sql = " select h from HostOsCategoryVO h, KvmHostHypervisorMetadataVO k where k.categoryUuid = h.uuid " + - "and k.managementNodeUuid = :mnId " + - "and h.architecture = :arch " + - "and h.osReleaseVersion = :os " + - "order by k.createDate desc"; - - List resultList = dbf.getEntityManager().createQuery(sql, HostOsCategoryVO.class) - .setParameter("arch", architecture) - .setParameter("os", osReleaseVersion) - .setParameter("mnId", Platform.getManagementServerId()) - .setMaxResults(1) - .getResultList(); - if (!resultList.isEmpty()) { - vo = resultList.get(0); - } - - caches.put(key, vo); - results.put(hostUuid, vo); + results.put(hostUuid, metadataStore.find(architecture, osReleaseVersion, hypervisor)); } return results; } + public static boolean isExpectedHypervisorMetadataAvailable() { + KvmHypervisorMetadataStore metadataStore = org.zstack.core.Platform.getComponentLoader() + .getComponent(KvmHypervisorMetadataStore.class); + return metadataStore.isAvailable(); + } + /** * Strip a leading {@code V} or {@code v} from an OS version string when the * next character is a digit. Some distributions (notably Kylin Linux Advanced diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorInfoManagerImpl.java b/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorInfoManagerImpl.java index df330171cf6..bb908f069bb 100644 --- a/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorInfoManagerImpl.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorInfoManagerImpl.java @@ -47,6 +47,8 @@ public class KvmHypervisorInfoManagerImpl implements KvmHypervisorInfoManager, C private EventFacade events; @Autowired private CloudBus bus; + @Autowired + private KvmHypervisorMetadataStore metadataStore; @Override public void save(GetVirtualizerInfoRsp rsp) { @@ -164,32 +166,30 @@ private void collectHostMatchTargetInfo(Map uuid Set hostUuidSet = uuidInfoMap.values().stream() .filter(info -> info.matchTargetVersion == null) .filter(info -> HostVO.class.getSimpleName().equals(info.resourceType)) - .filter(info -> KvmHostHypervisorMetadataVO.class.getSimpleName().equals(info.matchTargetResourceType)) + .filter(info -> HostExpectedHypervisorMetadata.class.getSimpleName().equals(info.matchTargetResourceType)) .map(info -> info.uuid) .collect(Collectors.toSet()); if (hostUuidSet.isEmpty()) { return; } - final Map uuidCategoryMap = - KvmHypervisorInfoHelper.collectExpectedHypervisorInfoForHosts(hostUuidSet); - uuidCategoryMap.forEach((uuid, category) -> { - if (category == null) { - return; - } - - ResourceHypervisorInfo info = uuidInfoMap.get(uuid); - final KvmHostHypervisorMetadataVO metadata = category.getMetadataList() - .stream() - .filter(m -> m.getHypervisor().equals(info.virtualizer)) - .findAny() - .orElse(null); - if (metadata == null) { - return; - } + Map> hostInfosByHypervisor = uuidInfoMap.values().stream() + .filter(info -> hostUuidSet.contains(info.uuid)) + .collect(Collectors.groupingBy(info -> info.virtualizer)); + hostInfosByHypervisor.forEach((hypervisor, infos) -> { + final Map uuidExpectedMap = + KvmHypervisorInfoHelper.collectExpectedHypervisorInfoForHosts( + infos.stream().map(info -> info.uuid).collect(Collectors.toSet()), hypervisor); + + uuidExpectedMap.forEach((uuid, metadata) -> { + if (metadata == null) { + return; + } - info.matchTargetUuid = metadata.getUuid(); - info.matchTargetVersion = metadata.getVersion(); + ResourceHypervisorInfo info = uuidInfoMap.get(uuid); + info.matchTargetUuid = metadata.getUuid(); + info.matchTargetVersion = metadata.getVersion(); + }); }); } @@ -206,16 +206,6 @@ private void collectHypervisorInfoVo(Map uuidInf } } - private KvmHypervisorInfoVO updateKvmHypervisorInfoVO(String uuid, KvmHypervisorInfoVO vo, VirtualizerInfoTO to) { - if (vo == null) { - vo = new KvmHypervisorInfoVO(); - vo.setUuid(uuid); - } - vo.setHypervisor(to.getVirtualizer()); - vo.setVersion(to.getVersion()); - return vo; - } - @Override public void clean(String uuid) { SQL.New(KvmHypervisorInfoVO.class).eq(KvmHypervisorInfoVO_.uuid, uuid).delete(); @@ -224,10 +214,14 @@ public void clean(String uuid) { @Override public void refreshMetadata() { List collected = collector.collect(); - boolean anyRecordUpdated = saveMetadataList(collected); - if (anyRecordUpdated) { - refreshHostMatchState(); + boolean metadataUpdated = metadataStore.refresh(collected); + if (!metadataUpdated) { + logger.warn("no hypervisor metadata collected from DVD, skip refresh to preserve existing metadata"); + return; } + + saveMetadataList(metadataStore.listForCompatibility()); + refreshHostMatchState(); } private void registerRefreshVmHypervisorHooks() { @@ -263,13 +257,7 @@ private void onVmStart(String vmUuid) { bus.send(message); // no need to reply } - private boolean saveMetadataList(List definitions) { - List categoryVOS = definitions.stream() - .map(this::mapToHostOsCategory) - .collect(Collectors.collectingAndThen( - Collectors.toCollection(() -> new TreeSet<>(Comparator - .comparing(HostOsCategoryVO::getArchitecture) - .thenComparing(HostOsCategoryVO::getOsReleaseVersion))), ArrayList::new)); + private boolean saveMetadataList(List categoryVOS) { return saveHostOsCategoryList(categoryVOS); } @@ -280,7 +268,7 @@ protected boolean saveHostOsCategoryList(List categoryVOS) { return false; } - // refresh all metadata with current management node + // refresh all compatibility metadata with current management node SQL.New(KvmHostHypervisorMetadataVO.class) .eq(KvmHostHypervisorMetadataVO_.managementNodeUuid, Platform.getManagementServerId()) .delete(); @@ -299,22 +287,16 @@ protected boolean saveHostOsCategoryList(List categoryVOS) { List needPersistCategories = new ArrayList<>(); List metadataList = new ArrayList<>(); - // fill KvmHostHypervisorMetadataVO.categoryUuid - // fill HostOsCategoryVO.uuid if it is new for (HostOsCategoryVO category : categoryVOS) { HostOsCategoryVO realCategory = existsCategories.stream() .filter(c -> c.getArchitecture().equals(category.getArchitecture())) .filter(c -> c.getOsReleaseVersion().equals(category.getOsReleaseVersion())) .findAny().orElse(null); - + if (realCategory == null) { - String newUuid = Platform.getUuid(); - category.setUuid(newUuid); - category.getMetadataList().forEach(m -> m.setCategoryUuid(newUuid)); needPersistCategories.add(category); } else { - String uuid = realCategory.getUuid(); - category.getMetadataList().forEach(m -> m.setCategoryUuid(uuid)); + category.getMetadataList().forEach(m -> m.setCategoryUuid(realCategory.getUuid())); } metadataList.addAll(category.getMetadataList()); @@ -368,6 +350,7 @@ private void refreshHostMatchState() { if (originState == state) { return; } + updatedMap.compute(state, (v, list) -> { if (list == null) { return new ArrayList<>(Arrays.asList(uuid)); @@ -393,27 +376,6 @@ private Map refreshHostMatchStateFragment(Set definitions); + + HostExpectedHypervisorMetadata find(String architecture, String osReleaseVersion, String hypervisor); + + List listForCompatibility(); + + boolean isAvailable(); +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorMetadataStoreImpl.java b/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorMetadataStoreImpl.java new file mode 100644 index 00000000000..9e5fbb24ade --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/KvmHypervisorMetadataStoreImpl.java @@ -0,0 +1,149 @@ +package org.zstack.kvm.hypervisor; + +import org.zstack.core.Platform; +import org.zstack.kvm.hypervisor.datatype.HostOsCategoryVO; +import org.zstack.kvm.hypervisor.datatype.KvmHostHypervisorMetadataVO; +import org.zstack.utils.CollectionUtils; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import static org.zstack.kvm.hypervisor.HypervisorMetadataCollector.HypervisorMetadataDefinition; + +public class KvmHypervisorMetadataStoreImpl implements KvmHypervisorMetadataStore { + private final AtomicReference snapshot = new AtomicReference<>(MetadataSnapshot.empty()); + + @Override + public boolean refresh(List definitions) { + if (CollectionUtils.isEmpty(definitions)) { + return false; + } + + MetadataSnapshot next = MetadataSnapshot.from(definitions); + if (!next.isAvailable()) { + return false; + } + + snapshot.set(next); + return true; + } + + @Override + public HostExpectedHypervisorMetadata find(String architecture, String osReleaseVersion, String hypervisor) { + return snapshot.get().find(architecture, osReleaseVersion, hypervisor); + } + + @Override + public List listForCompatibility() { + return snapshot.get().categories(); + } + + @Override + public boolean isAvailable() { + return snapshot.get().isAvailable(); + } + + private static class MetadataSnapshot { + private final Map expectedByKey; + private final List categories; + + private MetadataSnapshot(Map expectedByKey, + List categories) { + this.expectedByKey = expectedByKey; + this.categories = categories; + } + + static MetadataSnapshot empty() { + return new MetadataSnapshot(new HashMap<>(), new ArrayList<>()); + } + + static MetadataSnapshot from(List definitions) { + Map expectedByKey = new HashMap<>(); + Map categoriesByKey = new HashMap<>(); + + for (HypervisorMetadataDefinition definition : definitions) { + String categoryKey = categoryKey(definition.getArchitecture(), definition.getOsReleaseVersion()); + HostOsCategoryVO category = categoriesByKey.computeIfAbsent(categoryKey, key -> { + HostOsCategoryVO vo = new HostOsCategoryVO(); + vo.setUuid(Platform.getUuid()); + vo.setArchitecture(definition.getArchitecture()); + vo.setOsReleaseVersion(definition.getOsReleaseVersion()); + vo.setMetadataList(new ArrayList<>()); + return vo; + }); + + KvmHostHypervisorMetadataVO metadata = new KvmHostHypervisorMetadataVO(); + metadata.setUuid(Platform.getUuid()); + metadata.setCategoryUuid(category.getUuid()); + metadata.setManagementNodeUuid(Platform.getManagementServerId()); + metadata.setHypervisor(definition.getHypervisor()); + metadata.setVersion(definition.getVersion()); + category.getMetadataList().add(metadata); + + HostExpectedHypervisorMetadata expected = new HostExpectedHypervisorMetadata(); + expected.setUuid(metadata.getUuid()); + expected.setArchitecture(definition.getArchitecture()); + expected.setOsReleaseVersion(definition.getOsReleaseVersion()); + expected.setHypervisor(definition.getHypervisor()); + expected.setVersion(definition.getVersion()); + expectedByKey.put(expectedKey(definition.getArchitecture(), definition.getOsReleaseVersion(), definition.getHypervisor()), expected); + } + + List categories = categoriesByKey.values().stream() + .sorted(Comparator.comparing(HostOsCategoryVO::getArchitecture) + .thenComparing(HostOsCategoryVO::getOsReleaseVersion)) + .collect(Collectors.toList()); + categories.forEach(c -> c.getMetadataList().sort(Comparator.comparing(KvmHostHypervisorMetadataVO::getHypervisor))); + + return new MetadataSnapshot(expectedByKey, categories); + } + + HostExpectedHypervisorMetadata find(String architecture, String osReleaseVersion, String hypervisor) { + return expectedByKey.get(expectedKey(architecture, osReleaseVersion, hypervisor)); + } + + List categories() { + return categories.stream() + .map(MetadataSnapshot::copy) + .collect(Collectors.toList()); + } + + boolean isAvailable() { + return !expectedByKey.isEmpty(); + } + + private static String categoryKey(String architecture, String osReleaseVersion) { + return String.format("%s|%s", architecture, osReleaseVersion); + } + + private static String expectedKey(String architecture, String osReleaseVersion, String hypervisor) { + return String.format("%s|%s|%s", architecture, osReleaseVersion, hypervisor); + } + + private static HostOsCategoryVO copy(HostOsCategoryVO source) { + HostOsCategoryVO category = new HostOsCategoryVO(); + category.setUuid(source.getUuid()); + category.setArchitecture(source.getArchitecture()); + category.setOsReleaseVersion(source.getOsReleaseVersion()); + category.setMetadataList(source.getMetadataList().stream() + .map(MetadataSnapshot::copy) + .collect(Collectors.toList())); + return category; + } + + private static KvmHostHypervisorMetadataVO copy(KvmHostHypervisorMetadataVO source) { + KvmHostHypervisorMetadataVO metadata = new KvmHostHypervisorMetadataVO(); + metadata.setUuid(source.getUuid()); + metadata.setCategoryUuid(source.getCategoryUuid()); + metadata.setManagementNodeUuid(source.getManagementNodeUuid()); + metadata.setHypervisor(source.getHypervisor()); + metadata.setVersion(source.getVersion()); + return metadata; + } + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/datatype/ResourceHypervisorInfo.java b/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/datatype/ResourceHypervisorInfo.java index b25573c64d9..4238b831abf 100644 --- a/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/datatype/ResourceHypervisorInfo.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/hypervisor/datatype/ResourceHypervisorInfo.java @@ -7,6 +7,7 @@ import org.zstack.header.vm.VmInstanceVO_; import org.zstack.header.vo.ResourceVO; import org.zstack.header.vo.ResourceVO_; +import org.zstack.kvm.hypervisor.HostExpectedHypervisorMetadata; import org.zstack.kvm.hypervisor.KvmHypervisorInfoHelper; import java.util.*; @@ -45,7 +46,7 @@ public static ResourceHypervisorInfo fromVmVirtualizerInfo(VirtualizerInfoTO inf public static ResourceHypervisorInfo fromHostVirtualizerInfo(VirtualizerInfoTO info) { ResourceHypervisorInfo result = from(info); result.resourceType = HostVO.class.getSimpleName(); - result.matchTargetResourceType = KvmHostHypervisorMetadataVO.class.getSimpleName(); + result.matchTargetResourceType = HostExpectedHypervisorMetadata.class.getSimpleName(); return result; } @@ -107,7 +108,7 @@ private static void fillMatchTargetInfo(List inventories collectMatchTargetOfVm(vmInventories); } - // collect expect version of Host : from HostOsCategoryVO + // collect expect version of Host from the local MN repository metadata cache List hostInventories = inventories.stream() .filter(inv -> inv.resourceType.equals(HostVO.class.getSimpleName())) .collect(Collectors.toList()); @@ -148,28 +149,24 @@ private static void collectMatchTargetOfVm(List inventor } private static void collectMatchTargetOfHost(List inventories) { - inventories.forEach(info -> info.matchTargetResourceType = KvmHostHypervisorMetadataVO.class.getSimpleName()); - - Map hostExpectedMap = - KvmHypervisorInfoHelper.collectExpectedHypervisorInfoForHosts(inventories.stream() - .map(info -> info.uuid) - .collect(Collectors.toSet())); - - for (ResourceHypervisorInfo hostInventory : inventories) { - HostOsCategoryVO category = hostExpectedMap.get(hostInventory.uuid); - if (category == null) { - continue; + inventories.forEach(info -> info.matchTargetResourceType = HostExpectedHypervisorMetadata.class.getSimpleName()); + + Map> hostInventoriesByHypervisor = inventories.stream() + .collect(Collectors.groupingBy(info -> info.virtualizer)); + hostInventoriesByHypervisor.forEach((hypervisor, list) -> { + Map hostExpectedMap = + KvmHypervisorInfoHelper.collectExpectedHypervisorInfoForHosts( + list.stream().map(info -> info.uuid).collect(Collectors.toSet()), hypervisor); + + for (ResourceHypervisorInfo hostInventory : list) { + HostExpectedHypervisorMetadata metadata = hostExpectedMap.get(hostInventory.uuid); + if (metadata == null) { + continue; + } + hostInventory.matchTargetVersion = metadata.getVersion(); + hostInventory.matchTargetUuid = metadata.getUuid(); } - KvmHostHypervisorMetadataVO metadata = category.getMetadataList().stream() - .filter(m -> m.getHypervisor().equals(hostInventory.virtualizer)) - .findAny() - .orElse(null); - if (metadata == null) { - continue; - } - hostInventory.matchTargetVersion = metadata.getVersion(); - hostInventory.matchTargetUuid = metadata.getUuid(); - } + }); } public KvmHypervisorInfoVO generate() { @@ -179,7 +176,14 @@ public KvmHypervisorInfoVO generate() { } vo.setHypervisor(virtualizer); vo.setVersion(version); - vo.setMatchState(KvmHypervisorInfoHelper.isQemuVersionMatched(version, matchTargetVersion)); + boolean preserveHostMatchState = matchTargetVersion == null + && vo.getMatchState() != null + && HostVO.class.getSimpleName().equals(resourceType) + && HostExpectedHypervisorMetadata.class.getSimpleName().equals(matchTargetResourceType) + && !KvmHypervisorInfoHelper.isExpectedHypervisorMetadataAvailable(); + if (!preserveHostMatchState) { + vo.setMatchState(KvmHypervisorInfoHelper.isQemuVersionMatched(version, matchTargetVersion)); + } return vo; } diff --git a/test/src/test/resources/springConfigXml/Kvm.xml b/test/src/test/resources/springConfigXml/Kvm.xml index 9001b76bda8..ec757841d12 100755 --- a/test/src/test/resources/springConfigXml/Kvm.xml +++ b/test/src/test/resources/springConfigXml/Kvm.xml @@ -220,6 +220,8 @@ + + From 8d3aa6f2167e9425fdcaaf912601fc60ff98272b Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Tue, 9 Jun 2026 13:36:15 +0800 Subject: [PATCH 10/54] [upgrade]: prevent automatic agent upgrade during grayscale Resolves: ZSTAC-85435 Change-Id: I13c0cdf736d086c2c70e9d2651b60c56c0dc5115 --- .../zstack/core/upgrade/UpgradeChecker.java | 137 +++++++++++------- .../java/org/zstack/kvm/KVMHostFactory.java | 33 ++++- .../VirtualRouterManagerImpl.java | 19 ++- .../VirtualRouterDeployAgentFlow.java | 9 -- .../vyos/VyosDeployAgentFlow.java | 5 - 5 files changed, 124 insertions(+), 79 deletions(-) diff --git a/core/src/main/java/org/zstack/core/upgrade/UpgradeChecker.java b/core/src/main/java/org/zstack/core/upgrade/UpgradeChecker.java index d0f4c73156a..aaef6d91ca6 100644 --- a/core/src/main/java/org/zstack/core/upgrade/UpgradeChecker.java +++ b/core/src/main/java/org/zstack/core/upgrade/UpgradeChecker.java @@ -57,18 +57,19 @@ public class UpgradeChecker implements Component, GlobalApiMessageInterceptor { protected PluginRegistry pluginRgty; private static Map> grayUpgradeConfigMap = new HashMap<>(); - private static ConcurrentLinkedQueue grayScaleApiWhiteList = new ConcurrentLinkedQueue<>(); + private static volatile ConcurrentLinkedQueue grayScaleApiWhiteList = new ConcurrentLinkedQueue<>(); private static Set predefinedApiClassSet = new HashSet<>(); + private static boolean globalConfigExtensionInstalled = false; private static String INITIAL_AGENT_VERSION = "3.10.38"; @Override public boolean start() { - if (!UpgradeGlobalConfig.GRAYSCALE_UPGRADE.value(Boolean.class)) { - return true; - } - initGrayScaleConfig(); populateGlobalConfigForGrayscaleUpgrade(); + + if (UpgradeGlobalConfig.GRAYSCALE_UPGRADE.value(Boolean.class)) { + initGrayScaleConfig(); + } return true; } @@ -87,27 +88,16 @@ private void initPredefinedApiClassSet() { predefinedApiClassSet.add(APIUpdateClusterOSMsg.class.getSimpleName()); } - private void populateGlobalConfigForGrayscaleUpgrade() { + private synchronized void populateGlobalConfigForGrayscaleUpgrade() { initPredefinedApiClassSet(); - grayScaleApiWhiteList.addAll(predefinedApiClassSet); - List predefinedClasses; - try { - predefinedClasses = Arrays.asList(UpgradeGlobalConfig.ALLOWED_API_LIST_GRAYSCALE_UPGRADING.value().split(",")); - } catch (PatternSyntaxException exception) { - throw new CloudRuntimeException(String.format("Failed to split config value by ','," + - ", because %s. Please input a string separate api by ','", exception)); - } - grayScaleApiWhiteList.addAll(predefinedClasses); + refreshGrayScaleApiWhiteList(UpgradeGlobalConfig.ALLOWED_API_LIST_GRAYSCALE_UPGRADING.value()); + if (globalConfigExtensionInstalled) { + return; + } UpgradeGlobalConfig.ALLOWED_API_LIST_GRAYSCALE_UPGRADING .installValidateExtension((category, name, oldValue, newValue) -> { - List apiClassNames; - try { - apiClassNames = Arrays.asList(newValue.split(",")); - } catch (PatternSyntaxException exception) { - throw new GlobalConfigException(String.format("Failed to split config value by ','," + - ", because %s. Please input a string separate api by ','", exception)); - } + List apiClassNames = parseGrayScaleApiClassNames(newValue); List matchedApiClassName = apiClassNames.stream() .filter(className -> APIMessage.apiMessageClasses @@ -115,6 +105,7 @@ private void populateGlobalConfigForGrayscaleUpgrade() { .anyMatch(clazz -> clazz.getSimpleName().equals(className))) .collect(Collectors.toList()); + apiClassNames = new ArrayList<>(apiClassNames); apiClassNames.removeAll(matchedApiClassName); if (!apiClassNames.isEmpty()) { throw new GlobalConfigException(String.format("Failed to find api class name: %s", apiClassNames)); @@ -122,13 +113,40 @@ private void populateGlobalConfigForGrayscaleUpgrade() { }); UpgradeGlobalConfig.ALLOWED_API_LIST_GRAYSCALE_UPGRADING.installUpdateExtension((oldConfig, newConfig) -> { - List apiClassNames = Arrays.asList(newConfig.value().split(",")); - grayScaleApiWhiteList.clear(); - - apiClassNames.removeAll(predefinedApiClassSet); - grayScaleApiWhiteList.addAll(predefinedApiClassSet); - grayScaleApiWhiteList.addAll(apiClassNames); + refreshGrayScaleApiWhiteList(newConfig.value()); }); + globalConfigExtensionInstalled = true; + } + + private List parseGrayScaleApiClassNames(String value) { + try { + return Arrays.stream(StringUtils.defaultString(value).split(",")) + .map(String::trim) + .filter(StringUtils::isNotEmpty) + .collect(Collectors.toList()); + } catch (PatternSyntaxException exception) { + throw new GlobalConfigException(String.format("Failed to split config value by ','," + + ", because %s. Please input a string separate api by ','", exception)); + } + } + + private void refreshGrayScaleApiWhiteList(String configValue) { + List apiClassNames = parseGrayScaleApiClassNames(configValue); + + ConcurrentLinkedQueue whiteList = new ConcurrentLinkedQueue<>(); + whiteList.addAll(predefinedApiClassSet); + apiClassNames.removeAll(predefinedApiClassSet); + whiteList.addAll(apiClassNames); + grayScaleApiWhiteList = whiteList; + } + + public synchronized void addPredefinedApiClassName(String className) { + if (StringUtils.isEmpty(className)) { + return; + } + + predefinedApiClassSet.add(className); + refreshGrayScaleApiWhiteList(UpgradeGlobalConfig.ALLOWED_API_LIST_GRAYSCALE_UPGRADING.value()); } public void initGrayScaleConfig() { @@ -191,14 +209,15 @@ public ErrorCode checkAgentHttpParamChanges(String agentUuid, String commandName } else { agentVersionVO = versionVO; } + String currentAgentVersion = agentVersionVO.getCurrentVersion() == null ? INITIAL_AGENT_VERSION : agentVersionVO.getCurrentVersion(); // if agent version not changed skip gray scale check - if (agentVersionVO.getExpectVersion().equals(agentVersionVO.getCurrentVersion())) { + if (agentVersionVO.getExpectVersion().equals(currentAgentVersion)) { logger.trace(String.format("agent[uuid: %s] expected version: %s, current version :%s matched," + " skip grayscale upgrade check", agentUuid, - agentVersionVO.getCurrentVersion(), - agentVersionVO.getCurrentVersion())); + currentAgentVersion, + currentAgentVersion)); return null; } @@ -235,7 +254,7 @@ public ErrorCode checkAgentHttpParamChanges(String agentUuid, String commandName for (Map fields : relatedFieldsVersionMap) { // check if current command has unexpected versions logger.debug("grayscale compare fields " + JSONObjectUtil.toJsonString(fields)); - VersionComparator currentVersion = new VersionComparator(agentVersionVO.getCurrentVersion()); + VersionComparator currentVersion = new VersionComparator(currentAgentVersion); Set> entries = fields.entrySet() .stream() .filter(entry -> {//logger.debug(String.format("entry key: %s, value:%s", entry.getKey(), entry.getValue())); @@ -252,7 +271,7 @@ public ErrorCode checkAgentHttpParamChanges(String agentUuid, String commandName if (entries.isEmpty()) { if (logger.isTraceEnabled()) { StringBuilder sb = new StringBuilder(); - sb.append(String.format("agent[uuid: %s] current version: %s\n", agentUuid, agentVersionVO.getCurrentVersion())); + sb.append(String.format("agent[uuid: %s] current version: %s\n", agentUuid, currentAgentVersion)); fields.forEach((key, value) -> sb.append(String.format("field: %s, support version: %s\n", key, value))); sb.append("all fields is supported by current agent, allow operations"); logger.trace(sb.toString()); @@ -282,7 +301,7 @@ public ErrorCode checkAgentHttpParamChanges(String agentUuid, String commandName if (entries.isEmpty()) { if (logger.isTraceEnabled()) { StringBuilder sb = new StringBuilder(); - sb.append(String.format("agent[uuid: %s] current version: %s\n", agentUuid, agentVersionVO.getCurrentVersion())); + sb.append(String.format("agent[uuid: %s] current version: %s\n", agentUuid, currentAgentVersion)); fields.forEach((key, value) -> sb.append(String.format("field: %s, support version: %s\n", key, value))); sb.append("after check those fields' usage in command, allow operations"); logger.trace(sb.toString()); @@ -292,18 +311,18 @@ public ErrorCode checkAgentHttpParamChanges(String agentUuid, String commandName StringBuilder sb = new StringBuilder(); sb.append(String.format("This operation is not allowed on host[uuid:%s] during grayscale upgrade: \n", agentUuid)); - entries.forEach(entry -> sb.append(String.format("field: %s, current agent version %s, support version: %s\n", entry.getKey(), agentVersionVO.getCurrentVersion(), entry.getValue()))); + entries.forEach(entry -> sb.append(String.format("field: %s, current agent version %s, support version: %s\n", entry.getKey(), currentAgentVersion, entry.getValue()))); return operr(ORG_ZSTACK_CORE_UPGRADE_10001, sb.toString()); } return null; } - public void updateAgentVersion(String agentUuid, String agentType, String expectVersion, String currentVersion) { - if (!UpgradeGlobalConfig.GRAYSCALE_UPGRADE.value(Boolean.class)) { - return; - } + public void refreshAgentExpectedVersion(String agentUuid, String agentType, String expectVersion) { + updateAgentVersion(agentUuid, agentType, expectVersion, null); + } + public void updateAgentVersion(String agentUuid, String agentType, String expectVersion, String currentVersion) { AgentVersionVO agentVersionVO = dbf.findByUuid(agentUuid, AgentVersionVO.class); if (agentVersionVO == null) { agentVersionVO = new AgentVersionVO(); @@ -322,27 +341,33 @@ public void updateAgentVersion(String agentUuid, String agentType, String expect return; } - if (currentVersion == null) { - logger.trace(String.format("Update agent[uuid: %s] version to null is not supported, skip updating", agentUuid)); - return; + String originExpectVersion = agentVersionVO.getExpectVersion(); + String originCurrentVersion = agentVersionVO.getCurrentVersion(); + boolean changed = false; + + if (!Objects.equals(originExpectVersion, expectVersion)) { + agentVersionVO.setExpectVersion(expectVersion); + changed = true; } - if (Objects.equals(agentVersionVO.getCurrentVersion(), currentVersion)) { - logger.trace(String.format("Agent[uuid: %s] version expected version: %s, current version: %s, not changed", agentUuid, agentVersionVO.getExpectVersion(), agentVersionVO.getCurrentVersion())); - return; + if (currentVersion != null && !Objects.equals(originCurrentVersion, currentVersion)) { + agentVersionVO.setCurrentVersion(currentVersion); + changed = true; } - String originCurrentVersion = agentVersionVO.getCurrentVersion(); - agentVersionVO.setCurrentVersion(currentVersion); - logger.trace(String.format("Update agent[uuid: %s] version\n" + - "From:\n" + - "expected version: %s, current version: %s\n" + - "To:\n" + - "expected version: %s, current version: %s\n", - agentUuid, - agentVersionVO.getExpectVersion(), originCurrentVersion, - agentVersionVO.getExpectVersion(), agentVersionVO.getCurrentVersion())); - dbf.update(agentVersionVO); + if (changed) { + logger.trace(String.format("Update agent[uuid: %s] version\n" + + "From:\n" + + "expected version: %s, current version: %s\n" + + "To:\n" + + "expected version: %s, current version: %s\n", + agentUuid, originExpectVersion, originCurrentVersion, + agentVersionVO.getExpectVersion(), agentVersionVO.getCurrentVersion())); + dbf.update(agentVersionVO); + } else { + logger.trace(String.format("Agent[uuid: %s] version expected version: %s, current version: %s, not changed", + agentUuid, agentVersionVO.getExpectVersion(), agentVersionVO.getCurrentVersion())); + } } /** diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java index 0bbb01f3148..b8d9d654249 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java @@ -10,6 +10,7 @@ import org.zstack.compute.vm.VmNicManager; import org.zstack.core.CoreGlobalProperty; import org.zstack.core.ansible.AnsibleFacade; +import org.zstack.core.ansible.AnsibleConstant; import org.zstack.core.jsonlabel.JsonLabel; import org.zstack.core.jsonlabel.JsonLabelInventory; import org.zstack.core.cloudbus.CloudBus; @@ -33,6 +34,8 @@ import org.zstack.core.thread.AsyncThread; import org.zstack.core.thread.PeriodicTask; import org.zstack.core.thread.ThreadFacade; +import org.zstack.core.upgrade.UpgradeChecker; +import org.zstack.core.upgrade.UpgradeGlobalConfig; import org.zstack.core.timeout.TimeHelper; import org.zstack.header.AbstractService; import org.zstack.header.Component; @@ -177,6 +180,8 @@ public class KVMHostFactory extends AbstractService implements HypervisorFactory @Autowired private GlobalConfigFacade gcf; @Autowired + private UpgradeChecker upgradeChecker; + @Autowired KvmVmSyncPingTask pingTask; private Future checkSocketChannelTimeoutThread; public static int skipHostPingTimeWhenKvmagentBusy = 300; @@ -262,16 +267,22 @@ public Host getHost(HostVO vo) { } private List getHostManagedByUs() { + return getHostManagedByUs(true); + } + + private List getHostManagedByUs(boolean connectedOnly) { int qun = 10000; - long amount = dbf.count(HostVO.class); + long amount = dbf.count(KVMHostVO.class); int times = (int) (amount / qun) + (amount % qun != 0 ? 1 : 0); List hostUuids = new ArrayList(); int start = 0; for (int i = 0; i < times; i++) { SimpleQuery q = dbf.createQuery(KVMHostVO.class); q.select(HostVO_.uuid); - // disconnected host will be handled by HostManager - q.add(HostVO_.status, SimpleQuery.Op.EQ, HostStatus.Connected); + if (connectedOnly) { + // disconnected host will be handled by HostManager + q.add(HostVO_.status, SimpleQuery.Op.EQ, HostStatus.Connected); + } q.setLimit(qun); q.setStart(start); List lst = q.listValue(); @@ -1121,18 +1132,28 @@ public void managementNodeReady() { return; } - if (!asf.isModuleChanged(KVMConstant.ANSIBLE_PLAYBOOK_NAME)) { + List allHostUuids = getHostManagedByUs(false); + for (String huuid : allHostUuids) { + upgradeChecker.refreshAgentExpectedVersion(huuid, AnsibleConstant.KVM_AGENT_NAME, dbf.getDbVersion()); + } + + if (UpgradeGlobalConfig.GRAYSCALE_UPGRADE.value(Boolean.class)) { + logger.debug(String.format("skip connecting kvm hosts on management node ready because grayscale upgrade is enabled, refreshed expected versions for hosts:%s", allHostUuids)); return; } - // KVM hosts need to deploy new agent - // connect hosts even if they are ConnectionState is Connected + if (!asf.isModuleChanged(KVMConstant.ANSIBLE_PLAYBOOK_NAME)) { + return; + } List hostUuids = getHostManagedByUs(); if (hostUuids.isEmpty()) { return; } + // KVM hosts need to deploy new agent + // connect hosts even if they are ConnectionState is Connected + logger.debug(String.format("need to connect kvm hosts because kvm agent changed, uuids:%s", hostUuids)); List msgs = new ArrayList(); diff --git a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterManagerImpl.java b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterManagerImpl.java index e6b4e2c15fb..f5a4366c190 100755 --- a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterManagerImpl.java +++ b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/VirtualRouterManagerImpl.java @@ -20,6 +20,7 @@ import org.zstack.core.thread.ChainTask; import org.zstack.core.thread.SyncTaskChain; import org.zstack.core.thread.ThreadFacade; +import org.zstack.core.upgrade.UpgradeChecker; import org.zstack.core.upgrade.UpgradeGlobalConfig; import org.zstack.core.workflow.FlowChainBuilder; import org.zstack.header.AbstractService; @@ -113,7 +114,8 @@ public class VirtualRouterManagerImpl extends AbstractService implements Virtual VmInstanceMigrateExtensionPoint, VmPreMigrationExtensionPoint { private final static CLogger logger = Utils.getLogger(VirtualRouterManagerImpl.class); - private final static List supportedL2NetworkTypes = new ArrayList(); + private final static List supportedL2NetworkTypes = new ArrayList(); + private NetworkServiceProviderInventory virtualRouterProvider; private final Map hypervisorBackends = new HashMap(); private final Map vrParallelismDegrees = new ConcurrentHashMap(); @@ -180,6 +182,8 @@ public class VirtualRouterManagerImpl extends AbstractService implements Virtual protected VirtualRouterHaBackend haBackend; @Autowired private ApplianceVmFactory apvmFactory; + @Autowired + private UpgradeChecker upgradeChecker; @Override @MessageSafe @@ -606,6 +610,7 @@ public void validateGlobalConfig(String category, String name, String oldValue, @Override public boolean start() { + upgradeChecker.addPredefinedApiClassName(APIReconnectVirtualRouterMsg.class.getSimpleName()); populateExtensions(); deployAnsible(); buildWorkFlowBuilder(); @@ -2287,12 +2292,20 @@ public void fail(ErrorCode errorCode) { @Override public void managementNodeReady() { + List vrVos = Q.New(VirtualRouterVmVO.class).list(); + String expectedVersion = new VirtualRouterMetadataOperator().getManagementVersion(); + for (VirtualRouterVmVO vrVo : vrVos) { + if (!destMaker.isManagedByUs(vrVo.getUuid())) { + continue; + } + upgradeChecker.refreshAgentExpectedVersion(vrVo.getUuid(), VirtualRouterConstant.VIRTUAL_ROUTER_PROVIDER_TYPE, expectedVersion); + } + if (UpgradeGlobalConfig.GRAYSCALE_UPGRADE.value(Boolean.class)) { - logger.debug("skip checking virtual router versions on management node ready because grayscale upgrade is enabled"); + logger.debug("skip checking virtual router versions on management node ready because grayscale upgrade is enabled, refreshed expected versions"); return; } - List vrVos = Q.New(VirtualRouterVmVO.class).list(); for (VirtualRouterVmVO vrVo : vrVos) { CheckVirtualRouterVmVersionMsg msg = new CheckVirtualRouterVmVersionMsg(); msg.setVirtualRouterVmUuid(vrVo.getUuid()); diff --git a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/lifecycle/VirtualRouterDeployAgentFlow.java b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/lifecycle/VirtualRouterDeployAgentFlow.java index 903f53d18d7..40abc08f5e8 100755 --- a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/lifecycle/VirtualRouterDeployAgentFlow.java +++ b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/lifecycle/VirtualRouterDeployAgentFlow.java @@ -11,7 +11,6 @@ import org.zstack.core.ansible.AnsibleRunner; import org.zstack.core.ansible.SshFileMd5Checker; import org.zstack.core.errorcode.ErrorFacade; -import org.zstack.core.upgrade.UpgradeChecker; import org.zstack.core.workflow.FlowChainBuilder; import org.zstack.core.workflow.ShareFlow; import org.zstack.header.core.Completion; @@ -52,9 +51,6 @@ public class VirtualRouterDeployAgentFlow extends NoRollbackFlow { private RESTFacade restf; @Autowired private ErrorFacade errf; - @Autowired - private UpgradeChecker upgradeChecker; - private final String agentPackageName = VirtualRouterGlobalProperty.AGENT_PACKAGE_NAME; private void continueConnect(final VmNicInventory mgmtNic, final Map data, final FlowTrigger completion) { @@ -158,11 +154,6 @@ public Class getReturnClass() { done(new FlowDoneHandler(completion) { @Override public void handle(Map data) { - if (CoreGlobalProperty.UNIT_TEST_ON) { - // update vyos agent version when open grayScaleUpgrade - upgradeChecker.updateAgentVersion(vr.getUuid(), VirtualRouterConstant.VIRTUAL_ROUTER_PROVIDER_TYPE, new VirtualRouterMetadataOperator().getManagementVersion(), new VirtualRouterMetadataOperator().getManagementVersion()); - } - completion.next(); } }); diff --git a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/vyos/VyosDeployAgentFlow.java b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/vyos/VyosDeployAgentFlow.java index a6b8c6386b3..af8a39b6e28 100755 --- a/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/vyos/VyosDeployAgentFlow.java +++ b/plugin/virtualRouterProvider/src/main/java/org/zstack/network/service/virtualrouter/vyos/VyosDeployAgentFlow.java @@ -88,8 +88,6 @@ private String getRemoteZvrbootBinPath(Map data) { public void run(FlowTrigger trigger, Map data) { final String vrUuid = (String) data.get(VmInstanceConstant.Params.vmInstanceUuid.toString()); if (CoreGlobalProperty.UNIT_TEST_ON) { - // update vyos agent version when open grayScaleUpgrade - upgradeChecker.updateAgentVersion(vrUuid, VirtualRouterConstant.VIRTUAL_ROUTER_PROVIDER_TYPE, new VirtualRouterMetadataOperator().getManagementVersion(), new VirtualRouterMetadataOperator().getManagementVersion()); trigger.next(); return; } @@ -207,9 +205,6 @@ private void deployAgent(int port) { String.format("%s/mn_zvr_version", remoteZvrDir) ).setPassword(REMOTE_PASS).setUsername(remoteUser).setHostname(mgmtNicIp).setPort(port).runErrorByExceptionAndClose(); } - - // update vyos agent version when open grayScaleUpgrade - upgradeChecker.updateAgentVersion(vrUuid, VirtualRouterConstant.VIRTUAL_ROUTER_PROVIDER_TYPE, new VirtualRouterMetadataOperator().getManagementVersion(), new VirtualRouterMetadataOperator().getManagementVersion()); } private void rebootAgent(int port, boolean forceReboot) { From 73ec38e8f669e234fe5101e1f201f78d75a95bd0 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Tue, 9 Jun 2026 16:56:28 +0900 Subject: [PATCH 11/54] [flatNetworkProvider]: batch host reconnect userdata Resolves: ZSTAC-85772 Change-Id: Idde06cbeb8494df0c3be49573193d4b5391f68dd --- .../service/flat/FlatUserdataBackend.java | 125 ++++++++++++++++-- 1 file changed, 114 insertions(+), 11 deletions(-) diff --git a/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatUserdataBackend.java b/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatUserdataBackend.java index f6d15a229aa..977cd54b953 100755 --- a/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatUserdataBackend.java +++ b/plugin/flatNetworkProvider/src/main/java/org/zstack/network/service/flat/FlatUserdataBackend.java @@ -225,11 +225,12 @@ class VmIpL3Uuid { String netmask; String l3Uuid; String dhcpServerIp; + String mac; } @Transactional(readOnly = true) private Map getVmIpL3Uuid(List vmUuids) { - String sql = "select vm.uuid, ip.ip, ip.l3NetworkUuid, ip.netmask from VmInstanceVO vm," + + String sql = "select vm.uuid, ip.ip, ip.l3NetworkUuid, ip.netmask, nic.mac from VmInstanceVO vm," + "VmNicVO nic, NetworkServiceL3NetworkRefVO ref," + "NetworkServiceProviderVO pro, UsedIpVO ip where " + " vm.uuid = nic.vmInstanceUuid and vm.uuid in (:uuids)" + @@ -253,12 +254,107 @@ private Map getVmIpL3Uuid(List vmUuids) { v.vmIp = t.get(1, String.class); v.l3Uuid = t.get(2, String.class); v.netmask = t.get(3, String.class); + v.mac = t.get(4, String.class); ret.put(vmUuid, v); } return ret; } + @Transactional(readOnly = true) + private Map getZoneNameByVmUuid(List vmUuids) { + if (vmUuids.isEmpty()) { + return Collections.emptyMap(); + } + + String sql = "select vm.uuid, zone.name from VmInstanceVO vm, ZoneVO zone " + + "where vm.zoneUuid = zone.uuid and vm.uuid in (:vmUuids)"; + TypedQuery q = dbf.getEntityManager().createQuery(sql, Tuple.class); + q.setParameter("vmUuids", vmUuids); + + Map ret = new HashMap<>(); + for (Tuple t : q.getResultList()) { + ret.put(t.get(0, String.class), t.get(1, String.class)); + } + + return ret; + } + + @Transactional(readOnly = true) + private Map getDnsServersIpByL3Uuid(Set l3Uuids) { + if (l3Uuids.isEmpty()) { + return Collections.emptyMap(); + } + + String sql = "select dns.l3NetworkUuid, dns.dns from L3NetworkDnsVO dns where dns.l3NetworkUuid in (:l3Uuids)"; + TypedQuery q = dbf.getEntityManager().createQuery(sql, Tuple.class); + q.setParameter("l3Uuids", l3Uuids); + + Map> dnsServers = new HashMap<>(); + for (Tuple t : q.getResultList()) { + String l3Uuid = t.get(0, String.class); + List servers = dnsServers.computeIfAbsent(l3Uuid, k -> new ArrayList<>()); + servers.add(t.get(1, String.class)); + } + + Map ret = new HashMap<>(); + dnsServers.forEach((l3Uuid, servers) -> ret.put(l3Uuid, String.join("\n", servers))); + return ret; + } + + @Transactional(readOnly = true) + private Map getVpcIdByL3Uuid(Set l3Uuids) { + if (l3Uuids.isEmpty()) { + return Collections.emptyMap(); + } + + String sql = "select distinct nic.l3NetworkUuid, vm.uuid " + + "from VmInstanceVO vm join VmNicVO nic on vm.uuid = nic.vmInstanceUuid " + + "where vm.type = :vmType and nic.l3NetworkUuid in (:l3Uuids)"; + TypedQuery q = dbf.getEntityManager().createQuery(sql, Tuple.class); + q.setParameter("vmType", VmInstanceConstant.APPLIANCE_VM_TYPE); + q.setParameter("l3Uuids", l3Uuids); + + Map ret = new HashMap<>(); + for (Tuple t : q.getResultList()) { + ret.putIfAbsent(t.get(0, String.class), t.get(1, String.class)); + } + + return ret; + } + + @Transactional(readOnly = true) + private Map> getNetworkInterfaceDetailsByVmUuid(List vmUuids) { + if (vmUuids.isEmpty()) { + return Collections.emptyMap(); + } + + String sql = "select DISTINCT nic.vmInstanceUuid, nic.mac, nic.ip, nic.netmask, nic.gateway, ipr.networkCidr " + + "from VmNicVO nic " + + "join IpRangeVO ipr on nic.l3NetworkUuid = ipr.l3NetworkUuid " + + "where nic.vmInstanceUuid in (:vmUuids) " + + "and nic.ipVersion = :ipVersion "; + + TypedQuery q = dbf.getEntityManager().createQuery(sql, Tuple.class); + q.setParameter("vmUuids", vmUuids); + q.setParameter("ipVersion", IPv6Constants.IPv4); + + Map> ret = new HashMap<>(); + for (Tuple t : q.getResultList()) { + String vmUuid = t.get(0, String.class); + NetworkInterfaceDetails details = new NetworkInterfaceDetails(); + details.macAddress = t.get(1, String.class); + details.ip = t.get(2, String.class); + details.netmask = t.get(3, String.class); + details.gateway = t.get(4, String.class); + details.vpcCidrBlock = t.get(5, String.class); + details.vSwitchCidrBlock = t.get(5, String.class); + ret.computeIfAbsent(vmUuid, k -> new ArrayList<>()).add(details); + } + + return ret; + } + private List getUserData() { List vmUuids = getVmsNeedUserdataOnHost(); if (vmUuids == null) { @@ -277,13 +373,16 @@ private List getUserData() { } Map> userdata = new UserdataBuilder().buildByVmUuids(vmUuids); - Set l3Uuids = new HashSet(); - for (VmIpL3Uuid l : vmipl3.values()) { - String dhcpIp = dhcpBackend.allocateDhcpIp(l.l3Uuid, IPv6Constants.IPv4); + Set l3Uuids = vmipl3.values().stream().map(l -> l.l3Uuid).collect(Collectors.toSet()); + Map l3DhcpIpMap = new HashMap<>(); + for (String l3Uuid : l3Uuids) { + String dhcpIp = dhcpBackend.allocateDhcpIp(l3Uuid, IPv6Constants.IPv4); if (dhcpIp != null) { - l.dhcpServerIp = dhcpIp; + l3DhcpIpMap.put(l3Uuid, dhcpIp); } - l3Uuids.add(l.l3Uuid); + } + for (VmIpL3Uuid l : vmipl3.values()) { + l.dhcpServerIp = l3DhcpIpMap.get(l.l3Uuid); } List tos = new ArrayList<>(); if (l3Uuids.isEmpty()) { @@ -293,16 +392,16 @@ private List getUserData() { List l2Uuids = Q.New(L3NetworkVO.class) .select(L3NetworkVO_.l2NetworkUuid).in(L3NetworkVO_.uuid, l3Uuids).listValues(); Map bridgesVlan = new BridgeVlanIdFinder().findByL2Uuids(l2Uuids); + Map dnsServers = getDnsServersIpByL3Uuid(l3Uuids); + Map vpcIds = getVpcIdByL3Uuid(l3Uuids); + Map zoneNames = getZoneNameByVmUuid(vmUuids); + Map> networkInterfaceDetails = getNetworkInterfaceDetailsByVmUuid(vmUuids); for (String vmuuid : vmUuids) { UserdataTO to = new UserdataTO(); MetadataTO mto = new MetadataTO(); mto.vmUuid = vmuuid; mto.vmHostname = VmSystemTags.HOSTNAME.getTokenByResourceUuid(vmuuid, VmSystemTags.HOSTNAME_TOKEN); - mto.regionName = getZoneNameByVmInstanceUuid(vmuuid); - mto.mac = getVmNicMacAddressByVmInstanceUuid(vmuuid); - mto.dnsServersIp = getDnsServersIpFromVm(vmuuid); - mto.vpcId = getVpcIdByVmInstanceUuid(vmuuid); to.metadata = mto; VmIpL3Uuid l = vmipl3.get(vmuuid); @@ -321,7 +420,11 @@ private List getUserData() { continue; } - to.networkInterfaces = getNetworkInterfaceDetails(vmuuid); + mto.regionName = zoneNames.get(vmuuid); + mto.mac = l.mac; + mto.dnsServersIp = dnsServers.get(l.l3Uuid); + mto.vpcId = vpcIds.get(l.l3Uuid); + to.networkInterfaces = networkInterfaceDetails.getOrDefault(vmuuid, Collections.emptyList()); to.dhcpServerIp = l.dhcpServerIp; to.vmIp = l.vmIp; to.netmask = l.netmask; From 12bbd86dd7b8114a12d6974f08daaa2453fa14da Mon Sep 17 00:00:00 2001 From: "haidong.pang" Date: Sun, 24 May 2026 16:57:47 +0800 Subject: [PATCH 12/54] [kvm]: refine iothread vq mapping support Update KVM capability and host test handling for the iothread VQ mapping data path on 5.5.28. Resolves: ZSTAC-82672 Change-Id: I5f6aedcac1c474e193f7a9c101951aaea8bc1eb2 --- .../java/org/zstack/kvm/KVMAgentCommands.java | 4 ++ .../main/java/org/zstack/kvm/KVMConstant.java | 6 ++- .../src/main/java/org/zstack/kvm/KVMHost.java | 31 ++++++++++-- .../integration/kvm/host/AddHostCase.groovy | 49 ++++++++++++++++++- 4 files changed, 83 insertions(+), 7 deletions(-) diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java index 6886c94beb5..42fb97a6799 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java @@ -707,6 +707,10 @@ public String getCpuArchitecture() { return cpuArchitecture; } + public void setCpuArchitecture(String cpuArchitecture) { + this.cpuArchitecture = cpuArchitecture; + } + public String getHostCpuModelName() { return hostCpuModelName; } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java index 5fa4635d3fe..6afa1c1a58e 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java @@ -113,8 +113,10 @@ public interface KVMConstant { String MIN_QEMU_LIVESNAPSHOT_VERSION = "1.3.0"; String MIN_LIBVIRT_LIVE_BLOCK_COMMIT_VERSION = "1.2.7"; String MIN_LIBVIRT_VIRTIO_SCSI_VERSION = "1.0.4"; - String MIN_QEMU_KVM_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION = "6.2.0-451"; - String MIN_LIBVIRT_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION = "8.0.0-163"; + String MIN_X86_64_QEMU_KVM_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION = "6.2.0-546"; + String MIN_X86_64_LIBVIRT_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION = "8.0.0-163"; + String MIN_AARCH64_QEMU_KVM_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION = "6.2.0-1141"; + String MIN_AARCH64_LIBVIRT_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION = "6.2.0-490"; String KVM_REPORT_VM_STATE = "/kvm/reportvmstate"; String KVM_RECONNECT_ME = "/kvm/reconnectme"; diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java index 73725c4ce7a..688319cd10f 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java @@ -136,12 +136,34 @@ public class KVMHost extends HostBase implements Host { protected static OperationChecker skipOperations = new OperationChecker(true); private static final Pattern NUMERIC_VERSION_TOKEN = Pattern.compile("\\d+(?:\\.\\d+)*"); - private static boolean isIothreadVqMappingSupported(String qemuKvmPackageVersion, String libvirtPackageVersion) { - return versionAtLeast(qemuKvmPackageVersion, KVMConstant.MIN_QEMU_KVM_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION) - && versionAtLeast(libvirtPackageVersion, KVMConstant.MIN_LIBVIRT_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION); + private static boolean isIothreadVqMappingSupported(String architecture, String qemuKvmPackageVersion, String libvirtPackageVersion) { + return versionAtLeast(qemuKvmPackageVersion, minQemuKvmIothreadVqMappingPackageVersion(architecture)) + && versionAtLeast(libvirtPackageVersion, minLibvirtIothreadVqMappingPackageVersion(architecture)); + } + + private static String minQemuKvmIothreadVqMappingPackageVersion(String architecture) { + if (CpuArchitecture.x86_64.name().equals(architecture)) { + return KVMConstant.MIN_X86_64_QEMU_KVM_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION; + } else if (CpuArchitecture.aarch64.name().equals(architecture)) { + return KVMConstant.MIN_AARCH64_QEMU_KVM_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION; + } + return null; + } + + private static String minLibvirtIothreadVqMappingPackageVersion(String architecture) { + if (CpuArchitecture.x86_64.name().equals(architecture)) { + return KVMConstant.MIN_X86_64_LIBVIRT_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION; + } else if (CpuArchitecture.aarch64.name().equals(architecture)) { + return KVMConstant.MIN_AARCH64_LIBVIRT_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION; + } + return null; } private static boolean versionAtLeast(String current, String required) { + if (StringUtils.isBlank(required)) { + return false; + } + String normalized = normalizePackageVersion(current); if (normalized == null) { return false; @@ -6545,7 +6567,8 @@ private void saveKvmHostRelatedFacts(HostFactResponse ret) { KVMSystemTags.QEMU_KVM_PACKAGE_VERSION.delete(self.getUuid()); } - if (isIothreadVqMappingSupported(ret.getQemuKvmPackageVersion(), ret.getLibvirtPackageVersion())) { + String architecture = StringUtils.isNotBlank(ret.getCpuArchitecture()) ? ret.getCpuArchitecture() : self.getArchitecture(); + if (isIothreadVqMappingSupported(architecture, ret.getQemuKvmPackageVersion(), ret.getLibvirtPackageVersion())) { recreateNonInherentTag(KVMSystemTags.IOTHREAD_VQ_MAPPING); } else { KVMSystemTags.IOTHREAD_VQ_MAPPING.delete(self.getUuid()); diff --git a/test/src/test/groovy/org/zstack/test/integration/kvm/host/AddHostCase.groovy b/test/src/test/groovy/org/zstack/test/integration/kvm/host/AddHostCase.groovy index 12ab7899dbd..887edc0376d 100644 --- a/test/src/test/groovy/org/zstack/test/integration/kvm/host/AddHostCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/kvm/host/AddHostCase.groovy @@ -6,6 +6,7 @@ import org.zstack.core.cloudbus.CloudBus import org.zstack.core.db.Q import org.zstack.header.errorcode.SysErrors import org.zstack.header.host.AddHostReply +import org.zstack.header.host.CpuArchitecture import org.zstack.header.host.HostConstant import org.zstack.header.host.HostStatus import org.zstack.header.host.HostVO @@ -58,6 +59,8 @@ class AddHostCase extends SubCase { testInnerAddHostMsg() testGetHypervisorTypes() testPackageVersionTagsClearedWhenFactMissing() + testIothreadVqMappingCapabilityUsesAarch64PackageVersion() + testIothreadVqMappingCapabilitySkipsUnsupportedArchitecture() testAddHostFailureRollback() testAddHostViaLongJob() testLongJobAddHostFailure() @@ -259,7 +262,7 @@ class AddHostCase extends SubCase { } void testPackageVersionTagsClearedWhenFactMissing() { - String qemuKvmPackageVersion = "6.2.0-451.g623f2a5caf.el8" + String qemuKvmPackageVersion = "${MIN_X86_64_QEMU_KVM_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION}.g623f2a5caf.el8" String libvirtPackageVersion = "8.0.0-163.gd30ff15b84.el8" env.afterSimulator(KVM_HOST_FACT_PATH) { HostFactResponse rsp -> @@ -280,6 +283,7 @@ class AddHostCase extends SubCase { assert KVMSystemTags.QEMU_KVM_PACKAGE_VERSION.getTokenByResourceUuid(host.uuid, KVMSystemTags.QEMU_KVM_PACKAGE_VERSION_TOKEN) == qemuKvmPackageVersion assert KVMSystemTags.LIBVIRT_PACKAGE_VERSION.getTokenByResourceUuid(host.uuid, KVMSystemTags.LIBVIRT_PACKAGE_VERSION_TOKEN) == libvirtPackageVersion + assert KVMSystemTags.IOTHREAD_VQ_MAPPING.hasTag(host.uuid) env.afterSimulator(KVM_HOST_FACT_PATH) { HostFactResponse rsp -> rsp.qemuKvmPackageVersion = null @@ -293,6 +297,49 @@ class AddHostCase extends SubCase { assert KVMSystemTags.QEMU_KVM_PACKAGE_VERSION.getTokenByResourceUuid(host.uuid, KVMSystemTags.QEMU_KVM_PACKAGE_VERSION_TOKEN) == null assert KVMSystemTags.LIBVIRT_PACKAGE_VERSION.getTokenByResourceUuid(host.uuid, KVMSystemTags.LIBVIRT_PACKAGE_VERSION_TOKEN) == null + assert !KVMSystemTags.IOTHREAD_VQ_MAPPING.hasTag(host.uuid) + } + + void testIothreadVqMappingCapabilityUsesAarch64PackageVersion() { + env.afterSimulator(KVM_HOST_FACT_PATH) { HostFactResponse rsp -> + rsp.cpuArchitecture = CpuArchitecture.aarch64.name() + rsp.qemuKvmPackageVersion = MIN_AARCH64_QEMU_KVM_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION + rsp.libvirtPackageVersion = MIN_AARCH64_LIBVIRT_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION + return rsp + } + + org.zstack.sdk.KVMHostInventory host = addKVMHost { + resourceUuid = Platform.uuid + sessionId = adminSession() + clusterUuid = cluster.uuid + name = "kvm-aarch64-iothread-vq-mapping" + managementIp = "127.0.0.7" + username = "root" + password = "password" + } as org.zstack.sdk.KVMHostInventory + + assert KVMSystemTags.IOTHREAD_VQ_MAPPING.hasTag(host.uuid) + } + + void testIothreadVqMappingCapabilitySkipsUnsupportedArchitecture() { + env.afterSimulator(KVM_HOST_FACT_PATH) { HostFactResponse rsp -> + rsp.cpuArchitecture = CpuArchitecture.loongarch64.name() + rsp.qemuKvmPackageVersion = MIN_X86_64_QEMU_KVM_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION + rsp.libvirtPackageVersion = MIN_X86_64_LIBVIRT_IOTHREAD_VQ_MAPPING_PACKAGE_VERSION + return rsp + } + + org.zstack.sdk.KVMHostInventory host = addKVMHost { + resourceUuid = Platform.uuid + sessionId = adminSession() + clusterUuid = cluster.uuid + name = "kvm-unsupported-arch-iothread-vq-mapping" + managementIp = "127.0.0.8" + username = "root" + password = "password" + } as org.zstack.sdk.KVMHostInventory + + assert !KVMSystemTags.IOTHREAD_VQ_MAPPING.hasTag(host.uuid) } void testGetHypervisorTypes() { From 5478e1a8d3e186d34291cf1eb73ab772bb683b51 Mon Sep 17 00:00:00 2001 From: J M Date: Wed, 10 Jun 2026 15:53:24 +0800 Subject: [PATCH 13/54] [vhost]: block online vhost attach without shared memory A running VM without shared memory cannot online-attach a vhost-user data volume. Reject it on the management node with a clear, actionable error instead of failing later on the kvm agent. Resolves: ZSTAC-85554 Change-Id: I7777746769777679686f62686c6d676566676574 --- .../global-error-de-DE.json | 1 + .../global-error-en_US.json | 1 + .../global-error-fr-FR.json | 1 + .../global-error-id-ID.json | 1 + .../global-error-ja-JP.json | 1 + .../global-error-ko-KR.json | 1 + .../global-error-ru-RU.json | 1 + .../global-error-th-TH.json | 1 + .../global-error-zh_CN.json | 1 + .../global-error-zh_TW.json | 1 + conf/springConfigXml/vhost.xml | 1 + .../zstack/vhost/kvm/KvmVhostNodeServer.java | 44 ++- ...VhostOnlineAttachSharedMemCheckCase.groovy | 343 ++++++++++++++++++ .../CloudOperationsErrorCode.java | 2 + 14 files changed, 399 insertions(+), 1 deletion(-) create mode 100644 test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/xinfini/VhostOnlineAttachSharedMemCheckCase.groovy diff --git a/conf/i18n/globalErrorCodeMapping/global-error-de-DE.json b/conf/i18n/globalErrorCodeMapping/global-error-de-DE.json index 3aeec70b44c..eb52bff974f 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-de-DE.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-de-DE.json @@ -2871,6 +2871,7 @@ "ORG_ZSTACK_CRYPTO_AUTH_10019": "Abrufen des Integritätspassworts aus der Datenbank fehlgeschlagen, da %s kein Integritätspasswort generiert", "ORG_ZSTACK_VHOST_KVM_10000": "vHostUser-Datenträger unterstützt nur den Virtio-Modus. Bitte überprüfen Sie, ob die Image-Plattform die erforderlichen Virtio-Treiber installiert hat.", "ORG_ZSTACK_VHOST_KVM_10001": "vHost-User-Datenträger unterstützt den Virtio-SCSI-Modus nicht; bitte deaktivieren Sie den Virtio-SCSI-Modus.", + "ORG_ZSTACK_VHOST_KVM_10002": "vhost-user-Datenträger kann nicht online angehängt werden: Bei der laufenden VM ist kein gemeinsam genutzter Speicher aktiviert. Fahren Sie die VM herunter und hängen Sie sie dann an, oder setzen Sie die globale Konfiguration 'generate.config.vhost.required' auf 'auto' oder 'true' und starten Sie die VM neu (dies deaktiviert Memory Overcommit für die VM).", "ORG_ZSTACK_NETWORK_L3_10029": "%s ist keine gültige IPv6-Adresse für die Cloud-Ressourcenkonfiguration", "ORG_ZSTACK_USBDEVICE_10005": "PassThrough unterstützt nur die Verwendung auf einer VM, die in der Host-Umgebung ausgeführt wird.", "ORG_ZSTACK_ALIYUN_ECS_10019": "Nur administrative Benutzer können den Parameter [onlyZstack] auf false setzen.", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-en_US.json b/conf/i18n/globalErrorCodeMapping/global-error-en_US.json index 5c54d1fd80f..7a1c8f255ec 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-en_US.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-en_US.json @@ -2963,6 +2963,7 @@ "ORG_ZSTACK_CRYPTO_AUTH_10019": "Get integrity password from database failed due to %s does not generate integrity password", "ORG_ZSTACK_VHOST_KVM_10000": "vHostUser disk only supports virtio mode. Please verify that the image platform has the necessary virtio drivers installed.", "ORG_ZSTACK_VHOST_KVM_10001": "vhost-user disk does not support virtio-scsi mode; please disable virtio-scsi mode.", + "ORG_ZSTACK_VHOST_KVM_10002": "Cannot attach vhost-user data volume online: the running VM does not have shared memory enabled. Either shut down the VM and then attach, or set the global config 'generate.config.vhost.required' to 'auto' or 'true' and restart the VM (this disables memory overcommit for the VM).", "ORG_ZSTACK_NETWORK_L3_10029": "%s is not a valid IPv6 address for cloud resource configuration", "ORG_ZSTACK_USBDEVICE_10005": "PassThrough only supports usage on a VM running in the host environment.", "ORG_ZSTACK_ALIYUN_ECS_10019": "Only administrative users can set the parameter [onlyZstack] to false.", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-fr-FR.json b/conf/i18n/globalErrorCodeMapping/global-error-fr-FR.json index b1ac5f6ba2c..00b52b1e03d 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-fr-FR.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-fr-FR.json @@ -2961,6 +2961,7 @@ "ORG_ZSTACK_CRYPTO_AUTH_10019": "Échec de l'obtention du mot de passe d'intégrité de la base de données car %s ne génère pas de mot de passe d'intégrité", "ORG_ZSTACK_VHOST_KVM_10000": "Le disque vHostUser ne prend en charge que le mode virtio. Veuillez vérifier que la plateforme d'image dispose des pilotes virtio nécessaires.", "ORG_ZSTACK_VHOST_KVM_10001": "Le disque vhost-user ne prend pas en charge le mode virtio-scsi ; veuillez désactiver le mode virtio-scsi.", + "ORG_ZSTACK_VHOST_KVM_10002": "Impossible d’attacher le disque vhost-user en ligne : la VM en cours d’exécution n’a pas la mémoire partagée activée. Arrêtez la VM puis attachez, ou définissez la configuration globale « generate.config.vhost.required » sur « auto » ou « true » et redémarrez la VM (cela désactive la surallocation mémoire pour la VM).", "ORG_ZSTACK_NETWORK_L3_10029": "%s n'est pas une adresse IPv6 valide pour la configuration des ressources cloud", "ORG_ZSTACK_USBDEVICE_10005": "PassThrough ne peut être utilisé que sur une machine virtuelle s'exécutant dans l'environnement hôte.", "ORG_ZSTACK_ALIYUN_ECS_10019": "Seuls les utilisateurs administratifs peuvent définir le paramètre [onlyZstack] sur false.", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-id-ID.json b/conf/i18n/globalErrorCodeMapping/global-error-id-ID.json index a1daa8a6768..4324b9a1774 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-id-ID.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-id-ID.json @@ -2961,6 +2961,7 @@ "ORG_ZSTACK_CRYPTO_AUTH_10019": "Gagal mendapatkan kata sandi integritas dari database karena %s tidak menghasilkan kata sandi integritas", "ORG_ZSTACK_VHOST_KVM_10000": "Disk vHostUser hanya mendukung mode virtio. Harap verifikasi bahwa platform gambar memiliki driver virtio yang diperlukan terinstal.", "ORG_ZSTACK_VHOST_KVM_10001": "Disk vhost-user tidak mendukung mode virtio-scsi; harap nonaktifkan mode virtio-scsi.", + "ORG_ZSTACK_VHOST_KVM_10002": "Tidak dapat memasang disk vhost-user secara online: VM yang berjalan tidak mengaktifkan shared memory. Matikan VM lalu pasang, atau atur konfigurasi global 'generate.config.vhost.required' ke 'auto' atau 'true' dan mulai ulang VM (ini menonaktifkan overcommit memori untuk VM).", "ORG_ZSTACK_NETWORK_L3_10029": "%s bukan alamat IPv6 yang valid untuk konfigurasi sumber daya cloud", "ORG_ZSTACK_USBDEVICE_10005": "PassThrough hanya didukung untuk penggunaan pada VM yang berjalan di lingkungan host.", "ORG_ZSTACK_ALIYUN_ECS_10019": "Hanya pengguna administratif yang dapat menetapkan parameter [onlyZstack] ke false.", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-ja-JP.json b/conf/i18n/globalErrorCodeMapping/global-error-ja-JP.json index 7f8132b76de..307986d2224 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-ja-JP.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-ja-JP.json @@ -2931,6 +2931,7 @@ "ORG_ZSTACK_CRYPTO_AUTH_10019": "データベースから整合性パスワードの取得に失敗しました。%sが整合性パスワードを生成していないためです", "ORG_ZSTACK_VHOST_KVM_10000": "vHostUserディスクはvirtioモードのみをサポートします。イメージプラットフォームに必要なvirtioドライバーがインストールされていることを確認してください。", "ORG_ZSTACK_VHOST_KVM_10001": "vhost-userディスクはvirtio-scsiモードをサポートしません。virtio-scsiモードを無効にしてください。", + "ORG_ZSTACK_VHOST_KVM_10002": "vhost-userデータボリュームをオンラインでアタッチできません:実行中のVMで共有メモリが有効化されていません。VMをシャットダウンしてからアタッチするか、グローバル設定 generate.config.vhost.required を auto または true に設定してVMを再起動してください(VMのメモリオーバーコミットが無効になります)。", "ORG_ZSTACK_NETWORK_L3_10029": "%sはクラウドリソース構成の有効なIPv6アドレスではありません", "ORG_ZSTACK_USBDEVICE_10005": "PassThroughはホスト環境で実行されているVMでのみ使用できます。", "ORG_ZSTACK_ALIYUN_ECS_10019": "パラメータ[onlyZstack]をfalseに設定できるのは管理者のみです。", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-ko-KR.json b/conf/i18n/globalErrorCodeMapping/global-error-ko-KR.json index b8188ee11b5..e9f65cdc59f 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-ko-KR.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-ko-KR.json @@ -2871,6 +2871,7 @@ "ORG_ZSTACK_CRYPTO_AUTH_10019": "데이터베이스에서 무결성 비밀번호를 가져오지 못했습니다. %s이(가) 무결성 비밀번호를 생성하지 않았기 때문입니다.", "ORG_ZSTACK_VHOST_KVM_10000": "vHostUser 디스크는 virtio 모드만 지원합니다. 이미지 플랫폼에 필요한 virtio 드라이버가 설치되어 있는지 확인하십시오.", "ORG_ZSTACK_VHOST_KVM_10001": "vhost-user 디스크는 virtio-scsi 모드를 지원하지 않습니다. virtio-scsi 모드를 비활성화하십시오.", + "ORG_ZSTACK_VHOST_KVM_10002": "vhost-user 데이터 볼륨을 온라인으로 연결할 수 없습니다: 실행 중인 VM에 공유 메모리가 활성화되어 있지 않습니다. VM을 종료한 후 연결하거나, 전역 구성 generate.config.vhost.required 를 auto 또는 true 로 설정하고 VM을 재시작하십시오(해당 VM의 메모리 오버커밋이 비활성화됩니다).", "ORG_ZSTACK_NETWORK_L3_10029": "%s은(는) 클라우드 리소스 구성에 유효한 IPv6 주소가 아닙니다", "ORG_ZSTACK_USBDEVICE_10005": "PassThrough는 호스트 환경에서 실행되는 VM에서만 사용하도록 지원됩니다.", "ORG_ZSTACK_ALIYUN_ECS_10019": "관리자 사용자만 매개변수 [onlyZstack]을 false로 설정할 수 있습니다.", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-ru-RU.json b/conf/i18n/globalErrorCodeMapping/global-error-ru-RU.json index 7a9891325cf..0d532e7b392 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-ru-RU.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-ru-RU.json @@ -2961,6 +2961,7 @@ "ORG_ZSTACK_CRYPTO_AUTH_10019": "Не удалось получить пароль целостности из базы данных, так как %s не сгенерировал пароль целостности", "ORG_ZSTACK_VHOST_KVM_10000": "Диск vHostUser поддерживает только режим virtio. Пожалуйста, убедитесь, что на платформе образа установлены необходимые драйверы virtio.", "ORG_ZSTACK_VHOST_KVM_10001": "Диск vhost-user не поддерживает режим virtio-scsi; пожалуйста, отключите режим virtio-scsi.", + "ORG_ZSTACK_VHOST_KVM_10002": "Невозможно подключить vhost-user диск онлайн: в работающей ВМ не включена общая память. Выключите ВМ и подключите диск, либо установите глобальную настройку «generate.config.vhost.required» в «auto» или «true» и перезапустите ВМ (это отключит переподписку памяти для ВМ).", "ORG_ZSTACK_NETWORK_L3_10029": "%s не является допустимым IPv6-адресом для конфигурации облачного ресурса", "ORG_ZSTACK_USBDEVICE_10005": "PassThrough поддерживается только на VM, работающей в хостовой среде.", "ORG_ZSTACK_ALIYUN_ECS_10019": "Только административные пользователи могут установить параметр [onlyZstack] в значение false.", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-th-TH.json b/conf/i18n/globalErrorCodeMapping/global-error-th-TH.json index 3d8a70d36ad..92f456b608b 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-th-TH.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-th-TH.json @@ -2961,6 +2961,7 @@ "ORG_ZSTACK_CRYPTO_AUTH_10019": "การรับรหัสผ่านความสมบูรณ์จากฐานข้อมูลล้มเหลวเนื่องจาก %s ไม่ได้สร้างรหัสผ่านความสมบูรณ์", "ORG_ZSTACK_VHOST_KVM_10000": "ดิสก์ vHostUser รองรับเฉพาะโหมด virtio เท่านั้น โปรดตรวจสอบว่าแพลตฟอร์มอิมเมจมีไดรเวอร์ virtio ที่จำเป็นติดตั้งอยู่", "ORG_ZSTACK_VHOST_KVM_10001": "ดิสก์ vhost-user ไม่รองรับโหมด virtio-scsi โปรดปิดใช้งานโหมด virtio-scsi", + "ORG_ZSTACK_VHOST_KVM_10002": "ไม่สามารถแนบดิสก์ vhost-user แบบออนไลน์ได้: VM ที่กำลังทำงานไม่ได้เปิดใช้หน่วยความจำแบบแชร์ กรุณาปิด VM แล้วแนบ หรือตั้งค่าคอนฟิกส่วนกลาง generate.config.vhost.required เป็น auto หรือ true แล้วรีสตาร์ท VM (จะปิดการ overcommit หน่วยความจำของ VM นี้)", "ORG_ZSTACK_NETWORK_L3_10029": "%s ไม่ใช่ที่อยู่ IPv6 ที่ถูกต้องสำหรับการกำหนดค่าทรัพยากรคลาว์", "ORG_ZSTACK_USBDEVICE_10005": "PassThrough รองรับการใช้งานบน VM ที่ทำงานในสภาพแวดล้อมโฮสต์เท่านั้น", "ORG_ZSTACK_ALIYUN_ECS_10019": "เฉพาะผู้ใช้ผู้ดูแลระบบเท่านั้นที่สามารถตั้งค่าพารามิเตอร์ [onlyZstack] เป็น false ได้", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-zh_CN.json b/conf/i18n/globalErrorCodeMapping/global-error-zh_CN.json index 481cd268b83..345a8e6c2f9 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-zh_CN.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-zh_CN.json @@ -2963,6 +2963,7 @@ "ORG_ZSTACK_CRYPTO_AUTH_10019": "从数据库获取完整性密码失败,因为 [%s] 未生成完整性密码", "ORG_ZSTACK_VHOST_KVM_10000": "vhostuser磁盘仅支持virtio模式,请检查镜像平台是否包含virtio驱动程序", "ORG_ZSTACK_VHOST_KVM_10001": "vhost-user磁盘不支持virtio-scsi模式,请关闭virtio-scsi模式", + "ORG_ZSTACK_VHOST_KVM_10002": "无法在线挂载vhost数据盘:该运行中云主机未启用共享内存。两种方式:1) 关机后再挂载;2) 将全局配置 generate.config.vhost.required 设为 auto 或 true 后重启该云主机(会禁用该云主机的内存超分)", "ORG_ZSTACK_NETWORK_L3_10029": "%s 不是一个有效的IPv6地址", "ORG_ZSTACK_USBDEVICE_10005": "PassThrough 只支持在运行中的主机 VM 上使用", "ORG_ZSTACK_ALIYUN_ECS_10019": "只有管理员可以将参数 [onlyZstack] 设置为 false", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-zh_TW.json b/conf/i18n/globalErrorCodeMapping/global-error-zh_TW.json index 714160303db..9b95c088387 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-zh_TW.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-zh_TW.json @@ -2961,6 +2961,7 @@ "ORG_ZSTACK_CRYPTO_AUTH_10019": "从數据库獲取完整性密碼失敗,因为 [%s] 未生成完整性密碼", "ORG_ZSTACK_VHOST_KVM_10000": "vhostuser磁碟仅支持virtio模式,請检查镜像平台是否包含virtio驱動程序", "ORG_ZSTACK_VHOST_KVM_10001": "vhost-user磁碟不支持virtio-scsi模式,請關閉virtio-scsi模式", + "ORG_ZSTACK_VHOST_KVM_10002": "無法在線掛載vhost資料磁碟:該執行中雲主機未啟用共享記憶體。兩種方式:1) 關機後再掛載;2) 將全域配置 generate.config.vhost.required 設為 auto 或 true 後重啟該雲主機(會停用該雲主機的記憶體超分配)", "ORG_ZSTACK_NETWORK_L3_10029": "%s 不是一個有效的IPv6地址", "ORG_ZSTACK_USBDEVICE_10005": "PassThrough 只支持在運行中的主機 VM 上使用", "ORG_ZSTACK_ALIYUN_ECS_10019": "只有管理員可以将參數 [onlyZstack] 設置为 false", diff --git a/conf/springConfigXml/vhost.xml b/conf/springConfigXml/vhost.xml index afe7d5abf54..f46e310ac7f 100644 --- a/conf/springConfigXml/vhost.xml +++ b/conf/springConfigXml/vhost.xml @@ -20,6 +20,7 @@ + diff --git a/plugin/vhost/src/main/java/org/zstack/vhost/kvm/KvmVhostNodeServer.java b/plugin/vhost/src/main/java/org/zstack/vhost/kvm/KvmVhostNodeServer.java index 8de40283c07..ace6a443eb2 100644 --- a/plugin/vhost/src/main/java/org/zstack/vhost/kvm/KvmVhostNodeServer.java +++ b/plugin/vhost/src/main/java/org/zstack/vhost/kvm/KvmVhostNodeServer.java @@ -6,13 +6,16 @@ import org.zstack.core.componentloader.PluginRegistry; import org.zstack.core.db.DatabaseFacade; import org.zstack.header.Component; +import org.zstack.header.core.Completion; import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.host.HostInventory; import org.zstack.header.storage.addon.primary.BaseVolumeInfo; import org.zstack.header.storage.addon.primary.PrimaryStorageNodeSvc; +import org.zstack.header.vm.VmAttachVolumeExtensionPoint; import org.zstack.header.vm.VmInstanceInventory; import org.zstack.header.vm.VmInstanceSpec; +import org.zstack.header.vm.VmInstanceState; import org.zstack.header.volume.VolumeInventory; import org.zstack.header.volume.VolumeProtocol; import org.zstack.header.volume.VolumeProtocolCapability; @@ -28,7 +31,8 @@ import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.*; public class KvmVhostNodeServer implements Component, KVMStartVmExtensionPoint, - KVMConvertVolumeExtensionPoint, KVMDetachVolumeExtensionPoint, KVMAttachVolumeExtensionPoint { + KVMConvertVolumeExtensionPoint, KVMDetachVolumeExtensionPoint, KVMAttachVolumeExtensionPoint, + VmAttachVolumeExtensionPoint { @Autowired private ExternalPrimaryStorageFactory extPsFactory; @Autowired @@ -169,4 +173,42 @@ public void beforeAttachVolume(KVMHostInventory host, VmInstanceInventory vm, Vo public void afterAttachVolume(KVMHostInventory host, VmInstanceInventory vm, VolumeInventory volume, KVMAgentCommands.AttachDataVolumeCmd cmd) {} @Override public void attachVolumeFailed(KVMHostInventory host, VmInstanceInventory vm, VolumeInventory volume, KVMAgentCommands.AttachDataVolumeCmd cmd, ErrorCode err, Map data) {} + + @Override + public void preAttachVolume(VmInstanceInventory vm, VolumeInventory volume, Completion completion) { + if (!VolumeProtocol.Vhost.name().equals(volume.getProtocol())) { + completion.success(); + return; + } + if (vm.getHostUuid() == null || !VmInstanceState.Running.toString().equals(vm.getState())) { + completion.success(); + return; + } + if (vmHasSharedMemory(vm)) { + completion.success(); + return; + } + completion.fail(argerr(ORG_ZSTACK_VHOST_KVM_10002, + "cannot attach vhost-user data volume online: the running VM does not have shared memory enabled." + + " Either shut down the VM and then attach, or set the global config" + + " 'generate.config.vhost.required' to 'auto' or 'true' and restart the VM" + + " (this disables memory overcommit for the VM)")); + } + + private boolean vmHasSharedMemory(VmInstanceInventory vm) { + if (vm.getAllVolumes() != null && vm.getAllVolumes().stream() + .anyMatch(v -> VolumeProtocol.Vhost.name().equals(v.getProtocol()))) { + return true; + } + return needSupportVhostPrimaryStorage(vm.getClusterUuid()); + } + + @Override + public void beforeAttachVolume(VmInstanceInventory vm, VolumeInventory volume, Map data) {} + + @Override + public void afterAttachVolume(VmInstanceInventory vm, VolumeInventory volume) {} + + @Override + public void failedToAttachVolume(VmInstanceInventory vm, VolumeInventory volume, ErrorCode errorCode, Map data) {} } diff --git a/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/xinfini/VhostOnlineAttachSharedMemCheckCase.groovy b/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/xinfini/VhostOnlineAttachSharedMemCheckCase.groovy new file mode 100644 index 00000000000..c06639c8ba2 --- /dev/null +++ b/test/src/test/groovy/org/zstack/test/integration/storage/primary/addon/xinfini/VhostOnlineAttachSharedMemCheckCase.groovy @@ -0,0 +1,343 @@ +package org.zstack.test.integration.storage.primary.addon.xinfini + +import org.springframework.http.HttpEntity +import org.zstack.core.db.Q +import org.zstack.core.db.SQL +import org.zstack.header.vm.VmInstanceState +import org.zstack.header.vm.VmInstanceVO +import org.zstack.header.vm.VmInstanceVO_ +import org.zstack.header.volume.VolumeVO +import org.zstack.header.volume.VolumeVO_ +import org.zstack.compute.vm.VmGlobalConfig +import org.zstack.core.cloudbus.CloudBus +import org.zstack.header.storage.backup.UploadImageToRemoteTargetMsg +import org.zstack.header.storage.backup.UploadImageToRemoteTargetReply +import org.zstack.kvm.KVMAgentCommands +import org.zstack.kvm.KVMConstant +import org.zstack.storage.backup.BackupStorageSystemTags +import org.zstack.tag.SystemTagCreator +import org.zstack.sdk.* +import org.zstack.test.integration.storage.StorageTest +import org.zstack.testlib.EnvSpec +import org.zstack.testlib.SubCase +import org.zstack.utils.data.SizeUnit +import org.zstack.utils.gson.JSONObjectUtil + +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_VHOST_KVM_10002 + +class VhostOnlineAttachSharedMemCheckCase extends SubCase { + + EnvSpec env + + ClusterInventory cephCluster + ClusterInventory vhostCluster + InstanceOfferingInventory instanceOffering + DiskOfferingInventory diskOffering + ImageInventory image + ImageInventory cephImage + L3NetworkInventory l3 + PrimaryStorageInventory cephPs + PrimaryStorageInventory vhostPs + BackupStorageInventory bs + HostInventory cephHost + HostInventory vhostHost + + String xinfiniUrl = "http://127.0.0.1:8989" + String xinfiniConfig = '{"token":"test-token","pools":[{"id":1,"name":"pool1"}],"nodes":[{"ip":"127.0.0.1","port":8989}]}' + + @Override + void clean() { + env.delete() + } + + @Override + void setup() { + useSpring(StorageTest.springSpec) + } + + @Override + void environment() { + env = makeEnv { + instanceOffering { + name = "instanceOffering" + memory = SizeUnit.GIGABYTE.toByte(4) + cpu = 2 + } + + diskOffering { + name = "diskOffering" + diskSize = SizeUnit.GIGABYTE.toByte(10) + } + + sftpBackupStorage { + name = "sftp" + url = "/sftp" + username = "root" + password = "password" + hostname = "127.0.0.4" + + image { + name = "image" + url = "http://zstack.org/download/test.qcow2" + virtio = true + } + } + + zone { + name = "zone" + description = "test" + + cluster { + name = "ceph-cluster" + hypervisorType = "KVM" + + kvm { + name = "ceph-kvm" + managementIp = "127.0.0.2" + username = "root" + password = "password" + } + + attachL2Network("l2") + attachPrimaryStorage("ceph-pri") + } + + cluster { + name = "vhost-cluster" + hypervisorType = "KVM" + + kvm { + name = "vhost-kvm" + managementIp = "127.0.0.3" + username = "root" + password = "password" + } + + attachL2Network("l2") + } + + l2NoVlanNetwork { + name = "l2" + physicalInterface = "eth0" + + l3Network { + name = "l3" + + ip { + startIp = "192.168.200.10" + endIp = "192.168.200.100" + netmask = "255.255.255.0" + gateway = "192.168.200.1" + } + } + } + + cephPrimaryStorage { + name = "ceph-pri" + description = "ceph primary" + totalCapacity = SizeUnit.GIGABYTE.toByte(500) + availableCapacity = SizeUnit.GIGABYTE.toByte(500) + url = "ceph://pri" + fsid = "aabbccdd-1234-5678-9abc-def012345678" + monUrls = ["root:password@127.0.0.2/?monPort=7777"] + } + + attachBackupStorage("sftp") + attachBackupStorage("ceph-bs") + } + + cephBackupStorage { + name = "ceph-bs" + description = "ceph backup storage" + totalCapacity = SizeUnit.GIGABYTE.toByte(500) + availableCapacity = SizeUnit.GIGABYTE.toByte(500) + url = "/ceph-bk" + fsid = "aabbccdd-1234-5678-9abc-def012345678" + monUrls = ["root:password@127.0.0.2/?monPort=7777"] + + image { + name = "ceph-image" + url = "http://zstack.org/download/ceph-image.qcow2" + } + } + } + } + + @Override + void test() { + env.create { + cephCluster = env.inventoryByName("ceph-cluster") as ClusterInventory + vhostCluster = env.inventoryByName("vhost-cluster") as ClusterInventory + instanceOffering = env.inventoryByName("instanceOffering") as InstanceOfferingInventory + diskOffering = env.inventoryByName("diskOffering") as DiskOfferingInventory + image = env.inventoryByName("image") as ImageInventory + cephImage = env.inventoryByName("ceph-image") as ImageInventory + l3 = env.inventoryByName("l3") as L3NetworkInventory + cephPs = env.inventoryByName("ceph-pri") as PrimaryStorageInventory + bs = env.inventoryByName("sftp") as BackupStorageInventory + cephHost = env.inventoryByName("ceph-kvm") as HostInventory + vhostHost = env.inventoryByName("vhost-kvm") as HostInventory + + VmGlobalConfig.GENERATE_CONFIG_VHOST_REQUIRED.updateValue("false") + + setupVhostPs() + setupAttachVolumeSimulator() + testCephRootRunningVmBlocksVhostOnlineAttach() + testCephRootStoppedVmAllowsVhostAttach() + testVhostRootRunningVmAllowsVhostOnlineAttach() + } + } + + void setupVhostPs() { + SystemTagCreator creator = BackupStorageSystemTags.ISCSI_INITIATOR_NAME.newSystemTagCreator(bs.uuid) + creator.setTagByTokens([(BackupStorageSystemTags.ISCSI_INITIATOR_NAME_TOKEN): "iqn.1994-05.com.redhat:fc16b4d4fb3f"]) + creator.inherent = false + creator.recreate = true + creator.create() + + vhostPs = addExternalPrimaryStorage { + name = "xinfini-vhost" + zoneUuid = (env.inventoryByName("zone") as ZoneInventory).uuid + url = xinfiniUrl + identity = "xinfini" + config = xinfiniConfig + defaultOutputProtocol = "Vhost" + } as ExternalPrimaryStorageInventory + + attachPrimaryStorageToCluster { + primaryStorageUuid = vhostPs.uuid + clusterUuid = vhostCluster.uuid + } + attachPrimaryStorageToCluster { + primaryStorageUuid = vhostPs.uuid + clusterUuid = cephCluster.uuid + } + } + + void setupAttachVolumeSimulator() { + env.message(UploadImageToRemoteTargetMsg.class) { UploadImageToRemoteTargetMsg msg, CloudBus bus -> + bus.reply(msg, new UploadImageToRemoteTargetReply()) + } + + env.afterSimulator(KVMConstant.KVM_ATTACH_VOLUME) { KVMAgentCommands.AttachDataVolumeResponse rsp, HttpEntity e -> + def cmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.AttachDataVolumeCmd.class) + rsp.virtualDeviceInfoList = [] + return rsp + } + } + + void testCephRootRunningVmBlocksVhostOnlineAttach() { + VmInstanceInventory cephVm = createVmInstance { + name = "ceph-root-vm" + instanceOfferingUuid = instanceOffering.uuid + imageUuid = cephImage.uuid + l3NetworkUuids = [l3.uuid] + hostUuid = cephHost.uuid + } as VmInstanceInventory + + assert VmInstanceState.Running.toString() == cephVm.state + + VolumeInventory vhostVol = createDataVolume { + name = "vhost-data-vol" + diskOfferingUuid = diskOffering.uuid + primaryStorageUuid = vhostPs.uuid + } as VolumeInventory + + boolean kvmagentCalled = false + env.afterSimulator(KVMConstant.KVM_ATTACH_VOLUME) { rsp, HttpEntity e -> + kvmagentCalled = true + return rsp + } + + AttachDataVolumeToVmAction action = new AttachDataVolumeToVmAction() + action.vmInstanceUuid = cephVm.uuid + action.volumeUuid = vhostVol.uuid + action.sessionId = adminSession() + AttachDataVolumeToVmAction.Result ret = action.call() + + assert ret.error != null : "online attach of vhost volume to a no-shared-mem running VM must be rejected" + assert JSONObjectUtil.toJsonString(ret.error).contains(ORG_ZSTACK_VHOST_KVM_10002) : + "expected guard error ORG_ZSTACK_VHOST_KVM_10002 in the error chain, got: ${JSONObjectUtil.toJsonString(ret.error)}" + assert !kvmagentCalled : "kvmagent must not be called when MN pre-attach guard rejects" + + env.afterSimulator(KVMConstant.KVM_ATTACH_VOLUME) { KVMAgentCommands.AttachDataVolumeResponse rsp, HttpEntity e -> + rsp.virtualDeviceInfoList = [] + return rsp + } + + destroyVmInstance { uuid = cephVm.uuid } + expungeVmInstance { uuid = cephVm.uuid } + deleteDataVolume { uuid = vhostVol.uuid } + expungeDataVolume { uuid = vhostVol.uuid } + } + + void testCephRootStoppedVmAllowsVhostAttach() { + VmInstanceInventory cephVm = createVmInstance { + name = "ceph-root-vm-stopped" + instanceOfferingUuid = instanceOffering.uuid + imageUuid = cephImage.uuid + l3NetworkUuids = [l3.uuid] + hostUuid = cephHost.uuid + } as VmInstanceInventory + + stopVmInstance { uuid = cephVm.uuid } + cephVm = queryVmInstance { conditions = ["uuid=${cephVm.uuid}"] }[0] as VmInstanceInventory + assert VmInstanceState.Stopped.toString() == cephVm.state + + VolumeInventory vhostVol = createDataVolume { + name = "vhost-data-vol-offline" + diskOfferingUuid = diskOffering.uuid + primaryStorageUuid = vhostPs.uuid + } as VolumeInventory + + attachDataVolumeToVm { + vmInstanceUuid = cephVm.uuid + volumeUuid = vhostVol.uuid + } + + detachDataVolumeFromVm { uuid = vhostVol.uuid } + destroyVmInstance { uuid = cephVm.uuid } + expungeVmInstance { uuid = cephVm.uuid } + deleteDataVolume { uuid = vhostVol.uuid } + expungeDataVolume { uuid = vhostVol.uuid } + } + + void testVhostRootRunningVmAllowsVhostOnlineAttach() { + env.afterSimulator(KVMConstant.KVM_START_VM_PATH) { rsp, HttpEntity e -> + return rsp + } + + VmInstanceInventory vhostVm = createVmInstance { + name = "vhost-root-vm" + instanceOfferingUuid = instanceOffering.uuid + imageUuid = image.uuid + l3NetworkUuids = [l3.uuid] + hostUuid = vhostHost.uuid + rootVolumeSystemTags = [] + } as VmInstanceInventory + + assert VmInstanceState.Running.toString() == vhostVm.state + + SQL.New(VolumeVO.class) + .eq(VolumeVO_.uuid, vhostVm.rootVolumeUuid) + .set(VolumeVO_.protocol, "Vhost") + .update() + + VolumeInventory vhostVol2 = createDataVolume { + name = "vhost-data-vol2" + diskOfferingUuid = diskOffering.uuid + primaryStorageUuid = vhostPs.uuid + } as VolumeInventory + + attachDataVolumeToVm { + vmInstanceUuid = vhostVm.uuid + volumeUuid = vhostVol2.uuid + } + + detachDataVolumeFromVm { uuid = vhostVol2.uuid } + destroyVmInstance { uuid = vhostVm.uuid } + expungeVmInstance { uuid = vhostVm.uuid } + deleteDataVolume { uuid = vhostVol2.uuid } + expungeDataVolume { uuid = vhostVol2.uuid } + } +} diff --git a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java index 27c12405e70..33d69d6b87b 100644 --- a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java +++ b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java @@ -12199,6 +12199,8 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_VHOST_KVM_10001 = "ORG_ZSTACK_VHOST_KVM_10001"; + public static final String ORG_ZSTACK_VHOST_KVM_10002 = "ORG_ZSTACK_VHOST_KVM_10002"; + public static final String ORG_ZSTACK_STORAGE_BACKUP_SFTP_10000 = "ORG_ZSTACK_STORAGE_BACKUP_SFTP_10000"; public static final String ORG_ZSTACK_STORAGE_BACKUP_SFTP_10001 = "ORG_ZSTACK_STORAGE_BACKUP_SFTP_10001"; From 48bad06203250c2b0847adde93fbdb5d8820290d Mon Sep 17 00:00:00 2001 From: "haoyu.ding" Date: Thu, 11 Jun 2026 12:01:55 +0800 Subject: [PATCH 14/54] [errorcode]: add Cloud diagnostic error response This change keeps the legacy error field for REST compatibility and adds id/success=false/diagnostic only for failed responses. Successful responses keep the existing shape. Resolves: ZSTAC-85921 Change-Id: I7a6270636b66677574786e6a6e73616763626275 --- .../compute/vm/VmNicLifecycleManager.java | 13 +- .../global-error-en_US.json | 6 + .../global-error-zh_CN.json | 6 + .../zstack/header/errorcode/ErrorCode.java | 4 + .../header/errorcode/ErrorCodeDiagnostic.java | 102 ++++++ .../errorcode/ErrorCodeDiagnosticHelper.java | 327 ++++++++++++++++++ .../java/org/zstack/rest/ApiResponse.java | 39 +++ .../main/java/org/zstack/rest/RestServer.java | 93 ++++- .../errorcode/TestApiResponseDiagnostic.java | 59 ++++ .../errorcode/TestErrorCodeDiagnostic.java | 260 ++++++++++++++ .../TestErrorCodeDiagnosticI18n.java | 47 +++ .../CloudOperationsErrorCode.java | 12 + 12 files changed, 944 insertions(+), 24 deletions(-) create mode 100644 header/src/main/java/org/zstack/header/errorcode/ErrorCodeDiagnostic.java create mode 100644 header/src/main/java/org/zstack/header/errorcode/ErrorCodeDiagnosticHelper.java create mode 100644 test/src/test/java/org/zstack/test/core/errorcode/TestApiResponseDiagnostic.java create mode 100644 test/src/test/java/org/zstack/test/core/errorcode/TestErrorCodeDiagnostic.java create mode 100644 test/src/test/java/org/zstack/test/core/errorcode/TestErrorCodeDiagnosticI18n.java diff --git a/compute/src/main/java/org/zstack/compute/vm/VmNicLifecycleManager.java b/compute/src/main/java/org/zstack/compute/vm/VmNicLifecycleManager.java index 2a7348c416e..0721ca02de3 100644 --- a/compute/src/main/java/org/zstack/compute/vm/VmNicLifecycleManager.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmNicLifecycleManager.java @@ -8,7 +8,6 @@ import org.zstack.header.core.NoErrorCompletion; import org.zstack.header.core.workflow.*; import org.zstack.header.errorcode.ErrorCode; -import org.zstack.header.errorcode.SysErrors; import org.zstack.header.network.l3.L3NetworkInventory; import org.zstack.header.vm.*; import org.zstack.utils.Utils; @@ -17,6 +16,8 @@ import java.util.*; import java.util.stream.Collectors; +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.*; + public class VmNicLifecycleManager implements PreVmInstantiateResourceExtensionPoint, VmReleaseResourceExtensionPoint, @@ -68,7 +69,8 @@ private void runSetup(VmNicLifecycleContext context, Iterator it, try { nics = filterNics(ext, allNics); } catch (RuntimeException e) { - completion.fail(Platform.err(SysErrors.OPERATION_ERROR.toString(), SysErrors.OPERATION_ERROR, "%s", e.getMessage())); + completion.fail(Platform.operr(ORG_ZSTACK_COMPUTE_VM_10333, + "vm nic lifecycle pre-migration failed while selecting applicable NICs: %s", e.getMessage())); return; } if (nics.isEmpty()) { @@ -141,7 +144,7 @@ public void fail(ErrorCode errorCode) { } }); } catch (Throwable t) { - completion.fail(Platform.err(SysErrors.OPERATION_ERROR.toString(), SysErrors.OPERATION_ERROR, + completion.fail(Platform.operr(ORG_ZSTACK_COMPUTE_VM_10334, "%s.preMigrate(src=%s, dest=%s) threw exception: %s", ext.getClass().getSimpleName(), srcHostUuid, destHostUuid, t.getMessage())); } diff --git a/conf/i18n/globalErrorCodeMapping/global-error-en_US.json b/conf/i18n/globalErrorCodeMapping/global-error-en_US.json index 5c54d1fd80f..9efd31787f3 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-en_US.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-en_US.json @@ -448,6 +448,10 @@ "ORG_ZSTACK_STORAGE_CEPH_PRIMARY_10050": "Ceph ps[uuid\u003d%s] Root Pool Name Not Found", "ORG_ZSTACK_STORAGE_CEPH_PRIMARY_10052": "delete volume chain error, continue with volume deletion", "ORG_ZSTACK_COMPUTE_VM_10320": "VmInstanceStartExtensionPoint[%s] refuses to start virtual machine[uuid:%s] because %s", + "ORG_ZSTACK_COMPUTE_VM_10331": "VM NIC lifecycle setup failed while selecting applicable NICs: %s", + "ORG_ZSTACK_COMPUTE_VM_10332": "VM NIC lifecycle extension[%s] setup on host[uuid:%s] failed unexpectedly: %s", + "ORG_ZSTACK_COMPUTE_VM_10333": "VM NIC lifecycle pre-migration failed while selecting applicable NICs: %s", + "ORG_ZSTACK_COMPUTE_VM_10334": "VM NIC lifecycle extension[%s] pre-migration from host[uuid:%s] to host[uuid:%s] failed unexpectedly: %s", "ORG_ZSTACK_COMPUTE_HOST_10013": "networkInterface[name:%s] of instance[uuid:%s] cannot be found", "ORG_ZSTACK_CRYPTO_DATACRYPTO_INTEGRITY_10008": "encryption %s[id:%s] failed: %s", "ORG_ZSTACK_CRYPTO_DATACRYPTO_SMP_10000": "the shared mount point primary storage[uuid:%s, name:%s] cannot find any available host within the attached compute clusters", @@ -4179,6 +4183,8 @@ "ORG_ZSTACK_POLICYROUTE_10010": "NextHopIp must be in IPv4 format but found [%s]", "ORG_ZSTACK_CORE_REST_10012": "unable to retrieve %s within %sms", "ORG_ZSTACK_CORE_REST_10013": "kvmagent on host[uuid:%s] restarted at %s, pending HTTP call sent before %s aborted", + "ORG_ZSTACK_CORE_REST_10014": "Failed to handle REST API: %s", + "ORG_ZSTACK_CORE_REST_10015": "REST API request failed: %s", "ORG_ZSTACK_QUERY_10021": "field[%s] is not a primitive of the inventory %s; you cannot specify it in the parameter \u0027fields\u0027; valid fields are %s", "ORG_ZSTACK_NETWORK_SERVICE_FLAT_10034": "DHCP server IP [%s] already exists within L3 network [%s]", "ORG_ZSTACK_QUERY_10020": "entity metadata class[%s] lacks field[%s]", diff --git a/conf/i18n/globalErrorCodeMapping/global-error-zh_CN.json b/conf/i18n/globalErrorCodeMapping/global-error-zh_CN.json index 481cd268b83..4306ae02e5a 100644 --- a/conf/i18n/globalErrorCodeMapping/global-error-zh_CN.json +++ b/conf/i18n/globalErrorCodeMapping/global-error-zh_CN.json @@ -448,6 +448,10 @@ "ORG_ZSTACK_STORAGE_CEPH_PRIMARY_10050": "Ceph ps[uuid\u003d%s] 根池名称未找到", "ORG_ZSTACK_STORAGE_CEPH_PRIMARY_10052": "删除卷链错误,继续删除", "ORG_ZSTACK_COMPUTE_VM_10320": "VmInstanceStartExtensionPoint[%s] 拒绝启动 vm[uuid:%s],因为 %s", + "ORG_ZSTACK_COMPUTE_VM_10331": "虚拟机网卡生命周期 setup 阶段筛选适用网卡失败:%s", + "ORG_ZSTACK_COMPUTE_VM_10332": "虚拟机网卡生命周期扩展[%s]在主机[uuid:%s]上执行 setup 失败:%s", + "ORG_ZSTACK_COMPUTE_VM_10333": "虚拟机网卡生命周期迁移前阶段筛选适用网卡失败:%s", + "ORG_ZSTACK_COMPUTE_VM_10334": "虚拟机网卡生命周期扩展[%s]执行从主机[uuid:%s]到主机[uuid:%s]的迁移前检查失败:%s", "ORG_ZSTACK_COMPUTE_HOST_10013": "网络接口[name:%s]主机[uuid:%s]未找到", "ORG_ZSTACK_CRYPTO_DATACRYPTO_INTEGRITY_10008": "加密 %s[id:%s] 错误: %s", "ORG_ZSTACK_CRYPTO_DATACRYPTO_SMP_10000": "共享挂载点主存储[uuid:%s, 名称:%s]无法在附加的集群中找到任何可用主机", @@ -4179,6 +4183,8 @@ "ORG_ZSTACK_POLICYROUTE_10010": "下一跳IP必须是ipv4格式,但发现[%s]", "ORG_ZSTACK_CORE_REST_10012": "无法在 %sms 内回显 %s", "ORG_ZSTACK_CORE_REST_10013": "物理机[uuid:%s]上的 kvmagent 于 %s 重启,丢弃发出于 %s 之前的待处理 HTTP 请求", + "ORG_ZSTACK_CORE_REST_10014": "处理 REST API 失败:%s", + "ORG_ZSTACK_CORE_REST_10015": "REST API 请求失败:%s", "ORG_ZSTACK_QUERY_10021": "字段[%s]不是库存%s中的原始类型;你无法在参数\u0027fields\u0027中指定它;有效的字段是%s", "ORG_ZSTACK_NETWORK_SERVICE_FLAT_10034": "DHCP服务器IP [%s] 已存在于L3网络 [%s] 中", "ORG_ZSTACK_QUERY_10020": "实体元类[%s]不存在字段[%s]", diff --git a/header/src/main/java/org/zstack/header/errorcode/ErrorCode.java b/header/src/main/java/org/zstack/header/errorcode/ErrorCode.java index d180b6b4ccb..23e9784d53c 100755 --- a/header/src/main/java/org/zstack/header/errorcode/ErrorCode.java +++ b/header/src/main/java/org/zstack/header/errorcode/ErrorCode.java @@ -29,6 +29,10 @@ public class ErrorCode implements Serializable, Cloneable { @NoJsonSchema private String[] formatArgs; + public ErrorCodeDiagnostic toDiagnostic() { + return ErrorCodeDiagnosticHelper.toDiagnostic(this); + } + public String getMessage() { return message; } diff --git a/header/src/main/java/org/zstack/header/errorcode/ErrorCodeDiagnostic.java b/header/src/main/java/org/zstack/header/errorcode/ErrorCodeDiagnostic.java new file mode 100644 index 00000000000..e88d412785e --- /dev/null +++ b/header/src/main/java/org/zstack/header/errorcode/ErrorCodeDiagnostic.java @@ -0,0 +1,102 @@ +package org.zstack.header.errorcode; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; + +public class ErrorCodeDiagnostic implements Serializable { + private String component; + private String category; + private String code; + private String message; + private boolean reviewed; + @SerializedName("raw_message") + private RawMessage rawMessage; + + public static class RawMessage implements Serializable { + private String symptom; + private String cause; + private String solution; + private String note; + + public String getSymptom() { + return symptom; + } + + public void setSymptom(String symptom) { + this.symptom = symptom; + } + + public String getCause() { + return cause; + } + + public void setCause(String cause) { + this.cause = cause; + } + + public String getSolution() { + return solution; + } + + public void setSolution(String solution) { + this.solution = solution; + } + + public String getNote() { + return note; + } + + public void setNote(String note) { + this.note = note; + } + } + + public String getComponent() { + return component; + } + + public void setComponent(String component) { + this.component = component; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public String getCode() { + return code; + } + + public void setCode(String code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isReviewed() { + return reviewed; + } + + public void setReviewed(boolean reviewed) { + this.reviewed = reviewed; + } + + public RawMessage getRawMessage() { + return rawMessage; + } + + public void setRawMessage(RawMessage rawMessage) { + this.rawMessage = rawMessage; + } +} diff --git a/header/src/main/java/org/zstack/header/errorcode/ErrorCodeDiagnosticHelper.java b/header/src/main/java/org/zstack/header/errorcode/ErrorCodeDiagnosticHelper.java new file mode 100644 index 00000000000..18990532b6c --- /dev/null +++ b/header/src/main/java/org/zstack/header/errorcode/ErrorCodeDiagnosticHelper.java @@ -0,0 +1,327 @@ +package org.zstack.header.errorcode; + +import org.zstack.utils.string.ErrorCodeElaboration; + +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ErrorCodeDiagnosticHelper { + public static final String COMPONENT_CLOUD = "Cloud"; + public static final String CATEGORY_OTHER = "Other"; + public static final String OPAQUE_REVIEWED = "diagnosticReviewed"; + public static final String OPAQUE_CAUSE = "diagnosticCause"; + public static final String OPAQUE_SOLUTION = "diagnosticSolution"; + public static final String OPAQUE_NOTE = "diagnosticNote"; + + private static final int RESOURCE_CODE_BASE = 10000; + private static final int ARGUMENT_CODE_BASE = 20000; + private static final int AUTH_CODE_BASE = 30000; + private static final int EXTERNAL_CODE_BASE = 40000; + private static final int INTERNAL_CODE_BASE = 90000; + private static final int CODE_BAND_SIZE = 10000; + public static final String FALLBACK_CODE = String.format("%05d", INTERNAL_CODE_BASE); + private static final String CLOUD_ERROR_CODE_PREFIX = "ORG_ZSTACK_"; + + private static final Pattern DIAGNOSTIC_CODE_PATTERN = Pattern.compile(".*?(\\d+)$"); + private static final Pattern CATEGORY_CODE_SUFFIX_PATTERN = Pattern.compile("_\\d+$"); + private static final String[] RESOURCE_KEYWORDS = new String[]{"_VM_", "_HOST_", "_STORAGE_", "_NETWORK_", "_IMAGE_", "_COMPUTE_"}; + private static final String[] ARGUMENT_KEYWORDS = new String[]{"_CONFIG_", "_PARAM_", "_PARAMETER_", "_ARGUMENT_", "_QUERY_"}; + private static final String[] AUTH_KEYWORDS = new String[]{"_IDENTITY_", "_LOGIN_", "_AUTH_", "_ACCOUNT_", "_PERMISSION_", "_IAM_", "_RBAC_"}; + private static final String[] EXTERNAL_KEYWORDS = new String[]{"_DB_", "_DATABASE_", "_AGENT_", "_KVMAGENT_", "_THIRD_PARTY_", "_EXTERNAL_", "_HTTP_", "_REST_CALL_", "_EXTERNALSERVICE_"}; + + private ErrorCodeDiagnosticHelper() { + } + + public static ErrorCodeDiagnostic toDiagnostic(ErrorCode error) { + return toDiagnostic(error, Locale.getDefault().toString()); + } + + public static ErrorCodeDiagnostic toDiagnostic(ErrorCode error, String locale) { + ErrorCodeDiagnostic diagnostic = new ErrorCodeDiagnostic(); + diagnostic.setComponent(truncate(COMPONENT_CLOUD, 32)); + + if (error == null) { + diagnostic.setCategory(CATEGORY_OTHER); + diagnostic.setCode(FALLBACK_CODE); + diagnostic.setMessage(""); + diagnostic.setRawMessage(toFallbackRawMessage()); + return diagnostic; + } + + diagnostic.setCategory(resolveCategory(error)); + diagnostic.setCode(resolveCode(error)); + diagnostic.setMessage(truncate(resolveMessage(error), 200)); + diagnostic.setReviewed(isReviewed(error)); + diagnostic.setRawMessage(toRawMessage(error, diagnostic.isReviewed(), locale)); + return diagnostic; + } + + private static ErrorCodeDiagnostic.RawMessage toRawMessage(ErrorCode error, boolean reviewed, String locale) { + ErrorCodeDiagnostic.RawMessage raw = new ErrorCodeDiagnostic.RawMessage(); + String symptom = firstNonBlank(resolveElaborationMessage(error, locale), error.getReadableDetails(), error.getMessage(), error.getDescription(), error.getCode()); + raw.setSymptom(truncate(firstNonBlank(symptom, resolveMessage(error), FALLBACK_CODE), 200)); + + if (reviewed) { + raw.setCause(truncate(stringFromOpaque(error, OPAQUE_CAUSE), 500)); + raw.setSolution(truncate(stringFromOpaque(error, OPAQUE_SOLUTION), 1000)); + raw.setNote(truncate(stringFromOpaque(error, OPAQUE_NOTE), 500)); + } + + return raw; + } + + private static ErrorCodeDiagnostic.RawMessage toFallbackRawMessage() { + ErrorCodeDiagnostic.RawMessage raw = new ErrorCodeDiagnostic.RawMessage(); + raw.setSymptom(""); + return raw; + } + + private static String resolveCategory(ErrorCode error) { + String cloudCode = resolveCloudErrorCode(error); + if (cloudCode != null) { + String category = categoryFromCloudErrorCode(cloudCode); + if (category != null) { + return category; + } + } + + ErrorCode cause = firstCause(error); + if (cause != null) { + return resolveCategory(cause); + } + + return CATEGORY_OTHER; + } + + // ZCF uses component + category + code as the unique diagnostic key. + // Cloud modules reuse 10000-based suffixes, so category must come from + // the stable globalErrorCode prefix instead of display groups like Storage/Network. + private static String categoryFromCloudErrorCode(String cloudCode) { + String normalizedCode = cloudCode == null ? "" : cloudCode.toUpperCase(Locale.ROOT).replace('-', '_'); + if (!normalizedCode.startsWith(CLOUD_ERROR_CODE_PREFIX)) { + return null; + } + + String category = normalizedCode.substring(CLOUD_ERROR_CODE_PREFIX.length()); + category = CATEGORY_CODE_SUFFIX_PATTERN.matcher(category).replaceFirst(""); + if (!isNotBlank(category)) { + return null; + } + + if (category.startsWith("COMPUTE_VM_") || category.equals("COMPUTE_VM")) { + return category.substring("COMPUTE_".length()); + } + + if (category.startsWith("COMPUTE_HOST_") || category.equals("COMPUTE_HOST")) { + return category.substring("COMPUTE_".length()); + } + + return category; + } + + private static String resolveCode(ErrorCode error) { + String diagnosticCode = toDiagnosticCode(error); + if (diagnosticCode != null) { + return diagnosticCode; + } + + ErrorCode cause = firstCause(error); + return cause == null ? FALLBACK_CODE : resolveCode(cause); + } + + private static String resolveCloudErrorCode(ErrorCode error) { + String code = firstNonBlank(error.getGlobalErrorCode(), error.getCode()); + if (code != null) { + return code; + } + + ErrorCode cause = firstCause(error); + return cause == null ? null : resolveCloudErrorCode(cause); + } + + private static String toDiagnosticCode(ErrorCode error) { + String code = resolveCloudErrorCode(error); + if (code == null) { + return null; + } + + Integer suffix = numericSuffix(code); + if (suffix == null) { + return null; + } + + int normalized = suffix % CODE_BAND_SIZE; + int base = resolveCodeBase(error, code); + return String.format("%05d", base + normalized); + } + + private static Integer numericSuffix(String code) { + Matcher matcher = DIAGNOSTIC_CODE_PATTERN.matcher(code); + if (!matcher.matches()) { + return null; + } + + try { + return Integer.parseInt(matcher.group(1)); + } catch (NumberFormatException e) { + return null; + } + } + + // Band priority: ARGUMENT > AUTH > EXTERNAL > RESOURCE > CORE/REST > INTERNAL. + private static int resolveCodeBase(ErrorCode error, String cloudCode) { + String upperCode = cloudCode == null ? "" : cloudCode.toUpperCase(Locale.ROOT).replace('-', '_'); + String normalizedCode = normalizeCode(cloudCode); + + if (containsAny(normalizedCode, ARGUMENT_KEYWORDS)) { + return ARGUMENT_CODE_BASE; + } + + if (containsAny(normalizedCode, AUTH_KEYWORDS)) { + return AUTH_CODE_BASE; + } + + if (containsAny(normalizedCode, EXTERNAL_KEYWORDS)) { + return EXTERNAL_CODE_BASE; + } + + if (containsAny(normalizedCode, RESOURCE_KEYWORDS)) { + return RESOURCE_CODE_BASE; + } + + if (isInvalidArgument(error)) { + return ARGUMENT_CODE_BASE; + } + + if (upperCode.startsWith("ORG_ZSTACK_CORE_") || upperCode.startsWith("ORG_ZSTACK_PORTAL_") || + upperCode.startsWith("ORG_ZSTACK_REST_")) { + return INTERNAL_CODE_BASE; + } + + return INTERNAL_CODE_BASE; + } + + private static boolean isInvalidArgument(ErrorCode error) { + return error != null && error.isError(SysErrors.INVALID_ARGUMENT_ERROR); + } + + private static boolean containsAny(String value, String[] keywords) { + if (value == null) { + return false; + } + + for (String keyword : keywords) { + if (value.contains(keyword)) { + return true; + } + } + return false; + } + + private static String normalizeCode(String code) { + return code == null ? "" : "_" + code.toUpperCase(Locale.ROOT).replace('-', '_') + "_"; + } + + private static String resolveMessage(ErrorCode error) { + String message = firstNonBlank(error.getMessage(), error.getDetails(), error.getDescription(), error.getCode()); + if (message != null) { + return message; + } + + ErrorCode cause = firstCause(error); + return cause == null ? "" : resolveMessage(cause); + } + + private static String resolveElaborationMessage(ErrorCode error, String locale) { + ErrorCodeElaboration messages = firstMessages(error); + if (messages == null) { + return null; + } + + if (isZh(locale)) { + return firstNonBlank(messages.getMessage_cn(), messages.getMessage_en()); + } + return firstNonBlank(messages.getMessage_en(), messages.getMessage_cn()); + } + + private static ErrorCodeElaboration firstMessages(ErrorCode error) { + ErrorCode current = error; + while (current != null) { + if (current.getMessages() != null) { + return current.getMessages(); + } + if (current instanceof ErrorCodeList) { + ErrorCode cause = firstCause(current); + if (cause != null) { + ErrorCodeElaboration causeMessages = firstMessages(cause); + if (causeMessages != null) { + return causeMessages; + } + } + } + current = current.getCause(); + } + return null; + } + + private static ErrorCode firstCause(ErrorCode error) { + if (error.getCause() != null) { + return error.getCause(); + } + + if (error instanceof ErrorCodeList && ((ErrorCodeList) error).getCauses() != null && + !((ErrorCodeList) error).getCauses().isEmpty()) { + return ((ErrorCodeList) error).getCauses().get(0); + } + + return null; + } + + private static boolean isReviewed(ErrorCode error) { + Object reviewed = error.getFromOpaque(OPAQUE_REVIEWED); + if (reviewed instanceof Boolean) { + return (Boolean) reviewed; + } + if (reviewed instanceof String) { + return Boolean.parseBoolean((String) reviewed); + } + return false; + } + + private static String stringFromOpaque(ErrorCode error, String key) { + Object value = error.getFromOpaque(key); + return value == null ? null : value.toString(); + } + + private static String firstNonBlank(String... values) { + if (values == null) { + return null; + } + + for (String value : values) { + if (isNotBlank(value)) { + return value; + } + } + return null; + } + + private static boolean isNotBlank(String value) { + return value != null && !value.trim().isEmpty(); + } + + private static boolean isZh(String locale) { + if (locale == null) { + return Locale.getDefault().getLanguage().equals("zh"); + } + return locale.replace('-', '_').toLowerCase(Locale.ROOT).startsWith("zh"); + } + + private static String truncate(String value, int maxLength) { + if (value == null || value.length() <= maxLength) { + return value; + } + return value.substring(0, maxLength); + } +} diff --git a/rest/src/main/java/org/zstack/rest/ApiResponse.java b/rest/src/main/java/org/zstack/rest/ApiResponse.java index c7a9260fc6a..79cada4654b 100755 --- a/rest/src/main/java/org/zstack/rest/ApiResponse.java +++ b/rest/src/main/java/org/zstack/rest/ApiResponse.java @@ -1,19 +1,33 @@ package org.zstack.rest; import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.errorcode.ErrorCodeDiagnostic; +import org.zstack.header.errorcode.ErrorCodeDiagnosticHelper; import java.util.HashMap; import java.util.Map; +import java.util.UUID; /** * Created by xing5 on 2016/12/8. */ public class ApiResponse extends HashMap { + private String id; + private String success; private String location; private ErrorCode error; + private ErrorCodeDiagnostic diagnostic; private Map schema; private long apiTimeout; + public String getId() { + return id; + } + + public String getSuccess() { + return success; + } + public long getApiTimeout() { return apiTimeout; } @@ -45,8 +59,33 @@ public ErrorCode getError() { return error; } + public ErrorCodeDiagnostic getDiagnostic() { + return diagnostic; + } + + public void setId(String id) { + this.id = id; + put("id", id); + } + + public void setSuccess(boolean success) { + this.success = String.valueOf(success); + put("success", this.success); + } + + public void setDiagnostic(ErrorCodeDiagnostic diagnostic) { + this.diagnostic = diagnostic; + put("diagnostic", diagnostic); + } + public void setError(ErrorCode error) { this.error = error; put("error", error); } + + public void completeFailure(String id, String locale) { + setId(id == null ? UUID.randomUUID().toString().replace("-", "") : id); + setSuccess(false); + setDiagnostic(ErrorCodeDiagnosticHelper.toDiagnostic(error, locale)); + } } diff --git a/rest/src/main/java/org/zstack/rest/RestServer.java b/rest/src/main/java/org/zstack/rest/RestServer.java index ee3dbb0b355..db60ce944ea 100755 --- a/rest/src/main/java/org/zstack/rest/RestServer.java +++ b/rest/src/main/java/org/zstack/rest/RestServer.java @@ -721,13 +721,13 @@ private String getMatchPath(String path) { void handle(HttpServletRequest req, HttpServletResponse rsp) throws IOException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { RequestInfo info = new RequestInfo(req); + requestInfo.set(info); if (rateLimiter.isRateLimitExceeded(info.clientIp)) { - sendResponse(HttpStatus.TOO_MANY_REQUESTS.value(), "Rate limit exceeded", rsp); + sendRestErrorResponse(HttpStatus.TOO_MANY_REQUESTS.value(), "Rate limit exceeded", rsp); return; } - requestInfo.set(info); rsp.setCharacterEncoding("utf-8"); String path = getDecodedUrl(req); HttpEntity entity = toHttpEntity(req); @@ -793,7 +793,7 @@ void handle(HttpServletRequest req, HttpServletResponse rsp) throws IOException, finalApiSpan.setStatus(StatusCode.ERROR, e.error); finalApiSpan.setAttribute("http.status_code", e.statusCode); } - sendResponse(e.statusCode, e.error, rsp); + sendRestErrorResponse(e.statusCode, e.error, rsp); return; } @@ -811,7 +811,7 @@ void handle(HttpServletRequest req, HttpServletResponse rsp) throws IOException, finalApiSpan.setStatus(StatusCode.ERROR, "API not found"); finalApiSpan.setAttribute("http.status_code", HttpStatus.NOT_FOUND.value()); } - sendResponse(HttpStatus.NOT_FOUND.value(), String.format("no api mapping to %s", path), rsp); + sendRestErrorResponse(HttpStatus.NOT_FOUND.value(), String.format("no api mapping to %s", path), rsp); return; } @@ -829,7 +829,7 @@ void handle(HttpServletRequest req, HttpServletResponse rsp) throws IOException, finalApiSpan.setStatus(StatusCode.ERROR, e.error); finalApiSpan.setAttribute("http.status_code", e.statusCode); } - sendResponse(e.statusCode, e.error, rsp); + sendRestErrorResponse(e.statusCode, e.error, rsp); } catch (Throwable e) { logger.warn(String.format("failed to handle API to %s", path), e); if (finalApiSpan != null) { @@ -837,7 +837,10 @@ void handle(HttpServletRequest req, HttpServletResponse rsp) throws IOException, finalApiSpan.setStatus(StatusCode.ERROR, e.getMessage()); finalApiSpan.setAttribute("http.status_code", HttpStatus.INTERNAL_SERVER_ERROR.value()); } - sendResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage(), rsp); + ApiResponse response = new ApiResponse(); + response.setError(Platform.inerr(org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_CORE_REST_10014, + "failed to handle REST API: %s", e.getMessage())); + sendResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), response, rsp); } } finally { endSpanAndScope(finalApiSpan, finalScope); @@ -890,7 +893,7 @@ private boolean needLog(RequestInfo req){ private void handleJobQuery(HttpServletRequest req, HttpServletResponse rsp) throws IOException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { if (!req.getMethod().equals(HttpMethod.GET.name())) { - sendResponse(HttpStatus.METHOD_NOT_ALLOWED.value(), "only GET method is allowed for querying job status", rsp); + sendRestErrorResponse(HttpStatus.METHOD_NOT_ALLOWED.value(), "only GET method is allowed for querying job status", rsp); return; } @@ -899,7 +902,7 @@ private void handleJobQuery(HttpServletRequest req, HttpServletResponse rsp) thr AsyncRestQueryResult ret = asyncStore.query(uuid); if (ret.getState() == AsyncRestState.expired) { - sendResponse(HttpStatus.NOT_FOUND.value(), "the job has been expired", rsp); + sendRestErrorResponse(HttpStatus.NOT_FOUND.value(), "the job has been expired", rsp); return; } @@ -921,16 +924,16 @@ private void handleJobQuery(HttpServletRequest req, HttpServletResponse rsp) thr sendResponse(HttpStatus.OK.value(), response, rsp); } else { response.setError(evt.getError()); - sendResponse(HttpStatus.SERVICE_UNAVAILABLE.value(), response, rsp); + sendResponse(HttpStatus.SERVICE_UNAVAILABLE.value(), response, rsp, firstNonBlank(evt.getApiId(), uuid)); } } private void sendResponse(int statusCode, ApiResponse response, HttpServletResponse rsp) throws IOException { - // centralized localization: override message with client's preferred locale - if (response.getError() != null) { - String locale = resolveLocale(); - i18nService.localizeErrorCode(response.getError(), locale); - } + sendResponse(statusCode, response, rsp, null); + } + + private void sendResponse(int statusCode, ApiResponse response, HttpServletResponse rsp, String apiId) throws IOException { + prepareApiResponse(response, rsp, apiId); RequestInfo info = requestInfo.get(); if (requestLogger.isTraceEnabled() && needLog(info)) { @@ -948,6 +951,60 @@ private void sendResponse(int statusCode, ApiResponse response, HttpServletRespo rsp.getWriter().write(body == null ? "" : body); } + private void sendJsonObjectResponse(int statusCode, ApiResponse response, HttpServletResponse rsp, String apiId) throws IOException { + prepareApiResponse(response, rsp, apiId); + sendResponse(statusCode, JSONObjectUtil.toJsonString(response), rsp); + } + + private void sendRestErrorResponse(int statusCode, String message, HttpServletResponse rsp) throws IOException { + ApiResponse response = new ApiResponse(); + response.setError(Platform.err(org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_CORE_REST_10015, + sysErrorForStatus(statusCode), "%s", message)); + sendResponse(statusCode, response, rsp); + } + + private org.zstack.header.errorcode.SysErrors sysErrorForStatus(int statusCode) { + if (statusCode == HttpStatus.NOT_FOUND.value()) { + return org.zstack.header.errorcode.SysErrors.RESOURCE_NOT_FOUND; + } + if (statusCode == HttpStatus.BAD_REQUEST.value() || statusCode == HttpStatus.METHOD_NOT_ALLOWED.value()) { + return org.zstack.header.errorcode.SysErrors.INVALID_ARGUMENT_ERROR; + } + if (statusCode >= HttpStatus.INTERNAL_SERVER_ERROR.value()) { + return org.zstack.header.errorcode.SysErrors.INTERNAL; + } + return org.zstack.header.errorcode.SysErrors.OPERATION_ERROR; + } + + private void prepareApiResponse(ApiResponse response, HttpServletResponse rsp, String apiId) { + String locale = resolveLocale(); + rsp.setHeader("Content-Language", toLanguageTag(locale)); + if (response.getError() != null) { + i18nService.localizeErrorCode(response.getError(), locale); + response.completeFailure(apiId, locale); + } + } + + private String toLanguageTag(String locale) { + if (locale == null) { + return LocaleUtils.DEFAULT_LOCALE.replace('_', '-'); + } + return locale.replace('_', '-'); + } + + private String firstNonBlank(String... values) { + if (values == null) { + return null; + } + + for (String value : values) { + if (StringUtils.isNotBlank(value)) { + return value; + } + } + return null; + } + private void handleNonUniqueApi(Collection apis, HttpEntity entity, HttpServletRequest req, HttpServletResponse rsp) throws RestException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException, IOException { Map m = JSONObjectUtil.toObject(entity.getBody(), LinkedHashMap.class); Api api; @@ -1435,17 +1492,15 @@ private String resolveLocale() { return LocaleUtils.resolveLocale(acceptLanguage, i18nService.getAvailableLocales()); } - private void sendReplyResponse(MessageReply reply, Api api, HttpServletResponse rsp) throws IOException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { + private void sendReplyResponse(MessageReply reply, Api api, HttpServletResponse rsp, String apiId) throws IOException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { ApiResponse response = new ApiResponse(); if (!reply.isSuccess()) { - String locale = resolveLocale(); - i18nService.localizeErrorCode(reply.getError(), locale); response.setError(reply.getError()); // use JSONObjectUtil (which disables HTML escaping) to keep the same // serialization behavior as before; CloudBusGson.httpGson escapes '\'' to // '\u0027' which breaks SDK-side string assertions (ZSTAC-71075 etc.) - sendResponse(HttpStatus.SERVICE_UNAVAILABLE.value(), JSONObjectUtil.toJsonString(response), rsp); + sendJsonObjectResponse(HttpStatus.SERVICE_UNAVAILABLE.value(), response, rsp, apiId); return; } @@ -1458,7 +1513,7 @@ private void sendReplyResponse(MessageReply reply, Api api, HttpServletResponse private void sendMessage(APIMessage msg, Api api, HttpServletResponse rsp) throws IOException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { if (msg instanceof APISyncCallMessage) { MessageReply reply = bus.call(msg); - sendReplyResponse(reply, api, rsp); + sendReplyResponse(reply, api, rsp, msg.getId()); } else { RequestData d = new RequestData(); d.apiMessage = msg; diff --git a/test/src/test/java/org/zstack/test/core/errorcode/TestApiResponseDiagnostic.java b/test/src/test/java/org/zstack/test/core/errorcode/TestApiResponseDiagnostic.java new file mode 100644 index 00000000000..a5e8ba1400c --- /dev/null +++ b/test/src/test/java/org/zstack/test/core/errorcode/TestApiResponseDiagnostic.java @@ -0,0 +1,59 @@ +package org.zstack.test.core.errorcode; + +import org.junit.Assert; +import org.junit.Test; +import org.zstack.header.errorcode.ErrorCode; +import org.zstack.rest.ApiResponse; +import org.zstack.utils.gson.JSONObjectUtil; + +public class TestApiResponseDiagnostic { + @Test + public void testFailureResponseKeepsLegacyErrorAndAddsDiagnostic() { + ErrorCode error = new ErrorCode("SYS.1006", "Operation Error", "raw symptom"); + error.setGlobalErrorCode("ORG_ZSTACK_COMPUTE_VM_10331"); + error.setMessage("VM NIC lifecycle setup failed"); + + ApiResponse response = new ApiResponse(); + response.setError(error); + response.completeFailure("7e9bb780c1d94c46a2dd12bf4f8debaa", "en_US"); + + Assert.assertEquals("7e9bb780c1d94c46a2dd12bf4f8debaa", response.getId()); + Assert.assertEquals("false", response.getSuccess()); + Assert.assertSame(error, response.getError()); + Assert.assertNotNull(response.getDiagnostic()); + Assert.assertEquals("Cloud", response.getDiagnostic().getComponent()); + Assert.assertEquals("VM", response.getDiagnostic().getCategory()); + Assert.assertEquals("10331", response.getDiagnostic().getCode()); + Assert.assertEquals("VM NIC lifecycle setup failed", response.getDiagnostic().getMessage()); + + String json = JSONObjectUtil.toJsonString(response); + Assert.assertTrue(json.contains("\"error\"")); + Assert.assertTrue(json.contains("\"diagnostic\"")); + Assert.assertTrue(json.contains("\"success\":\"false\"")); + } + + @Test + public void testSuccessResponseKeepsLegacyShapeWithoutDiagnosticFields() { + ApiResponse response = new ApiResponse(); + + Assert.assertNull(response.getId()); + Assert.assertNull(response.getSuccess()); + Assert.assertNull(response.getDiagnostic()); + Assert.assertFalse(response.containsKey("id")); + Assert.assertFalse(response.containsKey("success")); + Assert.assertFalse(response.containsKey("diagnostic")); + } + + @Test + public void testFallbackIdIsGeneratedForFailureWhenApiIdIsMissing() { + ErrorCode error = new ErrorCode("SYS.1006", "Operation Error", "raw symptom"); + + ApiResponse response = new ApiResponse(); + response.setError(error); + response.completeFailure(null, "en_US"); + + Assert.assertNotNull(response.getId()); + Assert.assertTrue(response.getId().matches("^[0-9a-f]{32}$")); + Assert.assertEquals("false", response.getSuccess()); + } +} diff --git a/test/src/test/java/org/zstack/test/core/errorcode/TestErrorCodeDiagnostic.java b/test/src/test/java/org/zstack/test/core/errorcode/TestErrorCodeDiagnostic.java new file mode 100644 index 00000000000..7ff4e579fe7 --- /dev/null +++ b/test/src/test/java/org/zstack/test/core/errorcode/TestErrorCodeDiagnostic.java @@ -0,0 +1,260 @@ +package org.zstack.test.core.errorcode; + +import org.junit.Assert; +import org.junit.Test; +import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.errorcode.ErrorCodeDiagnostic; +import org.zstack.header.errorcode.ErrorCodeDiagnosticHelper; +import org.zstack.header.errorcode.ErrorCodeList; +import org.zstack.header.errorcode.SysErrors; +import org.zstack.utils.clouderrorcode.CloudOperationsErrorCode; +import org.zstack.utils.gson.JSONObjectUtil; +import org.zstack.utils.string.ErrorCodeElaboration; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +public class TestErrorCodeDiagnostic { + @Test + public void testMapGlobalErrorCodeAndLocalizedMessage() { + ErrorCode error = new ErrorCode("SYS.1006", "Operation Error", "raw primary storage detail"); + error.setGlobalErrorCode("ORG_ZSTACK_STORAGE_PRIMARY_10039"); + error.setMessage("主存储不存在"); + + ErrorCodeDiagnostic diagnostic = error.toDiagnostic(); + + Assert.assertEquals("Cloud", diagnostic.getComponent()); + Assert.assertEquals("STORAGE_PRIMARY", diagnostic.getCategory()); + Assert.assertEquals("10039", diagnostic.getCode()); + Assert.assertEquals("主存储不存在", diagnostic.getMessage()); + Assert.assertEquals("raw primary storage detail", diagnostic.getRawMessage().getSymptom()); + Assert.assertNull(diagnostic.getRawMessage().getCause()); + Assert.assertNull(diagnostic.getRawMessage().getSolution()); + } + + @Test + public void testFallbackCategoryFromVmPrefix() { + ErrorCode error = new ErrorCode("SYS.1006", "Operation Error", "setup nic failed"); + error.setGlobalErrorCode("ORG_ZSTACK_COMPUTE_VM_10331"); + + ErrorCodeDiagnostic diagnostic = ErrorCodeDiagnosticHelper.toDiagnostic(error); + + Assert.assertEquals("VM", diagnostic.getCategory()); + Assert.assertEquals("10331", diagnostic.getCode()); + Assert.assertEquals("setup nic failed", diagnostic.getMessage()); + } + + @Test + public void testRestCoreErrorMapsToInternalDiagnosticBand() { + ErrorCode error = new ErrorCode("SYS.1006", "Operation Error", "not found"); + error.setGlobalErrorCode("ORG_ZSTACK_CORE_REST_10015"); + + ErrorCodeDiagnostic diagnostic = ErrorCodeDiagnosticHelper.toDiagnostic(error); + + Assert.assertEquals("CORE_REST", diagnostic.getCategory()); + Assert.assertEquals("90015", diagnostic.getCode()); + } + + @Test + public void testInvalidArgumentErrorMapsToArgumentDiagnosticBand() { + ErrorCode error = new ErrorCode(SysErrors.INVALID_ARGUMENT_ERROR.toString(), "Invalid Argument", "invalid parameter"); + error.setGlobalErrorCode("ORG_ZSTACK_CORE_ARGUMENT_10015"); + + ErrorCodeDiagnostic diagnostic = ErrorCodeDiagnosticHelper.toDiagnostic(error); + + Assert.assertEquals("20015", diagnostic.getCode()); + } + + @Test + public void testSysInvalidArgumentErrorMapsToArgumentDiagnosticBand() { + ErrorCode error = new ErrorCode(SysErrors.INVALID_ARGUMENT_ERROR.toString(), "Invalid Argument", "invalid parameter"); + + ErrorCodeDiagnostic diagnostic = ErrorCodeDiagnosticHelper.toDiagnostic(error); + + Assert.assertEquals("21007", diagnostic.getCode()); + } + + @Test + public void testIdentityLoginErrorMapsToAuthDiagnosticBand() { + ErrorCode error = new ErrorCode("SYS.1006", "Operation Error", "login failed"); + error.setGlobalErrorCode("ORG_ZSTACK_IDENTITY_LOGIN_10000"); + + ErrorCodeDiagnostic diagnostic = ErrorCodeDiagnosticHelper.toDiagnostic(error); + + Assert.assertEquals("IDENTITY_LOGIN", diagnostic.getCategory()); + Assert.assertEquals("30000", diagnostic.getCode()); + } + + @Test + public void testExternalDependencyErrorMapsToExternalDiagnosticBand() { + ErrorCode error = new ErrorCode("SYS.1006", "Operation Error", "agent call failed"); + error.setGlobalErrorCode("ORG_ZSTACK_CORE_KVMAGENT_10042"); + + ErrorCodeDiagnostic diagnostic = ErrorCodeDiagnosticHelper.toDiagnostic(error); + + Assert.assertEquals("40042", diagnostic.getCode()); + } + + @Test + public void testStorageSubModulesUseDistinctDiagnosticCategories() { + ErrorCode ceph = new ErrorCode("SYS.1006", "Operation Error", "ceph backup failed"); + ceph.setGlobalErrorCode("ORG_ZSTACK_STORAGE_CEPH_BACKUP_10000"); + ErrorCode imageStore = new ErrorCode("SYS.1006", "Operation Error", "imagestore backup failed"); + imageStore.setGlobalErrorCode("ORG_ZSTACK_STORAGE_BACKUP_IMAGESTORE_10000"); + + ErrorCodeDiagnostic cephDiagnostic = ErrorCodeDiagnosticHelper.toDiagnostic(ceph); + ErrorCodeDiagnostic imageStoreDiagnostic = ErrorCodeDiagnosticHelper.toDiagnostic(imageStore); + + Assert.assertEquals("STORAGE_CEPH_BACKUP", cephDiagnostic.getCategory()); + Assert.assertEquals("STORAGE_BACKUP_IMAGESTORE", imageStoreDiagnostic.getCategory()); + Assert.assertEquals("10000", cephDiagnostic.getCode()); + Assert.assertEquals("10000", imageStoreDiagnostic.getCode()); + Assert.assertNotEquals(uniqueDiagnosticKey(cephDiagnostic), uniqueDiagnosticKey(imageStoreDiagnostic)); + } + + @Test + public void testAutoscalingAndApplianceVmDoNotFallBackToSameOtherDiagnosticKey() { + ErrorCode autoscaling = new ErrorCode("SYS.1006", "Operation Error", "autoscaling failed"); + autoscaling.setGlobalErrorCode("ORG_ZSTACK_AUTOSCALING_10000"); + ErrorCode applianceVm = new ErrorCode("SYS.1006", "Operation Error", "appliance vm failed"); + applianceVm.setGlobalErrorCode("ORG_ZSTACK_APPLIANCEVM_10000"); + + ErrorCodeDiagnostic autoscalingDiagnostic = ErrorCodeDiagnosticHelper.toDiagnostic(autoscaling); + ErrorCodeDiagnostic applianceVmDiagnostic = ErrorCodeDiagnosticHelper.toDiagnostic(applianceVm); + + Assert.assertEquals("AUTOSCALING", autoscalingDiagnostic.getCategory()); + Assert.assertEquals("APPLIANCEVM", applianceVmDiagnostic.getCategory()); + Assert.assertEquals("90000", autoscalingDiagnostic.getCode()); + Assert.assertEquals("90000", applianceVmDiagnostic.getCode()); + Assert.assertNotEquals(uniqueDiagnosticKey(autoscalingDiagnostic), uniqueDiagnosticKey(applianceVmDiagnostic)); + } + + @Test + public void testCloudOperationsErrorCodeDiagnosticKeysAreUnique() throws IllegalAccessException { + Set keys = new HashSet<>(); + for (Field field : CloudOperationsErrorCode.class.getDeclaredFields()) { + int modifiers = field.getModifiers(); + if (!Modifier.isStatic(modifiers) || !Modifier.isFinal(modifiers) || field.getType() != String.class || + !field.getName().startsWith("ORG_ZSTACK_")) { + continue; + } + + String globalErrorCode = (String) field.get(null); + ErrorCode error = new ErrorCode("SYS.1006", "Operation Error", globalErrorCode); + error.setGlobalErrorCode(globalErrorCode); + ErrorCodeDiagnostic diagnostic = ErrorCodeDiagnosticHelper.toDiagnostic(error); + String key = uniqueDiagnosticKey(diagnostic); + Assert.assertTrue(String.format("duplicated diagnostic key[%s] from globalErrorCode[%s]", key, globalErrorCode), keys.add(key)); + } + } + + @Test + public void testElaborationCategoryDoesNotOverrideStableDiagnosticCategory() { + ErrorCode error = new ErrorCode("SYS.1006", "Operation Error", "host connection failed"); + error.setGlobalErrorCode("ORG_ZSTACK_COMPUTE_VM_10331"); + ErrorCodeElaboration elaboration = new ErrorCodeElaboration(); + elaboration.setCategory("HOST"); + elaboration.setMessage_en("Host connection failed."); + error.setMessages(elaboration); + + ErrorCodeDiagnostic diagnostic = ErrorCodeDiagnosticHelper.toDiagnostic(error); + + Assert.assertEquals("VM", diagnostic.getCategory()); + Assert.assertEquals("Host connection failed.", diagnostic.getRawMessage().getSymptom()); + } + + @Test + public void testUnreviewedDiagnosticDoesNotExposeCauseAndSolution() { + ErrorCode error = new ErrorCode("SYS.1006", "Operation Error", "raw symptom"); + error.withOpaque(ErrorCodeDiagnosticHelper.OPAQUE_CAUSE, "not reviewed cause"); + error.withOpaque(ErrorCodeDiagnosticHelper.OPAQUE_SOLUTION, "not reviewed solution"); + + ErrorCodeDiagnostic diagnostic = ErrorCodeDiagnosticHelper.toDiagnostic(error, "en_US"); + + Assert.assertFalse(diagnostic.isReviewed()); + Assert.assertEquals("raw symptom", diagnostic.getRawMessage().getSymptom()); + Assert.assertNull(diagnostic.getRawMessage().getCause()); + Assert.assertNull(diagnostic.getRawMessage().getSolution()); + + String json = JSONObjectUtil.toJsonString(diagnostic); + Assert.assertFalse(json.contains("\"cause\"")); + Assert.assertFalse(json.contains("\"solution\"")); + } + + @Test + public void testReviewedDiagnosticExposesCauseSolutionAndNote() { + ErrorCode error = new ErrorCode("SYS.1006", "Operation Error", "raw symptom"); + error.withOpaque(ErrorCodeDiagnosticHelper.OPAQUE_REVIEWED, true); + error.withOpaque(ErrorCodeDiagnosticHelper.OPAQUE_CAUSE, "reviewed cause"); + error.withOpaque(ErrorCodeDiagnosticHelper.OPAQUE_SOLUTION, "reviewed solution"); + error.withOpaque(ErrorCodeDiagnosticHelper.OPAQUE_NOTE, "reviewed note"); + + ErrorCodeDiagnostic diagnostic = ErrorCodeDiagnosticHelper.toDiagnostic(error); + + Assert.assertTrue(diagnostic.isReviewed()); + Assert.assertEquals("reviewed cause", diagnostic.getRawMessage().getCause()); + Assert.assertEquals("reviewed solution", diagnostic.getRawMessage().getSolution()); + Assert.assertEquals("reviewed note", diagnostic.getRawMessage().getNote()); + } + + @Test + public void testJsonFieldNamesMatchZcfDiagnosticContract() { + ErrorCode error = new ErrorCode("SYS.1006", "Operation Error", "raw symptom"); + error.setGlobalErrorCode("ORG_ZSTACK_NETWORK_L3_10077"); + error.setMessage("network failure"); + + String json = JSONObjectUtil.toJsonString(ErrorCodeDiagnosticHelper.toDiagnostic(error)); + + Assert.assertTrue(json.contains("\"component\":\"Cloud\"")); + Assert.assertTrue(json.contains("\"category\":\"NETWORK_L3\"")); + Assert.assertTrue(json.contains("\"code\":\"10077\"")); + Assert.assertTrue(json.contains("\"message\":\"network failure\"")); + Assert.assertTrue(json.contains("\"raw_message\"")); + Assert.assertFalse(json.contains("\"rawMessage\"")); + Assert.assertTrue(json.contains("\"symptom\":\"raw symptom\"")); + Assert.assertFalse(json.contains("\"cause\"")); + Assert.assertFalse(json.contains("\"solution\"")); + } + + @Test + public void testErrorCodeListFallsBackToFirstCause() { + ErrorCode cause = new ErrorCode("SYS.1006", "Operation Error", "first cause symptom"); + cause.setGlobalErrorCode("ORG_ZSTACK_STORAGE_PRIMARY_10039"); + cause.setMessage("primary storage failure"); + + ErrorCodeList list = new ErrorCodeList(); + list.setCauses(Collections.singletonList(cause)); + + ErrorCodeDiagnostic diagnostic = ErrorCodeDiagnosticHelper.toDiagnostic(list); + + Assert.assertEquals("STORAGE_PRIMARY", diagnostic.getCategory()); + Assert.assertEquals("10039", diagnostic.getCode()); + Assert.assertEquals("primary storage failure", diagnostic.getMessage()); + } + + @Test + public void testDiagnosticCodeFallsBackToSafeNumericCode() { + ErrorCode error = new ErrorCode("CUSTOM_ERROR", "Operation Error", "raw symptom"); + + ErrorCodeDiagnostic diagnostic = ErrorCodeDiagnosticHelper.toDiagnostic(error); + + Assert.assertEquals(ErrorCodeDiagnosticHelper.FALLBACK_CODE, diagnostic.getCode()); + } + + @Test + public void testUnreviewedChineseDiagnosticDoesNotAddFallbackSolution() { + ErrorCode error = new ErrorCode("SYS.1006", "Operation Error", "raw symptom"); + + ErrorCodeDiagnostic diagnostic = ErrorCodeDiagnosticHelper.toDiagnostic(error, "zh_CN"); + + Assert.assertEquals("raw symptom", diagnostic.getRawMessage().getSymptom()); + Assert.assertNull(diagnostic.getRawMessage().getSolution()); + } + + private String uniqueDiagnosticKey(ErrorCodeDiagnostic diagnostic) { + return String.format("%s:%s:%s", diagnostic.getComponent(), diagnostic.getCategory(), diagnostic.getCode()); + } +} diff --git a/test/src/test/java/org/zstack/test/core/errorcode/TestErrorCodeDiagnosticI18n.java b/test/src/test/java/org/zstack/test/core/errorcode/TestErrorCodeDiagnosticI18n.java new file mode 100644 index 00000000000..426e2329eb3 --- /dev/null +++ b/test/src/test/java/org/zstack/test/core/errorcode/TestErrorCodeDiagnosticI18n.java @@ -0,0 +1,47 @@ +package org.zstack.test.core.errorcode; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.zstack.core.errorcode.GlobalErrorCodeI18nService; +import org.zstack.core.errorcode.GlobalErrorCodeI18nServiceImpl; +import org.zstack.header.errorcode.ErrorCode; +import org.zstack.header.errorcode.ErrorCodeDiagnostic; +import org.zstack.header.errorcode.ErrorCodeDiagnosticHelper; + +public class TestErrorCodeDiagnosticI18n { + private GlobalErrorCodeI18nService i18nService; + + @Before + public void setUp() throws Exception { + GlobalErrorCodeI18nServiceImpl service = new GlobalErrorCodeI18nServiceImpl(); + service.start(); + i18nService = service; + } + + @Test + public void testVmNicLifecycleErrorCodeI18nAndDiagnostic() { + ErrorCode error = new ErrorCode("SYS.1006", "Operation Error", + "vm nic lifecycle setup failed while selecting applicable NICs: demo"); + error.setGlobalErrorCode("ORG_ZSTACK_COMPUTE_VM_10331"); + error.setFormatArgs(new String[]{"demo"}); + + i18nService.localizeErrorCode(error, "zh_CN"); + Assert.assertTrue(error.getMessage().contains("虚拟机网卡生命周期")); + + ErrorCodeDiagnostic zhDiagnostic = ErrorCodeDiagnosticHelper.toDiagnostic(error); + Assert.assertEquals("Cloud", zhDiagnostic.getComponent()); + Assert.assertEquals("VM", zhDiagnostic.getCategory()); + Assert.assertEquals("10331", zhDiagnostic.getCode()); + Assert.assertEquals(error.getMessage(), zhDiagnostic.getMessage()); + Assert.assertNull(zhDiagnostic.getRawMessage().getCause()); + Assert.assertNull(zhDiagnostic.getRawMessage().getSolution()); + + i18nService.localizeErrorCode(error, "en_US"); + Assert.assertTrue(error.getMessage().contains("VM NIC lifecycle setup failed")); + + i18nService.localizeErrorCode(error, "pt_BR"); + Assert.assertTrue("unsupported locale should fallback to en_US", + error.getMessage().contains("VM NIC lifecycle setup failed")); + } +} diff --git a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java index 27c12405e70..29e9503506f 100644 --- a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java +++ b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java @@ -9950,6 +9950,14 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_COMPUTE_VM_10330 = "ORG_ZSTACK_COMPUTE_VM_10330"; + public static final String ORG_ZSTACK_COMPUTE_VM_10331 = "ORG_ZSTACK_COMPUTE_VM_10331"; + + public static final String ORG_ZSTACK_COMPUTE_VM_10332 = "ORG_ZSTACK_COMPUTE_VM_10332"; + + public static final String ORG_ZSTACK_COMPUTE_VM_10333 = "ORG_ZSTACK_COMPUTE_VM_10333"; + + public static final String ORG_ZSTACK_COMPUTE_VM_10334 = "ORG_ZSTACK_COMPUTE_VM_10334"; + public static final String ORG_ZSTACK_IDENTITY_LOGIN_10000 = "ORG_ZSTACK_IDENTITY_LOGIN_10000"; public static final String ORG_ZSTACK_STORAGE_VOLUME_BLOCK_EXPON_10000 = "ORG_ZSTACK_STORAGE_VOLUME_BLOCK_EXPON_10000"; @@ -10619,6 +10627,10 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_CORE_REST_10013 = "ORG_ZSTACK_CORE_REST_10013"; + public static final String ORG_ZSTACK_CORE_REST_10014 = "ORG_ZSTACK_CORE_REST_10014"; + + public static final String ORG_ZSTACK_CORE_REST_10015 = "ORG_ZSTACK_CORE_REST_10015"; + public static final String ORG_ZSTACK_LICENSE_10000 = "ORG_ZSTACK_LICENSE_10000"; public static final String ORG_ZSTACK_LICENSE_10001 = "ORG_ZSTACK_LICENSE_10001"; From fc9e9a5557d0b022f5e7f8ff8be35f01ed1bc7d6 Mon Sep 17 00:00:00 2001 From: "haoyu.ding" Date: Thu, 11 Jun 2026 16:49:16 +0800 Subject: [PATCH 15/54] [kvm]: Add KVM VM CPU hardware virtualization config DBImpact GlobalConfigImpact Resolves: ZSTAC-83181 Change-Id: I69756d697976666c78736a7165656975676b6969 --- VERSION | 2 +- conf/db/upgrade/V5.5.28__schema.sql | 15 + conf/globalConfig/kvm.xml | 8 + .../java/org/zstack/kvm/KVMAgentCommands.java | 10 + .../java/org/zstack/kvm/KVMGlobalConfig.java | 4 + .../src/main/java/org/zstack/kvm/KVMHost.java | 17 + .../java/org/zstack/kvm/KVMHostFactory.java | 11 + .../java/org/zstack/kvm/KVMHostUtils.java | 42 ++ .../kvm/KvmResourceConfigExtension.java | 13 +- .../vm/VmCpuHardwareVirtualizationCase.groovy | 464 ++++++++++++++++++ test/src/test/resources/globalConfig/kvm.xml | 8 + 11 files changed, 592 insertions(+), 2 deletions(-) create mode 100644 conf/db/upgrade/V5.5.28__schema.sql create mode 100644 test/src/test/groovy/org/zstack/test/integration/kvm/vm/VmCpuHardwareVirtualizationCase.groovy diff --git a/VERSION b/VERSION index aa87198d370..4c23f212761 100755 --- a/VERSION +++ b/VERSION @@ -1,3 +1,3 @@ MAJOR=5 MINOR=5 -UPDATE=22 +UPDATE=28 diff --git a/conf/db/upgrade/V5.5.28__schema.sql b/conf/db/upgrade/V5.5.28__schema.sql new file mode 100644 index 00000000000..51c35ebe810 --- /dev/null +++ b/conf/db/upgrade/V5.5.28__schema.sql @@ -0,0 +1,15 @@ +-- Do not filter by architecture here. The upgrade preserves previous Windows VM behavior across all architectures; +-- current kvmagent consumption is still gated by host CPU architecture at start time. +INSERT INTO `zstack`.`ResourceConfigVO` (`uuid`, `name`, `description`, `category`, `value`, `resourceUuid`, `resourceType`, `lastOpDate`, `createDate`) +SELECT REPLACE(UUID(), '-', ''), 'vm.cpu.hardwareVirtualization', 'enable or disable hardware virtualization feature in Windows guest cpuid', + 'kvm', 'true', vm.`uuid`, 'VmInstanceVO', NOW(), NOW() +FROM `zstack`.`VmInstanceVO` vm +WHERE (vm.`platform` IN ('Windows', 'WindowsVirtio') + OR LOWER(IFNULL(vm.`guestOsType`, '')) LIKE '%windows%') + AND NOT EXISTS ( + SELECT 1 + FROM `zstack`.`ResourceConfigVO` rc + WHERE rc.`resourceUuid` = vm.`uuid` + AND rc.`category` = 'kvm' + AND rc.`name` = 'vm.cpu.hardwareVirtualization' + ); diff --git a/conf/globalConfig/kvm.xml b/conf/globalConfig/kvm.xml index 76d53a5453a..7decad013fc 100755 --- a/conf/globalConfig/kvm.xml +++ b/conf/globalConfig/kvm.xml @@ -226,6 +226,14 @@ java.lang.Boolean + + kvm + vm.cpu.hardwareVirtualization + enable or disable hardware virtualization feature in Windows guest cpuid + false + java.lang.Boolean + + kvm vm.hyperv.clock.feature diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java index 5011d661659..d8b1ec0f9c0 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java @@ -2423,6 +2423,8 @@ public static class StartVmCmd extends vdiCmd implements VmAddOnsCmd { // cpuid hypervisor feature @GrayVersion(value = "5.0.0") private boolean cpuHypervisorFeature = true; + @GrayVersion(value = "5.5.28") + private Boolean cpuHardwareVirtualization; @GrayVersion(value = "5.0.0") private List oemStrings = new ArrayList<>(); @@ -2981,6 +2983,14 @@ public void setCpuHypervisorFeature(boolean cpuHypervisorFeature) { this.cpuHypervisorFeature = cpuHypervisorFeature; } + public Boolean getCpuHardwareVirtualization() { + return cpuHardwareVirtualization; + } + + public void setCpuHardwareVirtualization(Boolean cpuHardwareVirtualization) { + this.cpuHardwareVirtualization = cpuHardwareVirtualization; + } + public boolean isHypervClock() { return hypervClock; } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java index ee765aad40f..b5effa3048b 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMGlobalConfig.java @@ -94,6 +94,10 @@ public class KVMGlobalConfig { @BindResourceConfig({VmInstanceVO.class, ClusterVO.class}) public static GlobalConfig VM_CPU_HYPERVISOR_FEATURE = new GlobalConfig(CATEGORY, "vm.cpu.hypervisor.feature"); + @GlobalConfigValidation(validValues = {"true", "false"}) + @BindResourceConfig({VmInstanceVO.class}) + public static GlobalConfig VM_CPU_HARDWARE_VIRTUALIZATION = new GlobalConfig(CATEGORY, "vm.cpu.hardwareVirtualization"); + @GlobalConfigValidation(validValues = {"true", "false"}) @BindResourceConfig({VmInstanceVO.class}) public static GlobalConfig VM_HYPERV_CLOCK_FEATURE = new GlobalConfig(CATEGORY, "vm.hyperv.clock.feature"); diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java index 0c3ec6d2d23..de3206a8c86 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java @@ -4698,6 +4698,23 @@ protected void startVm(final VmInstanceSpec spec, final NeedReplyMessage msg, fi cmd.setCpuHypervisorFeature(rcf.getResourceConfigValue(KVMGlobalConfig.VM_CPU_HYPERVISOR_FEATURE, spec.getVmInventory().getUuid(), Boolean.class)); + String imageGuestOsType = spec.getImageSpec() == null || spec.getImageSpec().getInventory() == null ? + null : spec.getImageSpec().getInventory().getGuestOsType(); + if (KVMHostUtils.isWindowsVm(platform, spec.getVmInventory().getGuestOsType(), imageGuestOsType)) { + Boolean cpuHardwareVirtualization = rcf.getResourceConfigValue( + KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION, + spec.getVmInventory().getUuid(), + Boolean.class); + if (Boolean.FALSE.equals(cpuHardwareVirtualization)) { + if (KVMConstant.CPU_MODE_NONE.equals(vmCpuMode)) { + logger.info(String.format("skip disabling cpu hardware virtualization for vm[uuid:%s] because vm.cpuMode is none", + spec.getVmInventory().getUuid())); + } else { + // Only false is sent; true and null keep the kvmagent default CPU feature behavior. + cmd.setCpuHardwareVirtualization(cpuHardwareVirtualization); + } + } + } VirtualDeviceInfo memBalloon = new VirtualDeviceInfo(); memBalloon.setResourceUuid(vidm.MEM_BALLOON_UUID); diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java index 513add98a83..31b5baeb32c 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostFactory.java @@ -587,6 +587,17 @@ public void validateGlobalConfig(String category, String name, String oldValue, } }); + resourceConfig = rcf.getResourceConfig(KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.getIdentity()); + resourceConfig.installValidatorExtension((resourceUuid, oldValue, newValue) -> { + Boolean isWindowsVm = KVMHostUtils.isWindowsVmByUuid(resourceUuid); + if (isWindowsVm == null || isWindowsVm) { + return; + } + + throw new GlobalConfigException(String.format("ResourceConfig [category:%s, name:%s] only supports Windows VM, but vm[uuid:%s] is not Windows", + KVMGlobalConfig.CATEGORY, KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.getName(), resourceUuid)); + }); + restf.registerSyncHttpCallHandler(KVMConstant.KVM_RECONNECT_ME, ReconnectMeCmd.class, new SyncHttpCallHandler() { @Override public String handleSyncHttpCall(ReconnectMeCmd cmd) { diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostUtils.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostUtils.java index 82f040f15bb..1f634c3a3f9 100644 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostUtils.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostUtils.java @@ -4,6 +4,9 @@ import org.zstack.compute.host.HostSystemTags; import org.zstack.core.CoreGlobalProperty; import org.zstack.core.db.Q; +import org.zstack.header.image.ImagePlatform; +import org.zstack.header.image.ImageVO; +import org.zstack.header.image.ImageVO_; import org.zstack.header.network.l2.*; import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.tag.SystemTagVO; @@ -19,6 +22,7 @@ import org.zstack.utils.ssh.SshResult; import org.zstack.utils.ssh.SshShell; +import javax.persistence.Tuple; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; @@ -177,6 +181,44 @@ public static long calculateLibvirtRestartEchoTimeoutMillis(long vmCount) { return TimeUnit.SECONDS.toMillis(defaultTimeoutSeconds + extraSeconds); } + public static boolean isWindowsVm(String platform, String guestOsType) { + return isWindowsVm(platform, guestOsType, null); + } + + public static boolean isWindowsVm(String platform, String guestOsType, String imageGuestOsType) { + if (ImagePlatform.isType(platform, ImagePlatform.Windows, ImagePlatform.WindowsVirtio)) { + return true; + } + + // The VM guestOsType is authoritative after creation; image guestOsType is only a fallback. + String effectiveGuestOsType = guestOsType == null ? imageGuestOsType : guestOsType; + return effectiveGuestOsType != null && effectiveGuestOsType.toLowerCase(Locale.ROOT).contains("windows"); + } + + public static Boolean isWindowsVmByUuid(String vmUuid) { + Tuple vm = Q.New(VmInstanceVO.class) + .select(VmInstanceVO_.platform, VmInstanceVO_.guestOsType, VmInstanceVO_.imageUuid) + .eq(VmInstanceVO_.uuid, vmUuid) + .findTuple(); + if (vm == null) { + return null; + } + + String guestOsType = vm.get(1, String.class); + String imageGuestOsType = null; + if (guestOsType == null) { + String imageUuid = vm.get(2, String.class); + if (imageUuid != null) { + imageGuestOsType = Q.New(ImageVO.class) + .select(ImageVO_.guestOsType) + .eq(ImageVO_.uuid, imageUuid) + .findValue(); + } + } + + return isWindowsVm(vm.get(0, String.class), guestOsType, imageGuestOsType); + } + public static boolean shouldContinueReconnectOnAnsibleFailure(boolean isNewAdded, ErrorCode errorCode) { return !isNewAdded && isLibvirtSocketMaskSystemdTimeout(errorCode); } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KvmResourceConfigExtension.java b/plugin/kvm/src/main/java/org/zstack/kvm/KvmResourceConfigExtension.java index dde954bf718..917648f9209 100644 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KvmResourceConfigExtension.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KvmResourceConfigExtension.java @@ -1,8 +1,8 @@ package org.zstack.kvm; +import org.springframework.beans.factory.annotation.Autowired; import org.zstack.header.vm.ArchiveResourceConfigBundle; import org.zstack.header.vm.ResourceConfigMemorySnapshotExtensionPoint; -import org.springframework.beans.factory.annotation.Autowired; import org.zstack.resourceconfig.ResourceConfigFacade; import java.util.ArrayList; @@ -20,6 +20,17 @@ public List getNeedToArchiveRe bundle.setIdentity(KVMGlobalConfig.NESTED_VIRTUALIZATION.getIdentity()); bundle.setValue(rcf.getResourceConfigValue(KVMGlobalConfig.NESTED_VIRTUALIZATION, resourceUuid, String.class)); bundleList.add(bundle); + + if (Boolean.TRUE.equals(KVMHostUtils.isWindowsVmByUuid(resourceUuid))) { + Boolean cpuHardwareVirtualization = rcf.getResourceConfigValue(KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION, + resourceUuid, Boolean.class); + ArchiveResourceConfigBundle.ResourceConfigBundle cpuHardwareVirtualizationBundle = + new ArchiveResourceConfigBundle.ResourceConfigBundle(); + cpuHardwareVirtualizationBundle.setResourceUuid(resourceUuid); + cpuHardwareVirtualizationBundle.setIdentity(KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.getIdentity()); + cpuHardwareVirtualizationBundle.setValue(cpuHardwareVirtualization.toString()); + bundleList.add(cpuHardwareVirtualizationBundle); + } return bundleList; } } diff --git a/test/src/test/groovy/org/zstack/test/integration/kvm/vm/VmCpuHardwareVirtualizationCase.groovy b/test/src/test/groovy/org/zstack/test/integration/kvm/vm/VmCpuHardwareVirtualizationCase.groovy new file mode 100644 index 00000000000..317abe73396 --- /dev/null +++ b/test/src/test/groovy/org/zstack/test/integration/kvm/vm/VmCpuHardwareVirtualizationCase.groovy @@ -0,0 +1,464 @@ +package org.zstack.test.integration.kvm.vm + +import org.springframework.http.HttpEntity +import org.zstack.core.db.Q +import org.zstack.core.db.SQL +import org.zstack.header.image.ImagePlatform +import org.zstack.header.vm.VmInstanceVO +import org.zstack.header.vm.VmInstanceVO_ +import org.zstack.kvm.KVMAgentCommands +import org.zstack.kvm.KVMConstant +import org.zstack.kvm.KVMGlobalConfig +import org.zstack.resourceconfig.ResourceConfigVO +import org.zstack.resourceconfig.ResourceConfigVO_ +import org.zstack.header.vm.VmCreationStrategy +import org.zstack.sdk.VmInstanceInventory +import org.zstack.test.integration.kvm.KvmTest +import org.zstack.testlib.EnvSpec +import org.zstack.testlib.SubCase +import org.zstack.utils.data.SizeUnit +import org.zstack.utils.gson.JSONObjectUtil + +class VmCpuHardwareVirtualizationCase extends SubCase { + private static final String CPU_MODE_HOST_MODEL_TAG = "resourceConfig::kvm::vm.cpuMode::host-model" + private static final String CPU_MODE_NONE_TAG = "resourceConfig::kvm::vm.cpuMode::none" + private static final String CPU_HARDWARE_VIRTUALIZATION_TRUE_TAG = "resourceConfig::kvm::vm.cpu.hardwareVirtualization::true" + private static final String CPU_HARDWARE_VIRTUALIZATION_FALSE_TAG = "resourceConfig::kvm::vm.cpu.hardwareVirtualization::false" + + EnvSpec env + + @Override + void clean() { + env.delete() + } + + @Override + void setup() { + useSpring(KvmTest.springSpec) + } + + @Override + void environment() { + env = env { + instanceOffering { + name = "instanceOffering" + memory = SizeUnit.GIGABYTE.toByte(2) + cpu = 2 + } + + sftpBackupStorage { + name = "sftp" + url = "/sftp" + username = "root" + password = "password" + hostname = "localhost" + + image { + name = "windows-image" + url = "http://zstack.org/download/windows.qcow2" + platform = ImagePlatform.Windows + guestOsType = "Windows Server 2025" + architecture = "x86_64" + } + + image { + name = "windows-guest-os-image" + url = "http://zstack.org/download/windows-guest-os.qcow2" + platform = ImagePlatform.Linux + guestOsType = "Windows Server 2022" + architecture = "x86_64" + } + + image { + name = "linux-image" + url = "http://zstack.org/download/linux.qcow2" + platform = ImagePlatform.Linux + guestOsType = "Linux" + architecture = "x86_64" + } + } + + zone { + name = "zone" + description = "test" + + cluster { + name = "cluster" + hypervisorType = "KVM" + + kvm { + name = "kvm" + managementIp = "localhost" + username = "root" + password = "password" + } + + attachPrimaryStorage("local") + attachL2Network("l2") + } + + attachBackupStorage("sftp") + + localPrimaryStorage { + name = "local" + url = "/local_ps" + } + + l2NoVlanNetwork { + name = "l2" + physicalInterface = "eth0" + + l3Network { + name = "l3" + + ip { + startIp = "192.168.100.10" + endIp = "192.168.100.100" + netmask = "255.255.255.0" + gateway = "192.168.100.1" + } + } + } + } + } + } + + @Override + void test() { + env.create() + def originalValue = KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.value() + try { + KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.updateValue(false) + testWindowsVmWithCpuModeNoneDoesNotSendCpuHardwareVirtualization() + + testWindowsVmUsesGlobalFallbackAndResourceConfigOverrides() + testNewWindowsVmUsesGlobalTrueFallback() + testCreateWindowsVmWithExplicitResourceConfig() + testJustCreateWindowsVmUsesGlobalOnStart() + testGuestOsTypeWindowsUsesGlobalFallback() + testImageGuestOsTypeFallbackWhenVmGuestOsTypeMissing() + testNonWindowsVmRejectsConfig() + testNonWindowsVmChangedToWindowsUsesGlobalFallback() + } finally { + KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.updateValue(originalValue) + } + } + + void testWindowsVmWithCpuModeNoneDoesNotSendCpuHardwareVirtualization() { + def vm = createVmAndCaptureStartCmd("windows-cpu-mode-none-vm", "windows-image", + [CPU_MODE_NONE_TAG], false, { KVMAgentCommands.StartVmCmd cmd -> + // KVMHost maps the effective vm.cpuMode resource config to StartVmCmd.nestedVirtualization. + assert cmd.nestedVirtualization == KVMConstant.CPU_MODE_NONE + assert cmd.cpuHardwareVirtualization == null + }) + + assert explicitCpuHardwareVirtualizationValue(vm.uuid) == null + + destroyVmInstance { uuid = vm.uuid } + expungeVmInstance { uuid = vm.uuid } + } + + void testWindowsVmUsesGlobalFallbackAndResourceConfigOverrides() { + def vm = createVmAndCaptureStartCmd("windows-vm", "windows-image", { KVMAgentCommands.StartVmCmd cmd -> + assert cmd.nestedVirtualization == KVMConstant.CPU_MODE_HOST_MODEL + assert cmd.cpuHardwareVirtualization == false + }) + + assert explicitCpuHardwareVirtualizationValue(vm.uuid) == null + + KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.updateValue(true) + assertStartCmdOnReboot(vm.uuid) { KVMAgentCommands.StartVmCmd cmd -> + assert cmd.cpuHardwareVirtualization == null + } + + KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.updateValue(false) + + updateResourceConfig { + category = KVMGlobalConfig.CATEGORY + name = KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.name + value = Boolean.TRUE.toString() + resourceUuid = vm.uuid + } + + assert explicitCpuHardwareVirtualizationValue(vm.uuid) == Boolean.TRUE.toString() + assertStartCmdOnReboot(vm.uuid) { KVMAgentCommands.StartVmCmd cmd -> + assert cmd.cpuHardwareVirtualization == null + } + + updateResourceConfig { + category = KVMGlobalConfig.CATEGORY + name = KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.name + value = Boolean.FALSE.toString() + resourceUuid = vm.uuid + } + + assert explicitCpuHardwareVirtualizationValue(vm.uuid) == Boolean.FALSE.toString() + assertStartCmdOnReboot(vm.uuid) { KVMAgentCommands.StartVmCmd cmd -> + assert cmd.cpuHardwareVirtualization == false + } + + deleteResourceConfig { + category = KVMGlobalConfig.CATEGORY + name = KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.name + resourceUuid = vm.uuid + } + + assert explicitCpuHardwareVirtualizationValue(vm.uuid) == null + assertStartCmdOnReboot(vm.uuid) { KVMAgentCommands.StartVmCmd cmd -> + assert cmd.cpuHardwareVirtualization == false + } + + destroyVmInstance { uuid = vm.uuid } + expungeVmInstance { uuid = vm.uuid } + } + + void testNewWindowsVmUsesGlobalTrueFallback() { + KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.updateValue(true) + + def vm = createVmAndCaptureStartCmd("windows-global-true-vm", "windows-image", { KVMAgentCommands.StartVmCmd cmd -> + assert cmd.cpuHardwareVirtualization == null + }) + + assert explicitCpuHardwareVirtualizationValue(vm.uuid) == null + + destroyVmInstance { uuid = vm.uuid } + expungeVmInstance { uuid = vm.uuid } + KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.updateValue(false) + } + + void testCreateWindowsVmWithExplicitResourceConfig() { + KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.updateValue(false) + def trueVm = createVmAndCaptureStartCmd("windows-create-true-vm", "windows-image", + [CPU_HARDWARE_VIRTUALIZATION_TRUE_TAG], { KVMAgentCommands.StartVmCmd cmd -> + assert cmd.cpuHardwareVirtualization == null + }) + assert explicitCpuHardwareVirtualizationValue(trueVm.uuid) == Boolean.TRUE.toString() + destroyVmInstance { uuid = trueVm.uuid } + expungeVmInstance { uuid = trueVm.uuid } + + KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.updateValue(true) + def falseVm = createVmAndCaptureStartCmd("windows-create-false-vm", "windows-image", + [CPU_HARDWARE_VIRTUALIZATION_FALSE_TAG], { KVMAgentCommands.StartVmCmd cmd -> + assert cmd.cpuHardwareVirtualization == false + }) + assert explicitCpuHardwareVirtualizationValue(falseVm.uuid) == Boolean.FALSE.toString() + destroyVmInstance { uuid = falseVm.uuid } + expungeVmInstance { uuid = falseVm.uuid } + + KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.updateValue(false) + } + + void testJustCreateWindowsVmUsesGlobalOnStart() { + KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.updateValue(true) + + def image = env.inventoryByName("windows-image") + def l3 = env.inventoryByName("l3") + def instanceOffering = env.inventoryByName("instanceOffering") + + def vm = createVmInstance { + name = "windows-just-create-vm" + imageUuid = image.uuid + l3NetworkUuids = [l3.uuid] + instanceOfferingUuid = instanceOffering.uuid + strategy = VmCreationStrategy.JustCreate.toString() + systemTags = [CPU_MODE_HOST_MODEL_TAG] + } as VmInstanceInventory + + assert explicitCpuHardwareVirtualizationValue(vm.uuid) == null + + KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.updateValue(false) + KVMAgentCommands.StartVmCmd startCmd = captureStartCmd { + startVmInstance { + uuid = vm.uuid + } + } + assert startCmd.cpuHardwareVirtualization == false + + destroyVmInstance { uuid = vm.uuid } + expungeVmInstance { uuid = vm.uuid } + KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.updateValue(false) + } + + void testGuestOsTypeWindowsUsesGlobalFallback() { + KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.updateValue(false) + def vm = createVmAndCaptureStartCmd("windows-guest-os-vm", "windows-guest-os-image", { KVMAgentCommands.StartVmCmd cmd -> + assert cmd.cpuHardwareVirtualization == false + }) + + assert explicitCpuHardwareVirtualizationValue(vm.uuid) == null + + destroyVmInstance { uuid = vm.uuid } + expungeVmInstance { uuid = vm.uuid } + } + + void testImageGuestOsTypeFallbackWhenVmGuestOsTypeMissing() { + KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.updateValue(false) + + def image = env.inventoryByName("windows-guest-os-image") + def l3 = env.inventoryByName("l3") + def instanceOffering = env.inventoryByName("instanceOffering") + + def vm = createVmInstance { + name = "image-guest-os-fallback-vm" + imageUuid = image.uuid + l3NetworkUuids = [l3.uuid] + instanceOfferingUuid = instanceOffering.uuid + strategy = VmCreationStrategy.JustCreate.toString() + systemTags = [CPU_MODE_HOST_MODEL_TAG] + } as VmInstanceInventory + + assert Q.New(VmInstanceVO.class) + .select(VmInstanceVO_.platform) + .eq(VmInstanceVO_.uuid, vm.uuid) + .findValue() == ImagePlatform.Linux.toString() + + SQL.New(VmInstanceVO.class) + .eq(VmInstanceVO_.uuid, vm.uuid) + .set(VmInstanceVO_.guestOsType, null) + .update() + + updateResourceConfig { + category = KVMGlobalConfig.CATEGORY + name = KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.name + value = Boolean.FALSE.toString() + resourceUuid = vm.uuid + } + assert explicitCpuHardwareVirtualizationValue(vm.uuid) == Boolean.FALSE.toString() + + KVMAgentCommands.StartVmCmd startCmd = captureStartCmd { + startVmInstance { + uuid = vm.uuid + } + } + assert startCmd.cpuHardwareVirtualization == false + + destroyVmInstance { uuid = vm.uuid } + expungeVmInstance { uuid = vm.uuid } + } + + void testNonWindowsVmRejectsConfig() { + KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.updateValue(false) + def vm = createVmAndCaptureStartCmd("linux-vm", "linux-image", { KVMAgentCommands.StartVmCmd cmd -> + assert cmd.cpuHardwareVirtualization == null + }) + + assert explicitCpuHardwareVirtualizationValue(vm.uuid) == null + + expect(AssertionError.class) { + updateResourceConfig { + category = KVMGlobalConfig.CATEGORY + name = KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.name + value = Boolean.FALSE.toString() + resourceUuid = vm.uuid + } + } + assert explicitCpuHardwareVirtualizationValue(vm.uuid) == null + + destroyVmInstance { uuid = vm.uuid } + expungeVmInstance { uuid = vm.uuid } + + expect(AssertionError.class) { + def image = env.inventoryByName("linux-image") + def l3 = env.inventoryByName("l3") + def instanceOffering = env.inventoryByName("instanceOffering") + createVmInstance { + name = "linux-explicit-false-vm" + imageUuid = image.uuid + l3NetworkUuids = [l3.uuid] + instanceOfferingUuid = instanceOffering.uuid + systemTags = [CPU_MODE_HOST_MODEL_TAG, CPU_HARDWARE_VIRTUALIZATION_FALSE_TAG] + } + } + } + + void testNonWindowsVmChangedToWindowsUsesGlobalFallback() { + KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.updateValue(false) + + def vm = createVmAndCaptureStartCmd("linux-to-windows-vm", "linux-image", { KVMAgentCommands.StartVmCmd cmd -> + assert cmd.cpuHardwareVirtualization == null + }) + + assert explicitCpuHardwareVirtualizationValue(vm.uuid) == null + + updateVmInstance { + uuid = vm.uuid + platform = ImagePlatform.Windows.toString() + guestOsType = "Windows Server 2025" + } + + assert explicitCpuHardwareVirtualizationValue(vm.uuid) == null + assertStartCmdOnReboot(vm.uuid) { KVMAgentCommands.StartVmCmd cmd -> + assert cmd.cpuHardwareVirtualization == false + } + + destroyVmInstance { uuid = vm.uuid } + expungeVmInstance { uuid = vm.uuid } + } + + private VmInstanceInventory createVmAndCaptureStartCmd(String vmName, String imageName, Closure cmdAssertion) { + return createVmAndCaptureStartCmd(vmName, imageName, null, true, cmdAssertion) + } + + private VmInstanceInventory createVmAndCaptureStartCmd(String vmName, String imageName, List systemTags, Closure cmdAssertion) { + return createVmAndCaptureStartCmd(vmName, imageName, systemTags, true, cmdAssertion) + } + + private VmInstanceInventory createVmAndCaptureStartCmd(String vmName, String imageName, List systemTags, + boolean useHostModelCpuMode, Closure cmdAssertion) { + def image = env.inventoryByName(imageName) + def l3 = env.inventoryByName("l3") + def instanceOffering = env.inventoryByName("instanceOffering") + + VmInstanceInventory vm = null + KVMAgentCommands.StartVmCmd startCmd = captureStartCmd { + vm = createVmInstance { + name = vmName + imageUuid = image.uuid + l3NetworkUuids = [l3.uuid] + instanceOfferingUuid = instanceOffering.uuid + List mergedSystemTags = [] + if (useHostModelCpuMode) { + mergedSystemTags.add(CPU_MODE_HOST_MODEL_TAG) + } + if (systemTags != null) { + mergedSystemTags.addAll(systemTags) + } + if (!mergedSystemTags.isEmpty()) { + delegate.systemTags = mergedSystemTags + } + } as VmInstanceInventory + } + + cmdAssertion(startCmd) + return vm + } + + private void assertStartCmdOnReboot(String vmUuid, Closure cmdAssertion) { + KVMAgentCommands.StartVmCmd startCmd = captureStartCmd { + rebootVmInstance { + uuid = vmUuid + } + } + cmdAssertion(startCmd) + } + + private KVMAgentCommands.StartVmCmd captureStartCmd(Closure action) { + KVMAgentCommands.StartVmCmd startCmd = null + env.afterSimulator(KVMConstant.KVM_START_VM_PATH) { rsp, HttpEntity e -> + startCmd = JSONObjectUtil.toObject(e.body, KVMAgentCommands.StartVmCmd.class) + return rsp + } + action() + assert startCmd != null + return startCmd + } + + private String explicitCpuHardwareVirtualizationValue(String vmUuid) { + return Q.New(ResourceConfigVO.class) + .select(ResourceConfigVO_.value) + .eq(ResourceConfigVO_.category, KVMGlobalConfig.CATEGORY) + .eq(ResourceConfigVO_.name, KVMGlobalConfig.VM_CPU_HARDWARE_VIRTUALIZATION.name) + .eq(ResourceConfigVO_.resourceUuid, vmUuid) + .findValue() + } +} diff --git a/test/src/test/resources/globalConfig/kvm.xml b/test/src/test/resources/globalConfig/kvm.xml index 6fb42631828..26be39a8b2d 100755 --- a/test/src/test/resources/globalConfig/kvm.xml +++ b/test/src/test/resources/globalConfig/kvm.xml @@ -225,6 +225,14 @@ java.lang.Boolean + + kvm + vm.cpu.hardwareVirtualization + enable or disable hardware virtualization feature in Windows guest cpuid + false + java.lang.Boolean + + kvm vm.hyperv.clock.feature From 48df7611e42eb3b675f9c8d7ea3affe192e26e50 Mon Sep 17 00:00:00 2001 From: "xinhao.huang" Date: Fri, 12 Jun 2026 13:26:16 +0800 Subject: [PATCH 16/54] [pci-device]: clean duplicate mdev refs Clean duplicate PciDeviceMdevSpecRef rows during 5.5.28 upgrade. Tested: mvn -pl plugin/kvm -am -DskipTests install Resolves: ZSTAC-84442 Change-Id: I5e3d6db69ea05acef43f59aef850be82eaeab451 --- conf/db/upgrade/V5.5.28__schema.sql | 66 +++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/conf/db/upgrade/V5.5.28__schema.sql b/conf/db/upgrade/V5.5.28__schema.sql index 51c35ebe810..4ce2da95ba3 100644 --- a/conf/db/upgrade/V5.5.28__schema.sql +++ b/conf/db/upgrade/V5.5.28__schema.sql @@ -13,3 +13,69 @@ WHERE (vm.`platform` IN ('Windows', 'WindowsVirtio') AND rc.`category` = 'kvm' AND rc.`name` = 'vm.cpu.hardwareVirtualization' ); + +UPDATE `zstack`.`PciDeviceMdevSpecRefVO` keepRef +JOIN ( + SELECT `pciDeviceUuid`, `mdevSpecUuid`, MAX(`id`) AS `keepId`, MAX(`effective`) AS `effective` + FROM `zstack`.`PciDeviceMdevSpecRefVO` + GROUP BY `pciDeviceUuid`, `mdevSpecUuid` +) groupedRef ON keepRef.`id` = groupedRef.`keepId` +SET keepRef.`effective` = groupedRef.`effective`; + +DELETE duplicateRef FROM `zstack`.`PciDeviceMdevSpecRefVO` duplicateRef +JOIN `zstack`.`PciDeviceMdevSpecRefVO` keepRef + ON duplicateRef.`pciDeviceUuid` = keepRef.`pciDeviceUuid` + AND duplicateRef.`mdevSpecUuid` = keepRef.`mdevSpecUuid` + AND duplicateRef.`id` < keepRef.`id`; + +UPDATE `zstack`.`PciDeviceMdevSpecRefVO` ref +JOIN ( + SELECT activeRef.`id` + FROM `zstack`.`PciDeviceMdevSpecRefVO` activeRef + JOIN ( + SELECT `pciDeviceUuid` + FROM `zstack`.`PciDeviceMdevSpecRefVO` + WHERE `effective` = 1 + GROUP BY `pciDeviceUuid` + HAVING COUNT(*) > 1 + ) duplicatedPci ON activeRef.`pciDeviceUuid` = duplicatedPci.`pciDeviceUuid` + WHERE activeRef.`effective` = 1 + AND NOT EXISTS ( + SELECT 1 + FROM `zstack`.`MdevDeviceVO` mdev + WHERE mdev.`parentUuid` = activeRef.`pciDeviceUuid` + AND mdev.`mdevSpecUuid` = activeRef.`mdevSpecUuid` + ) +) staleRef ON ref.`id` = staleRef.`id` +SET ref.`effective` = 0; + +UPDATE `zstack`.`PciDeviceMdevSpecRefVO` oldRef +JOIN `zstack`.`PciDeviceMdevSpecRefVO` newRef + ON oldRef.`pciDeviceUuid` = newRef.`pciDeviceUuid` + AND oldRef.`effective` = 1 + AND newRef.`effective` = 1 + AND oldRef.`id` < newRef.`id` +SET oldRef.`effective` = 0; + +DROP PROCEDURE IF EXISTS addPciDeviceMdevSpecRefUniqueKey; +DELIMITER $$ +CREATE PROCEDURE addPciDeviceMdevSpecRefUniqueKey() +BEGIN + DECLARE index_count INT DEFAULT 0; + + SELECT COUNT(*) INTO index_count + FROM information_schema.statistics + WHERE table_schema = 'zstack' + AND table_name = 'PciDeviceMdevSpecRefVO' + AND index_name = 'ukPciDeviceMdevSpecRefVOPciUuidMdevSpecUuid'; + + IF index_count < 1 THEN + ALTER TABLE `zstack`.`PciDeviceMdevSpecRefVO` + ADD UNIQUE KEY `ukPciDeviceMdevSpecRefVOPciUuidMdevSpecUuid` (`pciDeviceUuid`, `mdevSpecUuid`); + END IF; + + SELECT CURTIME(); +END $$ +DELIMITER ; +CALL addPciDeviceMdevSpecRefUniqueKey(); +DROP PROCEDURE IF EXISTS addPciDeviceMdevSpecRefUniqueKey; From 4d4096ad9ac1ace71bbdbb7d251c67c205237c81 Mon Sep 17 00:00:00 2001 From: "hanyu.liang" Date: Mon, 1 Jun 2026 16:06:32 +0800 Subject: [PATCH 17/54] [license]: add license auth info api Resolves: ZCF-3922 Change-Id: If9579e1648bc614985e13855fc84ade1a49fc38a (cherry picked from commit 4bd879eb2a8fb0cd729cef0a5776bf4cd1b15ecc) --- sdk/src/main/java/SourceClassMap.java | 4 + .../GetLicenseAuthorizationInfoAction.java | 86 ++++++++++ .../GetLicenseAuthorizationInfoResult.java | 14 ++ .../zstack/sdk/LicenseAuthorizationInfo.java | 111 +++++++++++++ .../LicenseAuthorizationQuotaInventory.java | 119 ++++++++++++++ .../sdk/RegisterLicenseClientAction.java | 3 + .../java/org/zstack/testlib/ApiHelper.groovy | 147 +++++++++++------- 7 files changed, 424 insertions(+), 60 deletions(-) create mode 100644 sdk/src/main/java/org/zstack/sdk/GetLicenseAuthorizationInfoAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/GetLicenseAuthorizationInfoResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/LicenseAuthorizationInfo.java create mode 100644 sdk/src/main/java/org/zstack/sdk/LicenseAuthorizationQuotaInventory.java diff --git a/sdk/src/main/java/SourceClassMap.java b/sdk/src/main/java/SourceClassMap.java index 63756cc5fa5..4fdc5791b47 100644 --- a/sdk/src/main/java/SourceClassMap.java +++ b/sdk/src/main/java/SourceClassMap.java @@ -514,6 +514,8 @@ public class SourceClassMap { put("org.zstack.license.AdditionalLicenseInfo", "org.zstack.sdk.AdditionalLicenseInfo"); put("org.zstack.license.AdditionalLicenseType", "org.zstack.sdk.AdditionalLicenseType"); put("org.zstack.license.LicenseAddOnInventory", "org.zstack.sdk.LicenseAddOnInventory"); + put("org.zstack.license.LicenseAuthorizationInfo", "org.zstack.sdk.LicenseAuthorizationInfo"); + put("org.zstack.license.LicenseAuthorizationQuotaInventory", "org.zstack.sdk.LicenseAuthorizationQuotaInventory"); put("org.zstack.license.LicenseInventory", "org.zstack.sdk.LicenseInventory"); put("org.zstack.license.UKeyInventory", "org.zstack.sdk.UKeyInventory"); put("org.zstack.license.UKeyStatus", "org.zstack.sdk.UKeyStatus"); @@ -1233,6 +1235,8 @@ public class SourceClassMap { put("org.zstack.sdk.LdapResourceRefInventory", "org.zstack.login.entity.LdapResourceRefInventory"); put("org.zstack.sdk.LdapServerInventory", "org.zstack.ldap.LdapServerInventory"); put("org.zstack.sdk.LicenseAddOnInventory", "org.zstack.license.LicenseAddOnInventory"); + put("org.zstack.sdk.LicenseAuthorizationInfo", "org.zstack.license.LicenseAuthorizationInfo"); + put("org.zstack.sdk.LicenseAuthorizationQuotaInventory", "org.zstack.license.LicenseAuthorizationQuotaInventory"); put("org.zstack.sdk.LicenseInventory", "org.zstack.license.LicenseInventory"); put("org.zstack.sdk.LoadBalancerDataInventory", "org.zstack.observabilityServer.service.loadBalancer.LoadBalancerDataInventory"); put("org.zstack.sdk.LoadBalancerInventory", "org.zstack.network.service.lb.LoadBalancerInventory"); diff --git a/sdk/src/main/java/org/zstack/sdk/GetLicenseAuthorizationInfoAction.java b/sdk/src/main/java/org/zstack/sdk/GetLicenseAuthorizationInfoAction.java new file mode 100644 index 00000000000..cb7199f2c17 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/GetLicenseAuthorizationInfoAction.java @@ -0,0 +1,86 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class GetLicenseAuthorizationInfoAction extends AbstractAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.GetLicenseAuthorizationInfoResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + @Param(required = false) + public java.util.List systemTags; + + @Param(required = false) + public java.util.List userTags; + + @NonAPIParam + public boolean isSuppressCredentialCheck = true; + + @Param(required = false) + public String requestIp; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.GetLicenseAuthorizationInfoResult value = res.getResult(org.zstack.sdk.GetLicenseAuthorizationInfoResult.class); + ret.value = value == null ? new org.zstack.sdk.GetLicenseAuthorizationInfoResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "GET"; + info.path = "/licenses/authorization-info"; + info.needSession = false; + info.needPoll = false; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/GetLicenseAuthorizationInfoResult.java b/sdk/src/main/java/org/zstack/sdk/GetLicenseAuthorizationInfoResult.java new file mode 100644 index 00000000000..dd109e3c5ec --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/GetLicenseAuthorizationInfoResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + +import org.zstack.sdk.LicenseAuthorizationInfo; + +public class GetLicenseAuthorizationInfoResult { + public LicenseAuthorizationInfo info; + public void setInfo(LicenseAuthorizationInfo info) { + this.info = info; + } + public LicenseAuthorizationInfo getInfo() { + return this.info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/LicenseAuthorizationInfo.java b/sdk/src/main/java/org/zstack/sdk/LicenseAuthorizationInfo.java new file mode 100644 index 00000000000..f01f92de62e --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/LicenseAuthorizationInfo.java @@ -0,0 +1,111 @@ +package org.zstack.sdk; + +import org.zstack.sdk.LicenseInventory; + +public class LicenseAuthorizationInfo { + + public java.lang.String authType; + public void setAuthType(java.lang.String authType) { + this.authType = authType; + } + public java.lang.String getAuthType() { + return this.authType; + } + + public boolean connected; + public void setConnected(boolean connected) { + this.connected = connected; + } + public boolean getConnected() { + return this.connected; + } + + public java.lang.String serverUrl; + public void setServerUrl(java.lang.String serverUrl) { + this.serverUrl = serverUrl; + } + public java.lang.String getServerUrl() { + return this.serverUrl; + } + + public java.lang.String serverVersion; + public void setServerVersion(java.lang.String serverVersion) { + this.serverVersion = serverVersion; + } + public java.lang.String getServerVersion() { + return this.serverVersion; + } + + public java.lang.String siteUuid; + public void setSiteUuid(java.lang.String siteUuid) { + this.siteUuid = siteUuid; + } + public java.lang.String getSiteUuid() { + return this.siteUuid; + } + + public java.lang.String siteName; + public void setSiteName(java.lang.String siteName) { + this.siteName = siteName; + } + public java.lang.String getSiteName() { + return this.siteName; + } + + public java.lang.String productLine; + public void setProductLine(java.lang.String productLine) { + this.productLine = productLine; + } + public java.lang.String getProductLine() { + return this.productLine; + } + + public java.lang.String snapshotState; + public void setSnapshotState(java.lang.String snapshotState) { + this.snapshotState = snapshotState; + } + public java.lang.String getSnapshotState() { + return this.snapshotState; + } + + public java.lang.String source; + public void setSource(java.lang.String source) { + this.source = source; + } + public java.lang.String getSource() { + return this.source; + } + + public java.lang.String lastSyncDate; + public void setLastSyncDate(java.lang.String lastSyncDate) { + this.lastSyncDate = lastSyncDate; + } + public java.lang.String getLastSyncDate() { + return this.lastSyncDate; + } + + public LicenseInventory platformLicense; + public void setPlatformLicense(LicenseInventory platformLicense) { + this.platformLicense = platformLicense; + } + public LicenseInventory getPlatformLicense() { + return this.platformLicense; + } + + public java.util.List addOns; + public void setAddOns(java.util.List addOns) { + this.addOns = addOns; + } + public java.util.List getAddOns() { + return this.addOns; + } + + public java.util.List quotas; + public void setQuotas(java.util.List quotas) { + this.quotas = quotas; + } + public java.util.List getQuotas() { + return this.quotas; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/LicenseAuthorizationQuotaInventory.java b/sdk/src/main/java/org/zstack/sdk/LicenseAuthorizationQuotaInventory.java new file mode 100644 index 00000000000..89f4718a3d1 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/LicenseAuthorizationQuotaInventory.java @@ -0,0 +1,119 @@ +package org.zstack.sdk; + + + +public class LicenseAuthorizationQuotaInventory { + + public java.lang.String authEntity; + public void setAuthEntity(java.lang.String authEntity) { + this.authEntity = authEntity; + } + public java.lang.String getAuthEntity() { + return this.authEntity; + } + + public java.lang.String name; + public void setName(java.lang.String name) { + this.name = name; + } + public java.lang.String getName() { + return this.name; + } + + public java.lang.String product; + public void setProduct(java.lang.String product) { + this.product = product; + } + public java.lang.String getProduct() { + return this.product; + } + + public java.lang.String module; + public void setModule(java.lang.String module) { + this.module = module; + } + public java.lang.String getModule() { + return this.module; + } + + public java.lang.String licenseType; + public void setLicenseType(java.lang.String licenseType) { + this.licenseType = licenseType; + } + public java.lang.String getLicenseType() { + return this.licenseType; + } + + public java.lang.String quotaType; + public void setQuotaType(java.lang.String quotaType) { + this.quotaType = quotaType; + } + public java.lang.String getQuotaType() { + return this.quotaType; + } + + public long quota; + public void setQuota(long quota) { + this.quota = quota; + } + public long getQuota() { + return this.quota; + } + + public long used; + public void setUsed(long used) { + this.used = used; + } + public long getUsed() { + return this.used; + } + + public long available; + public void setAvailable(long available) { + this.available = available; + } + public long getAvailable() { + return this.available; + } + + public java.lang.String issuedDate; + public void setIssuedDate(java.lang.String issuedDate) { + this.issuedDate = issuedDate; + } + public java.lang.String getIssuedDate() { + return this.issuedDate; + } + + public java.lang.String expiredDate; + public void setExpiredDate(java.lang.String expiredDate) { + this.expiredDate = expiredDate; + } + public java.lang.String getExpiredDate() { + return this.expiredDate; + } + + public boolean expired; + public void setExpired(boolean expired) { + this.expired = expired; + } + public boolean getExpired() { + return this.expired; + } + + public boolean platform; + public void setPlatform(boolean platform) { + this.platform = platform; + } + public boolean getPlatform() { + return this.platform; + } + + public java.lang.String status; + public void setStatus(java.lang.String status) { + this.status = status; + } + public java.lang.String getStatus() { + return this.status; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/RegisterLicenseClientAction.java b/sdk/src/main/java/org/zstack/sdk/RegisterLicenseClientAction.java index aa6c6904668..a937ffab4ed 100644 --- a/sdk/src/main/java/org/zstack/sdk/RegisterLicenseClientAction.java +++ b/sdk/src/main/java/org/zstack/sdk/RegisterLicenseClientAction.java @@ -37,6 +37,9 @@ public Result throwExceptionIfError() { @Param(required = false, validRegexValues = "^[a-f0-9]{32}$", nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.lang.String siteUuid; + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String siteAccessKey; + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.lang.String baseAuthEntity; diff --git a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy index a09908b89ca..a58475e1650 100644 --- a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy +++ b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy @@ -21,20 +21,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -3689,6 +3689,33 @@ abstract class ApiHelper { } + def attachDGpuToVm(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.AttachDGpuToVmAction.class) Closure c) { + def a = new org.zstack.sdk.AttachDGpuToVmAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def attachDataVolumeToHost(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.AttachDataVolumeToHostAction.class) Closure c) { def a = new org.zstack.sdk.AttachDataVolumeToHostAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -18620,33 +18647,6 @@ abstract class ApiHelper { } - def attachDGpuToVm(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.AttachDGpuToVmAction.class) Closure c) { - def a = new org.zstack.sdk.AttachDGpuToVmAction() - a.sessionId = Test.currentEnvSpec?.session?.uuid - c.resolveStrategy = Closure.OWNER_FIRST - c.delegate = a - c() - - - if (System.getProperty("apipath") != null) { - if (a.apiId == null) { - a.apiId = Platform.uuid - } - - def tracker = new ApiPathTracker(a.apiId) - def out = errorOut(a.call()) - def path = tracker.getApiPath() - if (!path.isEmpty()) { - Test.apiPaths[a.class.name] = path.join(" --->\n") - } - - return out - } else { - return errorOut(a.call()) - } - } - - def detachBaremetalPxeServerFromCluster(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.DetachBaremetalPxeServerFromClusterAction.class) Closure c) { def a = new org.zstack.sdk.DetachBaremetalPxeServerFromClusterAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -23696,26 +23696,53 @@ abstract class ApiHelper { } + def getLicenseAuthorizationInfo(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.GetLicenseAuthorizationInfoAction.class) Closure c) { + def a = new org.zstack.sdk.GetLicenseAuthorizationInfoAction() + + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def getLicenseCapabilities(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.GetLicenseCapabilitiesAction.class) Closure c) { def a = new org.zstack.sdk.GetLicenseCapabilitiesAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -40683,33 +40710,6 @@ abstract class ApiHelper { } - def updateVmDGpu(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateVmDGpuAction.class) Closure c) { - def a = new org.zstack.sdk.UpdateVmDGpuAction() - a.sessionId = Test.currentEnvSpec?.session?.uuid - c.resolveStrategy = Closure.OWNER_FIRST - c.delegate = a - c() - - - if (System.getProperty("apipath") != null) { - if (a.apiId == null) { - a.apiId = Platform.uuid - } - - def tracker = new ApiPathTracker(a.apiId) - def out = errorOut(a.call()) - def path = tracker.getApiPath() - if (!path.isEmpty()) { - Test.apiPaths[a.class.name] = path.join(" --->\n") - } - - return out - } else { - return errorOut(a.call()) - } - } - - def setVmEmulatorPinning(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SetVmEmulatorPinningAction.class) Closure c) { def a = new org.zstack.sdk.SetVmEmulatorPinningAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -48108,6 +48108,33 @@ abstract class ApiHelper { } + def updateVmDGpu(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateVmDGpuAction.class) Closure c) { + def a = new org.zstack.sdk.UpdateVmDGpuAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def updateVmInstance(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateVmInstanceAction.class) Closure c) { def a = new org.zstack.sdk.UpdateVmInstanceAction() a.sessionId = Test.currentEnvSpec?.session?.uuid From 7dabfb88fa0b53d954ea46595cd9277b57d70d80 Mon Sep 17 00:00:00 2001 From: lianghy Date: Tue, 2 Jun 2026 21:43:25 +0800 Subject: [PATCH 18/54] [license]: complete auth info output Add localVersion and license user to license authorization info output. Resolves: ZCF-3922 Change-Id: I1c8a88f7a08d5b13c0e92c86abc9876d1d41f088 (cherry picked from commit 13b69c91e006740f74f1f09c50eff9eef3975eab) --- .../java/org/zstack/sdk/LicenseAuthorizationInfo.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sdk/src/main/java/org/zstack/sdk/LicenseAuthorizationInfo.java b/sdk/src/main/java/org/zstack/sdk/LicenseAuthorizationInfo.java index f01f92de62e..def485897e7 100644 --- a/sdk/src/main/java/org/zstack/sdk/LicenseAuthorizationInfo.java +++ b/sdk/src/main/java/org/zstack/sdk/LicenseAuthorizationInfo.java @@ -36,6 +36,14 @@ public java.lang.String getServerVersion() { return this.serverVersion; } + public java.lang.String localVersion; + public void setLocalVersion(java.lang.String localVersion) { + this.localVersion = localVersion; + } + public java.lang.String getLocalVersion() { + return this.localVersion; + } + public java.lang.String siteUuid; public void setSiteUuid(java.lang.String siteUuid) { this.siteUuid = siteUuid; From 9c510f17c3b34ed53180249874bd3e67bf371e41 Mon Sep 17 00:00:00 2001 From: "hanyu.liang" Date: Wed, 3 Jun 2026 18:04:36 +0800 Subject: [PATCH 19/54] [license]: support LS HTTPS SDK Regenerate SDK action fields for external License Server HTTPS TLS CA and server name configuration. Resolves: ZCF-3922 Change-Id: I7a4cfcf8b5fe57037ce9f5592370f9e1db42dabd (cherry picked from commit 13b4267c71289b50874cb77d05c1c721d56c058a) --- .../java/org/zstack/sdk/RegisterLicenseClientAction.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sdk/src/main/java/org/zstack/sdk/RegisterLicenseClientAction.java b/sdk/src/main/java/org/zstack/sdk/RegisterLicenseClientAction.java index a937ffab4ed..9177d409bbb 100644 --- a/sdk/src/main/java/org/zstack/sdk/RegisterLicenseClientAction.java +++ b/sdk/src/main/java/org/zstack/sdk/RegisterLicenseClientAction.java @@ -43,6 +43,12 @@ public Result throwExceptionIfError() { @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.lang.String baseAuthEntity; + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String tlsCaPem; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String tlsServerName; + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.lang.String bundle; From 40c3b6674fc2e78a667671a2e0e9f809645c728a Mon Sep 17 00:00:00 2001 From: "hanyu.liang" Date: Wed, 10 Jun 2026 15:48:25 +0800 Subject: [PATCH 20/54] [iam2]: add scim receiver runtime Add the shared SCIM receiver schema, header contract, and core runtime. Keep header limited to protocol DTOs and handler contracts, while core owns token/signature validation, event persistence, idempotency, version guards, and payload validation. Verification: - mvn -pl header -DskipTests clean install - mvn -pl core -DskipTests clean install - mvn -pl test-premium -Dtest=IAM2ScimReceiverCase -DfailIfNoTests=false test Resolves: ZCF-4158 Change-Id: Ie5627e6372fedecaeed37313a19e299adc700e98 --- conf/db/upgrade/V5.5.28__schema.sql | 19 + .../zstack/core/scim/ScimGlobalProperty.java | 13 + .../org/zstack/core/scim/ScimService.java | 377 ++++++++++++++++++ .../org/zstack/header/scim/ScimConstant.java | 15 + .../org/zstack/header/scim/ScimEventVO.java | 140 +++++++ .../org/zstack/header/scim/ScimException.java | 14 + .../org/zstack/header/scim/ScimMemberRef.java | 11 + .../org/zstack/header/scim/ScimOperation.java | 8 + .../org/zstack/header/scim/ScimPayload.java | 53 +++ .../header/scim/ScimResourceHandler.java | 7 + .../org/zstack/header/scim/ScimResult.java | 30 ++ 11 files changed, 687 insertions(+) create mode 100644 core/src/main/java/org/zstack/core/scim/ScimGlobalProperty.java create mode 100644 core/src/main/java/org/zstack/core/scim/ScimService.java create mode 100644 header/src/main/java/org/zstack/header/scim/ScimConstant.java create mode 100644 header/src/main/java/org/zstack/header/scim/ScimEventVO.java create mode 100644 header/src/main/java/org/zstack/header/scim/ScimException.java create mode 100644 header/src/main/java/org/zstack/header/scim/ScimMemberRef.java create mode 100644 header/src/main/java/org/zstack/header/scim/ScimOperation.java create mode 100644 header/src/main/java/org/zstack/header/scim/ScimPayload.java create mode 100644 header/src/main/java/org/zstack/header/scim/ScimResourceHandler.java create mode 100644 header/src/main/java/org/zstack/header/scim/ScimResult.java diff --git a/conf/db/upgrade/V5.5.28__schema.sql b/conf/db/upgrade/V5.5.28__schema.sql index 4ce2da95ba3..7c1ab9fe15f 100644 --- a/conf/db/upgrade/V5.5.28__schema.sql +++ b/conf/db/upgrade/V5.5.28__schema.sql @@ -79,3 +79,22 @@ END $$ DELIMITER ; CALL addPciDeviceMdevSpecRefUniqueKey(); DROP PROCEDURE IF EXISTS addPciDeviceMdevSpecRefUniqueKey; + +-- ZCF-4158: Store SCIM event application state. +CREATE TABLE IF NOT EXISTS `zstack`.`ScimEventVO` ( + `uuid` varchar(32) NOT NULL UNIQUE COMMENT 'uuid', + `clientId` varchar(128) NOT NULL DEFAULT 'default', + `eventId` varchar(255) NOT NULL, + `resourceType` varchar(64) NOT NULL, + `resourceId` varchar(255) NOT NULL, + `resourceVersion` bigint NOT NULL, + `operation` varchar(32) NOT NULL, + `status` varchar(32) NOT NULL, + `payloadHash` varchar(128) DEFAULT NULL, + `errorMessage` text DEFAULT NULL, + `lastOpDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00' ON UPDATE CURRENT_TIMESTAMP, + `createDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', + PRIMARY KEY (`uuid`), + UNIQUE KEY `ukScimEventVOClientEvent` (`clientId`, `eventId`), + KEY `idxScimEventVOResourceVersion` (`clientId`, `resourceType`, `resourceId`, `resourceVersion`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/core/src/main/java/org/zstack/core/scim/ScimGlobalProperty.java b/core/src/main/java/org/zstack/core/scim/ScimGlobalProperty.java new file mode 100644 index 00000000000..c8b28447bba --- /dev/null +++ b/core/src/main/java/org/zstack/core/scim/ScimGlobalProperty.java @@ -0,0 +1,13 @@ +package org.zstack.core.scim; + +import org.zstack.core.GlobalProperty; +import org.zstack.core.GlobalPropertyDefinition; + +@GlobalPropertyDefinition +public class ScimGlobalProperty { + @GlobalProperty(name = "scim.receiver.token", defaultValue = "") + public static String SCIM_RECEIVER_TOKEN; + + @GlobalProperty(name = "scim.receiver.signatureSecret", defaultValue = "") + public static String SCIM_RECEIVER_SIGNATURE_SECRET; +} diff --git a/core/src/main/java/org/zstack/core/scim/ScimService.java b/core/src/main/java/org/zstack/core/scim/ScimService.java new file mode 100644 index 00000000000..b7e798e84f4 --- /dev/null +++ b/core/src/main/java/org/zstack/core/scim/ScimService.java @@ -0,0 +1,377 @@ +package org.zstack.core.scim; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.core.Platform; +import org.zstack.core.db.DatabaseFacade; +import org.zstack.core.db.GLock; +import org.zstack.core.db.SQL; +import org.zstack.header.scim.ScimConstant; +import org.zstack.header.scim.ScimEventVO; +import org.zstack.header.scim.ScimException; +import org.zstack.header.scim.ScimOperation; +import org.zstack.header.scim.ScimPayload; +import org.zstack.header.scim.ScimResourceHandler; +import org.zstack.header.scim.ScimResult; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.servlet.http.HttpServletRequest; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.sql.Timestamp; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +public class ScimService { + private static final ObjectMapper mapper = new ObjectMapper(); + private static final int MAX_CLIENT_ID_LENGTH = 128; + private static final int MAX_EVENT_ID_LENGTH = 255; + private static final int MAX_RESOURCE_TYPE_LENGTH = 64; + private static final int MAX_RESOURCE_ID_LENGTH = 255; + private static final int MAX_ERROR_MESSAGE_LENGTH = 1024; + private static final long LOCK_TIMEOUT_SECONDS = TimeUnit.MINUTES.toSeconds(2); + private static final char[] HEX = "0123456789abcdef".toCharArray(); + private static final Set SENSITIVE_PAYLOAD_KEYS = new HashSet<>(Arrays.asList( + "password", "refreshtoken", "scimtoken", "token", "providersecret", "clientsecret", "secret" + )); + + @Autowired + private DatabaseFacade dbf; + @Autowired + private ScimResourceHandler resourceHandler; + + public ScimResult apply(HttpServletRequest request, String resourceType, String pathResourceId, String rawBody) { + String body = rawBody == null ? "" : rawBody; + verifyBearer(request); + String method = normalizeMethod(request); + validateMaxLength(resourceType, "resource type", MAX_RESOURCE_TYPE_LENGTH); + validateMaxLength(pathResourceId, "resource id", MAX_RESOURCE_ID_LENGTH); + String clientId = validateMaxLength(optionalHeader(request, ScimConstant.HEADER_CLIENT_ID, + ScimConstant.DEFAULT_CLIENT_ID), ScimConstant.HEADER_CLIENT_ID, MAX_CLIENT_ID_LENGTH); + String eventId = validateMaxLength(requiredHeader(request, ScimConstant.HEADER_EVENT_ID), + ScimConstant.HEADER_EVENT_ID, MAX_EVENT_ID_LENGTH); + long resourceVersion = parseResourceVersion(requiredHeader(request, ScimConstant.HEADER_RESOURCE_VERSION)); + verifySignature(request, clientId, eventId, resourceVersion, method, resourceType, pathResourceId, body); + + String canonicalType = resourceHandler.normalizeResourceType(resourceType); + validateMaxLength(canonicalType, "canonical resource type", MAX_RESOURCE_TYPE_LENGTH); + Map bodyMap = parseBody(body); + rejectSensitivePayloadKeys(bodyMap); + ScimPayload payload = mapper.convertValue(bodyMap, ScimPayload.class); + rejectSensitivePayloadKeys(payload.attributes); + String resourceId = validateMaxLength(firstNotBlank(pathResourceId, payload.uuid, payload.id, payload.externalId), + "resource id", MAX_RESOURCE_ID_LENGTH); + if (isBlank(resourceId)) { + throw new ScimException(400, "resource id is required"); + } + + ScimOperation operation = toOperation(method); + GLock lock = new GLock(lockName(clientId, canonicalType, resourceId), LOCK_TIMEOUT_SECONDS); + lock.lock(); + try { + return applyLocked(clientId, eventId, canonicalType, resourceId, resourceVersion, operation, body, payload); + } finally { + lock.unlock(); + } + } + + private ScimResult applyLocked(String clientId, String eventId, String resourceType, String resourceId, + long resourceVersion, ScimOperation operation, String body, ScimPayload payload) { + ScimEventVO existing = findEvent(clientId, eventId); + if (existing != null) { + return resultForExistingEvent(existing); + } + + Long latestVersion = latestAcceptedVersion(clientId, resourceType, resourceId); + if (latestVersion != null && resourceVersion <= latestVersion) { + throw new ScimException(409, + String.format("resource version[%s] is not newer than current[%s]", resourceVersion, latestVersion)); + } + + ScimEventVO event = reserveEvent(clientId, eventId, resourceType, resourceId, resourceVersion, operation, body); + if (!ScimConstant.EVENT_STATUS_PENDING.equals(event.getStatus())) { + return resultForExistingEvent(event); + } + + try { + resourceHandler.applyResource(operation, resourceType, resourceId, payload); + markEventApplied(event); + return ScimResult.applied(clientId, eventId, resourceType, resourceId, resourceVersion, operation.name()); + } catch (RuntimeException e) { + markEventFailed(event, e); + throw e; + } + } + + private void verifyBearer(HttpServletRequest request) { + String expected = ScimGlobalProperty.SCIM_RECEIVER_TOKEN; + if (isBlank(expected)) { + throw new ScimException(401, "SCIM receiver token is not configured"); + } + String auth = request.getHeader("Authorization"); + if (isBlank(auth) || !auth.startsWith("Bearer ")) { + throw new ScimException(401, "missing bearer token"); + } + String token = auth.substring("Bearer ".length()).trim(); + if (!constantTimeEquals(expected, token)) { + throw new ScimException(401, "invalid bearer token"); + } + } + + private String requiredHeader(HttpServletRequest request, String name) { + String value = request.getHeader(name); + if (isBlank(value)) { + throw new ScimException(400, String.format("missing header %s", name)); + } + return value.trim(); + } + + private String optionalHeader(HttpServletRequest request, String name, String defaultValue) { + String value = request.getHeader(name); + return isBlank(value) ? defaultValue : value.trim(); + } + + private String normalizeMethod(HttpServletRequest request) { + String method = request.getMethod(); + if (isBlank(method)) { + throw new ScimException(400, "missing HTTP method"); + } + return method.trim().toUpperCase(Locale.ROOT); + } + + private ScimOperation toOperation(String method) { + switch (method) { + case "POST": + return ScimOperation.CREATE; + case "PUT": + return ScimOperation.UPDATE; + case "PATCH": + return ScimOperation.PATCH; + case "DELETE": + return ScimOperation.DELETE; + default: + throw new ScimException(405, String.format("unsupported SCIM method[%s]", method)); + } + } + + private long parseResourceVersion(String value) { + try { + long version = Long.parseLong(value); + if (version <= 0) { + throw new ScimException(400, "resource version must be positive"); + } + return version; + } catch (NumberFormatException e) { + throw new ScimException(400, "invalid resource version"); + } + } + + private void verifySignature(HttpServletRequest request, String clientId, String eventId, long resourceVersion, + String method, String resourceType, String pathResourceId, String body) { + String secret = ScimGlobalProperty.SCIM_RECEIVER_SIGNATURE_SECRET; + if (isBlank(secret)) { + throw new ScimException(401, "SCIM signature secret is not configured"); + } + String actual = request.getHeader(ScimConstant.HEADER_SIGNATURE); + if (isBlank(actual)) { + throw new ScimException(401, "missing SCIM signature"); + } + String base = signatureBase(clientId, method, resourceType, pathResourceId, eventId, resourceVersion, body); + String expected = "sha256=" + hmacSha256(secret, base); + if (!constantTimeEquals(expected, actual.trim())) { + throw new ScimException(401, "invalid SCIM signature"); + } + } + + private void rejectSensitivePayloadKeys(Map values) { + if (values == null || values.isEmpty()) { + return; + } + + for (String key : values.keySet()) { + if (key != null && SENSITIVE_PAYLOAD_KEYS.contains(normalizePayloadKey(key))) { + throw new ScimException(400, String.format("SCIM payload must not contain sensitive field[%s]", key)); + } + } + } + + private String normalizePayloadKey(String key) { + return key.replace("_", "").replace("-", "").toLowerCase(Locale.ROOT); + } + + private Map parseBody(String body) { + if (isBlank(body)) { + return new java.util.HashMap<>(); + } + try { + return mapper.readValue(body, new TypeReference>() {}); + } catch (Exception e) { + throw new ScimException(400, "invalid JSON payload"); + } + } + + private ScimEventVO findEvent(String clientId, String eventId) { + return SQL.New("select vo from ScimEventVO vo where vo.clientId = :clientId and vo.eventId = :eventId", + ScimEventVO.class) + .param("clientId", clientId) + .param("eventId", eventId) + .find(); + } + + private Long latestAcceptedVersion(String clientId, String resourceType, String resourceId) { + return SQL.New("select max(vo.resourceVersion) from ScimEventVO vo" + + " where vo.clientId = :clientId" + + " and vo.resourceType = :resourceType" + + " and vo.resourceId = :resourceId" + + " and (vo.status = :pending or vo.status = :applied)", Long.class) + .param("clientId", clientId) + .param("resourceType", resourceType) + .param("resourceId", resourceId) + .param("pending", ScimConstant.EVENT_STATUS_PENDING) + .param("applied", ScimConstant.EVENT_STATUS_APPLIED) + .find(); + } + + private ScimEventVO reserveEvent(String clientId, String eventId, String resourceType, String resourceId, + long version, ScimOperation operation, String body) { + ScimEventVO vo = new ScimEventVO(); + vo.setUuid(Platform.getUuid()); + vo.setClientId(clientId); + vo.setEventId(eventId); + vo.setResourceType(resourceType); + vo.setResourceId(resourceId); + vo.setResourceVersion(version); + vo.setOperation(operation.name()); + vo.setStatus(ScimConstant.EVENT_STATUS_PENDING); + vo.setPayloadHash(sha256(body)); + Timestamp now = new Timestamp(System.currentTimeMillis()); + vo.setCreateDate(now); + vo.setLastOpDate(now); + try { + return dbf.persist(vo); + } catch (RuntimeException e) { + ScimEventVO existing = findEvent(clientId, eventId); + if (existing != null) { + return existing; + } + throw e; + } + } + + private void markEventApplied(ScimEventVO event) { + event.setStatus(ScimConstant.EVENT_STATUS_APPLIED); + event.setErrorMessage(null); + dbf.update(event); + } + + private void markEventFailed(ScimEventVO event, RuntimeException e) { + try { + event.setStatus(ScimConstant.EVENT_STATUS_FAILED); + event.setErrorMessage(trimError(e)); + dbf.update(event); + } catch (RuntimeException ignored) { + } + } + + private ScimResult resultForExistingEvent(ScimEventVO event) { + if (ScimConstant.EVENT_STATUS_APPLIED.equals(event.getStatus())) { + return ScimResult.duplicate(event.getClientId(), event.getEventId(), event.getResourceType(), + event.getResourceId(), event.getResourceVersion()); + } + if (ScimConstant.EVENT_STATUS_PENDING.equals(event.getStatus())) { + throw new ScimException(409, String.format("SCIM event[%s] is being processed", event.getEventId())); + } + throw new ScimException(409, String.format("SCIM event[%s] failed before: %s", + event.getEventId(), firstNotBlank(event.getErrorMessage(), "unknown error"))); + } + + private String firstNotBlank(String... values) { + for (String value : values) { + if (!isBlank(value)) { + return value; + } + } + return null; + } + + private String hmacSha256(String secret, String base) { + try { + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); + return hex(mac.doFinal(base.getBytes(StandardCharsets.UTF_8))); + } catch (Exception e) { + throw new ScimException(500, "failed to calculate signature"); + } + } + + private String sha256(String value) { + try { + return hex(MessageDigest.getInstance("SHA-256").digest(value.getBytes(StandardCharsets.UTF_8))); + } catch (Exception e) { + throw new ScimException(500, "failed to calculate payload hash"); + } + } + + private String hex(byte[] bytes) { + char[] chars = new char[bytes.length * 2]; + int index = 0; + for (byte b : bytes) { + int value = b & 0xff; + chars[index++] = HEX[value >>> 4]; + chars[index++] = HEX[value & 0x0f]; + } + return new String(chars); + } + + private boolean constantTimeEquals(String expected, String actual) { + if (expected == null || actual == null) { + return false; + } + return MessageDigest.isEqual(expected.getBytes(StandardCharsets.UTF_8), actual.getBytes(StandardCharsets.UTF_8)); + } + + private boolean isBlank(String value) { + return value == null || value.trim().isEmpty(); + } + + private String validateMaxLength(String value, String name, int maxLength) { + if (value != null && value.length() > maxLength) { + throw new ScimException(400, String.format("%s length must be <= %s", name, maxLength)); + } + return value; + } + + private String trimError(RuntimeException e) { + String message = e.getMessage(); + if (isBlank(message)) { + message = e.getClass().getName(); + } + return message.length() > MAX_ERROR_MESSAGE_LENGTH ? message.substring(0, MAX_ERROR_MESSAGE_LENGTH) : message; + } + + private String signatureBase(String clientId, String method, String resourceType, String pathResourceId, + String eventId, long resourceVersion, String body) { + return clientId + "\n" + + method + "\n" + + nullToEmpty(resourceType) + "\n" + + nullToEmpty(pathResourceId) + "\n" + + eventId + "\n" + + resourceVersion + "\n" + + body; + } + + private String lockName(String clientId, String resourceType, String resourceId) { + String key = clientId + "\n" + resourceType + "\n" + resourceId; + return "scim-" + sha256(key).substring(0, 58); + } + + private String nullToEmpty(String value) { + return value == null ? "" : value; + } +} diff --git a/header/src/main/java/org/zstack/header/scim/ScimConstant.java b/header/src/main/java/org/zstack/header/scim/ScimConstant.java new file mode 100644 index 00000000000..3af73391278 --- /dev/null +++ b/header/src/main/java/org/zstack/header/scim/ScimConstant.java @@ -0,0 +1,15 @@ +package org.zstack.header.scim; + +public class ScimConstant { + public static final String HEADER_CLIENT_ID = "X-SCIM-Client-Id"; + public static final String HEADER_EVENT_ID = "X-SCIM-Event-Id"; + public static final String HEADER_RESOURCE_VERSION = "X-SCIM-Resource-Version"; + public static final String HEADER_SIGNATURE = "X-SCIM-Signature"; + public static final String DEFAULT_CLIENT_ID = "default"; + public static final String EVENT_STATUS_PENDING = "PENDING"; + public static final String EVENT_STATUS_APPLIED = "APPLIED"; + public static final String EVENT_STATUS_FAILED = "FAILED"; + + private ScimConstant() { + } +} diff --git a/header/src/main/java/org/zstack/header/scim/ScimEventVO.java b/header/src/main/java/org/zstack/header/scim/ScimEventVO.java new file mode 100644 index 00000000000..f97b6985456 --- /dev/null +++ b/header/src/main/java/org/zstack/header/scim/ScimEventVO.java @@ -0,0 +1,140 @@ +package org.zstack.header.scim; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.PreUpdate; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.sql.Timestamp; + +@Entity +@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"clientId", "eventId"})}) +public class ScimEventVO { + @Id + @Column + private String uuid; + @Column + private String clientId; + @Column + private String eventId; + @Column + private String resourceType; + @Column + private String resourceId; + @Column + private long resourceVersion; + @Column + private String operation; + @Column + private String status; + @Column + private String payloadHash; + @Column + private String errorMessage; + @Column + private Timestamp createDate; + @Column + private Timestamp lastOpDate; + + @PreUpdate + private void preUpdate() { + lastOpDate = null; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public String getEventId() { + return eventId; + } + + public void setEventId(String eventId) { + this.eventId = eventId; + } + + public String getResourceType() { + return resourceType; + } + + public void setResourceType(String resourceType) { + this.resourceType = resourceType; + } + + public String getResourceId() { + return resourceId; + } + + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + public long getResourceVersion() { + return resourceVersion; + } + + public void setResourceVersion(long resourceVersion) { + this.resourceVersion = resourceVersion; + } + + public String getOperation() { + return operation; + } + + public void setOperation(String operation) { + this.operation = operation; + } + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getPayloadHash() { + return payloadHash; + } + + public void setPayloadHash(String payloadHash) { + this.payloadHash = payloadHash; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public Timestamp getCreateDate() { + return createDate; + } + + public void setCreateDate(Timestamp createDate) { + this.createDate = createDate; + } + + public Timestamp getLastOpDate() { + return lastOpDate; + } + + public void setLastOpDate(Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } +} diff --git a/header/src/main/java/org/zstack/header/scim/ScimException.java b/header/src/main/java/org/zstack/header/scim/ScimException.java new file mode 100644 index 00000000000..d0c09a51388 --- /dev/null +++ b/header/src/main/java/org/zstack/header/scim/ScimException.java @@ -0,0 +1,14 @@ +package org.zstack.header.scim; + +public class ScimException extends RuntimeException { + private final int statusCode; + + public ScimException(int statusCode, String message) { + super(message); + this.statusCode = statusCode; + } + + public int getStatusCode() { + return statusCode; + } +} diff --git a/header/src/main/java/org/zstack/header/scim/ScimMemberRef.java b/header/src/main/java/org/zstack/header/scim/ScimMemberRef.java new file mode 100644 index 00000000000..429ddf0fcf1 --- /dev/null +++ b/header/src/main/java/org/zstack/header/scim/ScimMemberRef.java @@ -0,0 +1,11 @@ +package org.zstack.header.scim; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class ScimMemberRef { + public String value; + public String id; + public String uuid; + public String display; +} diff --git a/header/src/main/java/org/zstack/header/scim/ScimOperation.java b/header/src/main/java/org/zstack/header/scim/ScimOperation.java new file mode 100644 index 00000000000..23ece5ccb69 --- /dev/null +++ b/header/src/main/java/org/zstack/header/scim/ScimOperation.java @@ -0,0 +1,8 @@ +package org.zstack.header.scim; + +public enum ScimOperation { + CREATE, + UPDATE, + PATCH, + DELETE +} diff --git a/header/src/main/java/org/zstack/header/scim/ScimPayload.java b/header/src/main/java/org/zstack/header/scim/ScimPayload.java new file mode 100644 index 00000000000..8c622008643 --- /dev/null +++ b/header/src/main/java/org/zstack/header/scim/ScimPayload.java @@ -0,0 +1,53 @@ +package org.zstack.header.scim; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +import java.util.List; +import java.util.Map; + +/** + * Generic SCIM payload accepted by the receiver. + * + * Resource identity is resolved in this order: request path id, {@link #uuid}, {@link #id}, then {@link #externalId}. + * Paired *Id and *Uuid fields carry the same logical identity from different upstream naming conventions; concrete + * resource handlers decide which field maps to their local model. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class ScimPayload { + public String id; + public String uuid; + public String externalId; + public String userName; + public String name; + public String displayName; + public String description; + public String type; + public Boolean active; + public Boolean enabled; + public String parentId; + public String parentUuid; + public String organizationId; + public String organizationUuid; + public String projectId; + public String projectUuid; + public String userId; + public String userUuid; + public String virtualIDUuid; + public String groupId; + public String groupUuid; + public String roleId; + public String roleUuid; + public String subjectType; + public String subjectId; + public String subjectUuid; + public String principalType; + public String accountId; + public String accountUuid; + public String tenantId; + public String tenantUuid; + public String targetAccountId; + public String targetAccountUuid; + public String linkAccountUuid; + public List members; + public Map attributes; +} diff --git a/header/src/main/java/org/zstack/header/scim/ScimResourceHandler.java b/header/src/main/java/org/zstack/header/scim/ScimResourceHandler.java new file mode 100644 index 00000000000..1a46b4c501d --- /dev/null +++ b/header/src/main/java/org/zstack/header/scim/ScimResourceHandler.java @@ -0,0 +1,7 @@ +package org.zstack.header.scim; + +public interface ScimResourceHandler { + String normalizeResourceType(String resourceType); + + void applyResource(ScimOperation operation, String resourceType, String resourceId, ScimPayload payload); +} diff --git a/header/src/main/java/org/zstack/header/scim/ScimResult.java b/header/src/main/java/org/zstack/header/scim/ScimResult.java new file mode 100644 index 00000000000..9da41346705 --- /dev/null +++ b/header/src/main/java/org/zstack/header/scim/ScimResult.java @@ -0,0 +1,30 @@ +package org.zstack.header.scim; + +public class ScimResult { + public String clientId; + public String eventId; + public String resourceType; + public String resourceId; + public long resourceVersion; + public String operation; + public boolean duplicate; + + public static ScimResult applied(String clientId, String eventId, String resourceType, String resourceId, + long version, String operation) { + ScimResult result = new ScimResult(); + result.clientId = clientId; + result.eventId = eventId; + result.resourceType = resourceType; + result.resourceId = resourceId; + result.resourceVersion = version; + result.operation = operation; + return result; + } + + public static ScimResult duplicate(String clientId, String eventId, String resourceType, String resourceId, + long version) { + ScimResult result = applied(clientId, eventId, resourceType, resourceId, version, "DUPLICATE"); + result.duplicate = true; + return result; + } +} From 9418fbeacd4913a56fd63b42b607c775669771be Mon Sep 17 00:00:00 2001 From: "hanyu.liang" Date: Tue, 16 Jun 2026 11:19:18 +0800 Subject: [PATCH 21/54] [iam2]: enforce scim uuid contract - Move SCIM receiver secrets from GlobalProperty to GlobalConfig. - Require X-SCIM timestamp and uuid-only resource identity. - Remove id/externalId compatibility fields from the shared SCIM payload. Resolves: ZCF-4158 Change-Id: Ie199565a00b0d1df07ac756820b131d0f8683462 --- .../zstack/core/scim/ScimGlobalConfig.java | 19 +++++ .../zstack/core/scim/ScimGlobalProperty.java | 13 ---- .../org/zstack/core/scim/ScimService.java | 70 +++++++++++++++---- .../org/zstack/header/scim/ScimConstant.java | 5 +- .../org/zstack/header/scim/ScimMemberRef.java | 1 - .../org/zstack/header/scim/ScimPayload.java | 18 +---- 6 files changed, 81 insertions(+), 45 deletions(-) create mode 100644 core/src/main/java/org/zstack/core/scim/ScimGlobalConfig.java delete mode 100644 core/src/main/java/org/zstack/core/scim/ScimGlobalProperty.java diff --git a/core/src/main/java/org/zstack/core/scim/ScimGlobalConfig.java b/core/src/main/java/org/zstack/core/scim/ScimGlobalConfig.java new file mode 100644 index 00000000000..29cd225179c --- /dev/null +++ b/core/src/main/java/org/zstack/core/scim/ScimGlobalConfig.java @@ -0,0 +1,19 @@ +package org.zstack.core.scim; + +import org.zstack.core.config.GlobalConfig; +import org.zstack.core.config.GlobalConfigDef; +import org.zstack.core.config.GlobalConfigDefinition; +import org.zstack.core.config.GlobalConfigValidation; + +@GlobalConfigDefinition +public class ScimGlobalConfig { + public static final String CATEGORY = "scim"; + + @GlobalConfigValidation(notEmpty = false) + @GlobalConfigDef(defaultValue = "", description = "Bearer token accepted by the SCIM receiver") + public static GlobalConfig RECEIVER_TOKEN = new GlobalConfig(CATEGORY, "receiver.token"); + + @GlobalConfigValidation(notEmpty = false) + @GlobalConfigDef(defaultValue = "", description = "Secret used to verify SCIM request signatures") + public static GlobalConfig RECEIVER_SIGNATURE_SECRET = new GlobalConfig(CATEGORY, "receiver.signatureSecret"); +} diff --git a/core/src/main/java/org/zstack/core/scim/ScimGlobalProperty.java b/core/src/main/java/org/zstack/core/scim/ScimGlobalProperty.java deleted file mode 100644 index c8b28447bba..00000000000 --- a/core/src/main/java/org/zstack/core/scim/ScimGlobalProperty.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.zstack.core.scim; - -import org.zstack.core.GlobalProperty; -import org.zstack.core.GlobalPropertyDefinition; - -@GlobalPropertyDefinition -public class ScimGlobalProperty { - @GlobalProperty(name = "scim.receiver.token", defaultValue = "") - public static String SCIM_RECEIVER_TOKEN; - - @GlobalProperty(name = "scim.receiver.signatureSecret", defaultValue = "") - public static String SCIM_RECEIVER_SIGNATURE_SECRET; -} diff --git a/core/src/main/java/org/zstack/core/scim/ScimService.java b/core/src/main/java/org/zstack/core/scim/ScimService.java index b7e798e84f4..cfdf7b780f0 100644 --- a/core/src/main/java/org/zstack/core/scim/ScimService.java +++ b/core/src/main/java/org/zstack/core/scim/ScimService.java @@ -51,13 +51,14 @@ public ScimResult apply(HttpServletRequest request, String resourceType, String verifyBearer(request); String method = normalizeMethod(request); validateMaxLength(resourceType, "resource type", MAX_RESOURCE_TYPE_LENGTH); - validateMaxLength(pathResourceId, "resource id", MAX_RESOURCE_ID_LENGTH); + validateMaxLength(pathResourceId, "resource uuid", MAX_RESOURCE_ID_LENGTH); String clientId = validateMaxLength(optionalHeader(request, ScimConstant.HEADER_CLIENT_ID, ScimConstant.DEFAULT_CLIENT_ID), ScimConstant.HEADER_CLIENT_ID, MAX_CLIENT_ID_LENGTH); String eventId = validateMaxLength(requiredHeader(request, ScimConstant.HEADER_EVENT_ID), ScimConstant.HEADER_EVENT_ID, MAX_EVENT_ID_LENGTH); long resourceVersion = parseResourceVersion(requiredHeader(request, ScimConstant.HEADER_RESOURCE_VERSION)); - verifySignature(request, clientId, eventId, resourceVersion, method, resourceType, pathResourceId, body); + String timestamp = requiredHeader(request, ScimConstant.HEADER_TIMESTAMP); + verifySignature(request, clientId, eventId, resourceVersion, timestamp, method, resourceType, pathResourceId, body); String canonicalType = resourceHandler.normalizeResourceType(resourceType); validateMaxLength(canonicalType, "canonical resource type", MAX_RESOURCE_TYPE_LENGTH); @@ -65,10 +66,15 @@ public ScimResult apply(HttpServletRequest request, String resourceType, String rejectSensitivePayloadKeys(bodyMap); ScimPayload payload = mapper.convertValue(bodyMap, ScimPayload.class); rejectSensitivePayloadKeys(payload.attributes); - String resourceId = validateMaxLength(firstNotBlank(pathResourceId, payload.uuid, payload.id, payload.externalId), - "resource id", MAX_RESOURCE_ID_LENGTH); + String pathResourceUuid = normalizeOptionalUuid(pathResourceId, "path resource uuid"); + String payloadUuid = normalizeOptionalUuid(payload.uuid, "payload uuid"); + if (!isBlank(pathResourceUuid) && !isBlank(payloadUuid) && !pathResourceUuid.equals(payloadUuid)) { + throw new ScimException(400, "path resource uuid and payload uuid must match"); + } + String resourceId = validateMaxLength(firstNotBlank(pathResourceUuid, payloadUuid), + "resource uuid", MAX_RESOURCE_ID_LENGTH); if (isBlank(resourceId)) { - throw new ScimException(400, "resource id is required"); + throw new ScimException(400, "resource uuid is required"); } ScimOperation operation = toOperation(method); @@ -110,7 +116,7 @@ private ScimResult applyLocked(String clientId, String eventId, String resourceT } private void verifyBearer(HttpServletRequest request) { - String expected = ScimGlobalProperty.SCIM_RECEIVER_TOKEN; + String expected = ScimGlobalConfig.RECEIVER_TOKEN.value(); if (isBlank(expected)) { throw new ScimException(401, "SCIM receiver token is not configured"); } @@ -173,8 +179,8 @@ private long parseResourceVersion(String value) { } private void verifySignature(HttpServletRequest request, String clientId, String eventId, long resourceVersion, - String method, String resourceType, String pathResourceId, String body) { - String secret = ScimGlobalProperty.SCIM_RECEIVER_SIGNATURE_SECRET; + String timestamp, String method, String resourceType, String pathResourceId, String body) { + String secret = ScimGlobalConfig.RECEIVER_SIGNATURE_SECRET.value(); if (isBlank(secret)) { throw new ScimException(401, "SCIM signature secret is not configured"); } @@ -182,7 +188,7 @@ private void verifySignature(HttpServletRequest request, String clientId, String if (isBlank(actual)) { throw new ScimException(401, "missing SCIM signature"); } - String base = signatureBase(clientId, method, resourceType, pathResourceId, eventId, resourceVersion, body); + String base = signatureBase(clientId, method, resourceType, pathResourceId, eventId, resourceVersion, timestamp, body); String expected = "sha256=" + hmacSha256(secret, base); if (!constantTimeEquals(expected, actual.trim())) { throw new ScimException(401, "invalid SCIM signature"); @@ -303,21 +309,33 @@ private String firstNotBlank(String... values) { private String hmacSha256(String secret, String base) { try { Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256")); + mac.init(new SecretKeySpec(signatureKey(secret), "HmacSHA256")); return hex(mac.doFinal(base.getBytes(StandardCharsets.UTF_8))); } catch (Exception e) { throw new ScimException(500, "failed to calculate signature"); } } + private byte[] signatureKey(String secret) { + return sha256Bytes("ziam-scim-signature\n" + secret); + } + private String sha256(String value) { try { - return hex(MessageDigest.getInstance("SHA-256").digest(value.getBytes(StandardCharsets.UTF_8))); + return hex(sha256Bytes(value)); } catch (Exception e) { throw new ScimException(500, "failed to calculate payload hash"); } } + private byte[] sha256Bytes(String value) { + try { + return MessageDigest.getInstance("SHA-256").digest(value.getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + throw new ScimException(500, "failed to calculate sha256"); + } + } + private String hex(byte[] bytes) { char[] chars = new char[bytes.length * 2]; int index = 0; @@ -347,6 +365,22 @@ private String validateMaxLength(String value, String name, int maxLength) { return value; } + private String normalizeOptionalUuid(String value, String name) { + if (isBlank(value)) { + return null; + } + String trimmed = value.trim(); + String compact = trimmed.replace("-", ""); + if (!compact.matches("[0-9a-fA-F]{32}")) { + throw new ScimException(400, String.format("%s must be a UUID", name)); + } + if (trimmed.indexOf('-') >= 0 && + !trimmed.matches("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")) { + throw new ScimException(400, String.format("%s must be a UUID", name)); + } + return compact.toLowerCase(Locale.ROOT); + } + private String trimError(RuntimeException e) { String message = e.getMessage(); if (isBlank(message)) { @@ -356,16 +390,24 @@ private String trimError(RuntimeException e) { } private String signatureBase(String clientId, String method, String resourceType, String pathResourceId, - String eventId, long resourceVersion, String body) { + String eventId, long resourceVersion, String timestamp, String body) { return clientId + "\n" + - method + "\n" + + method.toUpperCase(Locale.ROOT) + "\n" + nullToEmpty(resourceType) + "\n" + - nullToEmpty(pathResourceId) + "\n" + + signaturePathResourceId(method, pathResourceId) + "\n" + eventId + "\n" + resourceVersion + "\n" + + timestamp.trim() + "\n" + body; } + private String signaturePathResourceId(String method, String pathResourceId) { + if ("POST".equalsIgnoreCase(method)) { + return ""; + } + return nullToEmpty(pathResourceId); + } + private String lockName(String clientId, String resourceType, String resourceId) { String key = clientId + "\n" + resourceType + "\n" + resourceId; return "scim-" + sha256(key).substring(0, 58); diff --git a/header/src/main/java/org/zstack/header/scim/ScimConstant.java b/header/src/main/java/org/zstack/header/scim/ScimConstant.java index 3af73391278..ffe5c45d54b 100644 --- a/header/src/main/java/org/zstack/header/scim/ScimConstant.java +++ b/header/src/main/java/org/zstack/header/scim/ScimConstant.java @@ -1,9 +1,10 @@ package org.zstack.header.scim; public class ScimConstant { - public static final String HEADER_CLIENT_ID = "X-SCIM-Client-Id"; - public static final String HEADER_EVENT_ID = "X-SCIM-Event-Id"; + public static final String HEADER_CLIENT_ID = "X-SCIM-Client-UUID"; + public static final String HEADER_EVENT_ID = "X-SCIM-Event-UUID"; public static final String HEADER_RESOURCE_VERSION = "X-SCIM-Resource-Version"; + public static final String HEADER_TIMESTAMP = "X-SCIM-Timestamp"; public static final String HEADER_SIGNATURE = "X-SCIM-Signature"; public static final String DEFAULT_CLIENT_ID = "default"; public static final String EVENT_STATUS_PENDING = "PENDING"; diff --git a/header/src/main/java/org/zstack/header/scim/ScimMemberRef.java b/header/src/main/java/org/zstack/header/scim/ScimMemberRef.java index 429ddf0fcf1..83e9786c8ea 100644 --- a/header/src/main/java/org/zstack/header/scim/ScimMemberRef.java +++ b/header/src/main/java/org/zstack/header/scim/ScimMemberRef.java @@ -5,7 +5,6 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class ScimMemberRef { public String value; - public String id; public String uuid; public String display; } diff --git a/header/src/main/java/org/zstack/header/scim/ScimPayload.java b/header/src/main/java/org/zstack/header/scim/ScimPayload.java index 8c622008643..1235b6c3c63 100644 --- a/header/src/main/java/org/zstack/header/scim/ScimPayload.java +++ b/header/src/main/java/org/zstack/header/scim/ScimPayload.java @@ -8,15 +8,12 @@ /** * Generic SCIM payload accepted by the receiver. * - * Resource identity is resolved in this order: request path id, {@link #uuid}, {@link #id}, then {@link #externalId}. - * Paired *Id and *Uuid fields carry the same logical identity from different upstream naming conventions; concrete - * resource handlers decide which field maps to their local model. + * Resource identity is resolved from the request path UUID or {@link #uuid}. The receiver intentionally does not + * use SCIM id/externalId aliases because ZIAM owns stable UUID generation before publishing downstream events. */ @JsonIgnoreProperties(ignoreUnknown = true) public class ScimPayload { - public String id; public String uuid; - public String externalId; public String userName; public String name; public String displayName; @@ -24,30 +21,21 @@ public class ScimPayload { public String type; public Boolean active; public Boolean enabled; - public String parentId; public String parentUuid; - public String organizationId; public String organizationUuid; - public String projectId; public String projectUuid; - public String userId; public String userUuid; public String virtualIDUuid; - public String groupId; public String groupUuid; - public String roleId; public String roleUuid; public String subjectType; - public String subjectId; public String subjectUuid; public String principalType; - public String accountId; public String accountUuid; - public String tenantId; public String tenantUuid; - public String targetAccountId; public String targetAccountUuid; public String linkAccountUuid; + public String scopeUuid; public List members; public Map attributes; } From 4e92c82823f4dc9d61ec769a99b7d2e734c22bce Mon Sep 17 00:00:00 2001 From: "shan.wu" Date: Sun, 7 Jun 2026 19:10:55 +0800 Subject: [PATCH 22/54] [license]: manage license server lifecycle Register License Server as a local external service. Start it after the management node is ready. Keep stop as a no-op so ZStack will not stop it. Test: mvn -pl externalservice -am -DskipTests compile Resolves: ZCF-3922 Change-Id: I7f6a9c3e4b2d1a8f0c5e9d6b3a1f8c2d4e6f9a0b (cherry picked from commit 490df41e1b2b99d255e2d9726aa65bb080b0ca8f) --- conf/springConfigXml/externalService.xml | 6 + .../LicenseServerExternalService.java | 114 ++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 externalservice/src/main/java/org/zstack/externalservice/licenseserver/LicenseServerExternalService.java diff --git a/conf/springConfigXml/externalService.xml b/conf/springConfigXml/externalService.xml index 5b77228a770..eee33b2b75a 100755 --- a/conf/springConfigXml/externalService.xml +++ b/conf/springConfigXml/externalService.xml @@ -18,4 +18,10 @@ + + + + + + diff --git a/externalservice/src/main/java/org/zstack/externalservice/licenseserver/LicenseServerExternalService.java b/externalservice/src/main/java/org/zstack/externalservice/licenseserver/LicenseServerExternalService.java new file mode 100644 index 00000000000..2866fbf771e --- /dev/null +++ b/externalservice/src/main/java/org/zstack/externalservice/licenseserver/LicenseServerExternalService.java @@ -0,0 +1,114 @@ +package org.zstack.externalservice.licenseserver; + +import org.zstack.core.Platform; +import org.zstack.core.CoreGlobalProperty; +import org.zstack.core.externalservice.AbstractLocalExternalSystemdService; +import org.zstack.core.externalservice.ExternalService; +import org.zstack.core.externalservice.ExternalServiceManager; +import org.zstack.core.externalservice.ExternalServiceCapabilitiesBuilder; +import org.zstack.header.core.external.service.ExternalServiceCapabilities; +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.header.managementnode.ManagementNodeReadyExtensionPoint; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; + +import java.io.File; + +public class LicenseServerExternalService extends AbstractLocalExternalSystemdService implements ManagementNodeReadyExtensionPoint { + private static final CLogger logger = Utils.getLogger(LicenseServerExternalService.class); + + public static final String SYSTEMD_SERVICE_NAME = "zstack-license-server.service"; + public static final String SERVICE_TYPE = "LicenseServer"; + + @Autowired + private ExternalServiceManager externalServiceManager; + + private final ExternalServiceCapabilities capabilities = ExternalServiceCapabilitiesBuilder + .build() + .reloadConfig(false); + + @Override + public void managementNodeReady() { + if (CoreGlobalProperty.UNIT_TEST_ON) { + return; + } + + if (!isInstalled()) { + logger.warn(String.format("[External Service] %s is not installed, skip registering it", getName())); + return; + } + + try { + ExternalService service = externalServiceManager.getService(getName(), () -> this); + service.start(); + } catch (Exception t) { + logger.warn(String.format("[External Service] failed to start %s", getName()), t); + } + } + + @Override + protected String[] getCommandLineKeywords() { + return new String[] {"zstack-license-server", "--config"}; + } + + @Override + public String getName() { + return String.format("license-server-on-machine-%s", Platform.getManagementServerIp()); + } + + @Override + public String getServiceType() { + return SERVICE_TYPE; + } + + @Override + public String getSystemdServiceName() { + return SYSTEMD_SERVICE_NAME; + } + + @Override + public void start() { + if (!isInstalled()) { + logger.warn(String.format("[External Service] %s is not installed, skip starting it", getName())); + return; + } + + if (isAlive()) { + logger.debug(String.format("[External Service] %s is already running", getName())); + return; + } + + sysctl("start"); + logger.debug(String.format("[External Service] started %s", getName())); + } + + @Override + public void stop() { + logger.debug(String.format("[External Service] skip stopping %s, it is managed independently", getName())); + } + + @Override + public void restart() { + start(); + } + + @Override + public boolean isAlive() { + return getPID() != null; + } + + @Override + public ExternalServiceCapabilities getExternalServiceCapabilities() { + return capabilities; + } + + @Override + public void reload() { + logger.debug(String.format("[External Service] %s does not support reload config", getName())); + } + + private boolean isInstalled() { + return new File("/usr/lib/systemd/system/" + SYSTEMD_SERVICE_NAME).exists() + || new File("/etc/systemd/system/" + SYSTEMD_SERVICE_NAME).exists(); + } +} From 325c3f5afd9bd65684b4938eb0e44c1fe5ba5443 Mon Sep 17 00:00:00 2001 From: "haidong.pang" Date: Sat, 28 Feb 2026 03:04:01 +0000 Subject: [PATCH 23/54] [volume-cache]: add VM local volume cache Add VM local volume cache core API, inventory, persistence, KVM command, and SDK definitions. Related: ZSTAC-81715 Change-Id: I6d747a786b73746a6f756a6c72787479726e6765 --- .../org/zstack/compute/host/HostBase.java | 22 ++ .../zstack/compute/vm/VmAllocateHostFlow.java | 5 +- .../vm/VmAllocateHostForStoppedVmFlow.java | 1 + .../vm/VmInstanceExtensionPointEmitter.java | 9 +- conf/db/upgrade/V5.5.18__schema.sql | 46 ++++ conf/globalConfig/vmLocalVolumeCachePool.xml | 27 +++ conf/persistence.xml | 3 + conf/serviceConfig/host.xml | 4 + conf/serviceConfig/vmLocalVolumeCache.xml | 12 + conf/serviceConfig/vmLocalVolumeCachePool.xml | 37 +++ conf/springConfigXml/localVolumeCache.xml | 45 ++++ conf/zstack.xml | 1 + .../header/allocator/AllocateHostMsg.java | 19 +- .../header/allocator/HostAllocatorSpec.java | 22 +- .../header/host/APIGetBlockDevicesEvent.java | 45 ++++ .../APIGetBlockDevicesEventDoc_zh_cn.groovy | 32 +++ .../header/host/APIGetBlockDevicesMsg.java | 35 +++ .../APIGetBlockDevicesMsgDoc_zh_cn.groovy | 58 +++++ .../header/host/GetBlockDevicesOnHostMsg.java | 16 ++ .../host/GetBlockDevicesOnHostReply.java | 17 ++ .../header/host/HostBlockDeviceStruct.java | 148 ++++++++++++ .../HostBlockDeviceStructDoc_zh_cn.groovy | 81 +++++++ .../localVolumeCache/CachePoolMetadata.java | 186 +++++++++++++++ .../VmLocalVolumeCacheConstant.java | 5 + .../VmLocalVolumeCacheInventory.java | 108 +++++++++ ...mLocalVolumeCacheInventoryDoc_zh_cn.groovy | 63 +++++ .../VmLocalVolumeCacheMode.java | 5 + .../VmLocalVolumeCacheModeDoc_zh_cn.groovy | 15 ++ ...LocalVolumeCachePoolCapacityInventory.java | 85 +++++++ .../VmLocalVolumeCachePoolCapacityVO.java | 79 +++++++ .../VmLocalVolumeCachePoolCapacityVO_.java | 13 ++ .../VmLocalVolumeCachePoolInventory.java | 194 ++++++++++++++++ ...alVolumeCachePoolInventoryDoc_zh_cn.groovy | 90 ++++++++ .../VmLocalVolumeCachePoolState.java | 7 + ...mLocalVolumeCachePoolStateDoc_zh_cn.groovy | 21 ++ .../VmLocalVolumeCachePoolStatus.java | 8 + ...LocalVolumeCachePoolStatusDoc_zh_cn.groovy | 27 +++ .../VmLocalVolumeCachePoolVO.java | 143 ++++++++++++ .../VmLocalVolumeCachePoolVO_.java | 19 ++ .../VmLocalVolumeCacheState.java | 6 + .../VmLocalVolumeCacheStateDoc_zh_cn.groovy | 27 +++ .../VmLocalVolumeCacheVO.java | 109 +++++++++ .../VmLocalVolumeCacheVO_.java | 18 ++ .../BeforeVmAllocateHostExtensionPoint.java | 7 + .../CreateDataVolumeExtensionPoint.java | 4 + .../src/main/java/org/zstack/kvm/CacheTO.java | 51 +++++ .../java/org/zstack/kvm/KVMAgentCommands.java | 215 ++++++++++++++++++ .../main/java/org/zstack/kvm/KVMConstant.java | 1 + .../src/main/java/org/zstack/kvm/KVMHost.java | 31 +++ .../main/java/org/zstack/kvm/VolumeTO.java | 44 +++- sdk/src/main/java/SourceClassMap.java | 16 ++ sdk/src/main/java/org/zstack/sdk/CacheTO.java | 39 ++++ ...angeVmLocalVolumeCachePoolStateAction.java | 107 +++++++++ ...angeVmLocalVolumeCachePoolStateResult.java | 14 ++ .../CreateVmLocalVolumeCachePoolAction.java | 119 ++++++++++ .../CreateVmLocalVolumeCachePoolResult.java | 14 ++ .../DeleteVmLocalVolumeCachePoolAction.java | 104 +++++++++ .../DeleteVmLocalVolumeCachePoolResult.java | 7 + .../zstack/sdk/DisableVolumeCacheAction.java | 101 ++++++++ .../zstack/sdk/DisableVolumeCacheResult.java | 14 ++ .../zstack/sdk/EnableVolumeCacheAction.java | 107 +++++++++ .../zstack/sdk/EnableVolumeCacheResult.java | 14 ++ .../ExtendVmLocalVolumeCachePoolAction.java | 107 +++++++++ .../ExtendVmLocalVolumeCachePoolResult.java | 14 ++ .../org/zstack/sdk/GetBlockDevicesAction.java | 101 ++++++++ .../org/zstack/sdk/GetBlockDevicesResult.java | 14 ++ .../org/zstack/sdk/HostBlockDeviceStruct.java | 111 +++++++++ .../QueryVmLocalVolumeCachePoolAction.java | 75 ++++++ .../QueryVmLocalVolumeCachePoolResult.java | 22 ++ ...ReconnectVmLocalVolumeCachePoolAction.java | 104 +++++++++ ...ReconnectVmLocalVolumeCachePoolResult.java | 14 ++ ...cVmLocalVolumeCachePoolCapacityAction.java | 104 +++++++++ ...cVmLocalVolumeCachePoolCapacityResult.java | 14 ++ .../UpdateVmLocalVolumeCachePoolAction.java | 110 +++++++++ .../UpdateVmLocalVolumeCachePoolResult.java | 14 ++ .../sdk/VmLocalVolumeCacheInventory.java | 72 ++++++ .../zstack/sdk/VmLocalVolumeCacheMode.java | 5 + .../sdk/VmLocalVolumeCachePoolInventory.java | 120 ++++++++++ .../sdk/VmLocalVolumeCachePoolState.java | 6 + .../sdk/VmLocalVolumeCachePoolStatus.java | 7 + .../zstack/sdk/VmLocalVolumeCacheState.java | 6 + .../main/java/org/zstack/sdk/VolumeTO.java | 18 +- .../storage/volume/VolumeManagerImpl.java | 5 +- .../storage/volume/VolumeSystemTags.java | 9 + .../resources/springConfigXml/license.xml | 20 -- .../java/org/zstack/testlib/ApiHelper.groovy | 205 ++++++++++++++++- .../CloudOperationsErrorCode.java | 70 ++++-- 87 files changed, 4191 insertions(+), 78 deletions(-) create mode 100644 conf/db/upgrade/V5.5.18__schema.sql create mode 100644 conf/globalConfig/vmLocalVolumeCachePool.xml create mode 100644 conf/serviceConfig/vmLocalVolumeCache.xml create mode 100644 conf/serviceConfig/vmLocalVolumeCachePool.xml create mode 100644 conf/springConfigXml/localVolumeCache.xml create mode 100644 header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEvent.java create mode 100644 header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEventDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsg.java create mode 100644 header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsgDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostMsg.java create mode 100644 header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostReply.java create mode 100644 header/src/main/java/org/zstack/header/host/HostBlockDeviceStruct.java create mode 100644 header/src/main/java/org/zstack/header/host/HostBlockDeviceStructDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/CachePoolMetadata.java create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheConstant.java create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventory.java create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventoryDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheMode.java create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheModeDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityInventory.java create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityVO.java create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityVO_.java create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventory.java create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventoryDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolState.java create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStateDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatus.java create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatusDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO.java create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO_.java create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheState.java create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheStateDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO.java create mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO_.java create mode 100644 header/src/main/java/org/zstack/header/vm/BeforeVmAllocateHostExtensionPoint.java create mode 100644 plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java create mode 100644 sdk/src/main/java/org/zstack/sdk/CacheTO.java create mode 100644 sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/GetBlockDevicesAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/GetBlockDevicesResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/HostBlockDeviceStruct.java create mode 100644 sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheInventory.java create mode 100644 sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheMode.java create mode 100644 sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolInventory.java create mode 100644 sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolState.java create mode 100644 sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolStatus.java create mode 100644 sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheState.java delete mode 100755 test/src/test/resources/springConfigXml/license.xml diff --git a/compute/src/main/java/org/zstack/compute/host/HostBase.java b/compute/src/main/java/org/zstack/compute/host/HostBase.java index 919d92631fc..f377e3356d3 100755 --- a/compute/src/main/java/org/zstack/compute/host/HostBase.java +++ b/compute/src/main/java/org/zstack/compute/host/HostBase.java @@ -188,6 +188,8 @@ protected void handleApiMessage(APIMessage msg) { handle((APIPowerResetHostMsg) msg); } else if (msg instanceof APIGetHostPowerStatusMsg) { handle((APIGetHostPowerStatusMsg) msg); + } else if (msg instanceof APIGetBlockDevicesMsg) { + handle((APIGetBlockDevicesMsg) msg); } else { bus.dealWithUnknownMessage(msg); } @@ -214,6 +216,26 @@ public void run(MessageReply reply) { }); } + private void handle(APIGetBlockDevicesMsg msg) { + APIGetBlockDevicesEvent event = new APIGetBlockDevicesEvent(msg.getId()); + GetBlockDevicesOnHostMsg gmsg = new GetBlockDevicesOnHostMsg(); + gmsg.setHostUuid(msg.getHostUuid()); + bus.makeTargetServiceIdByResourceUuid(gmsg, HostConstant.SERVICE_ID, msg.getHostUuid()); + bus.send(gmsg, new CloudBusCallBack(msg) { + @Override + public void run(MessageReply reply) { + if (!reply.isSuccess()) { + event.setSuccess(false); + event.setError(reply.getError()); + } else { + GetBlockDevicesOnHostReply r = reply.castReply(); + event.setBlockDevices(r.getBlockDevices()); + } + bus.publish(event); + } + }); + } + private void handle(APIPowerResetHostMsg msg) { final APIPowerResetHostEvent event = new APIPowerResetHostEvent(msg.getId()); RebootHostMsg rebootHostMsg = new RebootHostMsg(); diff --git a/compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java index 3059e9dfc19..8993b278e9b 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmAllocateHostFlow.java @@ -6,7 +6,6 @@ import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.cloudbus.CloudBusCallBack; import org.zstack.core.db.DatabaseFacade; -import org.zstack.core.db.Q; import org.zstack.core.db.SQL; import org.zstack.core.errorcode.ErrorFacade; import org.zstack.header.allocator.*; @@ -15,7 +14,6 @@ import org.zstack.header.core.workflow.Flow; import org.zstack.header.core.workflow.FlowRollback; import org.zstack.header.core.workflow.FlowTrigger; -import org.zstack.header.errorcode.ErrorCode; import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.host.HostInventory; import org.zstack.header.host.HostVO; @@ -24,8 +22,6 @@ import org.zstack.header.message.APIMessage; import org.zstack.header.message.MessageReply; import org.zstack.header.network.l3.L3NetworkInventory; -import org.zstack.header.storage.primary.PrimaryStorageClusterRefVO; -import org.zstack.header.storage.primary.PrimaryStorageClusterRefVO_; import org.zstack.header.vm.*; import org.zstack.header.vm.VmInstanceConstant.VmOperation; import org.zstack.utils.CollectionUtils; @@ -162,6 +158,7 @@ public void run(final FlowTrigger chain, Map data) { } AllocateHostMsg msg = this.prepareMsg(spec); + extEmitter.beforeVmAllocateHost(msg, spec); bus.send(msg, new CloudBusCallBack(chain) { @Override diff --git a/compute/src/main/java/org/zstack/compute/vm/VmAllocateHostForStoppedVmFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmAllocateHostForStoppedVmFlow.java index 98f39b01372..d43d293d5b2 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmAllocateHostForStoppedVmFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmAllocateHostForStoppedVmFlow.java @@ -92,6 +92,7 @@ public String call(L3NetworkInventory arg) { msg.setAvoidHostUuids(spec.getAvoidHostUuids()); amsg = msg; + extEmitter.beforeVmAllocateHost(amsg, spec); bus.send(amsg, new CloudBusCallBack(chain) { @Override public void run(MessageReply reply) { diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstanceExtensionPointEmitter.java b/compute/src/main/java/org/zstack/compute/vm/VmInstanceExtensionPointEmitter.java index 78be6365828..ae03663c930 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstanceExtensionPointEmitter.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceExtensionPointEmitter.java @@ -6,6 +6,7 @@ import org.zstack.core.errorcode.ErrorFacade; import org.zstack.core.workflow.FlowChainBuilder; import org.zstack.header.Component; +import org.zstack.header.allocator.AllocateHostMsg; import org.zstack.header.core.Completion; import org.zstack.header.core.NoErrorCompletion; import org.zstack.header.core.WhileDoneCompletion; @@ -51,7 +52,7 @@ public class VmInstanceExtensionPointEmitter implements Component { private List cleanUpAfterVmChangeImageExtensionPoints; private List vmNicChangeStateExtensionPoints; private List sshKeyPairAssociateExtensionPoints; - private List afterUpdateVmNicMacExtensionPoints; + private List beforeVmAllocateHostExtensions; public List handleSystemTag(String vmUuid, List tags){ List errorCodes = new ArrayList<>(); @@ -614,6 +615,10 @@ public void cloneSshKeyPairsToVm(String originVmUuid, String destVmUuid) { }); } + public void beforeVmAllocateHost(AllocateHostMsg msg, VmInstanceSpec spec) { + CollectionUtils.safeForEach(beforeVmAllocateHostExtensions, ext -> ext.beforeVmAllocateHost(msg, spec)); + } + private void populateExtensions() { VmInstanceBeforeStartExtensions = pluginRgty.getExtensionList(VmInstanceBeforeStartExtensionPoint.class); VmInstanceResumeExtensionPoints = pluginRgty.getExtensionList(VmInstanceResumeExtensionPoint.class); @@ -631,7 +636,7 @@ private void populateExtensions() { cleanUpAfterVmChangeImageExtensionPoints = pluginRgty.getExtensionList(CleanUpAfterVmChangeImageExtensionPoint.class); vmNicChangeStateExtensionPoints = pluginRgty.getExtensionList(VmNicChangeStateExtensionPoint.class); sshKeyPairAssociateExtensionPoints = pluginRgty.getExtensionList(SshKeyPairAssociateExtensionPoint.class); - afterUpdateVmNicMacExtensionPoints = pluginRgty.getExtensionList(AfterUpdateVmNicMacExtensionPoint.class); + beforeVmAllocateHostExtensions = pluginRgty.getExtensionList(BeforeVmAllocateHostExtensionPoint.class); } @Override diff --git a/conf/db/upgrade/V5.5.18__schema.sql b/conf/db/upgrade/V5.5.18__schema.sql new file mode 100644 index 00000000000..788911627aa --- /dev/null +++ b/conf/db/upgrade/V5.5.18__schema.sql @@ -0,0 +1,46 @@ +CREATE TABLE IF NOT EXISTS `VmLocalVolumeCachePoolVO` ( + `uuid` VARCHAR(32) NOT NULL, + `hostUuid` VARCHAR(32) NOT NULL, + `name` VARCHAR(255) DEFAULT NULL, + `description` VARCHAR(2048) DEFAULT NULL, + `metadata` VARCHAR(2048) DEFAULT NULL, + `state` VARCHAR(32) NOT NULL, + `status` VARCHAR(32) NOT NULL, + `createDate` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', + `lastOpDate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`uuid`), + CONSTRAINT `fkVmLocalVolumeCachePoolVOHostEO` + FOREIGN KEY (`hostUuid`) REFERENCES `HostEO` (`uuid`) + ON DELETE CASCADE + ) ENGINE = InnoDB DEFAULT CHARSET = utf8; + +CREATE TABLE IF NOT EXISTS `VmLocalVolumeCachePoolCapacityVO` ( + `uuid` VARCHAR(32) NOT NULL, + `totalCapacity` BIGINT NOT NULL DEFAULT 0, + `availableCapacity` BIGINT NOT NULL DEFAULT 0, + `allocated` BIGINT NOT NULL DEFAULT 0, + `dirty` BIGINT NOT NULL DEFAULT 0, + PRIMARY KEY (`uuid`), + CONSTRAINT `fkVmLocalVolumeCachePoolCapacityVOVmLocalVolumeCachePoolVO` + FOREIGN KEY (`uuid`) REFERENCES `VmLocalVolumeCachePoolVO` (`uuid`) + ON DELETE CASCADE + ) ENGINE = InnoDB DEFAULT CHARSET = utf8; + +CREATE TABLE IF NOT EXISTS `VmLocalVolumeCacheVO` ( + `uuid` VARCHAR(32) NOT NULL, + `volumeUuid` VARCHAR(32) NOT NULL, + `poolUuid` VARCHAR(32) DEFAULT NULL, + `installPath` VARCHAR(2048) DEFAULT NULL, + `cacheMode` VARCHAR(32) NOT NULL, + `state` VARCHAR(32) NOT NULL, + `createDate` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', + `lastOpDate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`uuid`), + UNIQUE KEY `uniVmLocalVolumeCacheVOVolumeUuid` (`volumeUuid`), + CONSTRAINT `fkVmLocalVolumeCacheVOVolumeEO` + FOREIGN KEY (`volumeUuid`) REFERENCES `VolumeEO` (`uuid`) + ON DELETE CASCADE, + CONSTRAINT `fkVmLocalVolumeCacheVOPoolUuid` + FOREIGN KEY (`poolUuid`) REFERENCES `VmLocalVolumeCachePoolVO` (`uuid`) + ON DELETE SET NULL + ) ENGINE = InnoDB DEFAULT CHARSET = utf8; diff --git a/conf/globalConfig/vmLocalVolumeCachePool.xml b/conf/globalConfig/vmLocalVolumeCachePool.xml new file mode 100644 index 00000000000..666063156a1 --- /dev/null +++ b/conf/globalConfig/vmLocalVolumeCachePool.xml @@ -0,0 +1,27 @@ + + + + vmLocalVolumeCachePool + capacitySync.interval + Interval in seconds between periodic capacity sync tasks for local volume cache pools. + 300 + java.lang.Long + + + + vmLocalVolumeCachePool + healthCheck.interval + Interval in seconds between periodic health check tasks for local volume cache pools. + 60 + java.lang.Long + + + + vmLocalVolumeCachePool + gc.interval + Interval in seconds between periodic garbage collection tasks for local volume cache pools. + 3600 + java.lang.Long + + + diff --git a/conf/persistence.xml b/conf/persistence.xml index b66d6319ff7..504aa198879 100755 --- a/conf/persistence.xml +++ b/conf/persistence.xml @@ -217,6 +217,9 @@ org.zstack.network.hostNetworkInterface.lldp.entity.HostNetworkInterfaceLldpRefVO org.zstack.header.volume.block.BlockVolumeVO org.zstack.header.host.HostHwMonitorStatusVO + org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolVO + org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolCapacityVO + org.zstack.header.localVolumeCache.VmLocalVolumeCacheVO org.zstack.kvm.xmlhook.XmlHookVO org.zstack.kvm.xmlhook.XmlHookVmInstanceRefVO org.zstack.log.server.LogServerVO diff --git a/conf/serviceConfig/host.xml b/conf/serviceConfig/host.xml index bb076932f7e..e46b0a34e30 100755 --- a/conf/serviceConfig/host.xml +++ b/conf/serviceConfig/host.xml @@ -61,6 +61,10 @@ org.zstack.header.host.APIGetHostPowerStatusMsg + + org.zstack.header.host.APIGetBlockDevicesMsg + + org.zstack.header.host.APICreateHostNetworkServiceTypeMsg diff --git a/conf/serviceConfig/vmLocalVolumeCache.xml b/conf/serviceConfig/vmLocalVolumeCache.xml new file mode 100644 index 00000000000..b1ead4b02d2 --- /dev/null +++ b/conf/serviceConfig/vmLocalVolumeCache.xml @@ -0,0 +1,12 @@ + + + vmLocalVolumeCache + + + org.zstack.header.localVolumeCache.APIEnableVolumeCacheMsg + + + + org.zstack.header.localVolumeCache.APIDisableVolumeCacheMsg + + diff --git a/conf/serviceConfig/vmLocalVolumeCachePool.xml b/conf/serviceConfig/vmLocalVolumeCachePool.xml new file mode 100644 index 00000000000..4ea9301967d --- /dev/null +++ b/conf/serviceConfig/vmLocalVolumeCachePool.xml @@ -0,0 +1,37 @@ + + + vmLocalVolumeCachePool + + + org.zstack.header.localVolumeCache.APICreateVmLocalVolumeCachePoolMsg + + + + org.zstack.header.localVolumeCache.APIDeleteVmLocalVolumeCachePoolMsg + + + + org.zstack.header.localVolumeCache.APIUpdateVmLocalVolumeCachePoolMsg + + + + org.zstack.header.localVolumeCache.APIChangeVmLocalVolumeCachePoolStateMsg + + + + org.zstack.header.localVolumeCache.APIReconnectVmLocalVolumeCachePoolMsg + + + + org.zstack.header.localVolumeCache.APIExtendVmLocalVolumeCachePoolMsg + + + + org.zstack.header.localVolumeCache.APISyncVmLocalVolumeCachePoolCapacityMsg + + + + org.zstack.header.localVolumeCache.APIQueryVmLocalVolumeCachePoolMsg + query + + diff --git a/conf/springConfigXml/localVolumeCache.xml b/conf/springConfigXml/localVolumeCache.xml new file mode 100644 index 00000000000..89bd5c8104c --- /dev/null +++ b/conf/springConfigXml/localVolumeCache.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/conf/zstack.xml b/conf/zstack.xml index c0a5da2a80f..b723b6eadcc 100755 --- a/conf/zstack.xml +++ b/conf/zstack.xml @@ -122,4 +122,5 @@ + diff --git a/header/src/main/java/org/zstack/header/allocator/AllocateHostMsg.java b/header/src/main/java/org/zstack/header/allocator/AllocateHostMsg.java index cb0ddeeb081..bf70fbf1183 100755 --- a/header/src/main/java/org/zstack/header/allocator/AllocateHostMsg.java +++ b/header/src/main/java/org/zstack/header/allocator/AllocateHostMsg.java @@ -31,13 +31,7 @@ public class AllocateHostMsg extends NeedReplyMessage { private long oldMemoryCapacity = 0; private AllocationScene allocationScene; private String architecture; - private String accountUuid; - /** - * Allocation purpose. Defaults to ALLOCATE; LIST_CANDIDATES tells the - * downstream filters this is a candidate-listing call. Callers must gate - * LIST_CANDIDATES on admin permission. - */ - private HostAllocationPurpose purpose = HostAllocationPurpose.ALLOCATE; + private LinkedHashMap opaque; public List> getOptionalPrimaryStorageUuids() { return optionalPrimaryStorageUuids; @@ -219,15 +213,8 @@ public void setArchitecture(String architecture) { this.architecture = architecture; } - public String getAccountUuid() { return accountUuid; } + public LinkedHashMap getOpaque() { return opaque; } - public void setAccountUuid(String accountUuid) { this.accountUuid = accountUuid; } + public void setOpaque(LinkedHashMap opaque) { this.opaque = opaque; } - public HostAllocationPurpose getPurpose() { - return purpose; - } - - public void setPurpose(HostAllocationPurpose purpose) { - this.purpose = purpose == null ? HostAllocationPurpose.ALLOCATE : purpose; - } } diff --git a/header/src/main/java/org/zstack/header/allocator/HostAllocatorSpec.java b/header/src/main/java/org/zstack/header/allocator/HostAllocatorSpec.java index 8b9adfadc54..75ca66ea614 100755 --- a/header/src/main/java/org/zstack/header/allocator/HostAllocatorSpec.java +++ b/header/src/main/java/org/zstack/header/allocator/HostAllocatorSpec.java @@ -37,16 +37,15 @@ public class HostAllocatorSpec { private long oldMemoryCapacity = 0; private AllocationScene allocationScene; private String architecture; - private String accountUuid; - /** - * Allocation purpose. Defaults to ALLOCATE so existing call sites keep - * their current behavior. Filters may relax some checks (e.g. PCI device - * owner-RBAC) when this is LIST_CANDIDATES. - * - * Permission gating is the caller's responsibility; filters trust this - * value as-is. - */ - private HostAllocationPurpose purpose = HostAllocationPurpose.ALLOCATE; + private LinkedHashMap opaque; + + public LinkedHashMap getOpaque() { + return opaque; + } + + public void setOpaque(LinkedHashMap opaque) { + this.opaque = opaque; + } public AllocationScene getAllocationScene() { return allocationScene; @@ -279,6 +278,9 @@ public static HostAllocatorSpec fromAllocationMsg(AllocateHostMsg msg) { if (msg.getSystemTags() != null && !msg.getSystemTags().isEmpty()){ spec.setSystemTags(new ArrayList(msg.getSystemTags())); } + if (msg.getOpaque() != null) { + spec.setOpaque(new LinkedHashMap(msg.getOpaque())); + } return spec; } diff --git a/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEvent.java b/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEvent.java new file mode 100644 index 00000000000..dcb396d6796 --- /dev/null +++ b/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEvent.java @@ -0,0 +1,45 @@ +package org.zstack.header.host; + +import org.zstack.header.message.APIEvent; +import org.zstack.header.rest.RestResponse; + +import java.util.Collections; +import java.util.List; + +@RestResponse(fieldsTo = "all") +public class APIGetBlockDevicesEvent extends APIEvent { + private List blockDevices; + + public APIGetBlockDevicesEvent() { + } + + public APIGetBlockDevicesEvent(String apiId) { + super(apiId); + } + + public List getBlockDevices() { + return blockDevices; + } + + public void setBlockDevices(List blockDevices) { + this.blockDevices = blockDevices; + } + + public static APIGetBlockDevicesEvent __example__() { + APIGetBlockDevicesEvent event = new APIGetBlockDevicesEvent(); + HostBlockDeviceStruct struct = new HostBlockDeviceStruct(); + struct.setWwid("3600508b400105e5a0000800001490000"); + struct.setVendor("VMware"); + struct.setModel("Virtual disk"); + struct.setWwn("0x6000c2990b2c19db"); + struct.setSerial("6000c2990b2c19db"); + struct.setHctl("0:0:0:0"); + struct.setType("disk"); + struct.setSize(107374182400L); + struct.setPath("/dev/sda"); + struct.setSource("block"); + struct.setTransport("fc"); + event.setBlockDevices(Collections.singletonList(struct)); + return event; + } +} diff --git a/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEventDoc_zh_cn.groovy new file mode 100644 index 00000000000..c0ef654855d --- /dev/null +++ b/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEventDoc_zh_cn.groovy @@ -0,0 +1,32 @@ +package org.zstack.header.host + +import org.zstack.header.host.HostBlockDeviceStruct +import org.zstack.header.errorcode.ErrorCode + +doc { + + title "在这里输入结构的名称" + + ref { + name "blockDevices" + path "org.zstack.header.host.APIGetBlockDevicesEvent.blockDevices" + desc "null" + type "List" + since "5.5.6" + clz HostBlockDeviceStruct.class + } + field { + name "success" + desc "" + type "boolean" + since "5.5.6" + } + ref { + name "error" + path "org.zstack.header.host.APIGetBlockDevicesEvent.error" + desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null",false + type "ErrorCode" + since "5.5.6" + clz ErrorCode.class + } +} diff --git a/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsg.java b/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsg.java new file mode 100644 index 00000000000..8b5cc35e8b7 --- /dev/null +++ b/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsg.java @@ -0,0 +1,35 @@ +package org.zstack.header.host; + +import org.springframework.http.HttpMethod; +import org.zstack.header.message.APIMessage; +import org.zstack.header.message.APIParam; +import org.zstack.header.rest.RestRequest; + +@RestRequest( + path = "/hosts/{uuid}/block-devices", + method = HttpMethod.GET, + responseClass = APIGetBlockDevicesEvent.class +) +public class APIGetBlockDevicesMsg extends APIMessage implements HostMessage { + @APIParam(nonempty = true, resourceType = HostVO.class) + private String uuid; + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public String getHostUuid() { + return uuid; + } + + public static APIGetBlockDevicesMsg __example__() { + APIGetBlockDevicesMsg msg = new APIGetBlockDevicesMsg(); + msg.setUuid(uuid()); + return msg; + } +} diff --git a/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..41417dc0e75 --- /dev/null +++ b/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsgDoc_zh_cn.groovy @@ -0,0 +1,58 @@ +package org.zstack.header.host + +import org.zstack.header.host.APIGetBlockDevicesEvent + +doc { + title "GetBlockDevices" + + category "未知类别" + + desc """在这里填写API描述""" + + rest { + request { + url "GET /v1/hosts/{uuid}/block-devices" + + header (Authorization: 'OAuth the-session-uuid') + + clz APIGetBlockDevicesMsg.class + + desc """""" + + params { + + column { + name "uuid" + enclosedIn "" + desc "资源的UUID,唯一标示该资源" + location "url" + type "String" + optional false + since "5.5.6" + } + column { + name "systemTags" + enclosedIn "" + desc "系统标签" + location "query" + type "List" + optional true + since "5.5.6" + } + column { + name "userTags" + enclosedIn "" + desc "用户标签" + location "query" + type "List" + optional true + since "5.5.6" + } + } + } + + response { + clz APIGetBlockDevicesEvent.class + } + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostMsg.java b/header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostMsg.java new file mode 100644 index 00000000000..13e8764068b --- /dev/null +++ b/header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostMsg.java @@ -0,0 +1,16 @@ +package org.zstack.header.host; + +import org.zstack.header.message.NeedReplyMessage; + +public class GetBlockDevicesOnHostMsg extends NeedReplyMessage implements HostMessage { + private String hostUuid; + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } + + @Override + public String getHostUuid() { + return hostUuid; + } +} diff --git a/header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostReply.java b/header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostReply.java new file mode 100644 index 00000000000..1407c081abf --- /dev/null +++ b/header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostReply.java @@ -0,0 +1,17 @@ +package org.zstack.header.host; + +import org.zstack.header.message.MessageReply; + +import java.util.List; + +public class GetBlockDevicesOnHostReply extends MessageReply { + private List blockDevices; + + public List getBlockDevices() { + return blockDevices; + } + + public void setBlockDevices(List blockDevices) { + this.blockDevices = blockDevices; + } +} diff --git a/header/src/main/java/org/zstack/header/host/HostBlockDeviceStruct.java b/header/src/main/java/org/zstack/header/host/HostBlockDeviceStruct.java new file mode 100644 index 00000000000..f81e3b0aeef --- /dev/null +++ b/header/src/main/java/org/zstack/header/host/HostBlockDeviceStruct.java @@ -0,0 +1,148 @@ +package org.zstack.header.host; + +import java.io.Serializable; +import java.util.Objects; + +public class HostBlockDeviceStruct implements Serializable { + private String name; + private String wwid; + private String vendor; + private String model; + private String wwn; + private String serial; + private String hctl; + private String type; + private String path; + private Long size; + private String source; + private String transport; + private String targetIdentifier; + + public HostBlockDeviceStruct() { + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + HostBlockDeviceStruct that = (HostBlockDeviceStruct) o; + if (wwid != null) { + return wwid.equals(that.wwid); + } + if (that.wwid != null) { + return false; + } + return Objects.equals(path, that.path) && Objects.equals(hctl, that.hctl); + + + } + + @Override + public int hashCode() { + return wwid != null ? wwid.hashCode() : Objects.hash(path, hctl); + } + + public String getName() { + return name; + } + public void setName(String name) { + this.name = name; + } + + public String getWwid() { + return wwid; + } + + public void setWwid(String wwid) { + this.wwid = wwid; + } + + public String getVendor() { + return vendor; + } + + public void setVendor(String vendor) { + this.vendor = vendor; + } + + public String getModel() { + return model; + } + + public void setModel(String model) { + this.model = model; + } + + public String getWwn() { + return wwn; + } + + public void setWwn(String wwn) { + this.wwn = wwn; + } + + public String getSerial() { + return serial; + } + + public void setSerial(String serial) { + this.serial = serial; + } + + public String getHctl() { + return hctl; + } + + public void setHctl(String hctl) { + this.hctl = hctl; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public Long getSize() { + return size; + } + + public void setSize(Long size) { + this.size = size; + } + + public String getSource() { + return source; + } + + public void setSource(String source) { + this.source = source; + } + + public String getTransport() { + return transport; + } + + public void setTransport(String transport) { + this.transport = transport; + } + + public String getTargetIdentifier() { + return targetIdentifier; + } + + public void setTargetIdentifier(String targetIdentifier) { + this.targetIdentifier = targetIdentifier; + } +} diff --git a/header/src/main/java/org/zstack/header/host/HostBlockDeviceStructDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/host/HostBlockDeviceStructDoc_zh_cn.groovy new file mode 100644 index 00000000000..40707c0b932 --- /dev/null +++ b/header/src/main/java/org/zstack/header/host/HostBlockDeviceStructDoc_zh_cn.groovy @@ -0,0 +1,81 @@ +package org.zstack.header.host + +import java.lang.Long + +doc { + + title "在这里输入结构的名称" + + field { + name "wwid" + desc "" + type "String" + since "5.5.6" + } + field { + name "vendor" + desc "" + type "String" + since "5.5.6" + } + field { + name "model" + desc "" + type "String" + since "5.5.6" + } + field { + name "wwn" + desc "" + type "String" + since "5.5.6" + } + field { + name "serial" + desc "" + type "String" + since "5.5.6" + } + field { + name "hctl" + desc "" + type "String" + since "5.5.6" + } + field { + name "type" + desc "" + type "String" + since "5.5.6" + } + field { + name "path" + desc "" + type "String" + since "5.5.6" + } + field { + name "size" + desc "" + type "Long" + since "5.5.6" + } + field { + name "source" + desc "" + type "String" + since "5.5.6" + } + field { + name "transport" + desc "" + type "String" + since "5.5.6" + } + field { + name "targetIdentifier" + desc "" + type "String" + since "5.5.6" + } +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/CachePoolMetadata.java b/header/src/main/java/org/zstack/header/localVolumeCache/CachePoolMetadata.java new file mode 100644 index 00000000000..b82229fa282 --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/CachePoolMetadata.java @@ -0,0 +1,186 @@ +package org.zstack.header.localVolumeCache; + +import org.zstack.utils.gson.JSONObjectUtil; + +import java.io.Serializable; +import java.util.List; + +/** + * Strongly-typed representation of the metadata JSON stored in VmLocalVolumeCachePoolVO. + *

    + * VO stores metadata as a JSON String. VmLocalVolumeCachePoolInventory exposes it as a + * LinkedHashMap (following the ExternalPrimaryStorage addonInfo pattern). This POJO + * provides type-safe access for internal business logic. + *

    + * Structure mirrors the Python agent's CachePoolMetadata: + *

    + * {
    + *   "mountPoint": "/mnt/cache-pool-1",
    + *   "pvs": [{"pvUuid": "...", "pvName": "...", "pvDevicePath": "/dev/nvme0n1"}],
    + *   "vg": {"vgUuid": "...", "vgName": "..."},
    + *   "lv": {"lvUuid": "...", "lvName": "...", "lvPath": "..."},
    + *   "filesystem": {"fsUuid": "...", "fsType": "xfs"}
    + * }
    + * 
    + */ +public class CachePoolMetadata implements Serializable { + + public static class PVRef implements Serializable { + private String pvUuid; + private String pvName; + private String pvDevicePath; + + public String getPvUuid() { + return pvUuid; + } + + public void setPvUuid(String pvUuid) { + this.pvUuid = pvUuid; + } + + public String getPvName() { + return pvName; + } + + public void setPvName(String pvName) { + this.pvName = pvName; + } + + public String getPvDevicePath() { + return pvDevicePath; + } + + public void setPvDevicePath(String pvDevicePath) { + this.pvDevicePath = pvDevicePath; + } + } + + public static class VGRef implements Serializable { + private String vgUuid; + private String vgName; + + public String getVgUuid() { + return vgUuid; + } + + public void setVgUuid(String vgUuid) { + this.vgUuid = vgUuid; + } + + public String getVgName() { + return vgName; + } + + public void setVgName(String vgName) { + this.vgName = vgName; + } + } + + public static class LVRef implements Serializable { + private String lvUuid; + private String lvName; + private String lvPath; + + public String getLvUuid() { + return lvUuid; + } + + public void setLvUuid(String lvUuid) { + this.lvUuid = lvUuid; + } + + public String getLvName() { + return lvName; + } + + public void setLvName(String lvName) { + this.lvName = lvName; + } + + public String getLvPath() { + return lvPath; + } + + public void setLvPath(String lvPath) { + this.lvPath = lvPath; + } + } + + public static class FileSystemRef implements Serializable { + private String fsUuid; + private String fsType; + + public String getFsUuid() { + return fsUuid; + } + + public void setFsUuid(String fsUuid) { + this.fsUuid = fsUuid; + } + + public String getFsType() { + return fsType; + } + + public void setFsType(String fsType) { + this.fsType = fsType; + } + } + + private String mountPoint; + private List pvs; + private VGRef vg; + private LVRef lv; + private FileSystemRef filesystem; + + public String getMountPoint() { + return mountPoint; + } + + public void setMountPoint(String mountPoint) { + this.mountPoint = mountPoint; + } + + public List getPvs() { + return pvs; + } + + public void setPvs(List pvs) { + this.pvs = pvs; + } + + public VGRef getVg() { + return vg; + } + + public void setVg(VGRef vg) { + this.vg = vg; + } + + public LVRef getLv() { + return lv; + } + + public void setLv(LVRef lv) { + this.lv = lv; + } + + public FileSystemRef getFilesystem() { + return filesystem; + } + + public void setFilesystem(FileSystemRef filesystem) { + this.filesystem = filesystem; + } + + public static CachePoolMetadata fromJson(String json) { + if (json == null || json.isEmpty()) { + return new CachePoolMetadata(); + } + return JSONObjectUtil.toObject(json, CachePoolMetadata.class); + } + + public String toJson() { + return JSONObjectUtil.toJsonString(this); + } +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheConstant.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheConstant.java new file mode 100644 index 00000000000..d75cc7e46cb --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheConstant.java @@ -0,0 +1,5 @@ +package org.zstack.header.localVolumeCache; + +public interface VmLocalVolumeCacheConstant { + String CACHE_SERVICE_ID = "vmLocalVolumeCache"; +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventory.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventory.java new file mode 100644 index 00000000000..e0d5a306ba7 --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventory.java @@ -0,0 +1,108 @@ +package org.zstack.header.localVolumeCache; + +import org.zstack.header.configuration.PythonClassInventory; +import org.zstack.header.search.Inventory; + +import java.io.Serializable; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@Inventory(mappingVOClass = VmLocalVolumeCacheVO.class) +@PythonClassInventory +public class VmLocalVolumeCacheInventory implements Serializable { + private String uuid; + private String volumeUuid; + private String poolUuid; + private String installPath; + private VmLocalVolumeCacheMode cacheMode; + private VmLocalVolumeCacheState state; + private Timestamp createDate; + private Timestamp lastOpDate; + + public static VmLocalVolumeCacheInventory valueOf(VmLocalVolumeCacheVO vo) { + VmLocalVolumeCacheInventory inv = new VmLocalVolumeCacheInventory(); + inv.setUuid(vo.getUuid()); + inv.setVolumeUuid(vo.getVolumeUuid()); + inv.setPoolUuid(vo.getPoolUuid()); + inv.setInstallPath(vo.getInstallPath()); + inv.setCacheMode(vo.getCacheMode()); + inv.setState(vo.getState()); + inv.setCreateDate(vo.getCreateDate()); + inv.setLastOpDate(vo.getLastOpDate()); + return inv; + } + + public static List valueOf(Collection vos) { + List invs = new ArrayList<>(); + for (VmLocalVolumeCacheVO vo : vos) { + invs.add(valueOf(vo)); + } + return invs; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getVolumeUuid() { + return volumeUuid; + } + + public void setVolumeUuid(String volumeUuid) { + this.volumeUuid = volumeUuid; + } + + public String getPoolUuid() { + return poolUuid; + } + + public void setPoolUuid(String poolUuid) { + this.poolUuid = poolUuid; + } + + public String getInstallPath() { + return installPath; + } + + public void setInstallPath(String installPath) { + this.installPath = installPath; + } + + public VmLocalVolumeCacheMode getCacheMode() { + return cacheMode; + } + + public void setCacheMode(VmLocalVolumeCacheMode cacheMode) { + this.cacheMode = cacheMode; + } + + public VmLocalVolumeCacheState getState() { + return state; + } + + public void setState(VmLocalVolumeCacheState state) { + this.state = state; + } + + public Timestamp getCreateDate() { + return createDate; + } + + public void setCreateDate(Timestamp createDate) { + this.createDate = createDate; + } + + public Timestamp getLastOpDate() { + return lastOpDate; + } + + public void setLastOpDate(Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventoryDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventoryDoc_zh_cn.groovy new file mode 100644 index 00000000000..d098f92796a --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventoryDoc_zh_cn.groovy @@ -0,0 +1,63 @@ +package org.zstack.header.localVolumeCache + +import org.zstack.header.localVolumeCache.VmLocalVolumeCacheMode +import org.zstack.header.localVolumeCache.VmLocalVolumeCacheState +import java.sql.Timestamp + +doc { + + title "在这里输入结构的名称" + + field { + name "uuid" + desc "资源的UUID,唯一标示该资源" + type "String" + since "5.5.6" + } + field { + name "volumeUuid" + desc "云盘UUID" + type "String" + since "5.5.6" + } + field { + name "poolUuid" + desc "" + type "String" + since "5.5.6" + } + field { + name "installPath" + desc "" + type "String" + since "5.5.6" + } + ref { + name "cacheMode" + path "org.zstack.header.localVolumeCache.VmLocalVolumeCacheInventory.cacheMode" + desc "null" + type "VmLocalVolumeCacheMode" + since "5.5.6" + clz VmLocalVolumeCacheMode.class + } + ref { + name "state" + path "org.zstack.header.localVolumeCache.VmLocalVolumeCacheInventory.state" + desc "null" + type "VmLocalVolumeCacheState" + since "5.5.6" + clz VmLocalVolumeCacheState.class + } + field { + name "createDate" + desc "创建时间" + type "Timestamp" + since "5.5.6" + } + field { + name "lastOpDate" + desc "最后一次修改时间" + type "Timestamp" + since "5.5.6" + } +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheMode.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheMode.java new file mode 100644 index 00000000000..736262d3433 --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheMode.java @@ -0,0 +1,5 @@ +package org.zstack.header.localVolumeCache; + +public enum VmLocalVolumeCacheMode { + WriteBack, +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheModeDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheModeDoc_zh_cn.groovy new file mode 100644 index 00000000000..fcc06bb9590 --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheModeDoc_zh_cn.groovy @@ -0,0 +1,15 @@ +package org.zstack.header.localVolumeCache + + + +doc { + + title "在这里输入结构的名称" + + field { + name "WriteBack" + desc "" + type "VmLocalVolumeCacheMode" + since "5.5.6" + } +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityInventory.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityInventory.java new file mode 100644 index 00000000000..8ac4f3acbb3 --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityInventory.java @@ -0,0 +1,85 @@ +package org.zstack.header.localVolumeCache; + +import org.zstack.header.configuration.PythonClassInventory; +import org.zstack.header.search.Inventory; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@Inventory(mappingVOClass = VmLocalVolumeCachePoolCapacityVO.class) +@PythonClassInventory +public class VmLocalVolumeCachePoolCapacityInventory implements Serializable { + private String uuid; + private long totalCapacity; + private long availableCapacity; + private long allocated; + private long dirty; + + public static VmLocalVolumeCachePoolCapacityInventory valueOf(VmLocalVolumeCachePoolCapacityVO vo) { + if (vo == null) { + return null; + } + + VmLocalVolumeCachePoolCapacityInventory inv = new VmLocalVolumeCachePoolCapacityInventory(); + inv.setUuid(vo.getUuid()); + inv.setTotalCapacity(vo.getTotalCapacity()); + inv.setAvailableCapacity(vo.getAvailableCapacity()); + inv.setAllocated(vo.getAllocated()); + inv.setDirty(vo.getDirty()); + return inv; + } + + public static List valueOf(Collection vos) { + if (vos == null || vos.isEmpty()) { + return new ArrayList<>(); + } + List invs = new ArrayList<>(vos.size()); + + for (VmLocalVolumeCachePoolCapacityVO vo : vos) { + invs.add(valueOf(vo)); + } + return invs; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public long getTotalCapacity() { + return totalCapacity; + } + + public void setTotalCapacity(long totalCapacity) { + this.totalCapacity = totalCapacity; + } + + public long getAvailableCapacity() { + return availableCapacity; + } + + public void setAvailableCapacity(long availableCapacity) { + this.availableCapacity = availableCapacity; + } + + public long getAllocated() { + return allocated; + } + + public void setAllocated(long allocated) { + this.allocated = allocated; + } + + public long getDirty() { + return dirty; + } + + public void setDirty(long dirty) { + this.dirty = dirty; + } +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityVO.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityVO.java new file mode 100644 index 00000000000..4bdd60d387c --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityVO.java @@ -0,0 +1,79 @@ +package org.zstack.header.localVolumeCache; + +import org.zstack.header.vo.EntityGraph; +import org.zstack.header.vo.ForeignKey; +import org.zstack.header.vo.ForeignKey.ReferenceOption; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table +@EntityGraph( + parents = { + @EntityGraph.Neighbour(type = VmLocalVolumeCachePoolVO.class, myField = "uuid", targetField = "uuid") + } +) +public class VmLocalVolumeCachePoolCapacityVO { + @Id + @Column + @ForeignKey(parentEntityClass = VmLocalVolumeCachePoolVO.class, onDeleteAction = ReferenceOption.CASCADE) + private String uuid; + + @Column + private long totalCapacity; + + @Column + private long availableCapacity; + + @Column + private long allocated; + + @Column + private long dirty; + + public VmLocalVolumeCachePoolCapacityVO() { + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public long getTotalCapacity() { + return totalCapacity; + } + + public void setTotalCapacity(long totalCapacity) { + this.totalCapacity = totalCapacity; + } + + public long getAvailableCapacity() { + return availableCapacity; + } + + public void setAvailableCapacity(long availableCapacity) { + this.availableCapacity = availableCapacity; + } + + public long getAllocated() { + return allocated; + } + + public void setAllocated(long allocated) { + this.allocated = allocated; + } + + public long getDirty() { + return dirty; + } + + public void setDirty(long dirty) { + this.dirty = dirty; + } +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityVO_.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityVO_.java new file mode 100644 index 00000000000..c2012e9f0f7 --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityVO_.java @@ -0,0 +1,13 @@ +package org.zstack.header.localVolumeCache; + +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; + +@StaticMetamodel(VmLocalVolumeCachePoolCapacityVO.class) +public class VmLocalVolumeCachePoolCapacityVO_ { + public static volatile SingularAttribute uuid; + public static volatile SingularAttribute totalCapacity; + public static volatile SingularAttribute availableCapacity; + public static volatile SingularAttribute allocated; + public static volatile SingularAttribute dirty; +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventory.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventory.java new file mode 100644 index 00000000000..126aff258b9 --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventory.java @@ -0,0 +1,194 @@ +package org.zstack.header.localVolumeCache; + +import org.hibernate.Hibernate; +import org.zstack.header.configuration.PythonClassInventory; +import org.zstack.header.query.Queryable; +import org.zstack.header.search.Inventory; +import org.zstack.utils.gson.JSONObjectUtil; + +import javax.persistence.JoinColumn; +import java.io.Serializable; +import java.sql.Timestamp; +import java.util.*; + +@Inventory(mappingVOClass = VmLocalVolumeCachePoolVO.class) +@PythonClassInventory +public class VmLocalVolumeCachePoolInventory implements Serializable { + private String uuid; + private String hostUuid; + private String name; + private String description; + private LinkedHashMap metadata; + + @Queryable(mappingClass = VmLocalVolumeCachePoolCapacityInventory.class, + joinColumn = @JoinColumn(name = "uuid", referencedColumnName = "totalCapacity")) + private long totalCapacity; + + @Queryable(mappingClass = VmLocalVolumeCachePoolCapacityInventory.class, + joinColumn = @JoinColumn(name = "uuid", referencedColumnName = "availableCapacity")) + private long availableCapacity; + + @Queryable(mappingClass = VmLocalVolumeCachePoolCapacityInventory.class, + joinColumn = @JoinColumn(name = "uuid", referencedColumnName = "allocated")) + private long allocated; + + @Queryable(mappingClass = VmLocalVolumeCachePoolCapacityInventory.class, + joinColumn = @JoinColumn(name = "uuid", referencedColumnName = "dirty")) + private long dirty; + + private VmLocalVolumeCachePoolState state; + private VmLocalVolumeCachePoolStatus status; + private Set caches; + private Timestamp createDate; + private Timestamp lastOpDate; + + public static VmLocalVolumeCachePoolInventory valueOf(VmLocalVolumeCachePoolVO vo) { + VmLocalVolumeCachePoolInventory inv = new VmLocalVolumeCachePoolInventory(); + inv.setUuid(vo.getUuid()); + inv.setHostUuid(vo.getHostUuid()); + inv.setName(vo.getName()); + inv.setDescription(vo.getDescription()); + if (vo.getMetadata() != null) { + inv.setMetadata(JSONObjectUtil.toObject(vo.getMetadata(), LinkedHashMap.class)); + } + if (vo.getCapacity() != null) { + inv.setTotalCapacity(vo.getCapacity().getTotalCapacity()); + inv.setAvailableCapacity(vo.getCapacity().getAvailableCapacity()); + inv.setAllocated(vo.getCapacity().getAllocated()); + inv.setDirty(vo.getCapacity().getDirty()); + } + inv.setState(vo.getState()); + inv.setStatus(vo.getStatus()); + inv.setCreateDate(vo.getCreateDate()); + inv.setLastOpDate(vo.getLastOpDate()); + if(vo.getCaches() != null && Hibernate.isInitialized(vo.getCaches())) { + Set cacheInventories = new HashSet<>(); + for (VmLocalVolumeCacheVO cacheVO : vo.getCaches()) { + cacheInventories.add(cacheVO.toInventory()); + } + inv.setCaches(cacheInventories); + } + return inv; + } + + public static List valueOf(Collection vos) { + List invs = new ArrayList<>(); + for (VmLocalVolumeCachePoolVO vo : vos) { + invs.add(valueOf(vo)); + } + return invs; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getHostUuid() { + return hostUuid; + } + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public LinkedHashMap getMetadata() { + return metadata; + } + + public void setMetadata(LinkedHashMap metadata) { + this.metadata = metadata; + } + + public long getTotalCapacity() { + return totalCapacity; + } + + public void setTotalCapacity(long totalCapacity) { + this.totalCapacity = totalCapacity; + } + + public long getAvailableCapacity() { + return availableCapacity; + } + + public void setAvailableCapacity(long availableCapacity) { + this.availableCapacity = availableCapacity; + } + + public long getAllocated() { + return allocated; + } + + public void setAllocated(long allocated) { + this.allocated = allocated; + } + + public long getDirty() { + return dirty; + } + + public void setDirty(long dirty) { + this.dirty = dirty; + } + + public VmLocalVolumeCachePoolState getState() { + return state; + } + + public void setState(VmLocalVolumeCachePoolState state) { + this.state = state; + } + + public VmLocalVolumeCachePoolStatus getStatus() { + return status; + } + + public void setStatus(VmLocalVolumeCachePoolStatus status) { + this.status = status; + } + + public Set getCaches() { + return caches; + } + + public void setCaches(Set caches) { + this.caches = caches; + } + + public Timestamp getCreateDate() { + return createDate; + } + + public void setCreateDate(Timestamp createDate) { + this.createDate = createDate; + } + + public Timestamp getLastOpDate() { + return lastOpDate; + } + + public void setLastOpDate(Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } +} + diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventoryDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventoryDoc_zh_cn.groovy new file mode 100644 index 00000000000..b66843950d6 --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventoryDoc_zh_cn.groovy @@ -0,0 +1,90 @@ +package org.zstack.header.localVolumeCache + +import org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolState +import org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolStatus +import org.zstack.header.localVolumeCache.VmLocalVolumeCacheInventory +import java.sql.Timestamp + +doc { + + title "在这里输入结构的名称" + + field { + name "uuid" + desc "资源的UUID,唯一标示该资源" + type "String" + since "5.5.6" + } + field { + name "hostUuid" + desc "物理机UUID" + type "String" + since "5.5.6" + } + field { + name "name" + desc "资源名称" + type "String" + since "5.5.6" + } + field { + name "description" + desc "资源的详细描述" + type "String" + since "5.5.6" + } + field { + name "metadata" + desc "" + type "LinkedHashMap" + since "5.5.6" + } + field { + name "totalCapacity" + desc "" + type "long" + since "5.5.6" + } + field { + name "availableCapacity" + desc "" + type "long" + since "5.5.6" + } + ref { + name "state" + path "org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolInventory.state" + desc "null" + type "VmLocalVolumeCachePoolState" + since "5.5.6" + clz VmLocalVolumeCachePoolState.class + } + ref { + name "status" + path "org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolInventory.status" + desc "null" + type "VmLocalVolumeCachePoolStatus" + since "5.5.6" + clz VmLocalVolumeCachePoolStatus.class + } + ref { + name "caches" + path "org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolInventory.caches" + desc "null" + type "Set" + since "5.5.6" + clz VmLocalVolumeCacheInventory.class + } + field { + name "createDate" + desc "创建时间" + type "Timestamp" + since "5.5.6" + } + field { + name "lastOpDate" + desc "最后一次修改时间" + type "Timestamp" + since "5.5.6" + } +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolState.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolState.java new file mode 100644 index 00000000000..13f7aeb273e --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolState.java @@ -0,0 +1,7 @@ +package org.zstack.header.localVolumeCache; + +public enum VmLocalVolumeCachePoolState { + Enabled, + Disabled, +} + diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStateDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStateDoc_zh_cn.groovy new file mode 100644 index 00000000000..f1525aa58e4 --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStateDoc_zh_cn.groovy @@ -0,0 +1,21 @@ +package org.zstack.header.localVolumeCache + + + +doc { + + title "在这里输入结构的名称" + + field { + name "Enabled" + desc "" + type "VmLocalVolumeCachePoolState" + since "5.5.6" + } + field { + name "Disabled" + desc "" + type "VmLocalVolumeCachePoolState" + since "5.5.6" + } +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatus.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatus.java new file mode 100644 index 00000000000..6028d2c9121 --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatus.java @@ -0,0 +1,8 @@ +package org.zstack.header.localVolumeCache; + +public enum VmLocalVolumeCachePoolStatus { + Connecting, + Connected, + Disconnected, +} + diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatusDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatusDoc_zh_cn.groovy new file mode 100644 index 00000000000..2427adf7c81 --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatusDoc_zh_cn.groovy @@ -0,0 +1,27 @@ +package org.zstack.header.localVolumeCache + + + +doc { + + title "在这里输入结构的名称" + + field { + name "Connecting" + desc "" + type "VmLocalVolumeCachePoolStatus" + since "5.5.6" + } + field { + name "Connected" + desc "" + type "VmLocalVolumeCachePoolStatus" + since "5.5.6" + } + field { + name "Disconnected" + desc "" + type "VmLocalVolumeCachePoolStatus" + since "5.5.6" + } +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO.java new file mode 100644 index 00000000000..14b999f5dbd --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO.java @@ -0,0 +1,143 @@ +package org.zstack.header.localVolumeCache; + +import org.hibernate.annotations.NotFound; +import org.hibernate.annotations.NotFoundAction; +import org.zstack.header.host.HostEO; +import org.zstack.header.host.HostVO; +import org.zstack.header.vo.EntityGraph; +import org.zstack.header.vo.ForeignKey; +import org.zstack.header.vo.NoView; +import org.zstack.header.vo.ResourceVO; +import org.zstack.header.vo.ToInventory; + +import javax.persistence.*; +import java.sql.Timestamp; +import java.util.Set; + +@Entity +@Table +@EntityGraph( + parents = { + @EntityGraph.Neighbour(type = HostVO.class, myField = "hostUuid", targetField = "uuid") + } +) +public class VmLocalVolumeCachePoolVO extends ResourceVO implements ToInventory { + + @Column + @ForeignKey(parentEntityClass = HostEO.class, onDeleteAction = ForeignKey.ReferenceOption.CASCADE) + private String hostUuid; + + @Column + private String name; + + @Column + private String description; + + @Column(length = 2048) + private String metadata; + + @OneToOne(fetch = FetchType.EAGER) + @JoinColumn(name = "uuid") + @NoView + private VmLocalVolumeCachePoolCapacityVO capacity; + + @Column + @Enumerated(EnumType.STRING) + private VmLocalVolumeCachePoolState state; + + @Column + @Enumerated(EnumType.STRING) + private VmLocalVolumeCachePoolStatus status; + + @OneToMany(fetch = FetchType.LAZY) + @NotFound(action = NotFoundAction.IGNORE) + @JoinColumn(name = "poolUuid", insertable = false, updatable = false) + private Set caches; + + @Column + private Timestamp createDate; + + @Column + private Timestamp lastOpDate; + + @PreUpdate + private void preUpdate() { + lastOpDate = null; + } + + public String getHostUuid() { + return hostUuid; + } + + public void setHostUuid(String hostUuid) { + this.hostUuid = hostUuid; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getMetadata() { + return metadata; + } + + public void setMetadata(String metadata) { + this.metadata = metadata; + } + + public VmLocalVolumeCachePoolCapacityVO getCapacity() { + return capacity; + } + + public void setCapacity(VmLocalVolumeCachePoolCapacityVO capacity) { + this.capacity = capacity; + } + + public VmLocalVolumeCachePoolState getState() { + return state; + } + + public void setState(VmLocalVolumeCachePoolState state) { + this.state = state; + } + + public VmLocalVolumeCachePoolStatus getStatus() { + return status; + } + + public void setStatus(VmLocalVolumeCachePoolStatus status) { + this.status = status; + } + + public Set getCaches() { + return caches; + } + + public Timestamp getCreateDate() { + return createDate; + } + + public void setCreateDate(Timestamp createDate) { + this.createDate = createDate; + } + + public Timestamp getLastOpDate() { + return lastOpDate; + } + + public void setLastOpDate(Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO_.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO_.java new file mode 100644 index 00000000000..3d62bf0f718 --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO_.java @@ -0,0 +1,19 @@ +package org.zstack.header.localVolumeCache; + +import org.zstack.header.vo.ResourceVO_; + +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; +import java.sql.Timestamp; + +@StaticMetamodel(VmLocalVolumeCachePoolVO.class) +public class VmLocalVolumeCachePoolVO_ extends ResourceVO_ { + public static volatile SingularAttribute hostUuid; + public static volatile SingularAttribute name; + public static volatile SingularAttribute description; + public static volatile SingularAttribute metadata; + public static volatile SingularAttribute state; + public static volatile SingularAttribute status; + public static volatile SingularAttribute createDate; + public static volatile SingularAttribute lastOpDate; +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheState.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheState.java new file mode 100644 index 00000000000..3ec6af0a109 --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheState.java @@ -0,0 +1,6 @@ +package org.zstack.header.localVolumeCache; + +public enum VmLocalVolumeCacheState { + Uninstantiated, + Instantiated, +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheStateDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheStateDoc_zh_cn.groovy new file mode 100644 index 00000000000..a2ec9941de2 --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheStateDoc_zh_cn.groovy @@ -0,0 +1,27 @@ +package org.zstack.header.localVolumeCache + + + +doc { + + title "在这里输入结构的名称" + + field { + name "None" + desc "" + type "VmLocalVolumeCacheState" + since "5.5.6" + } + field { + name "Uninstantiated" + desc "" + type "VmLocalVolumeCacheState" + since "5.5.6" + } + field { + name "Instantiated" + desc "" + type "VmLocalVolumeCacheState" + since "5.5.6" + } +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO.java new file mode 100644 index 00000000000..ba45d6e865f --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO.java @@ -0,0 +1,109 @@ +package org.zstack.header.localVolumeCache; + +import org.zstack.header.vo.EntityGraph; +import org.zstack.header.vo.ForeignKey; +import org.zstack.header.vo.ResourceVO; +import org.zstack.header.vo.ToInventory; +import org.zstack.header.volume.VolumeEO; +import org.zstack.header.volume.VolumeVO; + +import javax.persistence.*; +import java.sql.Timestamp; + +@Entity +@Table(uniqueConstraints = { + @UniqueConstraint(columnNames = "volumeUuid") +}) +@EntityGraph( + parents = { + @EntityGraph.Neighbour(type = VolumeVO.class, myField = "volumeUuid", targetField = "uuid") + } +) +public class VmLocalVolumeCacheVO extends ResourceVO implements ToInventory { + + @Column + @ForeignKey(parentEntityClass = VolumeEO.class, onDeleteAction = ForeignKey.ReferenceOption.CASCADE) + private String volumeUuid; + + @Column + @ForeignKey(parentEntityClass = VmLocalVolumeCachePoolVO.class, onDeleteAction = ForeignKey.ReferenceOption.SET_NULL) + private String poolUuid; + + @Column(length = 2048) + private String installPath; + + @Column + @Enumerated(EnumType.STRING) + private VmLocalVolumeCacheMode cacheMode; + + @Column + @Enumerated(EnumType.STRING) + private VmLocalVolumeCacheState state; + + @Column + private Timestamp createDate; + + @Column + private Timestamp lastOpDate; + + @PreUpdate + private void preUpdate() { + lastOpDate = null; + } + + public String getVolumeUuid() { + return volumeUuid; + } + + public void setVolumeUuid(String volumeUuid) { + this.volumeUuid = volumeUuid; + } + + public String getPoolUuid() { + return poolUuid; + } + + public void setPoolUuid(String poolUuid) { + this.poolUuid = poolUuid; + } + + public String getInstallPath() { + return installPath; + } + + public void setInstallPath(String installPath) { + this.installPath = installPath; + } + + public VmLocalVolumeCacheMode getCacheMode() { + return cacheMode; + } + + public void setCacheMode(VmLocalVolumeCacheMode cacheMode) { + this.cacheMode = cacheMode; + } + + public VmLocalVolumeCacheState getState() { + return state; + } + + public void setState(VmLocalVolumeCacheState state) { + this.state = state; + } + + public Timestamp getCreateDate() { + return createDate; + } + + public void setCreateDate(Timestamp createDate) { + this.createDate = createDate; + } + + public Timestamp getLastOpDate() { + return lastOpDate; + } + + public void setLastOpDate(Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO_.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO_.java new file mode 100644 index 00000000000..a116f09dbe9 --- /dev/null +++ b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO_.java @@ -0,0 +1,18 @@ +package org.zstack.header.localVolumeCache; + +import org.zstack.header.vo.ResourceVO_; + +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; +import java.sql.Timestamp; + +@StaticMetamodel(VmLocalVolumeCacheVO.class) +public class VmLocalVolumeCacheVO_ extends ResourceVO_ { + public static volatile SingularAttribute volumeUuid; + public static volatile SingularAttribute poolUuid; + public static volatile SingularAttribute installPath; + public static volatile SingularAttribute cacheMode; + public static volatile SingularAttribute state; + public static volatile SingularAttribute createDate; + public static volatile SingularAttribute lastOpDate; +} diff --git a/header/src/main/java/org/zstack/header/vm/BeforeVmAllocateHostExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/BeforeVmAllocateHostExtensionPoint.java new file mode 100644 index 00000000000..ff8e53f070b --- /dev/null +++ b/header/src/main/java/org/zstack/header/vm/BeforeVmAllocateHostExtensionPoint.java @@ -0,0 +1,7 @@ +package org.zstack.header.vm; + +import org.zstack.header.allocator.AllocateHostMsg; + +public interface BeforeVmAllocateHostExtensionPoint { + void beforeVmAllocateHost(AllocateHostMsg msg, VmInstanceSpec spec); +} diff --git a/header/src/main/java/org/zstack/header/volume/CreateDataVolumeExtensionPoint.java b/header/src/main/java/org/zstack/header/volume/CreateDataVolumeExtensionPoint.java index 6ce8ed6ab7f..aa35047f577 100644 --- a/header/src/main/java/org/zstack/header/volume/CreateDataVolumeExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/volume/CreateDataVolumeExtensionPoint.java @@ -9,4 +9,8 @@ public interface CreateDataVolumeExtensionPoint { void beforeCreateVolume(VolumeInventory volume); void afterCreateVolume(VolumeVO volume); + + default void afterCreateVolume(VolumeVO volume, VolumeCreateMessage msg) { + afterCreateVolume(volume); + } } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java b/plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java new file mode 100644 index 00000000000..72b51906f0a --- /dev/null +++ b/plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java @@ -0,0 +1,51 @@ +package org.zstack.kvm; + +import org.zstack.header.localVolumeCache.VmLocalVolumeCacheInventory; + +public class CacheTO extends BaseVirtualDeviceTO { + private String cacheUuid; + private String poolUuid; + private String installPath; + private String cacheMode; + + public String getCacheUuid() { + return cacheUuid; + } + + public void setCacheUuid(String cacheUuid) { + this.cacheUuid = cacheUuid; + } + + public String getPoolUuid() { + return poolUuid; + } + + public void setPoolUuid(String poolUuid) { + this.poolUuid = poolUuid; + } + + public String getInstallPath() { + return installPath; + } + + public void setInstallPath(String installPath) { + this.installPath = installPath; + } + + public String getCacheMode() { + return cacheMode; + } + + public void setCacheMode(String cacheMode) { + this.cacheMode = cacheMode; + } + + public static CacheTO valueOf(VmLocalVolumeCacheInventory inv) { + CacheTO to = new CacheTO(); + to.setCacheUuid(inv.getUuid()); + to.setPoolUuid(inv.getPoolUuid()); + to.setInstallPath(inv.getInstallPath()); + to.setCacheMode(inv.getCacheMode() != null ? inv.getCacheMode().name() : null); + return to; + } +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java index 13a978d1467..1a3c0bb1101 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java @@ -8,8 +8,10 @@ import org.zstack.header.HasThreadContext; import org.zstack.header.agent.CancelCommand; import org.zstack.header.core.validation.Validation; +import org.zstack.header.host.HostBlockDeviceStruct; import org.zstack.header.host.HostNUMANode; import org.zstack.header.host.VmNicRedirectConfig; +import org.zstack.header.localVolumeCache.CachePoolMetadata; import org.zstack.header.log.NoLogging; import org.zstack.header.vm.*; import org.zstack.header.vm.devices.DeviceAddress; @@ -5370,4 +5372,217 @@ public void setMemoryUsage(long memoryUsage) { } } + // ======================================================================== + // Local Volume Cache — Command Definitions + // ======================================================================== + + public static class InitPoolCmd extends AgentCommand { + public String poolUuid; + public String mountPoint; + public boolean force; + public List pvs; + } + + public static class ConnectPoolCmd extends AgentCommand { + public String poolUuid; + public String mountPoint; + public boolean force; + } + + public static class ExtendPoolCmd extends AgentCommand { + public String poolUuid; + public String mountPoint; + public boolean force; + public List pvs; + } + + public static class DeletePoolCmd extends AgentCommand { + public String poolUuid; + public String mountPoint; + public boolean force; + } + + public static class CheckPoolCmd extends AgentCommand { + public String poolUuid; + public String mountPoint; + public boolean force; + } + + public static class GetPoolCapacityCmd extends AgentCommand { + public String poolUuid; + public String mountPoint; + public boolean force; + } + + public static class GCPoolCmd extends AgentCommand { + public String poolUuid; + public String mountPoint; + public boolean force; + public List volumes; + } + + public static class AllocateCacheCmd extends AgentCommand { + public String poolUuid; + public VolumeTO volume; + } + + public static class DeleteCacheCmd extends AgentCommand { + public String poolUuid; + public VolumeTO volume; + } + + public static class FlushCacheCmd extends AgentCommand { + public String poolUuid; + public VolumeTO volume; + } + + public static class GetCacheCapacityCmd extends AgentCommand { + public String poolUuid; + public VolumeTO volume; + } + + public static class AttachVolumeCacheCmd extends AgentCommand { + public String instanceUuid; + public VolumeTO volume; + } + + public static class DetachVolumeCacheCmd extends AgentCommand implements HasThreadContext { + public String instanceUuid; + public VolumeTO volume; + } + + // ======================================================================== + // Local Volume Cache — Ref Structures + // ======================================================================== + + public static class PVRef { + public String pvUuid; + public String pvName; + public String pvDevicePath; + } + + public static class PVHealthRef extends PVRef { + public Boolean healthy; + } + + public static class VGRef { + public String vgUuid; + public String vgName; + } + + public static class LVRef { + public String lvUuid; + public String lvName; + public String lvPath; + } + + public static class FileSystemRef { + public String fsUuid; + public String fsType; + } + + public static class VolumeRef { + public String volumeUuid; + public String installPath; + public String deviceType; + public String format; + public Long size; + } + + // ======================================================================== + // Local Volume Cache — Response Definitions + // ======================================================================== + + public static class PoolRsp extends AgentResponse { + public String poolUuid; + public String mountPoint; + public List pvs; + public VGRef vg; + public LVRef lv; + public FileSystemRef filesystem; + + public CachePoolMetadata toCachePoolMetadata() { + CachePoolMetadata metadata = new CachePoolMetadata(); + metadata.setMountPoint(mountPoint); + if (pvs != null) { + List pvRefs = pvs.stream().map(pv -> { + CachePoolMetadata.PVRef ref = new CachePoolMetadata.PVRef(); + ref.setPvUuid(pv.pvUuid); + ref.setPvName(pv.pvName); + ref.setPvDevicePath(pv.pvDevicePath); + return ref; + }).collect(Collectors.toList()); + metadata.setPvs(pvRefs); + } + if (vg != null) { + CachePoolMetadata.VGRef vgRef = new CachePoolMetadata.VGRef(); + vgRef.setVgUuid(vg.vgUuid); + vgRef.setVgName(vg.vgName); + metadata.setVg(vgRef); + } + if (lv != null) { + CachePoolMetadata.LVRef lvRef = new CachePoolMetadata.LVRef(); + lvRef.setLvUuid(lv.lvUuid); + lvRef.setLvName(lv.lvName); + lvRef.setLvPath(lv.lvPath); + metadata.setLv(lvRef); + } + if (filesystem != null) { + CachePoolMetadata.FileSystemRef fsRef = new CachePoolMetadata.FileSystemRef(); + fsRef.setFsUuid(filesystem.fsUuid); + fsRef.setFsType(filesystem.fsType); + metadata.setFilesystem(fsRef); + } + return metadata; + } + } + + public static class PoolHealthRsp extends AgentResponse { + public Boolean healthy; + public List pvs; + public Boolean vg; + public Boolean lv; + public Boolean filesystem; + } + + public static class PoolCapacityRsp extends AgentResponse { + public Long total; + public Long used; + public Long available; + public Long allocated; + public Long dirty; + } + + public static class CacheRsp extends AgentResponse { + public String installPath; + public Long virtualSize; + public Long actualSize; + } + + public static class GCPoolRsp extends AgentResponse { + public List gcFiles; + public Integer gcCount; + } + + public static class AttachVolumeCacheRsp extends AgentResponse { + } + + public static class DetachVolumeCacheRsp extends AgentResponse { + } + + public static class GetBlockDevicesCmd extends AgentCommand { + } + + public static class GetBlockDevicesRsp extends AgentResponse { + private List blockDevices; + + public List getBlockDevices() { + return blockDevices; + } + + public void setBlockDevices(List blockDevices) { + this.blockDevices = blockDevices; + } + } + } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java index c45ab09f616..96c4d05b200 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMConstant.java @@ -92,6 +92,7 @@ public interface KVMConstant { String GET_VIRTUALIZER_INFO_PATH = "/vm/getvirtualizerinfo"; String KVM_SCAN_VM_PORT_STATUS = "/host/vm/scanport"; String GET_DEV_CAPACITY = "/host/dev/capacity"; + String KVM_GET_BLOCK_DEVICES_PATH = "/host/blockdevices"; String KVM_CONFIG_PRIMARY_VM_PATH = "/primary/vm/config"; String KVM_CONFIG_SECONDARY_VM_PATH = "/secondary/vm/config"; String KVM_START_COLO_SYNC_PATH = "/start/colo/sync"; diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java index 5662ed4dfd0..2bc01951cb3 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java @@ -1,6 +1,7 @@ package org.zstack.kvm; import okhttp3.Response; + import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.util.Strings; import org.springframework.beans.factory.annotation.Autowired; @@ -233,6 +234,7 @@ public static Set parseSanIps(String sanOutput) { private String getVirtualizerInfo; private String scanVmPortPath; private String getDevCapacityPath; + private String getBlockDevicesPath; private String configPrimaryVmPath; private String configSecondaryVmPath; private String startColoSyncPath; @@ -429,6 +431,10 @@ public KVMHost(KVMHostVO self, KVMHostContext context) { ub.path(KVMConstant.GET_DEV_CAPACITY); getDevCapacityPath = ub.build().toString(); + ub = UriComponentsBuilder.fromHttpUrl(baseUrl); + ub.path(KVMConstant.KVM_GET_BLOCK_DEVICES_PATH); + getBlockDevicesPath = ub.build().toString(); + ub = UriComponentsBuilder.fromHttpUrl(baseUrl); ub.path(KVMConstant.KVM_CONFIG_PRIMARY_VM_PATH); configPrimaryVmPath = ub.build().toString(); @@ -732,11 +738,36 @@ protected void handleLocalMessage(Message msg) { handle((RestartKvmAgentMsg) msg); } else if (msg instanceof UpdateVmConsolePasswordOnHypervisorMsg) { handle((UpdateVmConsolePasswordOnHypervisorMsg) msg); + } else if (msg instanceof GetBlockDevicesOnHostMsg) { + handle((GetBlockDevicesOnHostMsg) msg); } else { super.handleLocalMessage(msg); } } + private void handle(GetBlockDevicesOnHostMsg msg) { + GetBlockDevicesOnHostReply reply = new GetBlockDevicesOnHostReply(); + KVMAgentCommands.GetBlockDevicesCmd cmd = new KVMAgentCommands.GetBlockDevicesCmd(); + new Http<>(getBlockDevicesPath, cmd, KVMAgentCommands.GetBlockDevicesRsp.class) + .call(new ReturnValueCompletion(msg) { + @Override + public void success(KVMAgentCommands.GetBlockDevicesRsp rsp) { + if (!rsp.isSuccess()) { + reply.setError(operr("operation error, because:%s", rsp.getError())); + } else { + reply.setBlockDevices(rsp.getBlockDevices()); + } + bus.reply(msg, reply); + } + + @Override + public void fail(ErrorCode errorCode) { + reply.setError(errorCode); + bus.reply(msg, reply); + } + }); + } + private void handle(RestartKvmAgentMsg msg) { RestartKvmAgentReply reply = new RestartKvmAgentReply(); thdf.singleFlightSubmit(new SingleFlightTask(msg) diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java b/plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java index 2ea9238f167..901bf60aee5 100644 --- a/plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java @@ -3,7 +3,11 @@ import org.zstack.core.Platform; import org.zstack.core.componentloader.PluginRegistry; import org.zstack.core.db.Q; +import org.zstack.header.host.HostInventory; import org.zstack.header.image.ImagePlatform; +import org.zstack.header.localVolumeCache.VmLocalVolumeCacheInventory; +import org.zstack.header.localVolumeCache.VmLocalVolumeCacheVO; +import org.zstack.header.localVolumeCache.VmLocalVolumeCacheVO_; import org.zstack.header.storage.primary.PrimaryStorageVO; import org.zstack.header.storage.primary.PrimaryStorageVO_; import org.zstack.header.vm.VmInstanceVO; @@ -11,6 +15,7 @@ import org.zstack.header.volume.VolumeInventory; import org.zstack.resourceconfig.ResourceConfigFacade; import org.zstack.header.volume.VolumeProtocol; +import org.springframework.beans.BeanUtils; import java.util.HashMap; import java.util.List; @@ -32,6 +37,7 @@ public class VolumeTO extends BaseVirtualDeviceTO { public static List exts; private String installPath; + private Long size; private int deviceId; private String deviceType = FILE; private String volumeUuid; @@ -52,6 +58,8 @@ public class VolumeTO extends BaseVirtualDeviceTO { private int ioThreadId; private String ioThreadPin; private int controllerIndex; + private CacheTO cache; + static { deviceTypes.put(VolumeProtocol.Vhost, VHOST); @@ -63,6 +71,7 @@ public VolumeTO() { public VolumeTO(VolumeTO other) { this.installPath = other.installPath; + this.size = other.size; this.deviceId = other.deviceId; this.deviceType = other.deviceType; this.volumeUuid = other.volumeUuid; @@ -81,6 +90,7 @@ public VolumeTO(VolumeTO other) { this.ioThreadId = other.ioThreadId; this.ioThreadPin = other.ioThreadPin; this.controllerIndex = other.controllerIndex; + this.cache = other.cache; } public static List valueOf(List vols, KVMHostInventory host) { @@ -95,10 +105,21 @@ public static VolumeTO valueOfWithOutExtension(VolumeInventory vol, KVMHostInven return valueOf(vol, host, platform, false); } + public static VolumeTO valueOfWithOutExtension(VolumeInventory vol, HostInventory host, String platform) { + return valueOf(vol, host, platform, false); + } + + public static VolumeTO valueOf(VolumeInventory vol, HostInventory host, String platform, boolean withExtension) { + KVMHostInventory kvmHostInventory = new KVMHostInventory(); + BeanUtils.copyProperties(host, kvmHostInventory); + return valueOf(vol, kvmHostInventory, platform, withExtension); + } + public static VolumeTO valueOf(VolumeInventory vol, KVMHostInventory host, String platform, boolean withExtension) { VolumeTO to = new VolumeTO(); to.setResourceUuid(vol.getUuid()); to.setInstallPath(vol.getInstallPath()); + to.setSize(vol.getSize()); if (vol.getDeviceId() != null) { to.setDeviceId(vol.getDeviceId()); } @@ -136,7 +157,16 @@ public static VolumeTO valueOf(VolumeInventory vol, KVMHostInventory host, Strin } for (KVMConvertVolumeExtensionPoint ext : exts) { to = ext.convertVolumeIfNeed(host, vol, to); - } + } + VmLocalVolumeCacheVO cacheVO = Q.New(VmLocalVolumeCacheVO.class) + .eq(VmLocalVolumeCacheVO_.volumeUuid, vol.getUuid()) + .find(); + if (cacheVO == null) { + return to; + } + VmLocalVolumeCacheInventory cacheInv = VmLocalVolumeCacheInventory.valueOf(cacheVO); + CacheTO cacheTO = CacheTO.valueOf(cacheInv); + to.setCache(cacheTO); return to; } @@ -202,6 +232,10 @@ public void setInstallPath(String installPath) { this.installPath = installPath; } + public Long getSize() { return size; } + + public void setSize(Long size) { this.size = size; } + public int getDeviceId() { return deviceId; } @@ -297,4 +331,12 @@ public int getControllerIndex() { public void setControllerIndex(int controllerIndex) { this.controllerIndex = controllerIndex; } + + public CacheTO getCache() { + return cache; + } + + public void setCache(CacheTO cache) { + this.cache = cache; + } } diff --git a/sdk/src/main/java/SourceClassMap.java b/sdk/src/main/java/SourceClassMap.java index 4fdc5791b47..055af74e30a 100644 --- a/sdk/src/main/java/SourceClassMap.java +++ b/sdk/src/main/java/SourceClassMap.java @@ -280,6 +280,7 @@ public class SourceClassMap { put("org.zstack.header.flowMeter.NetworkRouterFlowMeterRefInventory", "org.zstack.sdk.NetworkRouterFlowMeterRefInventory"); put("org.zstack.header.host.AddHostFromFileResult", "org.zstack.sdk.AddHostFromFileResult"); put("org.zstack.header.host.CpuArchitecture", "org.zstack.sdk.CpuArchitecture"); + put("org.zstack.header.host.HostBlockDeviceStruct", "org.zstack.sdk.HostBlockDeviceStruct"); put("org.zstack.header.host.HostInventory", "org.zstack.sdk.HostInventory"); put("org.zstack.header.host.HostIpmiInventory", "org.zstack.sdk.HostIpmiInventory"); put("org.zstack.header.host.HostNUMANode", "org.zstack.sdk.HostNUMANode"); @@ -325,6 +326,12 @@ public class SourceClassMap { put("org.zstack.header.image.ImageGroupInventory", "org.zstack.sdk.ImageGroupInventory"); put("org.zstack.header.image.ImageGroupRefInventory", "org.zstack.sdk.ImageGroupRefInventory"); put("org.zstack.header.image.ImageInventory", "org.zstack.sdk.ImageInventory"); + put("org.zstack.header.localVolumeCache.VmLocalVolumeCacheInventory", "org.zstack.sdk.VmLocalVolumeCacheInventory"); + put("org.zstack.header.localVolumeCache.VmLocalVolumeCacheMode", "org.zstack.sdk.VmLocalVolumeCacheMode"); + put("org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolInventory", "org.zstack.sdk.VmLocalVolumeCachePoolInventory"); + put("org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolState", "org.zstack.sdk.VmLocalVolumeCachePoolState"); + put("org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolStatus", "org.zstack.sdk.VmLocalVolumeCachePoolStatus"); + put("org.zstack.header.localVolumeCache.VmLocalVolumeCacheState", "org.zstack.sdk.VmLocalVolumeCacheState"); put("org.zstack.header.longjob.LongJobInventory", "org.zstack.sdk.LongJobInventory"); put("org.zstack.header.longjob.LongJobState", "org.zstack.sdk.LongJobState"); put("org.zstack.header.managementnode.ManagementNodeInventory", "org.zstack.sdk.ManagementNodeInventory"); @@ -496,6 +503,7 @@ public class SourceClassMap { put("org.zstack.ipsec.IPsecPeerCidrInventory", "org.zstack.sdk.IPsecPeerCidrInventory"); put("org.zstack.kvm.APIKvmRunShellEvent$ShellResult", "org.zstack.sdk.ShellResult"); put("org.zstack.kvm.BaseVirtualDeviceTO", "org.zstack.sdk.BaseVirtualDeviceTO"); + put("org.zstack.kvm.CacheTO", "org.zstack.sdk.CacheTO"); put("org.zstack.kvm.KVMAgentCommands$CdRomTO", "org.zstack.sdk.CdRomTO"); put("org.zstack.kvm.KVMAgentCommands$IsoTO", "org.zstack.sdk.IsoTO"); put("org.zstack.kvm.KVMAgentCommands$NicTO", "org.zstack.sdk.NicTO"); @@ -1016,6 +1024,7 @@ public class SourceClassMap { put("org.zstack.sdk.CCSCertificateUserRefInventory", "org.zstack.crypto.ccs.CCSCertificateUserRefInventory"); put("org.zstack.sdk.CCSCertificateUserState", "org.zstack.crypto.ccs.CCSCertificateUserState"); put("org.zstack.sdk.CSPSecretResourcePoolInventory", "org.zstack.crypto.securitymachine.thirdparty.csp.CSPSecretResourcePoolInventory"); + put("org.zstack.sdk.CacheTO", "org.zstack.kvm.CacheTO"); put("org.zstack.sdk.CasClientInventory", "org.zstack.sso.header.CasClientInventory"); put("org.zstack.sdk.CasState", "org.zstack.sso.header.CasState"); put("org.zstack.sdk.CbtTaskInventory", "org.zstack.header.cbt.CbtTaskInventory"); @@ -1146,6 +1155,7 @@ public class SourceClassMap { put("org.zstack.sdk.HardwareL2VxlanNetworkInventory", "org.zstack.sdnController.header.HardwareL2VxlanNetworkInventory"); put("org.zstack.sdk.HardwareL2VxlanNetworkPoolInventory", "org.zstack.sdnController.header.HardwareL2VxlanNetworkPoolInventory"); put("org.zstack.sdk.HbaDeviceInventory", "org.zstack.storage.device.hba.HbaDeviceInventory"); + put("org.zstack.sdk.HostBlockDeviceStruct", "org.zstack.header.host.HostBlockDeviceStruct"); put("org.zstack.sdk.HostConnectedStatus", "org.zstack.zops.HostConnectedStatus"); put("org.zstack.sdk.HostDiskCapacity", "org.zstack.storage.primary.local.APIGetLocalStorageHostDiskCapacityReply$HostDiskCapacity"); put("org.zstack.sdk.HostInventory", "org.zstack.header.host.HostInventory"); @@ -1589,6 +1599,12 @@ public class SourceClassMap { put("org.zstack.sdk.VmInstanceInventory", "org.zstack.header.vm.VmInstanceInventory"); put("org.zstack.sdk.VmInstanceMdevDeviceSpecRefInventory", "org.zstack.pciDevice.specification.mdev.VmInstanceMdevDeviceSpecRefInventory"); put("org.zstack.sdk.VmInstancePciDeviceSpecRefInventory", "org.zstack.pciDevice.specification.pci.VmInstancePciDeviceSpecRefInventory"); + put("org.zstack.sdk.VmLocalVolumeCacheInventory", "org.zstack.header.localVolumeCache.VmLocalVolumeCacheInventory"); + put("org.zstack.sdk.VmLocalVolumeCacheMode", "org.zstack.header.localVolumeCache.VmLocalVolumeCacheMode"); + put("org.zstack.sdk.VmLocalVolumeCachePoolInventory", "org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolInventory"); + put("org.zstack.sdk.VmLocalVolumeCachePoolState", "org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolState"); + put("org.zstack.sdk.VmLocalVolumeCachePoolStatus", "org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolStatus"); + put("org.zstack.sdk.VmLocalVolumeCacheState", "org.zstack.header.localVolumeCache.VmLocalVolumeCacheState"); put("org.zstack.sdk.VmMemoryBillingInventory", "org.zstack.billing.generator.vm.memory.VmMemoryBillingInventory"); put("org.zstack.sdk.VmMemorySpendingDetails", "org.zstack.billing.spendingcalculator.vm.VmMemorySpendingDetails"); put("org.zstack.sdk.VmModelMountInventory", "org.zstack.ai.entity.VmModelMountInventory"); diff --git a/sdk/src/main/java/org/zstack/sdk/CacheTO.java b/sdk/src/main/java/org/zstack/sdk/CacheTO.java new file mode 100644 index 00000000000..6439de03034 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/CacheTO.java @@ -0,0 +1,39 @@ +package org.zstack.sdk; + + + +public class CacheTO extends org.zstack.sdk.BaseVirtualDeviceTO { + + public java.lang.String cacheUuid; + public void setCacheUuid(java.lang.String cacheUuid) { + this.cacheUuid = cacheUuid; + } + public java.lang.String getCacheUuid() { + return this.cacheUuid; + } + + public java.lang.String poolUuid; + public void setPoolUuid(java.lang.String poolUuid) { + this.poolUuid = poolUuid; + } + public java.lang.String getPoolUuid() { + return this.poolUuid; + } + + public java.lang.String installPath; + public void setInstallPath(java.lang.String installPath) { + this.installPath = installPath; + } + public java.lang.String getInstallPath() { + return this.installPath; + } + + public java.lang.String cacheMode; + public void setCacheMode(java.lang.String cacheMode) { + this.cacheMode = cacheMode; + } + public java.lang.String getCacheMode() { + return this.cacheMode; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateAction.java b/sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateAction.java new file mode 100644 index 00000000000..d664d85a80a --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateAction.java @@ -0,0 +1,107 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class ChangeVmLocalVolumeCachePoolStateAction extends AbstractAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.ChangeVmLocalVolumeCachePoolStateResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String uuid; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String hostUuid; + + @Param(required = true, validValues = {"enable","disable"}, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String stateEvent; + + @Param(required = false) + public java.util.List systemTags; + + @Param(required = false) + public java.util.List userTags; + + @Param(required = false) + public String sessionId; + + @Param(required = false) + public String accessKeyId; + + @Param(required = false) + public String accessKeySecret; + + @Param(required = false) + public String requestIp; + + @NonAPIParam + public long timeout = -1; + + @NonAPIParam + public long pollingInterval = -1; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.ChangeVmLocalVolumeCachePoolStateResult value = res.getResult(org.zstack.sdk.ChangeVmLocalVolumeCachePoolStateResult.class); + ret.value = value == null ? new org.zstack.sdk.ChangeVmLocalVolumeCachePoolStateResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "PUT"; + info.path = "/hosts/{hostUuid}/local-volume-cache-pools/{uuid}/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "changeVmLocalVolumeCachePoolState"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateResult.java b/sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateResult.java new file mode 100644 index 00000000000..d4f1c9d6b10 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + +import org.zstack.sdk.VmLocalVolumeCachePoolInventory; + +public class ChangeVmLocalVolumeCachePoolStateResult { + public VmLocalVolumeCachePoolInventory inventory; + public void setInventory(VmLocalVolumeCachePoolInventory inventory) { + this.inventory = inventory; + } + public VmLocalVolumeCachePoolInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolAction.java b/sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolAction.java new file mode 100644 index 00000000000..3c3283758a5 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolAction.java @@ -0,0 +1,119 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class CreateVmLocalVolumeCachePoolAction extends AbstractAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.CreateVmLocalVolumeCachePoolResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String hostUuid; + + @Param(required = true, maxLength = 255, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String name; + + @Param(required = false, maxLength = 2048, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String description; + + @Param(required = true, nonempty = true, nullElements = false, emptyString = true, noTrim = false) + public java.util.List pvs; + + @Param(required = true, maxLength = 1024, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String mountPoint; + + @Param(required = false) + public java.lang.String resourceUuid; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.util.List tagUuids; + + @Param(required = false) + public java.util.List systemTags; + + @Param(required = false) + public java.util.List userTags; + + @Param(required = false) + public String sessionId; + + @Param(required = false) + public String accessKeyId; + + @Param(required = false) + public String accessKeySecret; + + @Param(required = false) + public String requestIp; + + @NonAPIParam + public long timeout = -1; + + @NonAPIParam + public long pollingInterval = -1; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.CreateVmLocalVolumeCachePoolResult value = res.getResult(org.zstack.sdk.CreateVmLocalVolumeCachePoolResult.class); + ret.value = value == null ? new org.zstack.sdk.CreateVmLocalVolumeCachePoolResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "POST"; + info.path = "/hosts/{hostUuid}/local-volume-cache-pools"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "params"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolResult.java b/sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolResult.java new file mode 100644 index 00000000000..f39fed20bae --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + +import org.zstack.sdk.VmLocalVolumeCachePoolInventory; + +public class CreateVmLocalVolumeCachePoolResult { + public VmLocalVolumeCachePoolInventory inventory; + public void setInventory(VmLocalVolumeCachePoolInventory inventory) { + this.inventory = inventory; + } + public VmLocalVolumeCachePoolInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolAction.java b/sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolAction.java new file mode 100644 index 00000000000..31d24e83c4c --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolAction.java @@ -0,0 +1,104 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class DeleteVmLocalVolumeCachePoolAction extends AbstractAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.DeleteVmLocalVolumeCachePoolResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String uuid; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String hostUuid; + + @Param(required = false) + public java.util.List systemTags; + + @Param(required = false) + public java.util.List userTags; + + @Param(required = false) + public String sessionId; + + @Param(required = false) + public String accessKeyId; + + @Param(required = false) + public String accessKeySecret; + + @Param(required = false) + public String requestIp; + + @NonAPIParam + public long timeout = -1; + + @NonAPIParam + public long pollingInterval = -1; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.DeleteVmLocalVolumeCachePoolResult value = res.getResult(org.zstack.sdk.DeleteVmLocalVolumeCachePoolResult.class); + ret.value = value == null ? new org.zstack.sdk.DeleteVmLocalVolumeCachePoolResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "DELETE"; + info.path = "/hosts/{hostUuid}/local-volume-cache-pools/{uuid}"; + info.needSession = true; + info.needPoll = true; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolResult.java b/sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolResult.java new file mode 100644 index 00000000000..5449db3bcc0 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolResult.java @@ -0,0 +1,7 @@ +package org.zstack.sdk; + + + +public class DeleteVmLocalVolumeCachePoolResult { + +} diff --git a/sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheAction.java b/sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheAction.java new file mode 100644 index 00000000000..1a748074582 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheAction.java @@ -0,0 +1,101 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class DisableVolumeCacheAction extends AbstractAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.DisableVolumeCacheResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String volumeUuid; + + @Param(required = false) + public java.util.List systemTags; + + @Param(required = false) + public java.util.List userTags; + + @Param(required = false) + public String sessionId; + + @Param(required = false) + public String accessKeyId; + + @Param(required = false) + public String accessKeySecret; + + @Param(required = false) + public String requestIp; + + @NonAPIParam + public long timeout = -1; + + @NonAPIParam + public long pollingInterval = -1; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.DisableVolumeCacheResult value = res.getResult(org.zstack.sdk.DisableVolumeCacheResult.class); + ret.value = value == null ? new org.zstack.sdk.DisableVolumeCacheResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "PUT"; + info.path = "/volumes/{volumeUuid}/local-volume-cache/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "disableVolumeCache"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheResult.java b/sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheResult.java new file mode 100644 index 00000000000..a0e0615ae44 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + +import org.zstack.sdk.VmLocalVolumeCacheInventory; + +public class DisableVolumeCacheResult { + public VmLocalVolumeCacheInventory inventory; + public void setInventory(VmLocalVolumeCacheInventory inventory) { + this.inventory = inventory; + } + public VmLocalVolumeCacheInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheAction.java b/sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheAction.java new file mode 100644 index 00000000000..19c2eb5eef4 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheAction.java @@ -0,0 +1,107 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class EnableVolumeCacheAction extends AbstractAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.EnableVolumeCacheResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String volumeUuid; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String poolUuid; + + @Param(required = false, validValues = {"WriteBack"}, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String cacheMode = "WriteBack"; + + @Param(required = false) + public java.util.List systemTags; + + @Param(required = false) + public java.util.List userTags; + + @Param(required = false) + public String sessionId; + + @Param(required = false) + public String accessKeyId; + + @Param(required = false) + public String accessKeySecret; + + @Param(required = false) + public String requestIp; + + @NonAPIParam + public long timeout = -1; + + @NonAPIParam + public long pollingInterval = -1; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.EnableVolumeCacheResult value = res.getResult(org.zstack.sdk.EnableVolumeCacheResult.class); + ret.value = value == null ? new org.zstack.sdk.EnableVolumeCacheResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "PUT"; + info.path = "/volumes/{volumeUuid}/local-volume-cache/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "enableVolumeCache"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheResult.java b/sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheResult.java new file mode 100644 index 00000000000..855e7901cb2 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + +import org.zstack.sdk.VmLocalVolumeCacheInventory; + +public class EnableVolumeCacheResult { + public VmLocalVolumeCacheInventory inventory; + public void setInventory(VmLocalVolumeCacheInventory inventory) { + this.inventory = inventory; + } + public VmLocalVolumeCacheInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolAction.java b/sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolAction.java new file mode 100644 index 00000000000..4dbcb0b5a22 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolAction.java @@ -0,0 +1,107 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class ExtendVmLocalVolumeCachePoolAction extends AbstractAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.ExtendVmLocalVolumeCachePoolResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String uuid; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String hostUuid; + + @Param(required = true, nonempty = true, nullElements = false, emptyString = true, noTrim = false) + public java.util.List pvs; + + @Param(required = false) + public java.util.List systemTags; + + @Param(required = false) + public java.util.List userTags; + + @Param(required = false) + public String sessionId; + + @Param(required = false) + public String accessKeyId; + + @Param(required = false) + public String accessKeySecret; + + @Param(required = false) + public String requestIp; + + @NonAPIParam + public long timeout = -1; + + @NonAPIParam + public long pollingInterval = -1; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.ExtendVmLocalVolumeCachePoolResult value = res.getResult(org.zstack.sdk.ExtendVmLocalVolumeCachePoolResult.class); + ret.value = value == null ? new org.zstack.sdk.ExtendVmLocalVolumeCachePoolResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "PUT"; + info.path = "/hosts/{hostUuid}/local-volume-cache-pools/{uuid}/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "extendVmLocalVolumeCachePool"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolResult.java b/sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolResult.java new file mode 100644 index 00000000000..6812129c6d7 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + +import org.zstack.sdk.VmLocalVolumeCachePoolInventory; + +public class ExtendVmLocalVolumeCachePoolResult { + public VmLocalVolumeCachePoolInventory inventory; + public void setInventory(VmLocalVolumeCachePoolInventory inventory) { + this.inventory = inventory; + } + public VmLocalVolumeCachePoolInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/GetBlockDevicesAction.java b/sdk/src/main/java/org/zstack/sdk/GetBlockDevicesAction.java new file mode 100644 index 00000000000..04a46543803 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/GetBlockDevicesAction.java @@ -0,0 +1,101 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class GetBlockDevicesAction extends AbstractAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.GetBlockDevicesResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + @Param(required = true, nonempty = true, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String uuid; + + @Param(required = false) + public java.util.List systemTags; + + @Param(required = false) + public java.util.List userTags; + + @Param(required = false) + public String sessionId; + + @Param(required = false) + public String accessKeyId; + + @Param(required = false) + public String accessKeySecret; + + @Param(required = false) + public String requestIp; + + @NonAPIParam + public long timeout = -1; + + @NonAPIParam + public long pollingInterval = -1; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.GetBlockDevicesResult value = res.getResult(org.zstack.sdk.GetBlockDevicesResult.class); + ret.value = value == null ? new org.zstack.sdk.GetBlockDevicesResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "GET"; + info.path = "/hosts/{uuid}/block-devices"; + info.needSession = true; + info.needPoll = true; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/GetBlockDevicesResult.java b/sdk/src/main/java/org/zstack/sdk/GetBlockDevicesResult.java new file mode 100644 index 00000000000..2a0a7c3d6f0 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/GetBlockDevicesResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + + + +public class GetBlockDevicesResult { + public java.util.List blockDevices; + public void setBlockDevices(java.util.List blockDevices) { + this.blockDevices = blockDevices; + } + public java.util.List getBlockDevices() { + return this.blockDevices; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/HostBlockDeviceStruct.java b/sdk/src/main/java/org/zstack/sdk/HostBlockDeviceStruct.java new file mode 100644 index 00000000000..a898e35d7a8 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/HostBlockDeviceStruct.java @@ -0,0 +1,111 @@ +package org.zstack.sdk; + + + +public class HostBlockDeviceStruct { + + public java.lang.String name; + public void setName(java.lang.String name) { + this.name = name; + } + public java.lang.String getName() { + return this.name; + } + + public java.lang.String wwid; + public void setWwid(java.lang.String wwid) { + this.wwid = wwid; + } + public java.lang.String getWwid() { + return this.wwid; + } + + public java.lang.String vendor; + public void setVendor(java.lang.String vendor) { + this.vendor = vendor; + } + public java.lang.String getVendor() { + return this.vendor; + } + + public java.lang.String model; + public void setModel(java.lang.String model) { + this.model = model; + } + public java.lang.String getModel() { + return this.model; + } + + public java.lang.String wwn; + public void setWwn(java.lang.String wwn) { + this.wwn = wwn; + } + public java.lang.String getWwn() { + return this.wwn; + } + + public java.lang.String serial; + public void setSerial(java.lang.String serial) { + this.serial = serial; + } + public java.lang.String getSerial() { + return this.serial; + } + + public java.lang.String hctl; + public void setHctl(java.lang.String hctl) { + this.hctl = hctl; + } + public java.lang.String getHctl() { + return this.hctl; + } + + public java.lang.String type; + public void setType(java.lang.String type) { + this.type = type; + } + public java.lang.String getType() { + return this.type; + } + + public java.lang.String path; + public void setPath(java.lang.String path) { + this.path = path; + } + public java.lang.String getPath() { + return this.path; + } + + public java.lang.Long size; + public void setSize(java.lang.Long size) { + this.size = size; + } + public java.lang.Long getSize() { + return this.size; + } + + public java.lang.String source; + public void setSource(java.lang.String source) { + this.source = source; + } + public java.lang.String getSource() { + return this.source; + } + + public java.lang.String transport; + public void setTransport(java.lang.String transport) { + this.transport = transport; + } + public java.lang.String getTransport() { + return this.transport; + } + + public java.lang.String targetIdentifier; + public void setTargetIdentifier(java.lang.String targetIdentifier) { + this.targetIdentifier = targetIdentifier; + } + public java.lang.String getTargetIdentifier() { + return this.targetIdentifier; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolAction.java b/sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolAction.java new file mode 100644 index 00000000000..0973b4128f2 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolAction.java @@ -0,0 +1,75 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class QueryVmLocalVolumeCachePoolAction extends QueryAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.QueryVmLocalVolumeCachePoolResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.QueryVmLocalVolumeCachePoolResult value = res.getResult(org.zstack.sdk.QueryVmLocalVolumeCachePoolResult.class); + ret.value = value == null ? new org.zstack.sdk.QueryVmLocalVolumeCachePoolResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "GET"; + info.path = "/hosts/local-volume-cache-pools"; + info.needSession = true; + info.needPoll = false; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolResult.java b/sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolResult.java new file mode 100644 index 00000000000..1bbeb747635 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolResult.java @@ -0,0 +1,22 @@ +package org.zstack.sdk; + + + +public class QueryVmLocalVolumeCachePoolResult { + public java.util.List inventories; + public void setInventories(java.util.List inventories) { + this.inventories = inventories; + } + public java.util.List getInventories() { + return this.inventories; + } + + public java.lang.Long total; + public void setTotal(java.lang.Long total) { + this.total = total; + } + public java.lang.Long getTotal() { + return this.total; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolAction.java b/sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolAction.java new file mode 100644 index 00000000000..8e559b77031 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolAction.java @@ -0,0 +1,104 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class ReconnectVmLocalVolumeCachePoolAction extends AbstractAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.ReconnectVmLocalVolumeCachePoolResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String uuid; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String hostUuid; + + @Param(required = false) + public java.util.List systemTags; + + @Param(required = false) + public java.util.List userTags; + + @Param(required = false) + public String sessionId; + + @Param(required = false) + public String accessKeyId; + + @Param(required = false) + public String accessKeySecret; + + @Param(required = false) + public String requestIp; + + @NonAPIParam + public long timeout = -1; + + @NonAPIParam + public long pollingInterval = -1; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.ReconnectVmLocalVolumeCachePoolResult value = res.getResult(org.zstack.sdk.ReconnectVmLocalVolumeCachePoolResult.class); + ret.value = value == null ? new org.zstack.sdk.ReconnectVmLocalVolumeCachePoolResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "PUT"; + info.path = "/hosts/{hostUuid}/local-volume-cache-pools/{uuid}/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "reconnectVmLocalVolumeCachePool"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolResult.java b/sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolResult.java new file mode 100644 index 00000000000..4bf7ed7fb4c --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + +import org.zstack.sdk.VmLocalVolumeCachePoolInventory; + +public class ReconnectVmLocalVolumeCachePoolResult { + public VmLocalVolumeCachePoolInventory inventory; + public void setInventory(VmLocalVolumeCachePoolInventory inventory) { + this.inventory = inventory; + } + public VmLocalVolumeCachePoolInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityAction.java b/sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityAction.java new file mode 100644 index 00000000000..f040ecef8fb --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityAction.java @@ -0,0 +1,104 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class SyncVmLocalVolumeCachePoolCapacityAction extends AbstractAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.SyncVmLocalVolumeCachePoolCapacityResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String uuid; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String hostUuid; + + @Param(required = false) + public java.util.List systemTags; + + @Param(required = false) + public java.util.List userTags; + + @Param(required = false) + public String sessionId; + + @Param(required = false) + public String accessKeyId; + + @Param(required = false) + public String accessKeySecret; + + @Param(required = false) + public String requestIp; + + @NonAPIParam + public long timeout = -1; + + @NonAPIParam + public long pollingInterval = -1; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.SyncVmLocalVolumeCachePoolCapacityResult value = res.getResult(org.zstack.sdk.SyncVmLocalVolumeCachePoolCapacityResult.class); + ret.value = value == null ? new org.zstack.sdk.SyncVmLocalVolumeCachePoolCapacityResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "PUT"; + info.path = "/hosts/{hostUuid}/local-volume-cache-pools/{uuid}/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "syncVmLocalVolumeCachePoolCapacity"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityResult.java b/sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityResult.java new file mode 100644 index 00000000000..4a0e7560833 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + +import org.zstack.sdk.VmLocalVolumeCachePoolInventory; + +public class SyncVmLocalVolumeCachePoolCapacityResult { + public VmLocalVolumeCachePoolInventory inventory; + public void setInventory(VmLocalVolumeCachePoolInventory inventory) { + this.inventory = inventory; + } + public VmLocalVolumeCachePoolInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolAction.java b/sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolAction.java new file mode 100644 index 00000000000..59bfc38e480 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolAction.java @@ -0,0 +1,110 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class UpdateVmLocalVolumeCachePoolAction extends AbstractAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.UpdateVmLocalVolumeCachePoolResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String uuid; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String hostUuid; + + @Param(required = false, maxLength = 255, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String name; + + @Param(required = false, maxLength = 2048, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String description; + + @Param(required = false) + public java.util.List systemTags; + + @Param(required = false) + public java.util.List userTags; + + @Param(required = false) + public String sessionId; + + @Param(required = false) + public String accessKeyId; + + @Param(required = false) + public String accessKeySecret; + + @Param(required = false) + public String requestIp; + + @NonAPIParam + public long timeout = -1; + + @NonAPIParam + public long pollingInterval = -1; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.UpdateVmLocalVolumeCachePoolResult value = res.getResult(org.zstack.sdk.UpdateVmLocalVolumeCachePoolResult.class); + ret.value = value == null ? new org.zstack.sdk.UpdateVmLocalVolumeCachePoolResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "PUT"; + info.path = "/hosts/{hostUuid}/local-volume-cache-pools/{uuid}/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "updateVmLocalVolumeCachePool"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolResult.java b/sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolResult.java new file mode 100644 index 00000000000..e736da0650a --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + +import org.zstack.sdk.VmLocalVolumeCachePoolInventory; + +public class UpdateVmLocalVolumeCachePoolResult { + public VmLocalVolumeCachePoolInventory inventory; + public void setInventory(VmLocalVolumeCachePoolInventory inventory) { + this.inventory = inventory; + } + public VmLocalVolumeCachePoolInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheInventory.java b/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheInventory.java new file mode 100644 index 00000000000..cded043590a --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheInventory.java @@ -0,0 +1,72 @@ +package org.zstack.sdk; + +import org.zstack.sdk.VmLocalVolumeCacheMode; +import org.zstack.sdk.VmLocalVolumeCacheState; + +public class VmLocalVolumeCacheInventory { + + public java.lang.String uuid; + public void setUuid(java.lang.String uuid) { + this.uuid = uuid; + } + public java.lang.String getUuid() { + return this.uuid; + } + + public java.lang.String volumeUuid; + public void setVolumeUuid(java.lang.String volumeUuid) { + this.volumeUuid = volumeUuid; + } + public java.lang.String getVolumeUuid() { + return this.volumeUuid; + } + + public java.lang.String poolUuid; + public void setPoolUuid(java.lang.String poolUuid) { + this.poolUuid = poolUuid; + } + public java.lang.String getPoolUuid() { + return this.poolUuid; + } + + public java.lang.String installPath; + public void setInstallPath(java.lang.String installPath) { + this.installPath = installPath; + } + public java.lang.String getInstallPath() { + return this.installPath; + } + + public VmLocalVolumeCacheMode cacheMode; + public void setCacheMode(VmLocalVolumeCacheMode cacheMode) { + this.cacheMode = cacheMode; + } + public VmLocalVolumeCacheMode getCacheMode() { + return this.cacheMode; + } + + public VmLocalVolumeCacheState state; + public void setState(VmLocalVolumeCacheState state) { + this.state = state; + } + public VmLocalVolumeCacheState getState() { + return this.state; + } + + public java.sql.Timestamp createDate; + public void setCreateDate(java.sql.Timestamp createDate) { + this.createDate = createDate; + } + public java.sql.Timestamp getCreateDate() { + return this.createDate; + } + + public java.sql.Timestamp lastOpDate; + public void setLastOpDate(java.sql.Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } + public java.sql.Timestamp getLastOpDate() { + return this.lastOpDate; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheMode.java b/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheMode.java new file mode 100644 index 00000000000..d52c60dc1e5 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheMode.java @@ -0,0 +1,5 @@ +package org.zstack.sdk; + +public enum VmLocalVolumeCacheMode { + WriteBack, +} diff --git a/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolInventory.java b/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolInventory.java new file mode 100644 index 00000000000..9f2bdc8599c --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolInventory.java @@ -0,0 +1,120 @@ +package org.zstack.sdk; + +import org.zstack.sdk.VmLocalVolumeCachePoolState; +import org.zstack.sdk.VmLocalVolumeCachePoolStatus; + +public class VmLocalVolumeCachePoolInventory { + + public java.lang.String uuid; + public void setUuid(java.lang.String uuid) { + this.uuid = uuid; + } + public java.lang.String getUuid() { + return this.uuid; + } + + public java.lang.String hostUuid; + public void setHostUuid(java.lang.String hostUuid) { + this.hostUuid = hostUuid; + } + public java.lang.String getHostUuid() { + return this.hostUuid; + } + + public java.lang.String name; + public void setName(java.lang.String name) { + this.name = name; + } + public java.lang.String getName() { + return this.name; + } + + public java.lang.String description; + public void setDescription(java.lang.String description) { + this.description = description; + } + public java.lang.String getDescription() { + return this.description; + } + + public java.util.LinkedHashMap metadata; + public void setMetadata(java.util.LinkedHashMap metadata) { + this.metadata = metadata; + } + public java.util.LinkedHashMap getMetadata() { + return this.metadata; + } + + public long totalCapacity; + public void setTotalCapacity(long totalCapacity) { + this.totalCapacity = totalCapacity; + } + public long getTotalCapacity() { + return this.totalCapacity; + } + + public long availableCapacity; + public void setAvailableCapacity(long availableCapacity) { + this.availableCapacity = availableCapacity; + } + public long getAvailableCapacity() { + return this.availableCapacity; + } + + public long allocated; + public void setAllocated(long allocated) { + this.allocated = allocated; + } + public long getAllocated() { + return this.allocated; + } + + public long dirty; + public void setDirty(long dirty) { + this.dirty = dirty; + } + public long getDirty() { + return this.dirty; + } + + public VmLocalVolumeCachePoolState state; + public void setState(VmLocalVolumeCachePoolState state) { + this.state = state; + } + public VmLocalVolumeCachePoolState getState() { + return this.state; + } + + public VmLocalVolumeCachePoolStatus status; + public void setStatus(VmLocalVolumeCachePoolStatus status) { + this.status = status; + } + public VmLocalVolumeCachePoolStatus getStatus() { + return this.status; + } + + public java.util.Set caches; + public void setCaches(java.util.Set caches) { + this.caches = caches; + } + public java.util.Set getCaches() { + return this.caches; + } + + public java.sql.Timestamp createDate; + public void setCreateDate(java.sql.Timestamp createDate) { + this.createDate = createDate; + } + public java.sql.Timestamp getCreateDate() { + return this.createDate; + } + + public java.sql.Timestamp lastOpDate; + public void setLastOpDate(java.sql.Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } + public java.sql.Timestamp getLastOpDate() { + return this.lastOpDate; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolState.java b/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolState.java new file mode 100644 index 00000000000..0d21999167e --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolState.java @@ -0,0 +1,6 @@ +package org.zstack.sdk; + +public enum VmLocalVolumeCachePoolState { + Enabled, + Disabled, +} diff --git a/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolStatus.java b/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolStatus.java new file mode 100644 index 00000000000..e0b5b46e695 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolStatus.java @@ -0,0 +1,7 @@ +package org.zstack.sdk; + +public enum VmLocalVolumeCachePoolStatus { + Connecting, + Connected, + Disconnected, +} diff --git a/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheState.java b/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheState.java new file mode 100644 index 00000000000..d1502f19e03 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheState.java @@ -0,0 +1,6 @@ +package org.zstack.sdk; + +public enum VmLocalVolumeCacheState { + Uninstantiated, + Instantiated, +} diff --git a/sdk/src/main/java/org/zstack/sdk/VolumeTO.java b/sdk/src/main/java/org/zstack/sdk/VolumeTO.java index 84eb1d8518f..adaa44f8fe7 100644 --- a/sdk/src/main/java/org/zstack/sdk/VolumeTO.java +++ b/sdk/src/main/java/org/zstack/sdk/VolumeTO.java @@ -1,6 +1,6 @@ package org.zstack.sdk; - +import org.zstack.sdk.CacheTO; public class VolumeTO extends org.zstack.sdk.BaseVirtualDeviceTO { @@ -12,6 +12,14 @@ public java.lang.String getInstallPath() { return this.installPath; } + public java.lang.Long size; + public void setSize(java.lang.Long size) { + this.size = size; + } + public java.lang.Long getSize() { + return this.size; + } + public int deviceId; public void setDeviceId(int deviceId) { this.deviceId = deviceId; @@ -156,4 +164,12 @@ public int getControllerIndex() { return this.controllerIndex; } + public CacheTO cache; + public void setCache(CacheTO cache) { + this.cache = cache; + } + public CacheTO getCache() { + return this.cache; + } + } diff --git a/storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java b/storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java index 248ff279765..101ec515b45 100755 --- a/storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java +++ b/storage/src/main/java/org/zstack/storage/volume/VolumeManagerImpl.java @@ -636,10 +636,9 @@ protected VolumeVO scripts() { } for (CreateDataVolumeExtensionPoint ext : exts) { - ext.afterCreateVolume(vo); + ext.afterCreateVolume(vo, msg); } vo = dbf.reload(vo); - new FireVolumeCanonicalEvent().fireVolumeStatusChangedEvent(null, VolumeInventory.valueOf(vo)); VolumeInventory inv = VolumeInventory.valueOf(vo); @@ -1067,7 +1066,7 @@ protected VolumeVO scripts() { } for (CreateDataVolumeExtensionPoint ext : exts) { - ext.afterCreateVolume(vo); + ext.afterCreateVolume(vo, msg); } dbf.reload(vo); diff --git a/storage/src/main/java/org/zstack/storage/volume/VolumeSystemTags.java b/storage/src/main/java/org/zstack/storage/volume/VolumeSystemTags.java index d20cebd9592..5a00cadfdc4 100644 --- a/storage/src/main/java/org/zstack/storage/volume/VolumeSystemTags.java +++ b/storage/src/main/java/org/zstack/storage/volume/VolumeSystemTags.java @@ -4,6 +4,7 @@ import org.zstack.header.storage.primary.PrimaryStorageVO; import org.zstack.header.tag.TagDefinition; import org.zstack.header.volume.VolumeVO; +import org.zstack.tag.EphemeralPatternSystemTag; import org.zstack.tag.EphemeralSystemTag; import org.zstack.tag.PatternedSystemTag; import org.zstack.tag.SystemTag; @@ -52,4 +53,12 @@ public class VolumeSystemTags { public static PatternedSystemTag VOLUME_QOS = new PatternedSystemTag(String.format("%s::{%s}", VOLUME_QOS_TOKEN, VOLUME_QOS_TOKEN), VolumeVO.class); public static SystemTag FAST_REVERT = new SystemTag("fast::revert", VolumeVO.class); + + public static EphemeralSystemTag VOLUME_CACHE_ENABLE = new EphemeralSystemTag("volumeCache::enable"); + + public static String CACHE_POOL_UUID_TOKEN = "poolUuid"; + public static EphemeralPatternSystemTag VOLUME_CACHE_POOL_UUID = new EphemeralPatternSystemTag(String.format("volumeCache::poolUuid::{%s}", CACHE_POOL_UUID_TOKEN), VolumeVO.class); + + public static String CACHE_MODE_TOKEN = "cacheMode"; + public static EphemeralPatternSystemTag VOLUME_CACHE_MODE = new EphemeralPatternSystemTag(String.format("volumeCache::cacheMode::{%s}", CACHE_MODE_TOKEN), VolumeVO.class); } diff --git a/test/src/test/resources/springConfigXml/license.xml b/test/src/test/resources/springConfigXml/license.xml deleted file mode 100755 index 981f89749f4..00000000000 --- a/test/src/test/resources/springConfigXml/license.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - diff --git a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy index a58475e1650..6b0082f3cc2 100644 --- a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy +++ b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy @@ -6713,6 +6713,33 @@ abstract class ApiHelper { } + def changeVmLocalVolumeCachePoolState(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ChangeVmLocalVolumeCachePoolStateAction.class) Closure c) { + def a = new org.zstack.sdk.ChangeVmLocalVolumeCachePoolStateAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def changeVmNicNetwork(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ChangeVmNicNetworkAction.class) Closure c) { def a = new org.zstack.sdk.ChangeVmNicNetworkAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -12140,6 +12167,33 @@ abstract class ApiHelper { } + def createVmLocalVolumeCachePool(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.CreateVmLocalVolumeCachePoolAction.class) Closure c) { + def a = new org.zstack.sdk.CreateVmLocalVolumeCachePoolAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def createVmNic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.CreateVmNicAction.class) Closure c) { def a = new org.zstack.sdk.CreateVmNicAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -17621,6 +17675,33 @@ abstract class ApiHelper { } + def deleteVmLocalVolumeCachePool(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.DeleteVmLocalVolumeCachePoolAction.class) Closure c) { + def a = new org.zstack.sdk.DeleteVmLocalVolumeCachePoolAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def deleteVmNic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.DeleteVmNicAction.class) Closure c) { def a = new org.zstack.sdk.DeleteVmNicAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -19835,8 +19916,8 @@ abstract class ApiHelper { } - def disableDGpuMode(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.DisableDGpuModeAction.class) Closure c) { - def a = new org.zstack.sdk.DisableDGpuModeAction() + def disableVolumeCache(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.DisableVolumeCacheAction.class) Closure c) { + def a = new org.zstack.sdk.DisableVolumeCacheAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -19997,8 +20078,8 @@ abstract class ApiHelper { } - def enableDGpuMode(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.EnableDGpuModeAction.class) Closure c) { - def a = new org.zstack.sdk.EnableDGpuModeAction() + def enableVolumeCache(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.EnableVolumeCacheAction.class) Closure c) { + def a = new org.zstack.sdk.EnableVolumeCacheAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20402,6 +20483,33 @@ abstract class ApiHelper { } + def extendVmLocalVolumeCachePool(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExtendVmLocalVolumeCachePoolAction.class) Closure c) { + def a = new org.zstack.sdk.ExtendVmLocalVolumeCachePoolAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def failoverFaultToleranceVm(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.FailoverFaultToleranceVmAction.class) Closure c) { def a = new org.zstack.sdk.FailoverFaultToleranceVmAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -21293,6 +21401,33 @@ abstract class ApiHelper { } + def getBlockDevices(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.GetBlockDevicesAction.class) Closure c) { + def a = new org.zstack.sdk.GetBlockDevicesAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def getBlockPrimaryStorageMetadata(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.GetBlockPrimaryStorageMetadataAction.class) Closure c) { def a = new org.zstack.sdk.GetBlockPrimaryStorageMetadataAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -36540,8 +36675,8 @@ abstract class ApiHelper { } - def queryVmModelMount(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.QueryVmModelMountAction.class) Closure c) { - def a = new org.zstack.sdk.QueryVmModelMountAction() + def queryVmLocalVolumeCachePool(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.QueryVmLocalVolumeCachePoolAction.class) Closure c) { + def a = new org.zstack.sdk.QueryVmLocalVolumeCachePoolAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38010,6 +38145,33 @@ abstract class ApiHelper { } + def reconnectVmLocalVolumeCachePool(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReconnectVmLocalVolumeCachePoolAction.class) Closure c) { + def a = new org.zstack.sdk.ReconnectVmLocalVolumeCachePoolAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def reconnectZdfs(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReconnectZdfsAction.class) Closure c) { def a = new org.zstack.sdk.ReconnectZdfsAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -42600,6 +42762,33 @@ abstract class ApiHelper { } + def syncVmLocalVolumeCachePoolCapacity(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncVmLocalVolumeCachePoolCapacityAction.class) Closure c) { + def a = new org.zstack.sdk.SyncVmLocalVolumeCachePoolCapacityAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def syncVolumeSize(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncVolumeSizeAction.class) Closure c) { def a = new org.zstack.sdk.SyncVolumeSizeAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -48162,8 +48351,8 @@ abstract class ApiHelper { } - def updateVmInstancePciDeviceSpecRef(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateVmInstancePciDeviceSpecRefAction.class) Closure c) { - def a = new org.zstack.sdk.UpdateVmInstancePciDeviceSpecRefAction() + def updateVmLocalVolumeCachePool(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateVmLocalVolumeCachePoolAction.class) Closure c) { + def a = new org.zstack.sdk.UpdateVmLocalVolumeCachePoolAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a diff --git a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java index 24d0b031e3c..273a5d2499c 100644 --- a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java +++ b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java @@ -16220,29 +16220,73 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_CRYPTO_SECURITYMACHINE_THIRDPARTY_PLUGIN_10006 = "ORG_ZSTACK_CRYPTO_SECURITYMACHINE_THIRDPARTY_PLUGIN_10006"; - public static final String ORG_ZSTACK_DGPU_10001 = "ORG_ZSTACK_DGPU_10001"; + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10001 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10001"; - public static final String ORG_ZSTACK_DGPU_10002 = "ORG_ZSTACK_DGPU_10002"; + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10002 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10002"; - public static final String ORG_ZSTACK_DGPU_10003 = "ORG_ZSTACK_DGPU_10003"; + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10003 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10003"; - public static final String ORG_ZSTACK_DGPU_10004 = "ORG_ZSTACK_DGPU_10004"; + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10004 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10004"; - public static final String ORG_ZSTACK_DGPU_10005 = "ORG_ZSTACK_DGPU_10005"; + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10005 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10005"; - public static final String ORG_ZSTACK_DGPU_10006 = "ORG_ZSTACK_DGPU_10006"; + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10006 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10006"; - public static final String ORG_ZSTACK_DGPU_10007 = "ORG_ZSTACK_DGPU_10007"; + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10007 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10007"; - public static final String ORG_ZSTACK_DGPU_10008 = "ORG_ZSTACK_DGPU_10008"; + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10008 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10008"; - public static final String ORG_ZSTACK_DGPU_10009 = "ORG_ZSTACK_DGPU_10009"; + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10009 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10009"; - public static final String ORG_ZSTACK_DGPU_10010 = "ORG_ZSTACK_DGPU_10010"; + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10010 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10010"; - public static final String ORG_ZSTACK_DGPU_10011 = "ORG_ZSTACK_DGPU_10011"; + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10011 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10011"; - public static final String ORG_ZSTACK_DGPU_10012 = "ORG_ZSTACK_DGPU_10012"; + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10012 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10012"; - public static final String ORG_ZSTACK_DGPU_10013 = "ORG_ZSTACK_DGPU_10013"; + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10013 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10013"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10014 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10014"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10015 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10015"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10016 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10016"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10017 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10017"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10018 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10018"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10019 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10019"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10020 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10020"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10021 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10021"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10022 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10022"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10023 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10023"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10024 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10024"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10025 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10025"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10026 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10026"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10027 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10027"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10028 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10028"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10029 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10029"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10030 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10030"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10031 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10031"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10032 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10032"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10033 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10033"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10034 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10034"; + + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10035 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10035"; } From 9b8a71b3bb30739d13ece22875d7718a10810686 Mon Sep 17 00:00:00 2001 From: "haidong.pang" Date: Mon, 20 Apr 2026 08:38:28 +0000 Subject: [PATCH 24/54] [volume-cache]: move cache configuration to premium APIEnableVolumeCacheMsg et al are defined in premium/mevoco; keeping their serviceConfig/springConfigXml/globalConfig in main repo causes ClassNotFoundException during ApiMessageProcessor boot when main repo test module loads conf/** into test-classes. Move the four conf files and the import from main-repo zstack.xml to premium. Resolves: ZSTAC-83616 Change-Id: I1a805c33c0fe7762bdc86fb886c4a02679ffb7d5 --- conf/globalConfig/vmLocalVolumeCachePool.xml | 27 ----------- conf/serviceConfig/vmLocalVolumeCache.xml | 12 ----- conf/serviceConfig/vmLocalVolumeCachePool.xml | 37 --------------- conf/springConfigXml/localVolumeCache.xml | 45 ------------------- conf/zstack.xml | 1 - 5 files changed, 122 deletions(-) delete mode 100644 conf/globalConfig/vmLocalVolumeCachePool.xml delete mode 100644 conf/serviceConfig/vmLocalVolumeCache.xml delete mode 100644 conf/serviceConfig/vmLocalVolumeCachePool.xml delete mode 100644 conf/springConfigXml/localVolumeCache.xml diff --git a/conf/globalConfig/vmLocalVolumeCachePool.xml b/conf/globalConfig/vmLocalVolumeCachePool.xml deleted file mode 100644 index 666063156a1..00000000000 --- a/conf/globalConfig/vmLocalVolumeCachePool.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - vmLocalVolumeCachePool - capacitySync.interval - Interval in seconds between periodic capacity sync tasks for local volume cache pools. - 300 - java.lang.Long - - - - vmLocalVolumeCachePool - healthCheck.interval - Interval in seconds between periodic health check tasks for local volume cache pools. - 60 - java.lang.Long - - - - vmLocalVolumeCachePool - gc.interval - Interval in seconds between periodic garbage collection tasks for local volume cache pools. - 3600 - java.lang.Long - - - diff --git a/conf/serviceConfig/vmLocalVolumeCache.xml b/conf/serviceConfig/vmLocalVolumeCache.xml deleted file mode 100644 index b1ead4b02d2..00000000000 --- a/conf/serviceConfig/vmLocalVolumeCache.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - vmLocalVolumeCache - - - org.zstack.header.localVolumeCache.APIEnableVolumeCacheMsg - - - - org.zstack.header.localVolumeCache.APIDisableVolumeCacheMsg - - diff --git a/conf/serviceConfig/vmLocalVolumeCachePool.xml b/conf/serviceConfig/vmLocalVolumeCachePool.xml deleted file mode 100644 index 4ea9301967d..00000000000 --- a/conf/serviceConfig/vmLocalVolumeCachePool.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - vmLocalVolumeCachePool - - - org.zstack.header.localVolumeCache.APICreateVmLocalVolumeCachePoolMsg - - - - org.zstack.header.localVolumeCache.APIDeleteVmLocalVolumeCachePoolMsg - - - - org.zstack.header.localVolumeCache.APIUpdateVmLocalVolumeCachePoolMsg - - - - org.zstack.header.localVolumeCache.APIChangeVmLocalVolumeCachePoolStateMsg - - - - org.zstack.header.localVolumeCache.APIReconnectVmLocalVolumeCachePoolMsg - - - - org.zstack.header.localVolumeCache.APIExtendVmLocalVolumeCachePoolMsg - - - - org.zstack.header.localVolumeCache.APISyncVmLocalVolumeCachePoolCapacityMsg - - - - org.zstack.header.localVolumeCache.APIQueryVmLocalVolumeCachePoolMsg - query - - diff --git a/conf/springConfigXml/localVolumeCache.xml b/conf/springConfigXml/localVolumeCache.xml deleted file mode 100644 index 89bd5c8104c..00000000000 --- a/conf/springConfigXml/localVolumeCache.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/conf/zstack.xml b/conf/zstack.xml index b723b6eadcc..c0a5da2a80f 100755 --- a/conf/zstack.xml +++ b/conf/zstack.xml @@ -122,5 +122,4 @@ - From a73f0fd6eec38ce44def8b542ce7be45f733f6db Mon Sep 17 00:00:00 2001 From: "haidong.pang" Date: Tue, 24 Mar 2026 07:13:52 +0000 Subject: [PATCH 25/54] [volume-cache]: set CacheTO in extension point Enable cache for an in-use volume omits some command fields sent to the agent. VmLocalVolumeCacheManagerImpl implements the convertVolumeIfNeed method of KVMConvertVolumeExtensionPoint. CacheTO should be set in the implementation of this extension point. Resolves: ZSTAC-83649 Change-Id: I69757864676978787767736a6f777a7562636b75 --- .../src/main/java/org/zstack/kvm/VolumeTO.java | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java b/plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java index 901bf60aee5..d6695b93208 100644 --- a/plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/VolumeTO.java @@ -5,9 +5,6 @@ import org.zstack.core.db.Q; import org.zstack.header.host.HostInventory; import org.zstack.header.image.ImagePlatform; -import org.zstack.header.localVolumeCache.VmLocalVolumeCacheInventory; -import org.zstack.header.localVolumeCache.VmLocalVolumeCacheVO; -import org.zstack.header.localVolumeCache.VmLocalVolumeCacheVO_; import org.zstack.header.storage.primary.PrimaryStorageVO; import org.zstack.header.storage.primary.PrimaryStorageVO_; import org.zstack.header.vm.VmInstanceVO; @@ -109,6 +106,10 @@ public static VolumeTO valueOfWithOutExtension(VolumeInventory vol, HostInventor return valueOf(vol, host, platform, false); } + public static VolumeTO valueOf(VolumeInventory vol, HostInventory host, String platform) { + return valueOf(vol, host, platform, true); + } + public static VolumeTO valueOf(VolumeInventory vol, HostInventory host, String platform, boolean withExtension) { KVMHostInventory kvmHostInventory = new KVMHostInventory(); BeanUtils.copyProperties(host, kvmHostInventory); @@ -158,16 +159,7 @@ public static VolumeTO valueOf(VolumeInventory vol, KVMHostInventory host, Strin for (KVMConvertVolumeExtensionPoint ext : exts) { to = ext.convertVolumeIfNeed(host, vol, to); } - VmLocalVolumeCacheVO cacheVO = Q.New(VmLocalVolumeCacheVO.class) - .eq(VmLocalVolumeCacheVO_.volumeUuid, vol.getUuid()) - .find(); - if (cacheVO == null) { - return to; - } - VmLocalVolumeCacheInventory cacheInv = VmLocalVolumeCacheInventory.valueOf(cacheVO); - CacheTO cacheTO = CacheTO.valueOf(cacheInv); - to.setCache(cacheTO); - return to; + return to; } private synchronized static void prepareExts() { From 18dc9aff65d4c56945e23911bdf1bd5984c2707a Mon Sep 17 00:00:00 2001 From: "haidong.pang" Date: Mon, 20 Apr 2026 12:34:01 +0000 Subject: [PATCH 26/54] [volume-cache]: simplify naming and decouple LVM Simplify naming and decouple LVM implementation details from the public API surface for the host-side volume cache feature. Naming rules: - Pool layer: VmLocalVolumeCachePool* -> HostCacheStore*, aligning with ImageStore/BackupStorage. - Entity layer: VmLocalVolumeCache* -> VolumeCache*, aligning with ImageCache. - Package: localVolumeCache -> volumeCache. Public contract no longer leaks LVM: - API field List pvs -> List devices. - DB: drop metadata VARCHAR(2048), add mountPoint VARCHAR(255) as a first-class column. - Delete CachePoolMetadata.java (PV/VG/LV/FS ref POJOs). - KVMAgentCommands PoolRsp/PoolHealthRsp no longer expose LVM topology to the MN; Agent keeps LVM state internally. Follow-up wiring: - SDK Actions renamed (SourceClassMap/ApiHelper.groovy updated). - Spring/Service XML and globalConfig renamed (volumeCache.xml / hostCacheStore.xml). - persistence.xml / zstack.xml / TestPremium.groovy import paths updated. Resolves: ZSTAC-81715 Change-Id: Ifb6a2b5339d1d628f64799d78b0b5baa409ac106 --- .../org/zstack/compute/host/HostBase.java | 1 + conf/db/upgrade/V5.5.18__schema.sql | 25 +- conf/persistence.xml | 6 +- .../header/host/APIGetBlockDevicesMsg.java | 11 + .../APIGetBlockDevicesMsgDoc_zh_cn.groovy | 9 + .../header/host/GetBlockDevicesOnHostMsg.java | 9 + .../localVolumeCache/CachePoolMetadata.java | 186 ---------- .../VmLocalVolumeCacheConstant.java | 5 - .../VmLocalVolumeCacheMode.java | 5 - .../VmLocalVolumeCachePoolCapacityVO_.java | 13 - ...alVolumeCachePoolInventoryDoc_zh_cn.groovy | 90 ----- .../VmLocalVolumeCachePoolState.java | 7 - .../VmLocalVolumeCachePoolStatus.java | 8 - .../VmLocalVolumeCachePoolVO_.java | 19 - .../VmLocalVolumeCacheState.java | 6 - .../VmLocalVolumeCacheStateDoc_zh_cn.groovy | 27 -- .../VmLocalVolumeCacheVO_.java | 18 - .../HostCacheStoreCapacityInventory.java} | 16 +- .../HostCacheStoreCapacityVO.java} | 10 +- .../HostCacheStoreCapacityVO_.java | 13 + .../HostCacheStoreDeviceInventory.java | 29 ++ .../volumeCache/HostCacheStoreDeviceRef.java | 63 ++++ .../HostCacheStoreDeviceRefsConverter.java | 52 +++ .../HostCacheStoreInventory.java} | 82 +++-- .../HostCacheStoreInventoryDoc_zh_cn.groovy | 90 +++++ .../volumeCache/HostCacheStoreState.java | 7 + .../HostCacheStoreStateDoc_zh_cn.groovy} | 6 +- .../volumeCache/HostCacheStoreStatus.java | 8 + .../HostCacheStoreStatusDoc_zh_cn.groovy} | 8 +- .../HostCacheStoreVO.java} | 52 ++- .../header/volumeCache/HostCacheStoreVO_.java | 19 + .../volumeCache/VolumeCacheConstant.java | 5 + .../VolumeCacheInventory.java} | 35 +- .../VolumeCacheInventoryDoc_zh_cn.groovy} | 30 +- .../header/volumeCache/VolumeCacheMode.java | 5 + .../VolumeCacheModeDoc_zh_cn.groovy} | 4 +- .../header/volumeCache/VolumeCacheStatus.java | 6 + .../VolumeCacheStatusDoc_zh_cn.groovy | 21 ++ .../VolumeCacheVO.java} | 22 +- .../header/volumeCache/VolumeCacheVO_.java | 18 + .../src/main/java/org/zstack/kvm/CacheTO.java | 4 +- .../java/org/zstack/kvm/KVMAgentCommands.java | 100 +----- .../src/main/java/org/zstack/kvm/KVMHost.java | 1 + sdk/src/main/java/SourceClassMap.java | 26 +- ...a => ChangeHostCacheStoreStateAction.java} | 10 +- .../sdk/ChangeHostCacheStoreStateResult.java | 14 + ...angeVmLocalVolumeCachePoolStateResult.java | 14 - ...n.java => CreateHostCacheStoreAction.java} | 10 +- .../sdk/CreateHostCacheStoreResult.java | 14 + .../CreateVmLocalVolumeCachePoolResult.java | 14 - ...n.java => DeleteHostCacheStoreAction.java} | 8 +- .../sdk/DeleteHostCacheStoreResult.java | 7 + .../DeleteVmLocalVolumeCachePoolResult.java | 7 - .../zstack/sdk/DisableVolumeCacheResult.java | 8 +- .../zstack/sdk/EnableVolumeCacheResult.java | 8 +- ...n.java => ExtendHostCacheStoreAction.java} | 12 +- .../sdk/ExtendHostCacheStoreResult.java | 14 + .../ExtendVmLocalVolumeCachePoolResult.java | 14 - .../org/zstack/sdk/GetBlockDevicesAction.java | 3 + .../sdk/HostCacheStoreDeviceInventory.java | 23 ++ ...tory.java => HostCacheStoreInventory.java} | 36 +- ...oolState.java => HostCacheStoreState.java} | 2 +- ...lStatus.java => HostCacheStoreStatus.java} | 2 +- ...on.java => QueryHostCacheStoreAction.java} | 8 +- ...lt.java => QueryHostCacheStoreResult.java} | 2 +- ...ava => ReconnectHostCacheStoreAction.java} | 10 +- .../sdk/ReconnectHostCacheStoreResult.java | 14 + ...ReconnectVmLocalVolumeCachePoolResult.java | 14 - ... => SyncHostCacheStoreCapacityAction.java} | 10 +- .../sdk/SyncHostCacheStoreCapacityResult.java | 14 + ...cVmLocalVolumeCachePoolCapacityResult.java | 14 - ...n.java => UpdateHostCacheStoreAction.java} | 10 +- .../sdk/UpdateHostCacheStoreResult.java | 14 + .../UpdateVmLocalVolumeCachePoolResult.java | 14 - .../zstack/sdk/VmLocalVolumeCacheState.java | 6 - ...ventory.java => VolumeCacheInventory.java} | 22 +- ...umeCacheMode.java => VolumeCacheMode.java} | 2 +- .../org/zstack/sdk/VolumeCacheStatus.java | 6 + .../java/org/zstack/testlib/ApiHelper.groovy | 335 ++++++++---------- 79 files changed, 922 insertions(+), 970 deletions(-) delete mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/CachePoolMetadata.java delete mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheConstant.java delete mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheMode.java delete mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityVO_.java delete mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventoryDoc_zh_cn.groovy delete mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolState.java delete mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatus.java delete mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO_.java delete mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheState.java delete mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheStateDoc_zh_cn.groovy delete mode 100644 header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO_.java rename header/src/main/java/org/zstack/header/{localVolumeCache/VmLocalVolumeCachePoolCapacityInventory.java => volumeCache/HostCacheStoreCapacityInventory.java} (71%) rename header/src/main/java/org/zstack/header/{localVolumeCache/VmLocalVolumeCachePoolCapacityVO.java => volumeCache/HostCacheStoreCapacityVO.java} (78%) create mode 100644 header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreCapacityVO_.java create mode 100644 header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreDeviceInventory.java create mode 100644 header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreDeviceRef.java create mode 100644 header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreDeviceRefsConverter.java rename header/src/main/java/org/zstack/header/{localVolumeCache/VmLocalVolumeCachePoolInventory.java => volumeCache/HostCacheStoreInventory.java} (60%) create mode 100644 header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreInventoryDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreState.java rename header/src/main/java/org/zstack/header/{localVolumeCache/VmLocalVolumeCachePoolStateDoc_zh_cn.groovy => volumeCache/HostCacheStoreStateDoc_zh_cn.groovy} (58%) create mode 100644 header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStatus.java rename header/src/main/java/org/zstack/header/{localVolumeCache/VmLocalVolumeCachePoolStatusDoc_zh_cn.groovy => volumeCache/HostCacheStoreStatusDoc_zh_cn.groovy} (59%) rename header/src/main/java/org/zstack/header/{localVolumeCache/VmLocalVolumeCachePoolVO.java => volumeCache/HostCacheStoreVO.java} (65%) create mode 100644 header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreVO_.java create mode 100644 header/src/main/java/org/zstack/header/volumeCache/VolumeCacheConstant.java rename header/src/main/java/org/zstack/header/{localVolumeCache/VmLocalVolumeCacheInventory.java => volumeCache/VolumeCacheInventory.java} (67%) rename header/src/main/java/org/zstack/header/{localVolumeCache/VmLocalVolumeCacheInventoryDoc_zh_cn.groovy => volumeCache/VolumeCacheInventoryDoc_zh_cn.groovy} (51%) create mode 100644 header/src/main/java/org/zstack/header/volumeCache/VolumeCacheMode.java rename header/src/main/java/org/zstack/header/{localVolumeCache/VmLocalVolumeCacheModeDoc_zh_cn.groovy => volumeCache/VolumeCacheModeDoc_zh_cn.groovy} (59%) create mode 100644 header/src/main/java/org/zstack/header/volumeCache/VolumeCacheStatus.java create mode 100644 header/src/main/java/org/zstack/header/volumeCache/VolumeCacheStatusDoc_zh_cn.groovy rename header/src/main/java/org/zstack/header/{localVolumeCache/VmLocalVolumeCacheVO.java => volumeCache/VolumeCacheVO.java} (76%) create mode 100644 header/src/main/java/org/zstack/header/volumeCache/VolumeCacheVO_.java rename sdk/src/main/java/org/zstack/sdk/{ChangeVmLocalVolumeCachePoolStateAction.java => ChangeHostCacheStoreStateAction.java} (85%) create mode 100644 sdk/src/main/java/org/zstack/sdk/ChangeHostCacheStoreStateResult.java delete mode 100644 sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateResult.java rename sdk/src/main/java/org/zstack/sdk/{CreateVmLocalVolumeCachePoolAction.java => CreateHostCacheStoreAction.java} (88%) create mode 100644 sdk/src/main/java/org/zstack/sdk/CreateHostCacheStoreResult.java delete mode 100644 sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolResult.java rename sdk/src/main/java/org/zstack/sdk/{DeleteVmLocalVolumeCachePoolAction.java => DeleteHostCacheStoreAction.java} (87%) create mode 100644 sdk/src/main/java/org/zstack/sdk/DeleteHostCacheStoreResult.java delete mode 100644 sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolResult.java rename sdk/src/main/java/org/zstack/sdk/{ExtendVmLocalVolumeCachePoolAction.java => ExtendHostCacheStoreAction.java} (84%) create mode 100644 sdk/src/main/java/org/zstack/sdk/ExtendHostCacheStoreResult.java delete mode 100644 sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/HostCacheStoreDeviceInventory.java rename sdk/src/main/java/org/zstack/sdk/{VmLocalVolumeCachePoolInventory.java => HostCacheStoreInventory.java} (75%) rename sdk/src/main/java/org/zstack/sdk/{VmLocalVolumeCachePoolState.java => HostCacheStoreState.java} (53%) rename sdk/src/main/java/org/zstack/sdk/{VmLocalVolumeCachePoolStatus.java => HostCacheStoreStatus.java} (60%) rename sdk/src/main/java/org/zstack/sdk/{QueryVmLocalVolumeCachePoolAction.java => QueryHostCacheStoreAction.java} (82%) rename sdk/src/main/java/org/zstack/sdk/{QueryVmLocalVolumeCachePoolResult.java => QueryHostCacheStoreResult.java} (90%) rename sdk/src/main/java/org/zstack/sdk/{ReconnectVmLocalVolumeCachePoolAction.java => ReconnectHostCacheStoreAction.java} (86%) create mode 100644 sdk/src/main/java/org/zstack/sdk/ReconnectHostCacheStoreResult.java delete mode 100644 sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolResult.java rename sdk/src/main/java/org/zstack/sdk/{SyncVmLocalVolumeCachePoolCapacityAction.java => SyncHostCacheStoreCapacityAction.java} (84%) create mode 100644 sdk/src/main/java/org/zstack/sdk/SyncHostCacheStoreCapacityResult.java delete mode 100644 sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityResult.java rename sdk/src/main/java/org/zstack/sdk/{UpdateVmLocalVolumeCachePoolAction.java => UpdateHostCacheStoreAction.java} (86%) create mode 100644 sdk/src/main/java/org/zstack/sdk/UpdateHostCacheStoreResult.java delete mode 100644 sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolResult.java delete mode 100644 sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheState.java rename sdk/src/main/java/org/zstack/sdk/{VmLocalVolumeCacheInventory.java => VolumeCacheInventory.java} (74%) rename sdk/src/main/java/org/zstack/sdk/{VmLocalVolumeCacheMode.java => VolumeCacheMode.java} (51%) create mode 100644 sdk/src/main/java/org/zstack/sdk/VolumeCacheStatus.java diff --git a/compute/src/main/java/org/zstack/compute/host/HostBase.java b/compute/src/main/java/org/zstack/compute/host/HostBase.java index f377e3356d3..df388331a89 100755 --- a/compute/src/main/java/org/zstack/compute/host/HostBase.java +++ b/compute/src/main/java/org/zstack/compute/host/HostBase.java @@ -220,6 +220,7 @@ private void handle(APIGetBlockDevicesMsg msg) { APIGetBlockDevicesEvent event = new APIGetBlockDevicesEvent(msg.getId()); GetBlockDevicesOnHostMsg gmsg = new GetBlockDevicesOnHostMsg(); gmsg.setHostUuid(msg.getHostUuid()); + gmsg.setIncludeInUse(msg.isIncludeInUse()); bus.makeTargetServiceIdByResourceUuid(gmsg, HostConstant.SERVICE_ID, msg.getHostUuid()); bus.send(gmsg, new CloudBusCallBack(msg) { @Override diff --git a/conf/db/upgrade/V5.5.18__schema.sql b/conf/db/upgrade/V5.5.18__schema.sql index 788911627aa..25ddef86a4d 100644 --- a/conf/db/upgrade/V5.5.18__schema.sql +++ b/conf/db/upgrade/V5.5.18__schema.sql @@ -1,46 +1,47 @@ -CREATE TABLE IF NOT EXISTS `VmLocalVolumeCachePoolVO` ( +CREATE TABLE IF NOT EXISTS `HostCacheStoreVO` ( `uuid` VARCHAR(32) NOT NULL, `hostUuid` VARCHAR(32) NOT NULL, `name` VARCHAR(255) DEFAULT NULL, `description` VARCHAR(2048) DEFAULT NULL, - `metadata` VARCHAR(2048) DEFAULT NULL, + `mountPoint` VARCHAR(255) DEFAULT NULL, + `devices` TEXT, `state` VARCHAR(32) NOT NULL, `status` VARCHAR(32) NOT NULL, `createDate` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', `lastOpDate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`uuid`), - CONSTRAINT `fkVmLocalVolumeCachePoolVOHostEO` + CONSTRAINT `fkHostCacheStoreVOHostEO` FOREIGN KEY (`hostUuid`) REFERENCES `HostEO` (`uuid`) ON DELETE CASCADE ) ENGINE = InnoDB DEFAULT CHARSET = utf8; -CREATE TABLE IF NOT EXISTS `VmLocalVolumeCachePoolCapacityVO` ( +CREATE TABLE IF NOT EXISTS `HostCacheStoreCapacityVO` ( `uuid` VARCHAR(32) NOT NULL, `totalCapacity` BIGINT NOT NULL DEFAULT 0, `availableCapacity` BIGINT NOT NULL DEFAULT 0, `allocated` BIGINT NOT NULL DEFAULT 0, `dirty` BIGINT NOT NULL DEFAULT 0, PRIMARY KEY (`uuid`), - CONSTRAINT `fkVmLocalVolumeCachePoolCapacityVOVmLocalVolumeCachePoolVO` - FOREIGN KEY (`uuid`) REFERENCES `VmLocalVolumeCachePoolVO` (`uuid`) + CONSTRAINT `fkHostCacheStoreCapacityVOHostCacheStoreVO` + FOREIGN KEY (`uuid`) REFERENCES `HostCacheStoreVO` (`uuid`) ON DELETE CASCADE ) ENGINE = InnoDB DEFAULT CHARSET = utf8; -CREATE TABLE IF NOT EXISTS `VmLocalVolumeCacheVO` ( +CREATE TABLE IF NOT EXISTS `VolumeCacheVO` ( `uuid` VARCHAR(32) NOT NULL, `volumeUuid` VARCHAR(32) NOT NULL, `poolUuid` VARCHAR(32) DEFAULT NULL, `installPath` VARCHAR(2048) DEFAULT NULL, `cacheMode` VARCHAR(32) NOT NULL, - `state` VARCHAR(32) NOT NULL, + `status` VARCHAR(32) NOT NULL, `createDate` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', `lastOpDate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`uuid`), - UNIQUE KEY `uniVmLocalVolumeCacheVOVolumeUuid` (`volumeUuid`), - CONSTRAINT `fkVmLocalVolumeCacheVOVolumeEO` + UNIQUE KEY `uniVolumeCacheVOVolumeUuid` (`volumeUuid`), + CONSTRAINT `fkVolumeCacheVOVolumeEO` FOREIGN KEY (`volumeUuid`) REFERENCES `VolumeEO` (`uuid`) ON DELETE CASCADE, - CONSTRAINT `fkVmLocalVolumeCacheVOPoolUuid` - FOREIGN KEY (`poolUuid`) REFERENCES `VmLocalVolumeCachePoolVO` (`uuid`) + CONSTRAINT `fkVolumeCacheVOPoolUuid` + FOREIGN KEY (`poolUuid`) REFERENCES `HostCacheStoreVO` (`uuid`) ON DELETE SET NULL ) ENGINE = InnoDB DEFAULT CHARSET = utf8; diff --git a/conf/persistence.xml b/conf/persistence.xml index 504aa198879..da9b18513d6 100755 --- a/conf/persistence.xml +++ b/conf/persistence.xml @@ -217,9 +217,9 @@ org.zstack.network.hostNetworkInterface.lldp.entity.HostNetworkInterfaceLldpRefVO org.zstack.header.volume.block.BlockVolumeVO org.zstack.header.host.HostHwMonitorStatusVO - org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolVO - org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolCapacityVO - org.zstack.header.localVolumeCache.VmLocalVolumeCacheVO + org.zstack.header.volumeCache.HostCacheStoreVO + org.zstack.header.volumeCache.HostCacheStoreCapacityVO + org.zstack.header.volumeCache.VolumeCacheVO org.zstack.kvm.xmlhook.XmlHookVO org.zstack.kvm.xmlhook.XmlHookVmInstanceRefVO org.zstack.log.server.LogServerVO diff --git a/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsg.java b/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsg.java index 8b5cc35e8b7..6173a55ac0f 100644 --- a/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsg.java +++ b/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsg.java @@ -14,6 +14,9 @@ public class APIGetBlockDevicesMsg extends APIMessage implements HostMessage { @APIParam(nonempty = true, resourceType = HostVO.class) private String uuid; + @APIParam(required = false) + private boolean includeInUse; + public String getUuid() { return uuid; } @@ -22,6 +25,14 @@ public void setUuid(String uuid) { this.uuid = uuid; } + public boolean isIncludeInUse() { + return includeInUse; + } + + public void setIncludeInUse(boolean includeInUse) { + this.includeInUse = includeInUse; + } + @Override public String getHostUuid() { return uuid; diff --git a/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsgDoc_zh_cn.groovy index 41417dc0e75..0a7ed39bc73 100644 --- a/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsgDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsgDoc_zh_cn.groovy @@ -48,6 +48,15 @@ doc { optional true since "5.5.6" } + column { + name "includeInUse" + enclosedIn "" + desc "" + location "query" + type "boolean" + optional true + since "5.5.6" + } } } diff --git a/header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostMsg.java b/header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostMsg.java index 13e8764068b..f3545193928 100644 --- a/header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostMsg.java +++ b/header/src/main/java/org/zstack/header/host/GetBlockDevicesOnHostMsg.java @@ -4,6 +4,7 @@ public class GetBlockDevicesOnHostMsg extends NeedReplyMessage implements HostMessage { private String hostUuid; + private boolean includeInUse; public void setHostUuid(String hostUuid) { this.hostUuid = hostUuid; @@ -13,4 +14,12 @@ public void setHostUuid(String hostUuid) { public String getHostUuid() { return hostUuid; } + + public boolean isIncludeInUse() { + return includeInUse; + } + + public void setIncludeInUse(boolean includeInUse) { + this.includeInUse = includeInUse; + } } diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/CachePoolMetadata.java b/header/src/main/java/org/zstack/header/localVolumeCache/CachePoolMetadata.java deleted file mode 100644 index b82229fa282..00000000000 --- a/header/src/main/java/org/zstack/header/localVolumeCache/CachePoolMetadata.java +++ /dev/null @@ -1,186 +0,0 @@ -package org.zstack.header.localVolumeCache; - -import org.zstack.utils.gson.JSONObjectUtil; - -import java.io.Serializable; -import java.util.List; - -/** - * Strongly-typed representation of the metadata JSON stored in VmLocalVolumeCachePoolVO. - *

    - * VO stores metadata as a JSON String. VmLocalVolumeCachePoolInventory exposes it as a - * LinkedHashMap (following the ExternalPrimaryStorage addonInfo pattern). This POJO - * provides type-safe access for internal business logic. - *

    - * Structure mirrors the Python agent's CachePoolMetadata: - *

    - * {
    - *   "mountPoint": "/mnt/cache-pool-1",
    - *   "pvs": [{"pvUuid": "...", "pvName": "...", "pvDevicePath": "/dev/nvme0n1"}],
    - *   "vg": {"vgUuid": "...", "vgName": "..."},
    - *   "lv": {"lvUuid": "...", "lvName": "...", "lvPath": "..."},
    - *   "filesystem": {"fsUuid": "...", "fsType": "xfs"}
    - * }
    - * 
    - */ -public class CachePoolMetadata implements Serializable { - - public static class PVRef implements Serializable { - private String pvUuid; - private String pvName; - private String pvDevicePath; - - public String getPvUuid() { - return pvUuid; - } - - public void setPvUuid(String pvUuid) { - this.pvUuid = pvUuid; - } - - public String getPvName() { - return pvName; - } - - public void setPvName(String pvName) { - this.pvName = pvName; - } - - public String getPvDevicePath() { - return pvDevicePath; - } - - public void setPvDevicePath(String pvDevicePath) { - this.pvDevicePath = pvDevicePath; - } - } - - public static class VGRef implements Serializable { - private String vgUuid; - private String vgName; - - public String getVgUuid() { - return vgUuid; - } - - public void setVgUuid(String vgUuid) { - this.vgUuid = vgUuid; - } - - public String getVgName() { - return vgName; - } - - public void setVgName(String vgName) { - this.vgName = vgName; - } - } - - public static class LVRef implements Serializable { - private String lvUuid; - private String lvName; - private String lvPath; - - public String getLvUuid() { - return lvUuid; - } - - public void setLvUuid(String lvUuid) { - this.lvUuid = lvUuid; - } - - public String getLvName() { - return lvName; - } - - public void setLvName(String lvName) { - this.lvName = lvName; - } - - public String getLvPath() { - return lvPath; - } - - public void setLvPath(String lvPath) { - this.lvPath = lvPath; - } - } - - public static class FileSystemRef implements Serializable { - private String fsUuid; - private String fsType; - - public String getFsUuid() { - return fsUuid; - } - - public void setFsUuid(String fsUuid) { - this.fsUuid = fsUuid; - } - - public String getFsType() { - return fsType; - } - - public void setFsType(String fsType) { - this.fsType = fsType; - } - } - - private String mountPoint; - private List pvs; - private VGRef vg; - private LVRef lv; - private FileSystemRef filesystem; - - public String getMountPoint() { - return mountPoint; - } - - public void setMountPoint(String mountPoint) { - this.mountPoint = mountPoint; - } - - public List getPvs() { - return pvs; - } - - public void setPvs(List pvs) { - this.pvs = pvs; - } - - public VGRef getVg() { - return vg; - } - - public void setVg(VGRef vg) { - this.vg = vg; - } - - public LVRef getLv() { - return lv; - } - - public void setLv(LVRef lv) { - this.lv = lv; - } - - public FileSystemRef getFilesystem() { - return filesystem; - } - - public void setFilesystem(FileSystemRef filesystem) { - this.filesystem = filesystem; - } - - public static CachePoolMetadata fromJson(String json) { - if (json == null || json.isEmpty()) { - return new CachePoolMetadata(); - } - return JSONObjectUtil.toObject(json, CachePoolMetadata.class); - } - - public String toJson() { - return JSONObjectUtil.toJsonString(this); - } -} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheConstant.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheConstant.java deleted file mode 100644 index d75cc7e46cb..00000000000 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheConstant.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.zstack.header.localVolumeCache; - -public interface VmLocalVolumeCacheConstant { - String CACHE_SERVICE_ID = "vmLocalVolumeCache"; -} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheMode.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheMode.java deleted file mode 100644 index 736262d3433..00000000000 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheMode.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.zstack.header.localVolumeCache; - -public enum VmLocalVolumeCacheMode { - WriteBack, -} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityVO_.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityVO_.java deleted file mode 100644 index c2012e9f0f7..00000000000 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityVO_.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.zstack.header.localVolumeCache; - -import javax.persistence.metamodel.SingularAttribute; -import javax.persistence.metamodel.StaticMetamodel; - -@StaticMetamodel(VmLocalVolumeCachePoolCapacityVO.class) -public class VmLocalVolumeCachePoolCapacityVO_ { - public static volatile SingularAttribute uuid; - public static volatile SingularAttribute totalCapacity; - public static volatile SingularAttribute availableCapacity; - public static volatile SingularAttribute allocated; - public static volatile SingularAttribute dirty; -} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventoryDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventoryDoc_zh_cn.groovy deleted file mode 100644 index b66843950d6..00000000000 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventoryDoc_zh_cn.groovy +++ /dev/null @@ -1,90 +0,0 @@ -package org.zstack.header.localVolumeCache - -import org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolState -import org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolStatus -import org.zstack.header.localVolumeCache.VmLocalVolumeCacheInventory -import java.sql.Timestamp - -doc { - - title "在这里输入结构的名称" - - field { - name "uuid" - desc "资源的UUID,唯一标示该资源" - type "String" - since "5.5.6" - } - field { - name "hostUuid" - desc "物理机UUID" - type "String" - since "5.5.6" - } - field { - name "name" - desc "资源名称" - type "String" - since "5.5.6" - } - field { - name "description" - desc "资源的详细描述" - type "String" - since "5.5.6" - } - field { - name "metadata" - desc "" - type "LinkedHashMap" - since "5.5.6" - } - field { - name "totalCapacity" - desc "" - type "long" - since "5.5.6" - } - field { - name "availableCapacity" - desc "" - type "long" - since "5.5.6" - } - ref { - name "state" - path "org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolInventory.state" - desc "null" - type "VmLocalVolumeCachePoolState" - since "5.5.6" - clz VmLocalVolumeCachePoolState.class - } - ref { - name "status" - path "org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolInventory.status" - desc "null" - type "VmLocalVolumeCachePoolStatus" - since "5.5.6" - clz VmLocalVolumeCachePoolStatus.class - } - ref { - name "caches" - path "org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolInventory.caches" - desc "null" - type "Set" - since "5.5.6" - clz VmLocalVolumeCacheInventory.class - } - field { - name "createDate" - desc "创建时间" - type "Timestamp" - since "5.5.6" - } - field { - name "lastOpDate" - desc "最后一次修改时间" - type "Timestamp" - since "5.5.6" - } -} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolState.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolState.java deleted file mode 100644 index 13f7aeb273e..00000000000 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolState.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.zstack.header.localVolumeCache; - -public enum VmLocalVolumeCachePoolState { - Enabled, - Disabled, -} - diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatus.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatus.java deleted file mode 100644 index 6028d2c9121..00000000000 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatus.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.zstack.header.localVolumeCache; - -public enum VmLocalVolumeCachePoolStatus { - Connecting, - Connected, - Disconnected, -} - diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO_.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO_.java deleted file mode 100644 index 3d62bf0f718..00000000000 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO_.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.zstack.header.localVolumeCache; - -import org.zstack.header.vo.ResourceVO_; - -import javax.persistence.metamodel.SingularAttribute; -import javax.persistence.metamodel.StaticMetamodel; -import java.sql.Timestamp; - -@StaticMetamodel(VmLocalVolumeCachePoolVO.class) -public class VmLocalVolumeCachePoolVO_ extends ResourceVO_ { - public static volatile SingularAttribute hostUuid; - public static volatile SingularAttribute name; - public static volatile SingularAttribute description; - public static volatile SingularAttribute metadata; - public static volatile SingularAttribute state; - public static volatile SingularAttribute status; - public static volatile SingularAttribute createDate; - public static volatile SingularAttribute lastOpDate; -} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheState.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheState.java deleted file mode 100644 index 3ec6af0a109..00000000000 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheState.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.zstack.header.localVolumeCache; - -public enum VmLocalVolumeCacheState { - Uninstantiated, - Instantiated, -} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheStateDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheStateDoc_zh_cn.groovy deleted file mode 100644 index a2ec9941de2..00000000000 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheStateDoc_zh_cn.groovy +++ /dev/null @@ -1,27 +0,0 @@ -package org.zstack.header.localVolumeCache - - - -doc { - - title "在这里输入结构的名称" - - field { - name "None" - desc "" - type "VmLocalVolumeCacheState" - since "5.5.6" - } - field { - name "Uninstantiated" - desc "" - type "VmLocalVolumeCacheState" - since "5.5.6" - } - field { - name "Instantiated" - desc "" - type "VmLocalVolumeCacheState" - since "5.5.6" - } -} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO_.java b/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO_.java deleted file mode 100644 index a116f09dbe9..00000000000 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO_.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.zstack.header.localVolumeCache; - -import org.zstack.header.vo.ResourceVO_; - -import javax.persistence.metamodel.SingularAttribute; -import javax.persistence.metamodel.StaticMetamodel; -import java.sql.Timestamp; - -@StaticMetamodel(VmLocalVolumeCacheVO.class) -public class VmLocalVolumeCacheVO_ extends ResourceVO_ { - public static volatile SingularAttribute volumeUuid; - public static volatile SingularAttribute poolUuid; - public static volatile SingularAttribute installPath; - public static volatile SingularAttribute cacheMode; - public static volatile SingularAttribute state; - public static volatile SingularAttribute createDate; - public static volatile SingularAttribute lastOpDate; -} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityInventory.java b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreCapacityInventory.java similarity index 71% rename from header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityInventory.java rename to header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreCapacityInventory.java index 8ac4f3acbb3..805cc4eef0c 100644 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityInventory.java +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreCapacityInventory.java @@ -1,4 +1,4 @@ -package org.zstack.header.localVolumeCache; +package org.zstack.header.volumeCache; import org.zstack.header.configuration.PythonClassInventory; import org.zstack.header.search.Inventory; @@ -8,21 +8,21 @@ import java.util.Collection; import java.util.List; -@Inventory(mappingVOClass = VmLocalVolumeCachePoolCapacityVO.class) +@Inventory(mappingVOClass = HostCacheStoreCapacityVO.class) @PythonClassInventory -public class VmLocalVolumeCachePoolCapacityInventory implements Serializable { +public class HostCacheStoreCapacityInventory implements Serializable { private String uuid; private long totalCapacity; private long availableCapacity; private long allocated; private long dirty; - public static VmLocalVolumeCachePoolCapacityInventory valueOf(VmLocalVolumeCachePoolCapacityVO vo) { + public static HostCacheStoreCapacityInventory valueOf(HostCacheStoreCapacityVO vo) { if (vo == null) { return null; } - VmLocalVolumeCachePoolCapacityInventory inv = new VmLocalVolumeCachePoolCapacityInventory(); + HostCacheStoreCapacityInventory inv = new HostCacheStoreCapacityInventory(); inv.setUuid(vo.getUuid()); inv.setTotalCapacity(vo.getTotalCapacity()); inv.setAvailableCapacity(vo.getAvailableCapacity()); @@ -31,13 +31,13 @@ public static VmLocalVolumeCachePoolCapacityInventory valueOf(VmLocalVolumeCache return inv; } - public static List valueOf(Collection vos) { + public static List valueOf(Collection vos) { if (vos == null || vos.isEmpty()) { return new ArrayList<>(); } - List invs = new ArrayList<>(vos.size()); + List invs = new ArrayList<>(vos.size()); - for (VmLocalVolumeCachePoolCapacityVO vo : vos) { + for (HostCacheStoreCapacityVO vo : vos) { invs.add(valueOf(vo)); } return invs; diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityVO.java b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreCapacityVO.java similarity index 78% rename from header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityVO.java rename to header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreCapacityVO.java index 4bdd60d387c..6ba371fa1d8 100644 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolCapacityVO.java +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreCapacityVO.java @@ -1,4 +1,4 @@ -package org.zstack.header.localVolumeCache; +package org.zstack.header.volumeCache; import org.zstack.header.vo.EntityGraph; import org.zstack.header.vo.ForeignKey; @@ -13,13 +13,13 @@ @Table @EntityGraph( parents = { - @EntityGraph.Neighbour(type = VmLocalVolumeCachePoolVO.class, myField = "uuid", targetField = "uuid") + @EntityGraph.Neighbour(type = HostCacheStoreVO.class, myField = "uuid", targetField = "uuid") } ) -public class VmLocalVolumeCachePoolCapacityVO { +public class HostCacheStoreCapacityVO { @Id @Column - @ForeignKey(parentEntityClass = VmLocalVolumeCachePoolVO.class, onDeleteAction = ReferenceOption.CASCADE) + @ForeignKey(parentEntityClass = HostCacheStoreVO.class, onDeleteAction = ReferenceOption.CASCADE) private String uuid; @Column @@ -34,7 +34,7 @@ public class VmLocalVolumeCachePoolCapacityVO { @Column private long dirty; - public VmLocalVolumeCachePoolCapacityVO() { + public HostCacheStoreCapacityVO() { } public String getUuid() { diff --git a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreCapacityVO_.java b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreCapacityVO_.java new file mode 100644 index 00000000000..312ec0d9f3d --- /dev/null +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreCapacityVO_.java @@ -0,0 +1,13 @@ +package org.zstack.header.volumeCache; + +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; + +@StaticMetamodel(HostCacheStoreCapacityVO.class) +public class HostCacheStoreCapacityVO_ { + public static volatile SingularAttribute uuid; + public static volatile SingularAttribute totalCapacity; + public static volatile SingularAttribute availableCapacity; + public static volatile SingularAttribute allocated; + public static volatile SingularAttribute dirty; +} diff --git a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreDeviceInventory.java b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreDeviceInventory.java new file mode 100644 index 00000000000..42acc0fb505 --- /dev/null +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreDeviceInventory.java @@ -0,0 +1,29 @@ +package org.zstack.header.volumeCache; + +import java.io.Serializable; + +/** + * Per-device entry surfaced on a HostCacheStoreInventory. + * + * Only persisted fields are carried here: path and wwid. Live fields + * (vendor/model/serial/size/present) are not enriched server-side; callers + * that need live device info should call the block-devices API on the host + * and join on wwid (preferred) or path (fallback). + */ +public class HostCacheStoreDeviceInventory implements Serializable { + private String path; + private String wwid; + + public static HostCacheStoreDeviceInventory valueOf(HostCacheStoreDeviceRef ref) { + HostCacheStoreDeviceInventory inv = new HostCacheStoreDeviceInventory(); + inv.setPath(ref.getPath()); + inv.setWwid(ref.getWwid()); + return inv; + } + + public String getPath() { return path; } + public void setPath(String path) { this.path = path; } + + public String getWwid() { return wwid; } + public void setWwid(String wwid) { this.wwid = wwid; } +} diff --git a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreDeviceRef.java b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreDeviceRef.java new file mode 100644 index 00000000000..b2ef5568b2b --- /dev/null +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreDeviceRef.java @@ -0,0 +1,63 @@ +package org.zstack.header.volumeCache; + +import java.io.Serializable; +import java.util.Objects; + +/** + * Reference to a block device that belongs to a HostCacheStore. + * + * path: always populated — taken from user input or resolved live device entry. + * wwid: optional — populated if the device has a stable WWID at the time of + * pool creation/extension; null otherwise. + * + * Equality/hashing is based on (wwid, path) so that a ref with a known wwid + * is only equal to another ref with the same wwid (regardless of path churn), + * and a wwid-less ref is identified by path. + */ +public class HostCacheStoreDeviceRef implements Serializable { + private String path; + private String wwid; + + public HostCacheStoreDeviceRef() { + } + + public HostCacheStoreDeviceRef(String path, String wwid) { + this.path = path; + this.wwid = wwid; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getWwid() { + return wwid; + } + + public void setWwid(String wwid) { + this.wwid = wwid; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof HostCacheStoreDeviceRef)) return false; + HostCacheStoreDeviceRef that = (HostCacheStoreDeviceRef) o; + if (wwid != null && that.wwid != null) { + return wwid.equals(that.wwid); + } + if (wwid != null || that.wwid != null) { + return false; + } + return Objects.equals(path, that.path); + } + + @Override + public int hashCode() { + return wwid != null ? wwid.hashCode() : Objects.hashCode(path); + } +} diff --git a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreDeviceRefsConverter.java b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreDeviceRefsConverter.java new file mode 100644 index 00000000000..0361fbbe365 --- /dev/null +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreDeviceRefsConverter.java @@ -0,0 +1,52 @@ +package org.zstack.header.volumeCache; + +import org.apache.commons.lang.StringUtils; +import org.zstack.utils.Utils; +import org.zstack.utils.gson.JSONObjectUtil; +import org.zstack.utils.logging.CLogger; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +import com.google.gson.reflect.TypeToken; + +/** + * JPA AttributeConverter that serializes a List + * to/from a JSON TEXT column. + */ +@Converter +public class HostCacheStoreDeviceRefsConverter + implements AttributeConverter, String> { + + private static final CLogger logger = Utils.getLogger(HostCacheStoreDeviceRefsConverter.class); + + private static final Type LIST_TYPE = + new TypeToken>() {}.getType(); + + @Override + public String convertToDatabaseColumn(List attribute) { + if (attribute == null || attribute.isEmpty()) { + return null; + } + return JSONObjectUtil.toJsonString(attribute); + } + + @Override + public List convertToEntityAttribute(String dbData) { + if (StringUtils.isEmpty(dbData)) { + return new ArrayList<>(); + } + try { + List result = + JSONObjectUtil.toList(dbData, LIST_TYPE); + return result == null ? new ArrayList<>() : result; + } catch (Exception e) { + logger.warn(String.format("failed to deserialize HostCacheStoreVO.devices json[%s]; " + + "treat as empty", dbData), e); + return new ArrayList<>(); + } + } +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventory.java b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreInventory.java similarity index 60% rename from header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventory.java rename to header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreInventory.java index 126aff258b9..22a1cef274b 100644 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolInventory.java +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreInventory.java @@ -1,56 +1,54 @@ -package org.zstack.header.localVolumeCache; +package org.zstack.header.volumeCache; import org.hibernate.Hibernate; import org.zstack.header.configuration.PythonClassInventory; import org.zstack.header.query.Queryable; import org.zstack.header.search.Inventory; -import org.zstack.utils.gson.JSONObjectUtil; import javax.persistence.JoinColumn; import java.io.Serializable; import java.sql.Timestamp; import java.util.*; -@Inventory(mappingVOClass = VmLocalVolumeCachePoolVO.class) +@Inventory(mappingVOClass = HostCacheStoreVO.class) @PythonClassInventory -public class VmLocalVolumeCachePoolInventory implements Serializable { +public class HostCacheStoreInventory implements Serializable { private String uuid; private String hostUuid; private String name; private String description; - private LinkedHashMap metadata; + private String mountPoint; - @Queryable(mappingClass = VmLocalVolumeCachePoolCapacityInventory.class, + @Queryable(mappingClass = HostCacheStoreCapacityInventory.class, joinColumn = @JoinColumn(name = "uuid", referencedColumnName = "totalCapacity")) private long totalCapacity; - @Queryable(mappingClass = VmLocalVolumeCachePoolCapacityInventory.class, + @Queryable(mappingClass = HostCacheStoreCapacityInventory.class, joinColumn = @JoinColumn(name = "uuid", referencedColumnName = "availableCapacity")) private long availableCapacity; - @Queryable(mappingClass = VmLocalVolumeCachePoolCapacityInventory.class, + @Queryable(mappingClass = HostCacheStoreCapacityInventory.class, joinColumn = @JoinColumn(name = "uuid", referencedColumnName = "allocated")) private long allocated; - @Queryable(mappingClass = VmLocalVolumeCachePoolCapacityInventory.class, + @Queryable(mappingClass = HostCacheStoreCapacityInventory.class, joinColumn = @JoinColumn(name = "uuid", referencedColumnName = "dirty")) private long dirty; - private VmLocalVolumeCachePoolState state; - private VmLocalVolumeCachePoolStatus status; - private Set caches; + private HostCacheStoreState state; + private HostCacheStoreStatus status; + private List devices; + private Set caches; private Timestamp createDate; private Timestamp lastOpDate; - public static VmLocalVolumeCachePoolInventory valueOf(VmLocalVolumeCachePoolVO vo) { - VmLocalVolumeCachePoolInventory inv = new VmLocalVolumeCachePoolInventory(); + public static HostCacheStoreInventory valueOf(HostCacheStoreVO vo) { + HostCacheStoreInventory inv = new HostCacheStoreInventory(); inv.setUuid(vo.getUuid()); inv.setHostUuid(vo.getHostUuid()); inv.setName(vo.getName()); inv.setDescription(vo.getDescription()); - if (vo.getMetadata() != null) { - inv.setMetadata(JSONObjectUtil.toObject(vo.getMetadata(), LinkedHashMap.class)); - } + inv.setMountPoint(vo.getMountPoint()); if (vo.getCapacity() != null) { inv.setTotalCapacity(vo.getCapacity().getTotalCapacity()); inv.setAvailableCapacity(vo.getCapacity().getAvailableCapacity()); @@ -61,9 +59,19 @@ public static VmLocalVolumeCachePoolInventory valueOf(VmLocalVolumeCachePoolVO v inv.setStatus(vo.getStatus()); inv.setCreateDate(vo.getCreateDate()); inv.setLastOpDate(vo.getLastOpDate()); - if(vo.getCaches() != null && Hibernate.isInitialized(vo.getCaches())) { - Set cacheInventories = new HashSet<>(); - for (VmLocalVolumeCacheVO cacheVO : vo.getCaches()) { + // persisted-only device refs (path + wwid). Callers that need live + // vendor/model/serial/size must call the host block-devices API and + // join on wwid (preferred) or path. + List deviceInvs = new ArrayList<>(); + if (vo.getDevices() != null) { + for (HostCacheStoreDeviceRef ref : vo.getDevices()) { + deviceInvs.add(HostCacheStoreDeviceInventory.valueOf(ref)); + } + } + inv.setDevices(deviceInvs); + if (vo.getCaches() != null && Hibernate.isInitialized(vo.getCaches())) { + Set cacheInventories = new HashSet<>(); + for (VolumeCacheVO cacheVO : vo.getCaches()) { cacheInventories.add(cacheVO.toInventory()); } inv.setCaches(cacheInventories); @@ -71,9 +79,9 @@ public static VmLocalVolumeCachePoolInventory valueOf(VmLocalVolumeCachePoolVO v return inv; } - public static List valueOf(Collection vos) { - List invs = new ArrayList<>(); - for (VmLocalVolumeCachePoolVO vo : vos) { + public static List valueOf(Collection vos) { + List invs = new ArrayList<>(); + for (HostCacheStoreVO vo : vos) { invs.add(valueOf(vo)); } return invs; @@ -111,12 +119,12 @@ public void setDescription(String description) { this.description = description; } - public LinkedHashMap getMetadata() { - return metadata; + public String getMountPoint() { + return mountPoint; } - public void setMetadata(LinkedHashMap metadata) { - this.metadata = metadata; + public void setMountPoint(String mountPoint) { + this.mountPoint = mountPoint; } public long getTotalCapacity() { @@ -151,30 +159,38 @@ public void setDirty(long dirty) { this.dirty = dirty; } - public VmLocalVolumeCachePoolState getState() { + public HostCacheStoreState getState() { return state; } - public void setState(VmLocalVolumeCachePoolState state) { + public void setState(HostCacheStoreState state) { this.state = state; } - public VmLocalVolumeCachePoolStatus getStatus() { + public HostCacheStoreStatus getStatus() { return status; } - public void setStatus(VmLocalVolumeCachePoolStatus status) { + public void setStatus(HostCacheStoreStatus status) { this.status = status; } - public Set getCaches() { + public Set getCaches() { return caches; } - public void setCaches(Set caches) { + public void setCaches(Set caches) { this.caches = caches; } + public List getDevices() { + return devices; + } + + public void setDevices(List devices) { + this.devices = devices; + } + public Timestamp getCreateDate() { return createDate; } diff --git a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreInventoryDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreInventoryDoc_zh_cn.groovy new file mode 100644 index 00000000000..8dd75b46d21 --- /dev/null +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreInventoryDoc_zh_cn.groovy @@ -0,0 +1,90 @@ +package org.zstack.header.volumeCache + +import org.zstack.header.volumeCache.HostCacheStoreState +import org.zstack.header.volumeCache.HostCacheStoreStatus +import org.zstack.header.volumeCache.VolumeCacheInventory +import java.sql.Timestamp + +doc { + + title "主机缓存存储清单" + + field { + name "uuid" + desc "资源的UUID,唯一标示该资源" + type "String" + since "5.5.6" + } + field { + name "hostUuid" + desc "物理机UUID" + type "String" + since "5.5.6" + } + field { + name "name" + desc "资源名称" + type "String" + since "5.5.6" + } + field { + name "description" + desc "资源的详细描述" + type "String" + since "5.5.6" + } + field { + name "mountPoint" + desc "缓存池挂载路径" + type "String" + since "5.5.6" + } + field { + name "totalCapacity" + desc "主机缓存存储的总容量,单位为字节" + type "long" + since "5.5.6" + } + field { + name "availableCapacity" + desc "主机缓存存储的可用容量,单位为字节" + type "long" + since "5.5.6" + } + ref { + name "state" + path "org.zstack.header.volumeCache.HostCacheStoreInventory.state" + desc "主机缓存存储的状态" + type "HostCacheStoreState" + since "5.5.6" + clz HostCacheStoreState.class + } + ref { + name "status" + path "org.zstack.header.volumeCache.HostCacheStoreInventory.status" + desc "主机缓存存储的健康状态" + type "HostCacheStoreStatus" + since "5.5.6" + clz HostCacheStoreStatus.class + } + ref { + name "caches" + path "org.zstack.header.volumeCache.HostCacheStoreInventory.caches" + desc "该主机缓存存储上的卷缓存集合" + type "Set" + since "5.5.6" + clz VolumeCacheInventory.class + } + field { + name "createDate" + desc "创建时间" + type "Timestamp" + since "5.5.6" + } + field { + name "lastOpDate" + desc "最后一次修改时间" + type "Timestamp" + since "5.5.6" + } +} diff --git a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreState.java b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreState.java new file mode 100644 index 00000000000..f34be5082f2 --- /dev/null +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreState.java @@ -0,0 +1,7 @@ +package org.zstack.header.volumeCache; + +public enum HostCacheStoreState { + Enabled, + Disabled, +} + diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStateDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStateDoc_zh_cn.groovy similarity index 58% rename from header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStateDoc_zh_cn.groovy rename to header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStateDoc_zh_cn.groovy index f1525aa58e4..425457696a4 100644 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStateDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStateDoc_zh_cn.groovy @@ -1,4 +1,4 @@ -package org.zstack.header.localVolumeCache +package org.zstack.header.volumeCache @@ -9,13 +9,13 @@ doc { field { name "Enabled" desc "" - type "VmLocalVolumeCachePoolState" + type "HostCacheStoreState" since "5.5.6" } field { name "Disabled" desc "" - type "VmLocalVolumeCachePoolState" + type "HostCacheStoreState" since "5.5.6" } } diff --git a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStatus.java b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStatus.java new file mode 100644 index 00000000000..3306b606b8e --- /dev/null +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStatus.java @@ -0,0 +1,8 @@ +package org.zstack.header.volumeCache; + +public enum HostCacheStoreStatus { + Connecting, + Connected, + Disconnected, +} + diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatusDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStatusDoc_zh_cn.groovy similarity index 59% rename from header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatusDoc_zh_cn.groovy rename to header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStatusDoc_zh_cn.groovy index 2427adf7c81..adcba1412e1 100644 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolStatusDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStatusDoc_zh_cn.groovy @@ -1,4 +1,4 @@ -package org.zstack.header.localVolumeCache +package org.zstack.header.volumeCache @@ -9,19 +9,19 @@ doc { field { name "Connecting" desc "" - type "VmLocalVolumeCachePoolStatus" + type "HostCacheStoreStatus" since "5.5.6" } field { name "Connected" desc "" - type "VmLocalVolumeCachePoolStatus" + type "HostCacheStoreStatus" since "5.5.6" } field { name "Disconnected" desc "" - type "VmLocalVolumeCachePoolStatus" + type "HostCacheStoreStatus" since "5.5.6" } } diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO.java b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreVO.java similarity index 65% rename from header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO.java rename to header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreVO.java index 14b999f5dbd..a0456ee86a0 100644 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCachePoolVO.java +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreVO.java @@ -1,4 +1,4 @@ -package org.zstack.header.localVolumeCache; +package org.zstack.header.volumeCache; import org.hibernate.annotations.NotFound; import org.hibernate.annotations.NotFoundAction; @@ -12,6 +12,8 @@ import javax.persistence.*; import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; import java.util.Set; @Entity @@ -21,7 +23,7 @@ @EntityGraph.Neighbour(type = HostVO.class, myField = "hostUuid", targetField = "uuid") } ) -public class VmLocalVolumeCachePoolVO extends ResourceVO implements ToInventory { +public class HostCacheStoreVO extends ResourceVO implements ToInventory { @Column @ForeignKey(parentEntityClass = HostEO.class, onDeleteAction = ForeignKey.ReferenceOption.CASCADE) @@ -33,26 +35,30 @@ public class VmLocalVolumeCachePoolVO extends ResourceVO implements ToInventory @Column private String description; - @Column(length = 2048) - private String metadata; + @Column(length = 255) + private String mountPoint; + + @Column(columnDefinition = "TEXT") + @Convert(converter = HostCacheStoreDeviceRefsConverter.class) + private List devices; @OneToOne(fetch = FetchType.EAGER) @JoinColumn(name = "uuid") @NoView - private VmLocalVolumeCachePoolCapacityVO capacity; + private HostCacheStoreCapacityVO capacity; @Column @Enumerated(EnumType.STRING) - private VmLocalVolumeCachePoolState state; + private HostCacheStoreState state; @Column @Enumerated(EnumType.STRING) - private VmLocalVolumeCachePoolStatus status; + private HostCacheStoreStatus status; @OneToMany(fetch = FetchType.LAZY) @NotFound(action = NotFoundAction.IGNORE) @JoinColumn(name = "poolUuid", insertable = false, updatable = false) - private Set caches; + private Set caches; @Column private Timestamp createDate; @@ -89,39 +95,47 @@ public void setDescription(String description) { this.description = description; } - public String getMetadata() { - return metadata; + public String getMountPoint() { + return mountPoint; + } + + public void setMountPoint(String mountPoint) { + this.mountPoint = mountPoint; + } + + public List getDevices() { + return devices == null ? new ArrayList<>() : devices; } - public void setMetadata(String metadata) { - this.metadata = metadata; + public void setDevices(List devices) { + this.devices = devices; } - public VmLocalVolumeCachePoolCapacityVO getCapacity() { + public HostCacheStoreCapacityVO getCapacity() { return capacity; } - public void setCapacity(VmLocalVolumeCachePoolCapacityVO capacity) { + public void setCapacity(HostCacheStoreCapacityVO capacity) { this.capacity = capacity; } - public VmLocalVolumeCachePoolState getState() { + public HostCacheStoreState getState() { return state; } - public void setState(VmLocalVolumeCachePoolState state) { + public void setState(HostCacheStoreState state) { this.state = state; } - public VmLocalVolumeCachePoolStatus getStatus() { + public HostCacheStoreStatus getStatus() { return status; } - public void setStatus(VmLocalVolumeCachePoolStatus status) { + public void setStatus(HostCacheStoreStatus status) { this.status = status; } - public Set getCaches() { + public Set getCaches() { return caches; } diff --git a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreVO_.java b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreVO_.java new file mode 100644 index 00000000000..0aff14f8a03 --- /dev/null +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreVO_.java @@ -0,0 +1,19 @@ +package org.zstack.header.volumeCache; + +import org.zstack.header.vo.ResourceVO_; + +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; +import java.sql.Timestamp; + +@StaticMetamodel(HostCacheStoreVO.class) +public class HostCacheStoreVO_ extends ResourceVO_ { + public static volatile SingularAttribute hostUuid; + public static volatile SingularAttribute name; + public static volatile SingularAttribute description; + public static volatile SingularAttribute mountPoint; + public static volatile SingularAttribute state; + public static volatile SingularAttribute status; + public static volatile SingularAttribute createDate; + public static volatile SingularAttribute lastOpDate; +} diff --git a/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheConstant.java b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheConstant.java new file mode 100644 index 00000000000..f27a450dc2d --- /dev/null +++ b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheConstant.java @@ -0,0 +1,5 @@ +package org.zstack.header.volumeCache; + +public interface VolumeCacheConstant { + String CACHE_SERVICE_ID = "volumeCache"; +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventory.java b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheInventory.java similarity index 67% rename from header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventory.java rename to header/src/main/java/org/zstack/header/volumeCache/VolumeCacheInventory.java index e0d5a306ba7..d0898de1d72 100644 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventory.java +++ b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheInventory.java @@ -1,4 +1,4 @@ -package org.zstack.header.localVolumeCache; +package org.zstack.header.volumeCache; import org.zstack.header.configuration.PythonClassInventory; import org.zstack.header.search.Inventory; @@ -9,34 +9,35 @@ import java.util.Collection; import java.util.List; -@Inventory(mappingVOClass = VmLocalVolumeCacheVO.class) +@Inventory(mappingVOClass = VolumeCacheVO.class) @PythonClassInventory -public class VmLocalVolumeCacheInventory implements Serializable { +public class VolumeCacheInventory implements Serializable { + private static final long serialVersionUID = 1L; private String uuid; private String volumeUuid; private String poolUuid; private String installPath; - private VmLocalVolumeCacheMode cacheMode; - private VmLocalVolumeCacheState state; + private VolumeCacheMode cacheMode; + private VolumeCacheStatus status; private Timestamp createDate; private Timestamp lastOpDate; - public static VmLocalVolumeCacheInventory valueOf(VmLocalVolumeCacheVO vo) { - VmLocalVolumeCacheInventory inv = new VmLocalVolumeCacheInventory(); + public static VolumeCacheInventory valueOf(VolumeCacheVO vo) { + VolumeCacheInventory inv = new VolumeCacheInventory(); inv.setUuid(vo.getUuid()); inv.setVolumeUuid(vo.getVolumeUuid()); inv.setPoolUuid(vo.getPoolUuid()); inv.setInstallPath(vo.getInstallPath()); inv.setCacheMode(vo.getCacheMode()); - inv.setState(vo.getState()); + inv.setStatus(vo.getStatus()); inv.setCreateDate(vo.getCreateDate()); inv.setLastOpDate(vo.getLastOpDate()); return inv; } - public static List valueOf(Collection vos) { - List invs = new ArrayList<>(); - for (VmLocalVolumeCacheVO vo : vos) { + public static List valueOf(Collection vos) { + List invs = new ArrayList<>(); + for (VolumeCacheVO vo : vos) { invs.add(valueOf(vo)); } return invs; @@ -74,20 +75,20 @@ public void setInstallPath(String installPath) { this.installPath = installPath; } - public VmLocalVolumeCacheMode getCacheMode() { + public VolumeCacheMode getCacheMode() { return cacheMode; } - public void setCacheMode(VmLocalVolumeCacheMode cacheMode) { + public void setCacheMode(VolumeCacheMode cacheMode) { this.cacheMode = cacheMode; } - public VmLocalVolumeCacheState getState() { - return state; + public VolumeCacheStatus getStatus() { + return status; } - public void setState(VmLocalVolumeCacheState state) { - this.state = state; + public void setStatus(VolumeCacheStatus status) { + this.status = status; } public Timestamp getCreateDate() { diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventoryDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheInventoryDoc_zh_cn.groovy similarity index 51% rename from header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventoryDoc_zh_cn.groovy rename to header/src/main/java/org/zstack/header/volumeCache/VolumeCacheInventoryDoc_zh_cn.groovy index d098f92796a..87b4947f82e 100644 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheInventoryDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheInventoryDoc_zh_cn.groovy @@ -1,12 +1,12 @@ -package org.zstack.header.localVolumeCache +package org.zstack.header.volumeCache -import org.zstack.header.localVolumeCache.VmLocalVolumeCacheMode -import org.zstack.header.localVolumeCache.VmLocalVolumeCacheState +import org.zstack.header.volumeCache.VolumeCacheMode +import org.zstack.header.volumeCache.VolumeCacheStatus import java.sql.Timestamp doc { - title "在这里输入结构的名称" + title "卷缓存清单" field { name "uuid" @@ -22,31 +22,31 @@ doc { } field { name "poolUuid" - desc "" + desc "主机缓存存储的UUID" type "String" since "5.5.6" } field { name "installPath" - desc "" + desc "卷缓存在主机上的安装路径" type "String" since "5.5.6" } ref { name "cacheMode" - path "org.zstack.header.localVolumeCache.VmLocalVolumeCacheInventory.cacheMode" - desc "null" - type "VmLocalVolumeCacheMode" + path "org.zstack.header.volumeCache.VolumeCacheInventory.cacheMode" + desc "卷缓存的读写模式" + type "VolumeCacheMode" since "5.5.6" - clz VmLocalVolumeCacheMode.class + clz VolumeCacheMode.class } ref { - name "state" - path "org.zstack.header.localVolumeCache.VmLocalVolumeCacheInventory.state" - desc "null" - type "VmLocalVolumeCacheState" + name "status" + path "org.zstack.header.volumeCache.VolumeCacheInventory.status" + desc "卷缓存的状态" + type "VolumeCacheStatus" since "5.5.6" - clz VmLocalVolumeCacheState.class + clz VolumeCacheStatus.class } field { name "createDate" diff --git a/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheMode.java b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheMode.java new file mode 100644 index 00000000000..94d55486b2b --- /dev/null +++ b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheMode.java @@ -0,0 +1,5 @@ +package org.zstack.header.volumeCache; + +public enum VolumeCacheMode { + WriteBack, +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheModeDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheModeDoc_zh_cn.groovy similarity index 59% rename from header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheModeDoc_zh_cn.groovy rename to header/src/main/java/org/zstack/header/volumeCache/VolumeCacheModeDoc_zh_cn.groovy index fcc06bb9590..270cd3a7518 100644 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheModeDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheModeDoc_zh_cn.groovy @@ -1,4 +1,4 @@ -package org.zstack.header.localVolumeCache +package org.zstack.header.volumeCache @@ -9,7 +9,7 @@ doc { field { name "WriteBack" desc "" - type "VmLocalVolumeCacheMode" + type "VolumeCacheMode" since "5.5.6" } } diff --git a/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheStatus.java b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheStatus.java new file mode 100644 index 00000000000..25f5080dfde --- /dev/null +++ b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheStatus.java @@ -0,0 +1,6 @@ +package org.zstack.header.volumeCache; + +public enum VolumeCacheStatus { + NotInstantiated, + Ready, +} diff --git a/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheStatusDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheStatusDoc_zh_cn.groovy new file mode 100644 index 00000000000..2bb27f02235 --- /dev/null +++ b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheStatusDoc_zh_cn.groovy @@ -0,0 +1,21 @@ +package org.zstack.header.volumeCache + + + +doc { + + title "卷缓存状态" + + field { + name "NotInstantiated" + desc "卷缓存尚未在主机缓存存储上实例化" + type "VolumeCacheStatus" + since "5.5.6" + } + field { + name "Ready" + desc "卷缓存已就绪,可供虚拟机使用" + type "VolumeCacheStatus" + since "5.5.6" + } +} diff --git a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO.java b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheVO.java similarity index 76% rename from header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO.java rename to header/src/main/java/org/zstack/header/volumeCache/VolumeCacheVO.java index ba45d6e865f..621aabf5630 100644 --- a/header/src/main/java/org/zstack/header/localVolumeCache/VmLocalVolumeCacheVO.java +++ b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheVO.java @@ -1,4 +1,4 @@ -package org.zstack.header.localVolumeCache; +package org.zstack.header.volumeCache; import org.zstack.header.vo.EntityGraph; import org.zstack.header.vo.ForeignKey; @@ -19,14 +19,14 @@ @EntityGraph.Neighbour(type = VolumeVO.class, myField = "volumeUuid", targetField = "uuid") } ) -public class VmLocalVolumeCacheVO extends ResourceVO implements ToInventory { +public class VolumeCacheVO extends ResourceVO implements ToInventory { @Column @ForeignKey(parentEntityClass = VolumeEO.class, onDeleteAction = ForeignKey.ReferenceOption.CASCADE) private String volumeUuid; @Column - @ForeignKey(parentEntityClass = VmLocalVolumeCachePoolVO.class, onDeleteAction = ForeignKey.ReferenceOption.SET_NULL) + @ForeignKey(parentEntityClass = HostCacheStoreVO.class, onDeleteAction = ForeignKey.ReferenceOption.SET_NULL) private String poolUuid; @Column(length = 2048) @@ -34,11 +34,11 @@ public class VmLocalVolumeCacheVO extends ResourceVO implements ToInventory { @Column @Enumerated(EnumType.STRING) - private VmLocalVolumeCacheMode cacheMode; + private VolumeCacheMode cacheMode; @Column @Enumerated(EnumType.STRING) - private VmLocalVolumeCacheState state; + private VolumeCacheStatus status; @Column private Timestamp createDate; @@ -75,20 +75,20 @@ public void setInstallPath(String installPath) { this.installPath = installPath; } - public VmLocalVolumeCacheMode getCacheMode() { + public VolumeCacheMode getCacheMode() { return cacheMode; } - public void setCacheMode(VmLocalVolumeCacheMode cacheMode) { + public void setCacheMode(VolumeCacheMode cacheMode) { this.cacheMode = cacheMode; } - public VmLocalVolumeCacheState getState() { - return state; + public VolumeCacheStatus getStatus() { + return status; } - public void setState(VmLocalVolumeCacheState state) { - this.state = state; + public void setStatus(VolumeCacheStatus status) { + this.status = status; } public Timestamp getCreateDate() { diff --git a/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheVO_.java b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheVO_.java new file mode 100644 index 00000000000..82479ea6788 --- /dev/null +++ b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheVO_.java @@ -0,0 +1,18 @@ +package org.zstack.header.volumeCache; + +import org.zstack.header.vo.ResourceVO_; + +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; +import java.sql.Timestamp; + +@StaticMetamodel(VolumeCacheVO.class) +public class VolumeCacheVO_ extends ResourceVO_ { + public static volatile SingularAttribute volumeUuid; + public static volatile SingularAttribute poolUuid; + public static volatile SingularAttribute installPath; + public static volatile SingularAttribute cacheMode; + public static volatile SingularAttribute status; + public static volatile SingularAttribute createDate; + public static volatile SingularAttribute lastOpDate; +} diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java b/plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java index 72b51906f0a..b049adb285f 100644 --- a/plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/CacheTO.java @@ -1,6 +1,6 @@ package org.zstack.kvm; -import org.zstack.header.localVolumeCache.VmLocalVolumeCacheInventory; +import org.zstack.header.volumeCache.VolumeCacheInventory; public class CacheTO extends BaseVirtualDeviceTO { private String cacheUuid; @@ -40,7 +40,7 @@ public void setCacheMode(String cacheMode) { this.cacheMode = cacheMode; } - public static CacheTO valueOf(VmLocalVolumeCacheInventory inv) { + public static CacheTO valueOf(VolumeCacheInventory inv) { CacheTO to = new CacheTO(); to.setCacheUuid(inv.getUuid()); to.setPoolUuid(inv.getPoolUuid()); diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java index 1a3c0bb1101..c5298f0aaf8 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java @@ -11,7 +11,6 @@ import org.zstack.header.host.HostBlockDeviceStruct; import org.zstack.header.host.HostNUMANode; import org.zstack.header.host.VmNicRedirectConfig; -import org.zstack.header.localVolumeCache.CachePoolMetadata; import org.zstack.header.log.NoLogging; import org.zstack.header.vm.*; import org.zstack.header.vm.devices.DeviceAddress; @@ -5373,14 +5372,14 @@ public void setMemoryUsage(long memoryUsage) { } // ======================================================================== - // Local Volume Cache — Command Definitions + // Volume Cache — Command Definitions // ======================================================================== public static class InitPoolCmd extends AgentCommand { public String poolUuid; public String mountPoint; public boolean force; - public List pvs; + public List devices; } public static class ConnectPoolCmd extends AgentCommand { @@ -5393,7 +5392,7 @@ public static class ExtendPoolCmd extends AgentCommand { public String poolUuid; public String mountPoint; public boolean force; - public List pvs; + public List devices; } public static class DeletePoolCmd extends AgentCommand { @@ -5452,97 +5451,17 @@ public static class DetachVolumeCacheCmd extends AgentCommand implements HasThre } // ======================================================================== - // Local Volume Cache — Ref Structures - // ======================================================================== - - public static class PVRef { - public String pvUuid; - public String pvName; - public String pvDevicePath; - } - - public static class PVHealthRef extends PVRef { - public Boolean healthy; - } - - public static class VGRef { - public String vgUuid; - public String vgName; - } - - public static class LVRef { - public String lvUuid; - public String lvName; - public String lvPath; - } - - public static class FileSystemRef { - public String fsUuid; - public String fsType; - } - - public static class VolumeRef { - public String volumeUuid; - public String installPath; - public String deviceType; - public String format; - public Long size; - } - - // ======================================================================== - // Local Volume Cache — Response Definitions + // Volume Cache — Response Definitions // ======================================================================== public static class PoolRsp extends AgentResponse { public String poolUuid; public String mountPoint; - public List pvs; - public VGRef vg; - public LVRef lv; - public FileSystemRef filesystem; - - public CachePoolMetadata toCachePoolMetadata() { - CachePoolMetadata metadata = new CachePoolMetadata(); - metadata.setMountPoint(mountPoint); - if (pvs != null) { - List pvRefs = pvs.stream().map(pv -> { - CachePoolMetadata.PVRef ref = new CachePoolMetadata.PVRef(); - ref.setPvUuid(pv.pvUuid); - ref.setPvName(pv.pvName); - ref.setPvDevicePath(pv.pvDevicePath); - return ref; - }).collect(Collectors.toList()); - metadata.setPvs(pvRefs); - } - if (vg != null) { - CachePoolMetadata.VGRef vgRef = new CachePoolMetadata.VGRef(); - vgRef.setVgUuid(vg.vgUuid); - vgRef.setVgName(vg.vgName); - metadata.setVg(vgRef); - } - if (lv != null) { - CachePoolMetadata.LVRef lvRef = new CachePoolMetadata.LVRef(); - lvRef.setLvUuid(lv.lvUuid); - lvRef.setLvName(lv.lvName); - lvRef.setLvPath(lv.lvPath); - metadata.setLv(lvRef); - } - if (filesystem != null) { - CachePoolMetadata.FileSystemRef fsRef = new CachePoolMetadata.FileSystemRef(); - fsRef.setFsUuid(filesystem.fsUuid); - fsRef.setFsType(filesystem.fsType); - metadata.setFilesystem(fsRef); - } - return metadata; - } } public static class PoolHealthRsp extends AgentResponse { public Boolean healthy; - public List pvs; - public Boolean vg; - public Boolean lv; - public Boolean filesystem; + public String reason; } public static class PoolCapacityRsp extends AgentResponse { @@ -5571,6 +5490,15 @@ public static class DetachVolumeCacheRsp extends AgentResponse { } public static class GetBlockDevicesCmd extends AgentCommand { + private boolean includeInUse; + + public boolean isIncludeInUse() { + return includeInUse; + } + + public void setIncludeInUse(boolean includeInUse) { + this.includeInUse = includeInUse; + } } public static class GetBlockDevicesRsp extends AgentResponse { diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java index 2bc01951cb3..fb08f33644b 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java @@ -748,6 +748,7 @@ protected void handleLocalMessage(Message msg) { private void handle(GetBlockDevicesOnHostMsg msg) { GetBlockDevicesOnHostReply reply = new GetBlockDevicesOnHostReply(); KVMAgentCommands.GetBlockDevicesCmd cmd = new KVMAgentCommands.GetBlockDevicesCmd(); + cmd.setIncludeInUse(msg.isIncludeInUse()); new Http<>(getBlockDevicesPath, cmd, KVMAgentCommands.GetBlockDevicesRsp.class) .call(new ReturnValueCompletion(msg) { @Override diff --git a/sdk/src/main/java/SourceClassMap.java b/sdk/src/main/java/SourceClassMap.java index 055af74e30a..62a29adbf6d 100644 --- a/sdk/src/main/java/SourceClassMap.java +++ b/sdk/src/main/java/SourceClassMap.java @@ -326,12 +326,6 @@ public class SourceClassMap { put("org.zstack.header.image.ImageGroupInventory", "org.zstack.sdk.ImageGroupInventory"); put("org.zstack.header.image.ImageGroupRefInventory", "org.zstack.sdk.ImageGroupRefInventory"); put("org.zstack.header.image.ImageInventory", "org.zstack.sdk.ImageInventory"); - put("org.zstack.header.localVolumeCache.VmLocalVolumeCacheInventory", "org.zstack.sdk.VmLocalVolumeCacheInventory"); - put("org.zstack.header.localVolumeCache.VmLocalVolumeCacheMode", "org.zstack.sdk.VmLocalVolumeCacheMode"); - put("org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolInventory", "org.zstack.sdk.VmLocalVolumeCachePoolInventory"); - put("org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolState", "org.zstack.sdk.VmLocalVolumeCachePoolState"); - put("org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolStatus", "org.zstack.sdk.VmLocalVolumeCachePoolStatus"); - put("org.zstack.header.localVolumeCache.VmLocalVolumeCacheState", "org.zstack.sdk.VmLocalVolumeCacheState"); put("org.zstack.header.longjob.LongJobInventory", "org.zstack.sdk.LongJobInventory"); put("org.zstack.header.longjob.LongJobState", "org.zstack.sdk.LongJobState"); put("org.zstack.header.managementnode.ManagementNodeInventory", "org.zstack.sdk.ManagementNodeInventory"); @@ -451,6 +445,13 @@ public class SourceClassMap { put("org.zstack.header.volume.block.BlockVolumeInventory", "org.zstack.sdk.BlockVolumeInventory"); put("org.zstack.header.volume.block.ExponBlockVolumeInventory", "org.zstack.sdk.ExponBlockVolumeInventory"); put("org.zstack.header.volume.block.XskyBlockVolumeInventory", "org.zstack.sdk.XskyBlockVolumeInventory"); + put("org.zstack.header.volumeCache.HostCacheStoreDeviceInventory", "org.zstack.sdk.HostCacheStoreDeviceInventory"); + put("org.zstack.header.volumeCache.HostCacheStoreInventory", "org.zstack.sdk.HostCacheStoreInventory"); + put("org.zstack.header.volumeCache.HostCacheStoreState", "org.zstack.sdk.HostCacheStoreState"); + put("org.zstack.header.volumeCache.HostCacheStoreStatus", "org.zstack.sdk.HostCacheStoreStatus"); + put("org.zstack.header.volumeCache.VolumeCacheInventory", "org.zstack.sdk.VolumeCacheInventory"); + put("org.zstack.header.volumeCache.VolumeCacheMode", "org.zstack.sdk.VolumeCacheMode"); + put("org.zstack.header.volumeCache.VolumeCacheStatus", "org.zstack.sdk.VolumeCacheStatus"); put("org.zstack.header.vpc.VpcRouterDnsInventory", "org.zstack.sdk.VpcRouterDnsInventory"); put("org.zstack.header.vpc.VpcRouterVmInventory", "org.zstack.sdk.VpcRouterVmInventory"); put("org.zstack.header.vpc.VpcSnatStateInventory", "org.zstack.sdk.VpcSnatStateInventory"); @@ -1156,6 +1157,10 @@ public class SourceClassMap { put("org.zstack.sdk.HardwareL2VxlanNetworkPoolInventory", "org.zstack.sdnController.header.HardwareL2VxlanNetworkPoolInventory"); put("org.zstack.sdk.HbaDeviceInventory", "org.zstack.storage.device.hba.HbaDeviceInventory"); put("org.zstack.sdk.HostBlockDeviceStruct", "org.zstack.header.host.HostBlockDeviceStruct"); + put("org.zstack.sdk.HostCacheStoreDeviceInventory", "org.zstack.header.volumeCache.HostCacheStoreDeviceInventory"); + put("org.zstack.sdk.HostCacheStoreInventory", "org.zstack.header.volumeCache.HostCacheStoreInventory"); + put("org.zstack.sdk.HostCacheStoreState", "org.zstack.header.volumeCache.HostCacheStoreState"); + put("org.zstack.sdk.HostCacheStoreStatus", "org.zstack.header.volumeCache.HostCacheStoreStatus"); put("org.zstack.sdk.HostConnectedStatus", "org.zstack.zops.HostConnectedStatus"); put("org.zstack.sdk.HostDiskCapacity", "org.zstack.storage.primary.local.APIGetLocalStorageHostDiskCapacityReply$HostDiskCapacity"); put("org.zstack.sdk.HostInventory", "org.zstack.header.host.HostInventory"); @@ -1599,12 +1604,6 @@ public class SourceClassMap { put("org.zstack.sdk.VmInstanceInventory", "org.zstack.header.vm.VmInstanceInventory"); put("org.zstack.sdk.VmInstanceMdevDeviceSpecRefInventory", "org.zstack.pciDevice.specification.mdev.VmInstanceMdevDeviceSpecRefInventory"); put("org.zstack.sdk.VmInstancePciDeviceSpecRefInventory", "org.zstack.pciDevice.specification.pci.VmInstancePciDeviceSpecRefInventory"); - put("org.zstack.sdk.VmLocalVolumeCacheInventory", "org.zstack.header.localVolumeCache.VmLocalVolumeCacheInventory"); - put("org.zstack.sdk.VmLocalVolumeCacheMode", "org.zstack.header.localVolumeCache.VmLocalVolumeCacheMode"); - put("org.zstack.sdk.VmLocalVolumeCachePoolInventory", "org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolInventory"); - put("org.zstack.sdk.VmLocalVolumeCachePoolState", "org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolState"); - put("org.zstack.sdk.VmLocalVolumeCachePoolStatus", "org.zstack.header.localVolumeCache.VmLocalVolumeCachePoolStatus"); - put("org.zstack.sdk.VmLocalVolumeCacheState", "org.zstack.header.localVolumeCache.VmLocalVolumeCacheState"); put("org.zstack.sdk.VmMemoryBillingInventory", "org.zstack.billing.generator.vm.memory.VmMemoryBillingInventory"); put("org.zstack.sdk.VmMemorySpendingDetails", "org.zstack.billing.spendingcalculator.vm.VmMemorySpendingDetails"); put("org.zstack.sdk.VmModelMountInventory", "org.zstack.ai.entity.VmModelMountInventory"); @@ -1626,6 +1625,9 @@ public class SourceClassMap { put("org.zstack.sdk.VniRangeInventory", "org.zstack.network.l2.vxlan.vxlanNetworkPool.VniRangeInventory"); put("org.zstack.sdk.VolumeBackupInventory", "org.zstack.header.storage.backup.VolumeBackupInventory"); put("org.zstack.sdk.VolumeBackupStorageRefInventory", "org.zstack.header.storage.backup.VolumeBackupStorageRefInventory"); + put("org.zstack.sdk.VolumeCacheInventory", "org.zstack.header.volumeCache.VolumeCacheInventory"); + put("org.zstack.sdk.VolumeCacheMode", "org.zstack.header.volumeCache.VolumeCacheMode"); + put("org.zstack.sdk.VolumeCacheStatus", "org.zstack.header.volumeCache.VolumeCacheStatus"); put("org.zstack.sdk.VolumeCbtBackupInfo", "org.zstack.header.cbt.VolumeCbtBackupInfo"); put("org.zstack.sdk.VolumeExternalBackupInfo", "org.zstack.externalbackup.VolumeExternalBackupInfo"); put("org.zstack.sdk.VolumeFormatReplyStruct", "org.zstack.header.volume.APIGetVolumeFormatReply$VolumeFormatReplyStruct"); diff --git a/sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateAction.java b/sdk/src/main/java/org/zstack/sdk/ChangeHostCacheStoreStateAction.java similarity index 85% rename from sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateAction.java rename to sdk/src/main/java/org/zstack/sdk/ChangeHostCacheStoreStateAction.java index d664d85a80a..ce12b2b9e90 100644 --- a/sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateAction.java +++ b/sdk/src/main/java/org/zstack/sdk/ChangeHostCacheStoreStateAction.java @@ -4,7 +4,7 @@ import java.util.Map; import org.zstack.sdk.*; -public class ChangeVmLocalVolumeCachePoolStateAction extends AbstractAction { +public class ChangeHostCacheStoreStateAction extends AbstractAction { private static final HashMap parameterMap = new HashMap<>(); @@ -12,7 +12,7 @@ public class ChangeVmLocalVolumeCachePoolStateAction extends AbstractAction { public static class Result { public ErrorCode error; - public org.zstack.sdk.ChangeVmLocalVolumeCachePoolStateResult value; + public org.zstack.sdk.ChangeHostCacheStoreStateResult value; public Result throwExceptionIfError() { if (error != null) { @@ -66,8 +66,8 @@ private Result makeResult(ApiResult res) { return ret; } - org.zstack.sdk.ChangeVmLocalVolumeCachePoolStateResult value = res.getResult(org.zstack.sdk.ChangeVmLocalVolumeCachePoolStateResult.class); - ret.value = value == null ? new org.zstack.sdk.ChangeVmLocalVolumeCachePoolStateResult() : value; + org.zstack.sdk.ChangeHostCacheStoreStateResult value = res.getResult(org.zstack.sdk.ChangeHostCacheStoreStateResult.class); + ret.value = value == null ? new org.zstack.sdk.ChangeHostCacheStoreStateResult() : value; return ret; } @@ -100,7 +100,7 @@ protected RestInfo getRestInfo() { info.path = "/hosts/{hostUuid}/local-volume-cache-pools/{uuid}/actions"; info.needSession = true; info.needPoll = true; - info.parameterName = "changeVmLocalVolumeCachePoolState"; + info.parameterName = "changeHostCacheStoreState"; return info; } diff --git a/sdk/src/main/java/org/zstack/sdk/ChangeHostCacheStoreStateResult.java b/sdk/src/main/java/org/zstack/sdk/ChangeHostCacheStoreStateResult.java new file mode 100644 index 00000000000..b155e084632 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/ChangeHostCacheStoreStateResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + +import org.zstack.sdk.HostCacheStoreInventory; + +public class ChangeHostCacheStoreStateResult { + public HostCacheStoreInventory inventory; + public void setInventory(HostCacheStoreInventory inventory) { + this.inventory = inventory; + } + public HostCacheStoreInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateResult.java b/sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateResult.java deleted file mode 100644 index d4f1c9d6b10..00000000000 --- a/sdk/src/main/java/org/zstack/sdk/ChangeVmLocalVolumeCachePoolStateResult.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.zstack.sdk; - -import org.zstack.sdk.VmLocalVolumeCachePoolInventory; - -public class ChangeVmLocalVolumeCachePoolStateResult { - public VmLocalVolumeCachePoolInventory inventory; - public void setInventory(VmLocalVolumeCachePoolInventory inventory) { - this.inventory = inventory; - } - public VmLocalVolumeCachePoolInventory getInventory() { - return this.inventory; - } - -} diff --git a/sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolAction.java b/sdk/src/main/java/org/zstack/sdk/CreateHostCacheStoreAction.java similarity index 88% rename from sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolAction.java rename to sdk/src/main/java/org/zstack/sdk/CreateHostCacheStoreAction.java index 3c3283758a5..27fbc8ae9c0 100644 --- a/sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolAction.java +++ b/sdk/src/main/java/org/zstack/sdk/CreateHostCacheStoreAction.java @@ -4,7 +4,7 @@ import java.util.Map; import org.zstack.sdk.*; -public class CreateVmLocalVolumeCachePoolAction extends AbstractAction { +public class CreateHostCacheStoreAction extends AbstractAction { private static final HashMap parameterMap = new HashMap<>(); @@ -12,7 +12,7 @@ public class CreateVmLocalVolumeCachePoolAction extends AbstractAction { public static class Result { public ErrorCode error; - public org.zstack.sdk.CreateVmLocalVolumeCachePoolResult value; + public org.zstack.sdk.CreateHostCacheStoreResult value; public Result throwExceptionIfError() { if (error != null) { @@ -35,7 +35,7 @@ public Result throwExceptionIfError() { public java.lang.String description; @Param(required = true, nonempty = true, nullElements = false, emptyString = true, noTrim = false) - public java.util.List pvs; + public java.util.List devices; @Param(required = true, maxLength = 1024, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.lang.String mountPoint; @@ -78,8 +78,8 @@ private Result makeResult(ApiResult res) { return ret; } - org.zstack.sdk.CreateVmLocalVolumeCachePoolResult value = res.getResult(org.zstack.sdk.CreateVmLocalVolumeCachePoolResult.class); - ret.value = value == null ? new org.zstack.sdk.CreateVmLocalVolumeCachePoolResult() : value; + org.zstack.sdk.CreateHostCacheStoreResult value = res.getResult(org.zstack.sdk.CreateHostCacheStoreResult.class); + ret.value = value == null ? new org.zstack.sdk.CreateHostCacheStoreResult() : value; return ret; } diff --git a/sdk/src/main/java/org/zstack/sdk/CreateHostCacheStoreResult.java b/sdk/src/main/java/org/zstack/sdk/CreateHostCacheStoreResult.java new file mode 100644 index 00000000000..321be0507db --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/CreateHostCacheStoreResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + +import org.zstack.sdk.HostCacheStoreInventory; + +public class CreateHostCacheStoreResult { + public HostCacheStoreInventory inventory; + public void setInventory(HostCacheStoreInventory inventory) { + this.inventory = inventory; + } + public HostCacheStoreInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolResult.java b/sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolResult.java deleted file mode 100644 index f39fed20bae..00000000000 --- a/sdk/src/main/java/org/zstack/sdk/CreateVmLocalVolumeCachePoolResult.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.zstack.sdk; - -import org.zstack.sdk.VmLocalVolumeCachePoolInventory; - -public class CreateVmLocalVolumeCachePoolResult { - public VmLocalVolumeCachePoolInventory inventory; - public void setInventory(VmLocalVolumeCachePoolInventory inventory) { - this.inventory = inventory; - } - public VmLocalVolumeCachePoolInventory getInventory() { - return this.inventory; - } - -} diff --git a/sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolAction.java b/sdk/src/main/java/org/zstack/sdk/DeleteHostCacheStoreAction.java similarity index 87% rename from sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolAction.java rename to sdk/src/main/java/org/zstack/sdk/DeleteHostCacheStoreAction.java index 31d24e83c4c..66d5a7b734a 100644 --- a/sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolAction.java +++ b/sdk/src/main/java/org/zstack/sdk/DeleteHostCacheStoreAction.java @@ -4,7 +4,7 @@ import java.util.Map; import org.zstack.sdk.*; -public class DeleteVmLocalVolumeCachePoolAction extends AbstractAction { +public class DeleteHostCacheStoreAction extends AbstractAction { private static final HashMap parameterMap = new HashMap<>(); @@ -12,7 +12,7 @@ public class DeleteVmLocalVolumeCachePoolAction extends AbstractAction { public static class Result { public ErrorCode error; - public org.zstack.sdk.DeleteVmLocalVolumeCachePoolResult value; + public org.zstack.sdk.DeleteHostCacheStoreResult value; public Result throwExceptionIfError() { if (error != null) { @@ -63,8 +63,8 @@ private Result makeResult(ApiResult res) { return ret; } - org.zstack.sdk.DeleteVmLocalVolumeCachePoolResult value = res.getResult(org.zstack.sdk.DeleteVmLocalVolumeCachePoolResult.class); - ret.value = value == null ? new org.zstack.sdk.DeleteVmLocalVolumeCachePoolResult() : value; + org.zstack.sdk.DeleteHostCacheStoreResult value = res.getResult(org.zstack.sdk.DeleteHostCacheStoreResult.class); + ret.value = value == null ? new org.zstack.sdk.DeleteHostCacheStoreResult() : value; return ret; } diff --git a/sdk/src/main/java/org/zstack/sdk/DeleteHostCacheStoreResult.java b/sdk/src/main/java/org/zstack/sdk/DeleteHostCacheStoreResult.java new file mode 100644 index 00000000000..016ef9e3dda --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/DeleteHostCacheStoreResult.java @@ -0,0 +1,7 @@ +package org.zstack.sdk; + + + +public class DeleteHostCacheStoreResult { + +} diff --git a/sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolResult.java b/sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolResult.java deleted file mode 100644 index 5449db3bcc0..00000000000 --- a/sdk/src/main/java/org/zstack/sdk/DeleteVmLocalVolumeCachePoolResult.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.zstack.sdk; - - - -public class DeleteVmLocalVolumeCachePoolResult { - -} diff --git a/sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheResult.java b/sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheResult.java index a0e0615ae44..6e6ead77a18 100644 --- a/sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheResult.java +++ b/sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheResult.java @@ -1,13 +1,13 @@ package org.zstack.sdk; -import org.zstack.sdk.VmLocalVolumeCacheInventory; +import org.zstack.sdk.VolumeCacheInventory; public class DisableVolumeCacheResult { - public VmLocalVolumeCacheInventory inventory; - public void setInventory(VmLocalVolumeCacheInventory inventory) { + public VolumeCacheInventory inventory; + public void setInventory(VolumeCacheInventory inventory) { this.inventory = inventory; } - public VmLocalVolumeCacheInventory getInventory() { + public VolumeCacheInventory getInventory() { return this.inventory; } diff --git a/sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheResult.java b/sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheResult.java index 855e7901cb2..c3737ddc737 100644 --- a/sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheResult.java +++ b/sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheResult.java @@ -1,13 +1,13 @@ package org.zstack.sdk; -import org.zstack.sdk.VmLocalVolumeCacheInventory; +import org.zstack.sdk.VolumeCacheInventory; public class EnableVolumeCacheResult { - public VmLocalVolumeCacheInventory inventory; - public void setInventory(VmLocalVolumeCacheInventory inventory) { + public VolumeCacheInventory inventory; + public void setInventory(VolumeCacheInventory inventory) { this.inventory = inventory; } - public VmLocalVolumeCacheInventory getInventory() { + public VolumeCacheInventory getInventory() { return this.inventory; } diff --git a/sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolAction.java b/sdk/src/main/java/org/zstack/sdk/ExtendHostCacheStoreAction.java similarity index 84% rename from sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolAction.java rename to sdk/src/main/java/org/zstack/sdk/ExtendHostCacheStoreAction.java index 4dbcb0b5a22..d3ebaa6b463 100644 --- a/sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolAction.java +++ b/sdk/src/main/java/org/zstack/sdk/ExtendHostCacheStoreAction.java @@ -4,7 +4,7 @@ import java.util.Map; import org.zstack.sdk.*; -public class ExtendVmLocalVolumeCachePoolAction extends AbstractAction { +public class ExtendHostCacheStoreAction extends AbstractAction { private static final HashMap parameterMap = new HashMap<>(); @@ -12,7 +12,7 @@ public class ExtendVmLocalVolumeCachePoolAction extends AbstractAction { public static class Result { public ErrorCode error; - public org.zstack.sdk.ExtendVmLocalVolumeCachePoolResult value; + public org.zstack.sdk.ExtendHostCacheStoreResult value; public Result throwExceptionIfError() { if (error != null) { @@ -32,7 +32,7 @@ public Result throwExceptionIfError() { public java.lang.String hostUuid; @Param(required = true, nonempty = true, nullElements = false, emptyString = true, noTrim = false) - public java.util.List pvs; + public java.util.List devices; @Param(required = false) public java.util.List systemTags; @@ -66,8 +66,8 @@ private Result makeResult(ApiResult res) { return ret; } - org.zstack.sdk.ExtendVmLocalVolumeCachePoolResult value = res.getResult(org.zstack.sdk.ExtendVmLocalVolumeCachePoolResult.class); - ret.value = value == null ? new org.zstack.sdk.ExtendVmLocalVolumeCachePoolResult() : value; + org.zstack.sdk.ExtendHostCacheStoreResult value = res.getResult(org.zstack.sdk.ExtendHostCacheStoreResult.class); + ret.value = value == null ? new org.zstack.sdk.ExtendHostCacheStoreResult() : value; return ret; } @@ -100,7 +100,7 @@ protected RestInfo getRestInfo() { info.path = "/hosts/{hostUuid}/local-volume-cache-pools/{uuid}/actions"; info.needSession = true; info.needPoll = true; - info.parameterName = "extendVmLocalVolumeCachePool"; + info.parameterName = "extendHostCacheStore"; return info; } diff --git a/sdk/src/main/java/org/zstack/sdk/ExtendHostCacheStoreResult.java b/sdk/src/main/java/org/zstack/sdk/ExtendHostCacheStoreResult.java new file mode 100644 index 00000000000..41b4eed7acd --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/ExtendHostCacheStoreResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + +import org.zstack.sdk.HostCacheStoreInventory; + +public class ExtendHostCacheStoreResult { + public HostCacheStoreInventory inventory; + public void setInventory(HostCacheStoreInventory inventory) { + this.inventory = inventory; + } + public HostCacheStoreInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolResult.java b/sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolResult.java deleted file mode 100644 index 6812129c6d7..00000000000 --- a/sdk/src/main/java/org/zstack/sdk/ExtendVmLocalVolumeCachePoolResult.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.zstack.sdk; - -import org.zstack.sdk.VmLocalVolumeCachePoolInventory; - -public class ExtendVmLocalVolumeCachePoolResult { - public VmLocalVolumeCachePoolInventory inventory; - public void setInventory(VmLocalVolumeCachePoolInventory inventory) { - this.inventory = inventory; - } - public VmLocalVolumeCachePoolInventory getInventory() { - return this.inventory; - } - -} diff --git a/sdk/src/main/java/org/zstack/sdk/GetBlockDevicesAction.java b/sdk/src/main/java/org/zstack/sdk/GetBlockDevicesAction.java index 04a46543803..952350d4e75 100644 --- a/sdk/src/main/java/org/zstack/sdk/GetBlockDevicesAction.java +++ b/sdk/src/main/java/org/zstack/sdk/GetBlockDevicesAction.java @@ -28,6 +28,9 @@ public Result throwExceptionIfError() { @Param(required = true, nonempty = true, nullElements = false, emptyString = true, noTrim = false) public java.lang.String uuid; + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public boolean includeInUse = false; + @Param(required = false) public java.util.List systemTags; diff --git a/sdk/src/main/java/org/zstack/sdk/HostCacheStoreDeviceInventory.java b/sdk/src/main/java/org/zstack/sdk/HostCacheStoreDeviceInventory.java new file mode 100644 index 00000000000..271eb872dda --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/HostCacheStoreDeviceInventory.java @@ -0,0 +1,23 @@ +package org.zstack.sdk; + + + +public class HostCacheStoreDeviceInventory { + + public java.lang.String path; + public void setPath(java.lang.String path) { + this.path = path; + } + public java.lang.String getPath() { + return this.path; + } + + public java.lang.String wwid; + public void setWwid(java.lang.String wwid) { + this.wwid = wwid; + } + public java.lang.String getWwid() { + return this.wwid; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolInventory.java b/sdk/src/main/java/org/zstack/sdk/HostCacheStoreInventory.java similarity index 75% rename from sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolInventory.java rename to sdk/src/main/java/org/zstack/sdk/HostCacheStoreInventory.java index 9f2bdc8599c..db8bc096610 100644 --- a/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolInventory.java +++ b/sdk/src/main/java/org/zstack/sdk/HostCacheStoreInventory.java @@ -1,9 +1,9 @@ package org.zstack.sdk; -import org.zstack.sdk.VmLocalVolumeCachePoolState; -import org.zstack.sdk.VmLocalVolumeCachePoolStatus; +import org.zstack.sdk.HostCacheStoreState; +import org.zstack.sdk.HostCacheStoreStatus; -public class VmLocalVolumeCachePoolInventory { +public class HostCacheStoreInventory { public java.lang.String uuid; public void setUuid(java.lang.String uuid) { @@ -37,12 +37,12 @@ public java.lang.String getDescription() { return this.description; } - public java.util.LinkedHashMap metadata; - public void setMetadata(java.util.LinkedHashMap metadata) { - this.metadata = metadata; + public java.lang.String mountPoint; + public void setMountPoint(java.lang.String mountPoint) { + this.mountPoint = mountPoint; } - public java.util.LinkedHashMap getMetadata() { - return this.metadata; + public java.lang.String getMountPoint() { + return this.mountPoint; } public long totalCapacity; @@ -77,22 +77,30 @@ public long getDirty() { return this.dirty; } - public VmLocalVolumeCachePoolState state; - public void setState(VmLocalVolumeCachePoolState state) { + public HostCacheStoreState state; + public void setState(HostCacheStoreState state) { this.state = state; } - public VmLocalVolumeCachePoolState getState() { + public HostCacheStoreState getState() { return this.state; } - public VmLocalVolumeCachePoolStatus status; - public void setStatus(VmLocalVolumeCachePoolStatus status) { + public HostCacheStoreStatus status; + public void setStatus(HostCacheStoreStatus status) { this.status = status; } - public VmLocalVolumeCachePoolStatus getStatus() { + public HostCacheStoreStatus getStatus() { return this.status; } + public java.util.List devices; + public void setDevices(java.util.List devices) { + this.devices = devices; + } + public java.util.List getDevices() { + return this.devices; + } + public java.util.Set caches; public void setCaches(java.util.Set caches) { this.caches = caches; diff --git a/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolState.java b/sdk/src/main/java/org/zstack/sdk/HostCacheStoreState.java similarity index 53% rename from sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolState.java rename to sdk/src/main/java/org/zstack/sdk/HostCacheStoreState.java index 0d21999167e..a43d5ed99b9 100644 --- a/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolState.java +++ b/sdk/src/main/java/org/zstack/sdk/HostCacheStoreState.java @@ -1,6 +1,6 @@ package org.zstack.sdk; -public enum VmLocalVolumeCachePoolState { +public enum HostCacheStoreState { Enabled, Disabled, } diff --git a/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolStatus.java b/sdk/src/main/java/org/zstack/sdk/HostCacheStoreStatus.java similarity index 60% rename from sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolStatus.java rename to sdk/src/main/java/org/zstack/sdk/HostCacheStoreStatus.java index e0b5b46e695..d3e8f562c39 100644 --- a/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCachePoolStatus.java +++ b/sdk/src/main/java/org/zstack/sdk/HostCacheStoreStatus.java @@ -1,6 +1,6 @@ package org.zstack.sdk; -public enum VmLocalVolumeCachePoolStatus { +public enum HostCacheStoreStatus { Connecting, Connected, Disconnected, diff --git a/sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolAction.java b/sdk/src/main/java/org/zstack/sdk/QueryHostCacheStoreAction.java similarity index 82% rename from sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolAction.java rename to sdk/src/main/java/org/zstack/sdk/QueryHostCacheStoreAction.java index 0973b4128f2..aa1d45241bd 100644 --- a/sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolAction.java +++ b/sdk/src/main/java/org/zstack/sdk/QueryHostCacheStoreAction.java @@ -4,7 +4,7 @@ import java.util.Map; import org.zstack.sdk.*; -public class QueryVmLocalVolumeCachePoolAction extends QueryAction { +public class QueryHostCacheStoreAction extends QueryAction { private static final HashMap parameterMap = new HashMap<>(); @@ -12,7 +12,7 @@ public class QueryVmLocalVolumeCachePoolAction extends QueryAction { public static class Result { public ErrorCode error; - public org.zstack.sdk.QueryVmLocalVolumeCachePoolResult value; + public org.zstack.sdk.QueryHostCacheStoreResult value; public Result throwExceptionIfError() { if (error != null) { @@ -34,8 +34,8 @@ private Result makeResult(ApiResult res) { return ret; } - org.zstack.sdk.QueryVmLocalVolumeCachePoolResult value = res.getResult(org.zstack.sdk.QueryVmLocalVolumeCachePoolResult.class); - ret.value = value == null ? new org.zstack.sdk.QueryVmLocalVolumeCachePoolResult() : value; + org.zstack.sdk.QueryHostCacheStoreResult value = res.getResult(org.zstack.sdk.QueryHostCacheStoreResult.class); + ret.value = value == null ? new org.zstack.sdk.QueryHostCacheStoreResult() : value; return ret; } diff --git a/sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolResult.java b/sdk/src/main/java/org/zstack/sdk/QueryHostCacheStoreResult.java similarity index 90% rename from sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolResult.java rename to sdk/src/main/java/org/zstack/sdk/QueryHostCacheStoreResult.java index 1bbeb747635..84ecb503c52 100644 --- a/sdk/src/main/java/org/zstack/sdk/QueryVmLocalVolumeCachePoolResult.java +++ b/sdk/src/main/java/org/zstack/sdk/QueryHostCacheStoreResult.java @@ -2,7 +2,7 @@ -public class QueryVmLocalVolumeCachePoolResult { +public class QueryHostCacheStoreResult { public java.util.List inventories; public void setInventories(java.util.List inventories) { this.inventories = inventories; diff --git a/sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolAction.java b/sdk/src/main/java/org/zstack/sdk/ReconnectHostCacheStoreAction.java similarity index 86% rename from sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolAction.java rename to sdk/src/main/java/org/zstack/sdk/ReconnectHostCacheStoreAction.java index 8e559b77031..96f421a28e4 100644 --- a/sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolAction.java +++ b/sdk/src/main/java/org/zstack/sdk/ReconnectHostCacheStoreAction.java @@ -4,7 +4,7 @@ import java.util.Map; import org.zstack.sdk.*; -public class ReconnectVmLocalVolumeCachePoolAction extends AbstractAction { +public class ReconnectHostCacheStoreAction extends AbstractAction { private static final HashMap parameterMap = new HashMap<>(); @@ -12,7 +12,7 @@ public class ReconnectVmLocalVolumeCachePoolAction extends AbstractAction { public static class Result { public ErrorCode error; - public org.zstack.sdk.ReconnectVmLocalVolumeCachePoolResult value; + public org.zstack.sdk.ReconnectHostCacheStoreResult value; public Result throwExceptionIfError() { if (error != null) { @@ -63,8 +63,8 @@ private Result makeResult(ApiResult res) { return ret; } - org.zstack.sdk.ReconnectVmLocalVolumeCachePoolResult value = res.getResult(org.zstack.sdk.ReconnectVmLocalVolumeCachePoolResult.class); - ret.value = value == null ? new org.zstack.sdk.ReconnectVmLocalVolumeCachePoolResult() : value; + org.zstack.sdk.ReconnectHostCacheStoreResult value = res.getResult(org.zstack.sdk.ReconnectHostCacheStoreResult.class); + ret.value = value == null ? new org.zstack.sdk.ReconnectHostCacheStoreResult() : value; return ret; } @@ -97,7 +97,7 @@ protected RestInfo getRestInfo() { info.path = "/hosts/{hostUuid}/local-volume-cache-pools/{uuid}/actions"; info.needSession = true; info.needPoll = true; - info.parameterName = "reconnectVmLocalVolumeCachePool"; + info.parameterName = "reconnectHostCacheStore"; return info; } diff --git a/sdk/src/main/java/org/zstack/sdk/ReconnectHostCacheStoreResult.java b/sdk/src/main/java/org/zstack/sdk/ReconnectHostCacheStoreResult.java new file mode 100644 index 00000000000..763b69056f6 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/ReconnectHostCacheStoreResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + +import org.zstack.sdk.HostCacheStoreInventory; + +public class ReconnectHostCacheStoreResult { + public HostCacheStoreInventory inventory; + public void setInventory(HostCacheStoreInventory inventory) { + this.inventory = inventory; + } + public HostCacheStoreInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolResult.java b/sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolResult.java deleted file mode 100644 index 4bf7ed7fb4c..00000000000 --- a/sdk/src/main/java/org/zstack/sdk/ReconnectVmLocalVolumeCachePoolResult.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.zstack.sdk; - -import org.zstack.sdk.VmLocalVolumeCachePoolInventory; - -public class ReconnectVmLocalVolumeCachePoolResult { - public VmLocalVolumeCachePoolInventory inventory; - public void setInventory(VmLocalVolumeCachePoolInventory inventory) { - this.inventory = inventory; - } - public VmLocalVolumeCachePoolInventory getInventory() { - return this.inventory; - } - -} diff --git a/sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityAction.java b/sdk/src/main/java/org/zstack/sdk/SyncHostCacheStoreCapacityAction.java similarity index 84% rename from sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityAction.java rename to sdk/src/main/java/org/zstack/sdk/SyncHostCacheStoreCapacityAction.java index f040ecef8fb..9bed2d835d8 100644 --- a/sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityAction.java +++ b/sdk/src/main/java/org/zstack/sdk/SyncHostCacheStoreCapacityAction.java @@ -4,7 +4,7 @@ import java.util.Map; import org.zstack.sdk.*; -public class SyncVmLocalVolumeCachePoolCapacityAction extends AbstractAction { +public class SyncHostCacheStoreCapacityAction extends AbstractAction { private static final HashMap parameterMap = new HashMap<>(); @@ -12,7 +12,7 @@ public class SyncVmLocalVolumeCachePoolCapacityAction extends AbstractAction { public static class Result { public ErrorCode error; - public org.zstack.sdk.SyncVmLocalVolumeCachePoolCapacityResult value; + public org.zstack.sdk.SyncHostCacheStoreCapacityResult value; public Result throwExceptionIfError() { if (error != null) { @@ -63,8 +63,8 @@ private Result makeResult(ApiResult res) { return ret; } - org.zstack.sdk.SyncVmLocalVolumeCachePoolCapacityResult value = res.getResult(org.zstack.sdk.SyncVmLocalVolumeCachePoolCapacityResult.class); - ret.value = value == null ? new org.zstack.sdk.SyncVmLocalVolumeCachePoolCapacityResult() : value; + org.zstack.sdk.SyncHostCacheStoreCapacityResult value = res.getResult(org.zstack.sdk.SyncHostCacheStoreCapacityResult.class); + ret.value = value == null ? new org.zstack.sdk.SyncHostCacheStoreCapacityResult() : value; return ret; } @@ -97,7 +97,7 @@ protected RestInfo getRestInfo() { info.path = "/hosts/{hostUuid}/local-volume-cache-pools/{uuid}/actions"; info.needSession = true; info.needPoll = true; - info.parameterName = "syncVmLocalVolumeCachePoolCapacity"; + info.parameterName = "syncHostCacheStoreCapacity"; return info; } diff --git a/sdk/src/main/java/org/zstack/sdk/SyncHostCacheStoreCapacityResult.java b/sdk/src/main/java/org/zstack/sdk/SyncHostCacheStoreCapacityResult.java new file mode 100644 index 00000000000..a5f8a6b7d32 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/SyncHostCacheStoreCapacityResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + +import org.zstack.sdk.HostCacheStoreInventory; + +public class SyncHostCacheStoreCapacityResult { + public HostCacheStoreInventory inventory; + public void setInventory(HostCacheStoreInventory inventory) { + this.inventory = inventory; + } + public HostCacheStoreInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityResult.java b/sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityResult.java deleted file mode 100644 index 4a0e7560833..00000000000 --- a/sdk/src/main/java/org/zstack/sdk/SyncVmLocalVolumeCachePoolCapacityResult.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.zstack.sdk; - -import org.zstack.sdk.VmLocalVolumeCachePoolInventory; - -public class SyncVmLocalVolumeCachePoolCapacityResult { - public VmLocalVolumeCachePoolInventory inventory; - public void setInventory(VmLocalVolumeCachePoolInventory inventory) { - this.inventory = inventory; - } - public VmLocalVolumeCachePoolInventory getInventory() { - return this.inventory; - } - -} diff --git a/sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolAction.java b/sdk/src/main/java/org/zstack/sdk/UpdateHostCacheStoreAction.java similarity index 86% rename from sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolAction.java rename to sdk/src/main/java/org/zstack/sdk/UpdateHostCacheStoreAction.java index 59bfc38e480..5c33479e8b4 100644 --- a/sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolAction.java +++ b/sdk/src/main/java/org/zstack/sdk/UpdateHostCacheStoreAction.java @@ -4,7 +4,7 @@ import java.util.Map; import org.zstack.sdk.*; -public class UpdateVmLocalVolumeCachePoolAction extends AbstractAction { +public class UpdateHostCacheStoreAction extends AbstractAction { private static final HashMap parameterMap = new HashMap<>(); @@ -12,7 +12,7 @@ public class UpdateVmLocalVolumeCachePoolAction extends AbstractAction { public static class Result { public ErrorCode error; - public org.zstack.sdk.UpdateVmLocalVolumeCachePoolResult value; + public org.zstack.sdk.UpdateHostCacheStoreResult value; public Result throwExceptionIfError() { if (error != null) { @@ -69,8 +69,8 @@ private Result makeResult(ApiResult res) { return ret; } - org.zstack.sdk.UpdateVmLocalVolumeCachePoolResult value = res.getResult(org.zstack.sdk.UpdateVmLocalVolumeCachePoolResult.class); - ret.value = value == null ? new org.zstack.sdk.UpdateVmLocalVolumeCachePoolResult() : value; + org.zstack.sdk.UpdateHostCacheStoreResult value = res.getResult(org.zstack.sdk.UpdateHostCacheStoreResult.class); + ret.value = value == null ? new org.zstack.sdk.UpdateHostCacheStoreResult() : value; return ret; } @@ -103,7 +103,7 @@ protected RestInfo getRestInfo() { info.path = "/hosts/{hostUuid}/local-volume-cache-pools/{uuid}/actions"; info.needSession = true; info.needPoll = true; - info.parameterName = "updateVmLocalVolumeCachePool"; + info.parameterName = "updateHostCacheStore"; return info; } diff --git a/sdk/src/main/java/org/zstack/sdk/UpdateHostCacheStoreResult.java b/sdk/src/main/java/org/zstack/sdk/UpdateHostCacheStoreResult.java new file mode 100644 index 00000000000..afb850a2eab --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/UpdateHostCacheStoreResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk; + +import org.zstack.sdk.HostCacheStoreInventory; + +public class UpdateHostCacheStoreResult { + public HostCacheStoreInventory inventory; + public void setInventory(HostCacheStoreInventory inventory) { + this.inventory = inventory; + } + public HostCacheStoreInventory getInventory() { + return this.inventory; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolResult.java b/sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolResult.java deleted file mode 100644 index e736da0650a..00000000000 --- a/sdk/src/main/java/org/zstack/sdk/UpdateVmLocalVolumeCachePoolResult.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.zstack.sdk; - -import org.zstack.sdk.VmLocalVolumeCachePoolInventory; - -public class UpdateVmLocalVolumeCachePoolResult { - public VmLocalVolumeCachePoolInventory inventory; - public void setInventory(VmLocalVolumeCachePoolInventory inventory) { - this.inventory = inventory; - } - public VmLocalVolumeCachePoolInventory getInventory() { - return this.inventory; - } - -} diff --git a/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheState.java b/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheState.java deleted file mode 100644 index d1502f19e03..00000000000 --- a/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheState.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.zstack.sdk; - -public enum VmLocalVolumeCacheState { - Uninstantiated, - Instantiated, -} diff --git a/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheInventory.java b/sdk/src/main/java/org/zstack/sdk/VolumeCacheInventory.java similarity index 74% rename from sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheInventory.java rename to sdk/src/main/java/org/zstack/sdk/VolumeCacheInventory.java index cded043590a..2670cde43e8 100644 --- a/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheInventory.java +++ b/sdk/src/main/java/org/zstack/sdk/VolumeCacheInventory.java @@ -1,9 +1,9 @@ package org.zstack.sdk; -import org.zstack.sdk.VmLocalVolumeCacheMode; -import org.zstack.sdk.VmLocalVolumeCacheState; +import org.zstack.sdk.VolumeCacheMode; +import org.zstack.sdk.VolumeCacheStatus; -public class VmLocalVolumeCacheInventory { +public class VolumeCacheInventory { public java.lang.String uuid; public void setUuid(java.lang.String uuid) { @@ -37,20 +37,20 @@ public java.lang.String getInstallPath() { return this.installPath; } - public VmLocalVolumeCacheMode cacheMode; - public void setCacheMode(VmLocalVolumeCacheMode cacheMode) { + public VolumeCacheMode cacheMode; + public void setCacheMode(VolumeCacheMode cacheMode) { this.cacheMode = cacheMode; } - public VmLocalVolumeCacheMode getCacheMode() { + public VolumeCacheMode getCacheMode() { return this.cacheMode; } - public VmLocalVolumeCacheState state; - public void setState(VmLocalVolumeCacheState state) { - this.state = state; + public VolumeCacheStatus status; + public void setStatus(VolumeCacheStatus status) { + this.status = status; } - public VmLocalVolumeCacheState getState() { - return this.state; + public VolumeCacheStatus getStatus() { + return this.status; } public java.sql.Timestamp createDate; diff --git a/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheMode.java b/sdk/src/main/java/org/zstack/sdk/VolumeCacheMode.java similarity index 51% rename from sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheMode.java rename to sdk/src/main/java/org/zstack/sdk/VolumeCacheMode.java index d52c60dc1e5..ba697e1c692 100644 --- a/sdk/src/main/java/org/zstack/sdk/VmLocalVolumeCacheMode.java +++ b/sdk/src/main/java/org/zstack/sdk/VolumeCacheMode.java @@ -1,5 +1,5 @@ package org.zstack.sdk; -public enum VmLocalVolumeCacheMode { +public enum VolumeCacheMode { WriteBack, } diff --git a/sdk/src/main/java/org/zstack/sdk/VolumeCacheStatus.java b/sdk/src/main/java/org/zstack/sdk/VolumeCacheStatus.java new file mode 100644 index 00000000000..758fa10c600 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/VolumeCacheStatus.java @@ -0,0 +1,6 @@ +package org.zstack.sdk; + +public enum VolumeCacheStatus { + NotInstantiated, + Ready, +} diff --git a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy index 6b0082f3cc2..b199e312f97 100644 --- a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy +++ b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy @@ -5714,8 +5714,8 @@ abstract class ApiHelper { } - def changeHaNetworkGroupState(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ChangeHaNetworkGroupStateAction.class) Closure c) { - def a = new org.zstack.sdk.ChangeHaNetworkGroupStateAction() + def changeHostCacheStoreState(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ChangeHostCacheStoreStateAction.class) Closure c) { + def a = new org.zstack.sdk.ChangeHostCacheStoreStateAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -6713,33 +6713,6 @@ abstract class ApiHelper { } - def changeVmLocalVolumeCachePoolState(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ChangeVmLocalVolumeCachePoolStateAction.class) Closure c) { - def a = new org.zstack.sdk.ChangeVmLocalVolumeCachePoolStateAction() - a.sessionId = Test.currentEnvSpec?.session?.uuid - c.resolveStrategy = Closure.OWNER_FIRST - c.delegate = a - c() - - - if (System.getProperty("apipath") != null) { - if (a.apiId == null) { - a.apiId = Platform.uuid - } - - def tracker = new ApiPathTracker(a.apiId) - def out = errorOut(a.call()) - def path = tracker.getApiPath() - if (!path.isEmpty()) { - Test.apiPaths[a.class.name] = path.join(" --->\n") - } - - return out - } else { - return errorOut(a.call()) - } - } - - def changeVmNicNetwork(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ChangeVmNicNetworkAction.class) Closure c) { def a = new org.zstack.sdk.ChangeVmNicNetworkAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -9818,6 +9791,33 @@ abstract class ApiHelper { } + def createHostCacheStore(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.CreateHostCacheStoreAction.class) Closure c) { + def a = new org.zstack.sdk.CreateHostCacheStoreAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def createHostNetworkServiceType(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.CreateHostNetworkServiceTypeAction.class) Closure c) { def a = new org.zstack.sdk.CreateHostNetworkServiceTypeAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -12167,33 +12167,6 @@ abstract class ApiHelper { } - def createVmLocalVolumeCachePool(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.CreateVmLocalVolumeCachePoolAction.class) Closure c) { - def a = new org.zstack.sdk.CreateVmLocalVolumeCachePoolAction() - a.sessionId = Test.currentEnvSpec?.session?.uuid - c.resolveStrategy = Closure.OWNER_FIRST - c.delegate = a - c() - - - if (System.getProperty("apipath") != null) { - if (a.apiId == null) { - a.apiId = Platform.uuid - } - - def tracker = new ApiPathTracker(a.apiId) - def out = errorOut(a.call()) - def path = tracker.getApiPath() - if (!path.isEmpty()) { - Test.apiPaths[a.class.name] = path.join(" --->\n") - } - - return out - } else { - return errorOut(a.call()) - } - } - - def createVmNic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.CreateVmNicAction.class) Closure c) { def a = new org.zstack.sdk.CreateVmNicAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -15029,6 +15002,33 @@ abstract class ApiHelper { } + def deleteHostCacheStore(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.DeleteHostCacheStoreAction.class) Closure c) { + def a = new org.zstack.sdk.DeleteHostCacheStoreAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def deleteHostNetworkServiceType(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.DeleteHostNetworkServiceTypeAction.class) Closure c) { def a = new org.zstack.sdk.DeleteHostNetworkServiceTypeAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -17675,33 +17675,6 @@ abstract class ApiHelper { } - def deleteVmLocalVolumeCachePool(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.DeleteVmLocalVolumeCachePoolAction.class) Closure c) { - def a = new org.zstack.sdk.DeleteVmLocalVolumeCachePoolAction() - a.sessionId = Test.currentEnvSpec?.session?.uuid - c.resolveStrategy = Closure.OWNER_FIRST - c.delegate = a - c() - - - if (System.getProperty("apipath") != null) { - if (a.apiId == null) { - a.apiId = Platform.uuid - } - - def tracker = new ApiPathTracker(a.apiId) - def out = errorOut(a.call()) - def path = tracker.getApiPath() - if (!path.isEmpty()) { - Test.apiPaths[a.class.name] = path.join(" --->\n") - } - - return out - } else { - return errorOut(a.call()) - } - } - - def deleteVmNic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.DeleteVmNicAction.class) Closure c) { def a = new org.zstack.sdk.DeleteVmNicAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -20483,8 +20456,8 @@ abstract class ApiHelper { } - def extendVmLocalVolumeCachePool(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExtendVmLocalVolumeCachePoolAction.class) Closure c) { - def a = new org.zstack.sdk.ExtendVmLocalVolumeCachePoolAction() + def extendHostCacheStore(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExtendHostCacheStoreAction.class) Closure c) { + def a = new org.zstack.sdk.ExtendHostCacheStoreAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -32151,6 +32124,35 @@ abstract class ApiHelper { } + def queryHostCacheStore(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.QueryHostCacheStoreAction.class) Closure c) { + def a = new org.zstack.sdk.QueryHostCacheStoreAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + a.conditions = a.conditions.collect { it.toString() } + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def queryHostNetworkBonding(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.QueryHostNetworkBondingAction.class) Closure c) { def a = new org.zstack.sdk.QueryHostNetworkBondingAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -36675,35 +36677,6 @@ abstract class ApiHelper { } - def queryVmLocalVolumeCachePool(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.QueryVmLocalVolumeCachePoolAction.class) Closure c) { - def a = new org.zstack.sdk.QueryVmLocalVolumeCachePoolAction() - a.sessionId = Test.currentEnvSpec?.session?.uuid - c.resolveStrategy = Closure.OWNER_FIRST - c.delegate = a - c() - - a.conditions = a.conditions.collect { it.toString() } - - - if (System.getProperty("apipath") != null) { - if (a.apiId == null) { - a.apiId = Platform.uuid - } - - def tracker = new ApiPathTracker(a.apiId) - def out = errorOut(a.call()) - def path = tracker.getApiPath() - if (!path.isEmpty()) { - Test.apiPaths[a.class.name] = path.join(" --->\n") - } - - return out - } else { - return errorOut(a.call()) - } - } - - def queryVmNic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.QueryVmNicAction.class) Closure c) { def a = new org.zstack.sdk.QueryVmNicAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -37956,8 +37929,8 @@ abstract class ApiHelper { } - def reconnectIPsecConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReconnectIPsecConnectionAction.class) Closure c) { - def a = new org.zstack.sdk.ReconnectIPsecConnectionAction() + def reconnectHostCacheStore(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReconnectHostCacheStoreAction.class) Closure c) { + def a = new org.zstack.sdk.ReconnectHostCacheStoreAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -37983,8 +37956,8 @@ abstract class ApiHelper { } - def reconnectImageStoreBackupStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReconnectImageStoreBackupStorageAction.class) Closure c) { - def a = new org.zstack.sdk.ReconnectImageStoreBackupStorageAction() + def reconnectIPsecConnection(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReconnectIPsecConnectionAction.class) Closure c) { + def a = new org.zstack.sdk.ReconnectIPsecConnectionAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38010,8 +37983,8 @@ abstract class ApiHelper { } - def reconnectNfvInst(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReconnectNfvInstAction.class) Closure c) { - def a = new org.zstack.sdk.ReconnectNfvInstAction() + def reconnectImageStoreBackupStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReconnectImageStoreBackupStorageAction.class) Closure c) { + def a = new org.zstack.sdk.ReconnectImageStoreBackupStorageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38037,8 +38010,8 @@ abstract class ApiHelper { } - def reconnectPrimaryStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReconnectPrimaryStorageAction.class) Closure c) { - def a = new org.zstack.sdk.ReconnectPrimaryStorageAction() + def reconnectNfvInst(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReconnectNfvInstAction.class) Closure c) { + def a = new org.zstack.sdk.ReconnectNfvInstAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38064,8 +38037,8 @@ abstract class ApiHelper { } - def reconnectSdnController(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReconnectSdnControllerAction.class) Closure c) { - def a = new org.zstack.sdk.ReconnectSdnControllerAction() + def reconnectPrimaryStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReconnectPrimaryStorageAction.class) Closure c) { + def a = new org.zstack.sdk.ReconnectPrimaryStorageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38091,8 +38064,8 @@ abstract class ApiHelper { } - def reconnectSftpBackupStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReconnectSftpBackupStorageAction.class) Closure c) { - def a = new org.zstack.sdk.ReconnectSftpBackupStorageAction() + def reconnectSdnController(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReconnectSdnControllerAction.class) Closure c) { + def a = new org.zstack.sdk.ReconnectSdnControllerAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38118,8 +38091,8 @@ abstract class ApiHelper { } - def reconnectVirtualRouter(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReconnectVirtualRouterAction.class) Closure c) { - def a = new org.zstack.sdk.ReconnectVirtualRouterAction() + def reconnectSftpBackupStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReconnectSftpBackupStorageAction.class) Closure c) { + def a = new org.zstack.sdk.ReconnectSftpBackupStorageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -38145,8 +38118,8 @@ abstract class ApiHelper { } - def reconnectVmLocalVolumeCachePool(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReconnectVmLocalVolumeCachePoolAction.class) Closure c) { - def a = new org.zstack.sdk.ReconnectVmLocalVolumeCachePoolAction() + def reconnectVirtualRouter(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ReconnectVirtualRouterAction.class) Closure c) { + def a = new org.zstack.sdk.ReconnectVirtualRouterAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -42465,8 +42438,8 @@ abstract class ApiHelper { } - def syncHybridEipFromRemote(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncHybridEipFromRemoteAction.class) Closure c) { - def a = new org.zstack.sdk.SyncHybridEipFromRemoteAction() + def syncHostCacheStoreCapacity(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncHostCacheStoreCapacityAction.class) Closure c) { + def a = new org.zstack.sdk.SyncHostCacheStoreCapacityAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -42492,8 +42465,8 @@ abstract class ApiHelper { } - def syncIdentityFromRemote(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncIdentityFromRemoteAction.class) Closure c) { - def a = new org.zstack.sdk.SyncIdentityFromRemoteAction() + def syncHybridEipFromRemote(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncHybridEipFromRemoteAction.class) Closure c) { + def a = new org.zstack.sdk.SyncHybridEipFromRemoteAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -42519,8 +42492,8 @@ abstract class ApiHelper { } - def syncImage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncImageAction.class) Closure c) { - def a = new org.zstack.sdk.SyncImageAction() + def syncIdentityFromRemote(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncIdentityFromRemoteAction.class) Closure c) { + def a = new org.zstack.sdk.SyncIdentityFromRemoteAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -42546,8 +42519,8 @@ abstract class ApiHelper { } - def syncImageFromImageStoreBackupStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncImageFromImageStoreBackupStorageAction.class) Closure c) { - def a = new org.zstack.sdk.SyncImageFromImageStoreBackupStorageAction() + def syncImage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncImageAction.class) Closure c) { + def a = new org.zstack.sdk.SyncImageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -42573,8 +42546,8 @@ abstract class ApiHelper { } - def syncImageSize(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncImageSizeAction.class) Closure c) { - def a = new org.zstack.sdk.SyncImageSizeAction() + def syncImageFromImageStoreBackupStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncImageFromImageStoreBackupStorageAction.class) Closure c) { + def a = new org.zstack.sdk.SyncImageFromImageStoreBackupStorageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -42600,8 +42573,8 @@ abstract class ApiHelper { } - def syncLdapServer(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncLdapServerAction.class) Closure c) { - def a = new org.zstack.sdk.SyncLdapServerAction() + def syncImageSize(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncImageSizeAction.class) Closure c) { + def a = new org.zstack.sdk.SyncImageSizeAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -42627,8 +42600,8 @@ abstract class ApiHelper { } - def syncNfvInstGroup(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncNfvInstGroupAction.class) Closure c) { - def a = new org.zstack.sdk.SyncNfvInstGroupAction() + def syncLdapServer(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncLdapServerAction.class) Closure c) { + def a = new org.zstack.sdk.SyncLdapServerAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -42654,8 +42627,8 @@ abstract class ApiHelper { } - def syncPrimaryStorageCapacity(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncPrimaryStorageCapacityAction.class) Closure c) { - def a = new org.zstack.sdk.SyncPrimaryStorageCapacityAction() + def syncNfvInstGroup(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncNfvInstGroupAction.class) Closure c) { + def a = new org.zstack.sdk.SyncNfvInstGroupAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -42681,8 +42654,8 @@ abstract class ApiHelper { } - def syncVCenter(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncVCenterAction.class) Closure c) { - def a = new org.zstack.sdk.SyncVCenterAction() + def syncPrimaryStorageCapacity(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncPrimaryStorageCapacityAction.class) Closure c) { + def a = new org.zstack.sdk.SyncPrimaryStorageCapacityAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -42708,8 +42681,8 @@ abstract class ApiHelper { } - def syncVirtualBorderRouterFromRemote(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncVirtualBorderRouterFromRemoteAction.class) Closure c) { - def a = new org.zstack.sdk.SyncVirtualBorderRouterFromRemoteAction() + def syncVCenter(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncVCenterAction.class) Closure c) { + def a = new org.zstack.sdk.SyncVCenterAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -42735,8 +42708,8 @@ abstract class ApiHelper { } - def syncVmClock(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncVmClockAction.class) Closure c) { - def a = new org.zstack.sdk.SyncVmClockAction() + def syncVirtualBorderRouterFromRemote(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncVirtualBorderRouterFromRemoteAction.class) Closure c) { + def a = new org.zstack.sdk.SyncVirtualBorderRouterFromRemoteAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -42762,8 +42735,8 @@ abstract class ApiHelper { } - def syncVmLocalVolumeCachePoolCapacity(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncVmLocalVolumeCachePoolCapacityAction.class) Closure c) { - def a = new org.zstack.sdk.SyncVmLocalVolumeCachePoolCapacityAction() + def syncVmClock(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.SyncVmClockAction.class) Closure c) { + def a = new org.zstack.sdk.SyncVmClockAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -45705,6 +45678,33 @@ abstract class ApiHelper { } + def updateHostCacheStore(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateHostCacheStoreAction.class) Closure c) { + def a = new org.zstack.sdk.UpdateHostCacheStoreAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def updateHostIommuState(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateHostIommuStateAction.class) Closure c) { def a = new org.zstack.sdk.UpdateHostIommuStateAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -48351,33 +48351,6 @@ abstract class ApiHelper { } - def updateVmLocalVolumeCachePool(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateVmLocalVolumeCachePoolAction.class) Closure c) { - def a = new org.zstack.sdk.UpdateVmLocalVolumeCachePoolAction() - a.sessionId = Test.currentEnvSpec?.session?.uuid - c.resolveStrategy = Closure.OWNER_FIRST - c.delegate = a - c() - - - if (System.getProperty("apipath") != null) { - if (a.apiId == null) { - a.apiId = Platform.uuid - } - - def tracker = new ApiPathTracker(a.apiId) - def out = errorOut(a.call()) - def path = tracker.getApiPath() - if (!path.isEmpty()) { - Test.apiPaths[a.class.name] = path.join(" --->\n") - } - - return out - } else { - return errorOut(a.call()) - } - } - - def updateVmNetworkConfig(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateVmNetworkConfigAction.class) Closure c) { def a = new org.zstack.sdk.UpdateVmNetworkConfigAction() a.sessionId = Test.currentEnvSpec?.session?.uuid From a4b9e98f4119920b21b1462c0f82d56efb7071cd Mon Sep 17 00:00:00 2001 From: "haidong.pang" Date: Mon, 27 Apr 2026 07:46:40 +0000 Subject: [PATCH 27/54] [volume-cache]: align REST URLs with class names Follow-up of zstack!9674 / premium!13604. Class names already migrated to HostCacheStore/VolumeCache, but REST URLs still used the old local-volume-cache-pools / local-volume-cache paths. Align URLs to new resource naming and ZStack REST plural convention. This commit only touches generated SDK Action files. Matching @RestRequest annotations and Doc groovy files are updated in the paired premium MR. Resolves: ZSTAC-81715 Change-Id: I7acd945e1ab297cc2d28a04cbb933c8c9721d63f --- .../java/org/zstack/sdk/ChangeHostCacheStoreStateAction.java | 2 +- .../main/java/org/zstack/sdk/CreateHostCacheStoreAction.java | 2 +- .../main/java/org/zstack/sdk/DeleteHostCacheStoreAction.java | 2 +- sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheAction.java | 2 +- sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheAction.java | 2 +- .../main/java/org/zstack/sdk/ExtendHostCacheStoreAction.java | 2 +- sdk/src/main/java/org/zstack/sdk/QueryHostCacheStoreAction.java | 2 +- .../main/java/org/zstack/sdk/ReconnectHostCacheStoreAction.java | 2 +- .../java/org/zstack/sdk/SyncHostCacheStoreCapacityAction.java | 2 +- .../main/java/org/zstack/sdk/UpdateHostCacheStoreAction.java | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/sdk/src/main/java/org/zstack/sdk/ChangeHostCacheStoreStateAction.java b/sdk/src/main/java/org/zstack/sdk/ChangeHostCacheStoreStateAction.java index ce12b2b9e90..f966acec4fe 100644 --- a/sdk/src/main/java/org/zstack/sdk/ChangeHostCacheStoreStateAction.java +++ b/sdk/src/main/java/org/zstack/sdk/ChangeHostCacheStoreStateAction.java @@ -97,7 +97,7 @@ protected Map getNonAPIParameterMap() { protected RestInfo getRestInfo() { RestInfo info = new RestInfo(); info.httpMethod = "PUT"; - info.path = "/hosts/{hostUuid}/local-volume-cache-pools/{uuid}/actions"; + info.path = "/hosts/{hostUuid}/host-cache-stores/{uuid}/actions"; info.needSession = true; info.needPoll = true; info.parameterName = "changeHostCacheStoreState"; diff --git a/sdk/src/main/java/org/zstack/sdk/CreateHostCacheStoreAction.java b/sdk/src/main/java/org/zstack/sdk/CreateHostCacheStoreAction.java index 27fbc8ae9c0..2ed9c44cb2d 100644 --- a/sdk/src/main/java/org/zstack/sdk/CreateHostCacheStoreAction.java +++ b/sdk/src/main/java/org/zstack/sdk/CreateHostCacheStoreAction.java @@ -109,7 +109,7 @@ protected Map getNonAPIParameterMap() { protected RestInfo getRestInfo() { RestInfo info = new RestInfo(); info.httpMethod = "POST"; - info.path = "/hosts/{hostUuid}/local-volume-cache-pools"; + info.path = "/hosts/{hostUuid}/host-cache-stores"; info.needSession = true; info.needPoll = true; info.parameterName = "params"; diff --git a/sdk/src/main/java/org/zstack/sdk/DeleteHostCacheStoreAction.java b/sdk/src/main/java/org/zstack/sdk/DeleteHostCacheStoreAction.java index 66d5a7b734a..b5f3be4589f 100644 --- a/sdk/src/main/java/org/zstack/sdk/DeleteHostCacheStoreAction.java +++ b/sdk/src/main/java/org/zstack/sdk/DeleteHostCacheStoreAction.java @@ -94,7 +94,7 @@ protected Map getNonAPIParameterMap() { protected RestInfo getRestInfo() { RestInfo info = new RestInfo(); info.httpMethod = "DELETE"; - info.path = "/hosts/{hostUuid}/local-volume-cache-pools/{uuid}"; + info.path = "/hosts/{hostUuid}/host-cache-stores/{uuid}"; info.needSession = true; info.needPoll = true; info.parameterName = ""; diff --git a/sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheAction.java b/sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheAction.java index 1a748074582..449ad576a0c 100644 --- a/sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheAction.java +++ b/sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheAction.java @@ -91,7 +91,7 @@ protected Map getNonAPIParameterMap() { protected RestInfo getRestInfo() { RestInfo info = new RestInfo(); info.httpMethod = "PUT"; - info.path = "/volumes/{volumeUuid}/local-volume-cache/actions"; + info.path = "/volumes/{volumeUuid}/volume-cache/actions"; info.needSession = true; info.needPoll = true; info.parameterName = "disableVolumeCache"; diff --git a/sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheAction.java b/sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheAction.java index 19c2eb5eef4..dd30fc5dd97 100644 --- a/sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheAction.java +++ b/sdk/src/main/java/org/zstack/sdk/EnableVolumeCacheAction.java @@ -97,7 +97,7 @@ protected Map getNonAPIParameterMap() { protected RestInfo getRestInfo() { RestInfo info = new RestInfo(); info.httpMethod = "PUT"; - info.path = "/volumes/{volumeUuid}/local-volume-cache/actions"; + info.path = "/volumes/{volumeUuid}/volume-cache/actions"; info.needSession = true; info.needPoll = true; info.parameterName = "enableVolumeCache"; diff --git a/sdk/src/main/java/org/zstack/sdk/ExtendHostCacheStoreAction.java b/sdk/src/main/java/org/zstack/sdk/ExtendHostCacheStoreAction.java index d3ebaa6b463..f645f32025a 100644 --- a/sdk/src/main/java/org/zstack/sdk/ExtendHostCacheStoreAction.java +++ b/sdk/src/main/java/org/zstack/sdk/ExtendHostCacheStoreAction.java @@ -97,7 +97,7 @@ protected Map getNonAPIParameterMap() { protected RestInfo getRestInfo() { RestInfo info = new RestInfo(); info.httpMethod = "PUT"; - info.path = "/hosts/{hostUuid}/local-volume-cache-pools/{uuid}/actions"; + info.path = "/hosts/{hostUuid}/host-cache-stores/{uuid}/actions"; info.needSession = true; info.needPoll = true; info.parameterName = "extendHostCacheStore"; diff --git a/sdk/src/main/java/org/zstack/sdk/QueryHostCacheStoreAction.java b/sdk/src/main/java/org/zstack/sdk/QueryHostCacheStoreAction.java index aa1d45241bd..69cab56ddcf 100644 --- a/sdk/src/main/java/org/zstack/sdk/QueryHostCacheStoreAction.java +++ b/sdk/src/main/java/org/zstack/sdk/QueryHostCacheStoreAction.java @@ -65,7 +65,7 @@ protected Map getNonAPIParameterMap() { protected RestInfo getRestInfo() { RestInfo info = new RestInfo(); info.httpMethod = "GET"; - info.path = "/hosts/local-volume-cache-pools"; + info.path = "/hosts/host-cache-stores"; info.needSession = true; info.needPoll = false; info.parameterName = ""; diff --git a/sdk/src/main/java/org/zstack/sdk/ReconnectHostCacheStoreAction.java b/sdk/src/main/java/org/zstack/sdk/ReconnectHostCacheStoreAction.java index 96f421a28e4..d674f007a21 100644 --- a/sdk/src/main/java/org/zstack/sdk/ReconnectHostCacheStoreAction.java +++ b/sdk/src/main/java/org/zstack/sdk/ReconnectHostCacheStoreAction.java @@ -94,7 +94,7 @@ protected Map getNonAPIParameterMap() { protected RestInfo getRestInfo() { RestInfo info = new RestInfo(); info.httpMethod = "PUT"; - info.path = "/hosts/{hostUuid}/local-volume-cache-pools/{uuid}/actions"; + info.path = "/hosts/{hostUuid}/host-cache-stores/{uuid}/actions"; info.needSession = true; info.needPoll = true; info.parameterName = "reconnectHostCacheStore"; diff --git a/sdk/src/main/java/org/zstack/sdk/SyncHostCacheStoreCapacityAction.java b/sdk/src/main/java/org/zstack/sdk/SyncHostCacheStoreCapacityAction.java index 9bed2d835d8..0d95b61e852 100644 --- a/sdk/src/main/java/org/zstack/sdk/SyncHostCacheStoreCapacityAction.java +++ b/sdk/src/main/java/org/zstack/sdk/SyncHostCacheStoreCapacityAction.java @@ -94,7 +94,7 @@ protected Map getNonAPIParameterMap() { protected RestInfo getRestInfo() { RestInfo info = new RestInfo(); info.httpMethod = "PUT"; - info.path = "/hosts/{hostUuid}/local-volume-cache-pools/{uuid}/actions"; + info.path = "/hosts/{hostUuid}/host-cache-stores/{uuid}/actions"; info.needSession = true; info.needPoll = true; info.parameterName = "syncHostCacheStoreCapacity"; diff --git a/sdk/src/main/java/org/zstack/sdk/UpdateHostCacheStoreAction.java b/sdk/src/main/java/org/zstack/sdk/UpdateHostCacheStoreAction.java index 5c33479e8b4..45220f1398e 100644 --- a/sdk/src/main/java/org/zstack/sdk/UpdateHostCacheStoreAction.java +++ b/sdk/src/main/java/org/zstack/sdk/UpdateHostCacheStoreAction.java @@ -100,7 +100,7 @@ protected Map getNonAPIParameterMap() { protected RestInfo getRestInfo() { RestInfo info = new RestInfo(); info.httpMethod = "PUT"; - info.path = "/hosts/{hostUuid}/local-volume-cache-pools/{uuid}/actions"; + info.path = "/hosts/{hostUuid}/host-cache-stores/{uuid}/actions"; info.needSession = true; info.needPoll = true; info.parameterName = "updateHostCacheStore"; From d4eaeb6c7663f410894693e815b39dd7ee6272b6 Mon Sep 17 00:00:00 2001 From: "haidong.pang" Date: Thu, 14 May 2026 16:24:15 +0800 Subject: [PATCH 28/54] [volume-cache]: rename GC pool in-use cache field Rename GCPoolCmd retain list from volumes to inUseCacheUuids. Resolves: ZSTAC-85164 Change-Id: Ia72f68775819e5193375ba29f87a4efdfb45430c --- plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java index c5298f0aaf8..1ce4c611975 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java @@ -5417,7 +5417,7 @@ public static class GCPoolCmd extends AgentCommand { public String poolUuid; public String mountPoint; public boolean force; - public List volumes; + public List inUseCacheUuids; } public static class AllocateCacheCmd extends AgentCommand { From 8ec7b16aa6a8e5688e6ed796817739e2d46a6d5d Mon Sep 17 00:00:00 2001 From: "haidong.pang" Date: Thu, 11 Jun 2026 18:37:44 +0800 Subject: [PATCH 29/54] [volume-cache]: add flush progress context Resolves: ZSTAC-85224 Change-Id: Iea2e1c8fee6e1e4e91bfc29041c4385bc4d34084 --- plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java index 1ce4c611975..3d96beab4de 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java @@ -5430,9 +5430,10 @@ public static class DeleteCacheCmd extends AgentCommand { public VolumeTO volume; } - public static class FlushCacheCmd extends AgentCommand { + public static class FlushCacheCmd extends AgentCommand implements HasThreadContext { public String poolUuid; public VolumeTO volume; + public String sendCommandUrl; } public static class GetCacheCapacityCmd extends AgentCommand { @@ -5448,6 +5449,7 @@ public static class AttachVolumeCacheCmd extends AgentCommand { public static class DetachVolumeCacheCmd extends AgentCommand implements HasThreadContext { public String instanceUuid; public VolumeTO volume; + public String sendCommandUrl; } // ======================================================================== From 4c24cec9ce9b828c6d7db0b10f3c815d7d1a57e2 Mon Sep 17 00:00:00 2001 From: "haidong.pang" Date: Wed, 10 Jun 2026 00:44:05 +0800 Subject: [PATCH 30/54] [volume-cache]: expose disable dry-run capacity fields Expose virtualSize and actualSize on VolumeCacheInventory, document the fields, and regenerate the Java SDK for DisableVolumeCache.dryRun. This keeps the API and SDK surface aligned with the disable-volume-cache dry-run precheck instead of the removed GetVolumeCache query API. Fold in the 5.5.28 rebase, review fixes, generated API documentation, SDK synchronization, and CI follow-up from the previous top commit. Resolves: ZSTAC-85172 Related: ZSTAC-81715 Change-Id: I9db2260710e085de51213538f1dba31053b27af1 --- .../vm/VmInstanceExtensionPointEmitter.java | 2 + conf/db/upgrade/V5.5.18__schema.sql | 47 ---- conf/db/upgrade/V5.5.28__schema.sql | 48 ++++ conf/serviceConfig/host.xml | 8 +- .../header/allocator/AllocateHostMsg.java | 20 +- .../header/allocator/HostAllocatorSpec.java | 10 + .../header/host/APIGetBlockDevicesEvent.java | 1 + .../APIGetBlockDevicesEventDoc_zh_cn.groovy | 12 +- .../APIGetBlockDevicesMsgDoc_zh_cn.groovy | 16 +- .../HostBlockDeviceStructDoc_zh_cn.groovy | 56 +++-- .../BeforeVmAllocateHostExtensionPoint.java | 6 + .../volumeCache/HostCacheStoreInventory.java | 1 - .../HostCacheStoreInventoryDoc_zh_cn.groovy | 45 +++- .../volumeCache/HostCacheStoreState.java | 1 - .../HostCacheStoreStateDoc_zh_cn.groovy | 10 +- .../volumeCache/HostCacheStoreStatus.java | 1 - .../HostCacheStoreStatusDoc_zh_cn.groovy | 14 +- .../volumeCache/VolumeCacheInventory.java | 18 ++ .../VolumeCacheInventoryDoc_zh_cn.groovy | 29 ++- .../VolumeCacheModeDoc_zh_cn.groovy | 6 +- .../VolumeCacheStatusDoc_zh_cn.groovy | 4 +- .../java/org/zstack/kvm/KVMAgentCommands.java | 2 +- .../src/main/java/org/zstack/kvm/KVMHost.java | 4 +- .../zstack/sdk/DisableVolumeCacheAction.java | 3 + .../org/zstack/sdk/VolumeCacheInventory.java | 16 ++ .../java/org/zstack/testlib/ApiHelper.groovy | 238 ++++++++++++++---- .../CloudOperationsErrorCode.java | 26 ++ 27 files changed, 458 insertions(+), 186 deletions(-) delete mode 100644 conf/db/upgrade/V5.5.18__schema.sql diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstanceExtensionPointEmitter.java b/compute/src/main/java/org/zstack/compute/vm/VmInstanceExtensionPointEmitter.java index ae03663c930..21105efc5eb 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstanceExtensionPointEmitter.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstanceExtensionPointEmitter.java @@ -52,6 +52,7 @@ public class VmInstanceExtensionPointEmitter implements Component { private List cleanUpAfterVmChangeImageExtensionPoints; private List vmNicChangeStateExtensionPoints; private List sshKeyPairAssociateExtensionPoints; + private List afterUpdateVmNicMacExtensionPoints; private List beforeVmAllocateHostExtensions; public List handleSystemTag(String vmUuid, List tags){ @@ -636,6 +637,7 @@ private void populateExtensions() { cleanUpAfterVmChangeImageExtensionPoints = pluginRgty.getExtensionList(CleanUpAfterVmChangeImageExtensionPoint.class); vmNicChangeStateExtensionPoints = pluginRgty.getExtensionList(VmNicChangeStateExtensionPoint.class); sshKeyPairAssociateExtensionPoints = pluginRgty.getExtensionList(SshKeyPairAssociateExtensionPoint.class); + afterUpdateVmNicMacExtensionPoints = pluginRgty.getExtensionList(AfterUpdateVmNicMacExtensionPoint.class); beforeVmAllocateHostExtensions = pluginRgty.getExtensionList(BeforeVmAllocateHostExtensionPoint.class); } diff --git a/conf/db/upgrade/V5.5.18__schema.sql b/conf/db/upgrade/V5.5.18__schema.sql deleted file mode 100644 index 25ddef86a4d..00000000000 --- a/conf/db/upgrade/V5.5.18__schema.sql +++ /dev/null @@ -1,47 +0,0 @@ -CREATE TABLE IF NOT EXISTS `HostCacheStoreVO` ( - `uuid` VARCHAR(32) NOT NULL, - `hostUuid` VARCHAR(32) NOT NULL, - `name` VARCHAR(255) DEFAULT NULL, - `description` VARCHAR(2048) DEFAULT NULL, - `mountPoint` VARCHAR(255) DEFAULT NULL, - `devices` TEXT, - `state` VARCHAR(32) NOT NULL, - `status` VARCHAR(32) NOT NULL, - `createDate` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', - `lastOpDate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`uuid`), - CONSTRAINT `fkHostCacheStoreVOHostEO` - FOREIGN KEY (`hostUuid`) REFERENCES `HostEO` (`uuid`) - ON DELETE CASCADE - ) ENGINE = InnoDB DEFAULT CHARSET = utf8; - -CREATE TABLE IF NOT EXISTS `HostCacheStoreCapacityVO` ( - `uuid` VARCHAR(32) NOT NULL, - `totalCapacity` BIGINT NOT NULL DEFAULT 0, - `availableCapacity` BIGINT NOT NULL DEFAULT 0, - `allocated` BIGINT NOT NULL DEFAULT 0, - `dirty` BIGINT NOT NULL DEFAULT 0, - PRIMARY KEY (`uuid`), - CONSTRAINT `fkHostCacheStoreCapacityVOHostCacheStoreVO` - FOREIGN KEY (`uuid`) REFERENCES `HostCacheStoreVO` (`uuid`) - ON DELETE CASCADE - ) ENGINE = InnoDB DEFAULT CHARSET = utf8; - -CREATE TABLE IF NOT EXISTS `VolumeCacheVO` ( - `uuid` VARCHAR(32) NOT NULL, - `volumeUuid` VARCHAR(32) NOT NULL, - `poolUuid` VARCHAR(32) DEFAULT NULL, - `installPath` VARCHAR(2048) DEFAULT NULL, - `cacheMode` VARCHAR(32) NOT NULL, - `status` VARCHAR(32) NOT NULL, - `createDate` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', - `lastOpDate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - PRIMARY KEY (`uuid`), - UNIQUE KEY `uniVolumeCacheVOVolumeUuid` (`volumeUuid`), - CONSTRAINT `fkVolumeCacheVOVolumeEO` - FOREIGN KEY (`volumeUuid`) REFERENCES `VolumeEO` (`uuid`) - ON DELETE CASCADE, - CONSTRAINT `fkVolumeCacheVOPoolUuid` - FOREIGN KEY (`poolUuid`) REFERENCES `HostCacheStoreVO` (`uuid`) - ON DELETE SET NULL - ) ENGINE = InnoDB DEFAULT CHARSET = utf8; diff --git a/conf/db/upgrade/V5.5.28__schema.sql b/conf/db/upgrade/V5.5.28__schema.sql index 7c1ab9fe15f..6a8a9e1e1b1 100644 --- a/conf/db/upgrade/V5.5.28__schema.sql +++ b/conf/db/upgrade/V5.5.28__schema.sql @@ -98,3 +98,51 @@ CREATE TABLE IF NOT EXISTS `zstack`.`ScimEventVO` ( UNIQUE KEY `ukScimEventVOClientEvent` (`clientId`, `eventId`), KEY `idxScimEventVOResourceVersion` (`clientId`, `resourceType`, `resourceId`, `resourceVersion`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `HostCacheStoreVO` ( + `uuid` VARCHAR(32) NOT NULL, + `hostUuid` VARCHAR(32) NOT NULL, + `name` VARCHAR(255) DEFAULT NULL, + `description` VARCHAR(2048) DEFAULT NULL, + `mountPoint` VARCHAR(255) DEFAULT NULL, + `devices` TEXT, + `state` VARCHAR(32) NOT NULL, + `status` VARCHAR(32) NOT NULL, + `createDate` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', + `lastOpDate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`uuid`), + CONSTRAINT `fkHostCacheStoreVOHostEO` + FOREIGN KEY (`hostUuid`) REFERENCES `HostEO` (`uuid`) + ON DELETE CASCADE + ) ENGINE = InnoDB DEFAULT CHARSET = utf8; + +CREATE TABLE IF NOT EXISTS `HostCacheStoreCapacityVO` ( + `uuid` VARCHAR(32) NOT NULL, + `totalCapacity` BIGINT NOT NULL DEFAULT 0, + `availableCapacity` BIGINT NOT NULL DEFAULT 0, + `allocated` BIGINT NOT NULL DEFAULT 0, + `dirty` BIGINT NOT NULL DEFAULT 0, + PRIMARY KEY (`uuid`), + CONSTRAINT `fkHostCacheStoreCapacityVOHostCacheStoreVO` + FOREIGN KEY (`uuid`) REFERENCES `HostCacheStoreVO` (`uuid`) + ON DELETE CASCADE + ) ENGINE = InnoDB DEFAULT CHARSET = utf8; + +CREATE TABLE IF NOT EXISTS `VolumeCacheVO` ( + `uuid` VARCHAR(32) NOT NULL, + `volumeUuid` VARCHAR(32) NOT NULL, + `poolUuid` VARCHAR(32) DEFAULT NULL, + `installPath` VARCHAR(2048) DEFAULT NULL, + `cacheMode` VARCHAR(32) NOT NULL, + `status` VARCHAR(32) NOT NULL, + `createDate` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00', + `lastOpDate` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`uuid`), + UNIQUE KEY `uniVolumeCacheVOVolumeUuid` (`volumeUuid`), + CONSTRAINT `fkVolumeCacheVOVolumeEO` + FOREIGN KEY (`volumeUuid`) REFERENCES `VolumeEO` (`uuid`) + ON DELETE CASCADE, + CONSTRAINT `fkVolumeCacheVOPoolUuid` + FOREIGN KEY (`poolUuid`) REFERENCES `HostCacheStoreVO` (`uuid`) + ON DELETE SET NULL + ) ENGINE = InnoDB DEFAULT CHARSET = utf8; diff --git a/conf/serviceConfig/host.xml b/conf/serviceConfig/host.xml index e46b0a34e30..b91f5c79059 100755 --- a/conf/serviceConfig/host.xml +++ b/conf/serviceConfig/host.xml @@ -61,10 +61,10 @@ org.zstack.header.host.APIGetHostPowerStatusMsg - - org.zstack.header.host.APIGetBlockDevicesMsg - - + + org.zstack.header.host.APIGetBlockDevicesMsg + + org.zstack.header.host.APICreateHostNetworkServiceTypeMsg diff --git a/header/src/main/java/org/zstack/header/allocator/AllocateHostMsg.java b/header/src/main/java/org/zstack/header/allocator/AllocateHostMsg.java index bf70fbf1183..132637b8635 100755 --- a/header/src/main/java/org/zstack/header/allocator/AllocateHostMsg.java +++ b/header/src/main/java/org/zstack/header/allocator/AllocateHostMsg.java @@ -31,6 +31,13 @@ public class AllocateHostMsg extends NeedReplyMessage { private long oldMemoryCapacity = 0; private AllocationScene allocationScene; private String architecture; + private String accountUuid; + /** + * Allocation purpose. Defaults to ALLOCATE; LIST_CANDIDATES tells the + * downstream filters this is a candidate-listing call. Callers must gate + * LIST_CANDIDATES on admin permission. + */ + private HostAllocationPurpose purpose = HostAllocationPurpose.ALLOCATE; private LinkedHashMap opaque; public List> getOptionalPrimaryStorageUuids() { @@ -213,8 +220,19 @@ public void setArchitecture(String architecture) { this.architecture = architecture; } + public String getAccountUuid() { return accountUuid; } + + public void setAccountUuid(String accountUuid) { this.accountUuid = accountUuid; } + + public HostAllocationPurpose getPurpose() { + return purpose; + } + + public void setPurpose(HostAllocationPurpose purpose) { + this.purpose = purpose == null ? HostAllocationPurpose.ALLOCATE : purpose; + } + public LinkedHashMap getOpaque() { return opaque; } public void setOpaque(LinkedHashMap opaque) { this.opaque = opaque; } - } diff --git a/header/src/main/java/org/zstack/header/allocator/HostAllocatorSpec.java b/header/src/main/java/org/zstack/header/allocator/HostAllocatorSpec.java index 75ca66ea614..33d39896c82 100755 --- a/header/src/main/java/org/zstack/header/allocator/HostAllocatorSpec.java +++ b/header/src/main/java/org/zstack/header/allocator/HostAllocatorSpec.java @@ -37,6 +37,16 @@ public class HostAllocatorSpec { private long oldMemoryCapacity = 0; private AllocationScene allocationScene; private String architecture; + private String accountUuid; + /** + * Allocation purpose. Defaults to ALLOCATE so existing call sites keep + * their current behavior. Filters may relax some checks (e.g. PCI device + * owner-RBAC) when this is LIST_CANDIDATES. + * + * Permission gating is the caller's responsibility; filters trust this + * value as-is. + */ + private HostAllocationPurpose purpose = HostAllocationPurpose.ALLOCATE; private LinkedHashMap opaque; public LinkedHashMap getOpaque() { diff --git a/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEvent.java b/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEvent.java index dcb396d6796..4beda1600ea 100644 --- a/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEvent.java +++ b/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEvent.java @@ -28,6 +28,7 @@ public void setBlockDevices(List blockDevices) { public static APIGetBlockDevicesEvent __example__() { APIGetBlockDevicesEvent event = new APIGetBlockDevicesEvent(); HostBlockDeviceStruct struct = new HostBlockDeviceStruct(); + struct.setName("sda"); struct.setWwid("3600508b400105e5a0000800001490000"); struct.setVendor("VMware"); struct.setModel("Virtual disk"); diff --git a/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEventDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEventDoc_zh_cn.groovy index c0ef654855d..6394de7e2c3 100644 --- a/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEventDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesEventDoc_zh_cn.groovy @@ -5,28 +5,28 @@ import org.zstack.header.errorcode.ErrorCode doc { - title "在这里输入结构的名称" + title "获取主机块设备事件" ref { name "blockDevices" path "org.zstack.header.host.APIGetBlockDevicesEvent.blockDevices" - desc "null" + desc "主机上的块设备列表" type "List" - since "5.5.6" + since "5.5.28" clz HostBlockDeviceStruct.class } field { name "success" - desc "" + desc "操作是否成功" type "boolean" - since "5.5.6" + since "5.5.28" } ref { name "error" path "org.zstack.header.host.APIGetBlockDevicesEvent.error" desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null",false type "ErrorCode" - since "5.5.6" + since "5.5.28" clz ErrorCode.class } } diff --git a/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsgDoc_zh_cn.groovy index 0a7ed39bc73..944237801bf 100644 --- a/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsgDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/host/APIGetBlockDevicesMsgDoc_zh_cn.groovy @@ -5,9 +5,9 @@ import org.zstack.header.host.APIGetBlockDevicesEvent doc { title "GetBlockDevices" - category "未知类别" + category "host" - desc """在这里填写API描述""" + desc """获取指定物理机上的块设备列表""" rest { request { @@ -17,7 +17,7 @@ doc { clz APIGetBlockDevicesMsg.class - desc """""" + desc """获取指定物理机上的块设备列表""" params { @@ -28,7 +28,7 @@ doc { location "url" type "String" optional false - since "5.5.6" + since "5.5.28" } column { name "systemTags" @@ -37,7 +37,7 @@ doc { location "query" type "List" optional true - since "5.5.6" + since "5.5.28" } column { name "userTags" @@ -46,16 +46,16 @@ doc { location "query" type "List" optional true - since "5.5.6" + since "5.5.28" } column { name "includeInUse" enclosedIn "" - desc "" + desc "是否包含已被系统使用的块设备" location "query" type "boolean" optional true - since "5.5.6" + since "5.5.28" } } } diff --git a/header/src/main/java/org/zstack/header/host/HostBlockDeviceStructDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/host/HostBlockDeviceStructDoc_zh_cn.groovy index 40707c0b932..1c3589f149d 100644 --- a/header/src/main/java/org/zstack/header/host/HostBlockDeviceStructDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/host/HostBlockDeviceStructDoc_zh_cn.groovy @@ -4,78 +4,84 @@ import java.lang.Long doc { - title "在这里输入结构的名称" + title "主机块设备结构" + field { + name "name" + desc "块设备名称" + type "String" + since "5.5.28" + } field { name "wwid" - desc "" + desc "块设备全局唯一标识" type "String" - since "5.5.6" + since "5.5.28" } field { name "vendor" - desc "" + desc "块设备厂商" type "String" - since "5.5.6" + since "5.5.28" } field { name "model" - desc "" + desc "块设备型号" type "String" - since "5.5.6" + since "5.5.28" } field { name "wwn" - desc "" + desc "块设备全局名称" type "String" - since "5.5.6" + since "5.5.28" } field { name "serial" - desc "" + desc "块设备序列号" type "String" - since "5.5.6" + since "5.5.28" } field { name "hctl" - desc "" + desc "块设备的 HCTL 地址" type "String" - since "5.5.6" + since "5.5.28" } field { name "type" - desc "" + desc "块设备类型" type "String" - since "5.5.6" + since "5.5.28" } field { name "path" - desc "" + desc "块设备路径" type "String" - since "5.5.6" + since "5.5.28" } field { name "size" - desc "" + desc "块设备容量,单位为字节" type "Long" - since "5.5.6" + since "5.5.28" } field { name "source" - desc "" + desc "块设备来源" type "String" - since "5.5.6" + since "5.5.28" } field { name "transport" - desc "" + desc "块设备传输协议" type "String" - since "5.5.6" + since "5.5.28" } field { name "targetIdentifier" - desc "" + desc "块设备目标标识" type "String" - since "5.5.6" + since "5.5.28" } } diff --git a/header/src/main/java/org/zstack/header/vm/BeforeVmAllocateHostExtensionPoint.java b/header/src/main/java/org/zstack/header/vm/BeforeVmAllocateHostExtensionPoint.java index ff8e53f070b..ec53a9b68f7 100644 --- a/header/src/main/java/org/zstack/header/vm/BeforeVmAllocateHostExtensionPoint.java +++ b/header/src/main/java/org/zstack/header/vm/BeforeVmAllocateHostExtensionPoint.java @@ -3,5 +3,11 @@ import org.zstack.header.allocator.AllocateHostMsg; public interface BeforeVmAllocateHostExtensionPoint { + /** + * Invoked before host allocation message delivery so extensions can adjust allocation context. + * + * @param msg host allocation message that will be sent + * @param spec current VM allocation specification + */ void beforeVmAllocateHost(AllocateHostMsg msg, VmInstanceSpec spec); } diff --git a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreInventory.java b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreInventory.java index 22a1cef274b..5bd9ec39cba 100644 --- a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreInventory.java +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreInventory.java @@ -207,4 +207,3 @@ public void setLastOpDate(Timestamp lastOpDate) { this.lastOpDate = lastOpDate; } } - diff --git a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreInventoryDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreInventoryDoc_zh_cn.groovy index 8dd75b46d21..29f95a80fc2 100644 --- a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreInventoryDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreInventoryDoc_zh_cn.groovy @@ -2,6 +2,7 @@ package org.zstack.header.volumeCache import org.zstack.header.volumeCache.HostCacheStoreState import org.zstack.header.volumeCache.HostCacheStoreStatus +import org.zstack.header.volumeCache.HostCacheStoreDeviceInventory import org.zstack.header.volumeCache.VolumeCacheInventory import java.sql.Timestamp @@ -13,50 +14,62 @@ doc { name "uuid" desc "资源的UUID,唯一标示该资源" type "String" - since "5.5.6" + since "5.5.28" } field { name "hostUuid" desc "物理机UUID" type "String" - since "5.5.6" + since "5.5.28" } field { name "name" desc "资源名称" type "String" - since "5.5.6" + since "5.5.28" } field { name "description" desc "资源的详细描述" type "String" - since "5.5.6" + since "5.5.28" } field { name "mountPoint" desc "缓存池挂载路径" type "String" - since "5.5.6" + since "5.5.28" } field { name "totalCapacity" desc "主机缓存存储的总容量,单位为字节" type "long" - since "5.5.6" + since "5.5.28" } field { name "availableCapacity" desc "主机缓存存储的可用容量,单位为字节" type "long" - since "5.5.6" + since "5.5.28" + } + field { + name "allocated" + desc "主机缓存存储已分配的容量,单位为字节" + type "long" + since "5.5.28" + } + field { + name "dirty" + desc "主机缓存存储待回收的脏数据容量,单位为字节" + type "long" + since "5.5.28" } ref { name "state" path "org.zstack.header.volumeCache.HostCacheStoreInventory.state" desc "主机缓存存储的状态" type "HostCacheStoreState" - since "5.5.6" + since "5.5.28" clz HostCacheStoreState.class } ref { @@ -64,27 +77,35 @@ doc { path "org.zstack.header.volumeCache.HostCacheStoreInventory.status" desc "主机缓存存储的健康状态" type "HostCacheStoreStatus" - since "5.5.6" + since "5.5.28" clz HostCacheStoreStatus.class } + ref { + name "devices" + path "org.zstack.header.volumeCache.HostCacheStoreInventory.devices" + desc "组成该主机缓存存储的块设备列表" + type "List" + since "5.5.28" + clz HostCacheStoreDeviceInventory.class + } ref { name "caches" path "org.zstack.header.volumeCache.HostCacheStoreInventory.caches" desc "该主机缓存存储上的卷缓存集合" type "Set" - since "5.5.6" + since "5.5.28" clz VolumeCacheInventory.class } field { name "createDate" desc "创建时间" type "Timestamp" - since "5.5.6" + since "5.5.28" } field { name "lastOpDate" desc "最后一次修改时间" type "Timestamp" - since "5.5.6" + since "5.5.28" } } diff --git a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreState.java b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreState.java index f34be5082f2..8e33b2e04e0 100644 --- a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreState.java +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreState.java @@ -4,4 +4,3 @@ public enum HostCacheStoreState { Enabled, Disabled, } - diff --git a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStateDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStateDoc_zh_cn.groovy index 425457696a4..4f1e6226e55 100644 --- a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStateDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStateDoc_zh_cn.groovy @@ -4,18 +4,18 @@ package org.zstack.header.volumeCache doc { - title "在这里输入结构的名称" + title "主机缓存存储启用状态" field { name "Enabled" - desc "" + desc "主机缓存存储已启用,可参与缓存相关操作" type "HostCacheStoreState" - since "5.5.6" + since "5.5.28" } field { name "Disabled" - desc "" + desc "主机缓存存储已禁用,不参与缓存相关操作" type "HostCacheStoreState" - since "5.5.6" + since "5.5.28" } } diff --git a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStatus.java b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStatus.java index 3306b606b8e..dfcd750088a 100644 --- a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStatus.java +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStatus.java @@ -5,4 +5,3 @@ public enum HostCacheStoreStatus { Connected, Disconnected, } - diff --git a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStatusDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStatusDoc_zh_cn.groovy index adcba1412e1..c4b6034f43f 100644 --- a/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStatusDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/volumeCache/HostCacheStoreStatusDoc_zh_cn.groovy @@ -4,24 +4,24 @@ package org.zstack.header.volumeCache doc { - title "在这里输入结构的名称" + title "主机缓存存储连接状态" field { name "Connecting" - desc "" + desc "正在与主机缓存存储建立连接" type "HostCacheStoreStatus" - since "5.5.6" + since "5.5.28" } field { name "Connected" - desc "" + desc "已连接,可正常执行缓存存储操作" type "HostCacheStoreStatus" - since "5.5.6" + since "5.5.28" } field { name "Disconnected" - desc "" + desc "连接断开,当前不可执行缓存存储操作" type "HostCacheStoreStatus" - since "5.5.6" + since "5.5.28" } } diff --git a/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheInventory.java b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheInventory.java index d0898de1d72..9a4b85a9b49 100644 --- a/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheInventory.java +++ b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheInventory.java @@ -19,6 +19,8 @@ public class VolumeCacheInventory implements Serializable { private String installPath; private VolumeCacheMode cacheMode; private VolumeCacheStatus status; + private Long virtualSize; + private Long actualSize; private Timestamp createDate; private Timestamp lastOpDate; @@ -91,6 +93,22 @@ public void setStatus(VolumeCacheStatus status) { this.status = status; } + public Long getVirtualSize() { + return virtualSize; + } + + public void setVirtualSize(Long virtualSize) { + this.virtualSize = virtualSize; + } + + public Long getActualSize() { + return actualSize; + } + + public void setActualSize(Long actualSize) { + this.actualSize = actualSize; + } + public Timestamp getCreateDate() { return createDate; } diff --git a/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheInventoryDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheInventoryDoc_zh_cn.groovy index 87b4947f82e..e51a37353a9 100644 --- a/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheInventoryDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheInventoryDoc_zh_cn.groovy @@ -2,6 +2,7 @@ package org.zstack.header.volumeCache import org.zstack.header.volumeCache.VolumeCacheMode import org.zstack.header.volumeCache.VolumeCacheStatus +import java.lang.Long import java.sql.Timestamp doc { @@ -12,32 +13,32 @@ doc { name "uuid" desc "资源的UUID,唯一标示该资源" type "String" - since "5.5.6" + since "5.5.28" } field { name "volumeUuid" desc "云盘UUID" type "String" - since "5.5.6" + since "5.5.28" } field { name "poolUuid" desc "主机缓存存储的UUID" type "String" - since "5.5.6" + since "5.5.28" } field { name "installPath" desc "卷缓存在主机上的安装路径" type "String" - since "5.5.6" + since "5.5.28" } ref { name "cacheMode" path "org.zstack.header.volumeCache.VolumeCacheInventory.cacheMode" desc "卷缓存的读写模式" type "VolumeCacheMode" - since "5.5.6" + since "5.5.28" clz VolumeCacheMode.class } ref { @@ -45,19 +46,31 @@ doc { path "org.zstack.header.volumeCache.VolumeCacheInventory.status" desc "卷缓存的状态" type "VolumeCacheStatus" - since "5.5.6" + since "5.5.28" clz VolumeCacheStatus.class } + field { + name "virtualSize" + desc "卷缓存虚拟大小" + type "Long" + since "5.5.28" + } + field { + name "actualSize" + desc "卷缓存实际占用大小" + type "Long" + since "5.5.28" + } field { name "createDate" desc "创建时间" type "Timestamp" - since "5.5.6" + since "5.5.28" } field { name "lastOpDate" desc "最后一次修改时间" type "Timestamp" - since "5.5.6" + since "5.5.28" } } diff --git a/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheModeDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheModeDoc_zh_cn.groovy index 270cd3a7518..5f8e17570cb 100644 --- a/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheModeDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheModeDoc_zh_cn.groovy @@ -4,12 +4,12 @@ package org.zstack.header.volumeCache doc { - title "在这里输入结构的名称" + title "卷缓存模式" field { name "WriteBack" - desc "" + desc "写回模式,写入数据先进入缓存再异步回写到后端卷" type "VolumeCacheMode" - since "5.5.6" + since "5.5.28" } } diff --git a/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheStatusDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheStatusDoc_zh_cn.groovy index 2bb27f02235..e636f22c8f7 100644 --- a/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheStatusDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/volumeCache/VolumeCacheStatusDoc_zh_cn.groovy @@ -10,12 +10,12 @@ doc { name "NotInstantiated" desc "卷缓存尚未在主机缓存存储上实例化" type "VolumeCacheStatus" - since "5.5.6" + since "5.5.28" } field { name "Ready" desc "卷缓存已就绪,可供虚拟机使用" type "VolumeCacheStatus" - since "5.5.6" + since "5.5.28" } } diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java index 3d96beab4de..e963aa9e2ad 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMAgentCommands.java @@ -1325,7 +1325,7 @@ public static class NicTO extends BaseVirtualDeviceTO { // for vDPA & dpdkvhostuserclient nic private String srcPath; - + private Boolean cleanTraffic; private Boolean isolated; diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java index fb08f33644b..7a5a0bcf99f 100755 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHost.java @@ -525,7 +525,7 @@ void call(String resourceUuid, ReturnValueCompletion completion) { completion.fail(errorCode); return; } - + Map header = new HashMap<>(); header.put(Constants.AGENT_HTTP_HEADER_RESOURCE_UUID, resourceUuid == null ? self.getUuid() : resourceUuid); runBeforeAsyncJsonPostExts(header); @@ -602,7 +602,7 @@ void runBeforeAsyncJsonPostExts(Map header) { commandStr = JSONObjectUtil.toJsonString(commandMap); } } - + @Override protected void handleApiMessage(APIMessage msg) { super.handleApiMessage(msg); diff --git a/sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheAction.java b/sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheAction.java index 449ad576a0c..6327e8257be 100644 --- a/sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheAction.java +++ b/sdk/src/main/java/org/zstack/sdk/DisableVolumeCacheAction.java @@ -28,6 +28,9 @@ public Result throwExceptionIfError() { @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.lang.String volumeUuid; + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public boolean dryRun = false; + @Param(required = false) public java.util.List systemTags; diff --git a/sdk/src/main/java/org/zstack/sdk/VolumeCacheInventory.java b/sdk/src/main/java/org/zstack/sdk/VolumeCacheInventory.java index 2670cde43e8..cc5433dae00 100644 --- a/sdk/src/main/java/org/zstack/sdk/VolumeCacheInventory.java +++ b/sdk/src/main/java/org/zstack/sdk/VolumeCacheInventory.java @@ -53,6 +53,22 @@ public VolumeCacheStatus getStatus() { return this.status; } + public java.lang.Long virtualSize; + public void setVirtualSize(java.lang.Long virtualSize) { + this.virtualSize = virtualSize; + } + public java.lang.Long getVirtualSize() { + return this.virtualSize; + } + + public java.lang.Long actualSize; + public void setActualSize(java.lang.Long actualSize) { + this.actualSize = actualSize; + } + public java.lang.Long getActualSize() { + return this.actualSize; + } + public java.sql.Timestamp createDate; public void setCreateDate(java.sql.Timestamp createDate) { this.createDate = createDate; diff --git a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy index b199e312f97..e67c077d98a 100644 --- a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy +++ b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy @@ -5714,8 +5714,8 @@ abstract class ApiHelper { } - def changeHostCacheStoreState(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ChangeHostCacheStoreStateAction.class) Closure c) { - def a = new org.zstack.sdk.ChangeHostCacheStoreStateAction() + def changeHaNetworkGroupState(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ChangeHaNetworkGroupStateAction.class) Closure c) { + def a = new org.zstack.sdk.ChangeHaNetworkGroupStateAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -5741,6 +5741,32 @@ abstract class ApiHelper { } + def changeHostCacheStoreState(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ChangeHostCacheStoreStateAction.class) Closure c) { + def a = new org.zstack.sdk.ChangeHostCacheStoreStateAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def changeHostNetworkInterfaceLldpMode(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ChangeHostNetworkInterfaceLldpModeAction.class) Closure c) { def a = new org.zstack.sdk.ChangeHostNetworkInterfaceLldpModeAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -9797,20 +9823,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -15008,20 +15034,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -19889,8 +19915,8 @@ abstract class ApiHelper { } - def disableVolumeCache(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.DisableVolumeCacheAction.class) Closure c) { - def a = new org.zstack.sdk.DisableVolumeCacheAction() + def disableDGpuMode(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.DisableDGpuModeAction.class) Closure c) { + def a = new org.zstack.sdk.DisableDGpuModeAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -19916,6 +19942,32 @@ abstract class ApiHelper { } + def disableVolumeCache(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.DisableVolumeCacheAction.class) Closure c) { + def a = new org.zstack.sdk.DisableVolumeCacheAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def discoverExternalPrimaryStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.DiscoverExternalPrimaryStorageAction.class) Closure c) { def a = new org.zstack.sdk.DiscoverExternalPrimaryStorageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -20051,8 +20103,8 @@ abstract class ApiHelper { } - def enableVolumeCache(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.EnableVolumeCacheAction.class) Closure c) { - def a = new org.zstack.sdk.EnableVolumeCacheAction() + def enableDGpuMode(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.EnableDGpuModeAction.class) Closure c) { + def a = new org.zstack.sdk.EnableDGpuModeAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20078,6 +20130,32 @@ abstract class ApiHelper { } + def enableVolumeCache(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.EnableVolumeCacheAction.class) Closure c) { + def a = new org.zstack.sdk.EnableVolumeCacheAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def executeAutoScalingRule(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExecuteAutoScalingRuleAction.class) Closure c) { def a = new org.zstack.sdk.ExecuteAutoScalingRuleAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -20186,26 +20264,26 @@ abstract class ApiHelper { } - def exportBuildApp(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExportBuildAppAction.class) Closure c) { - def a = new org.zstack.sdk.ExportBuildAppAction() + def extendHostCacheStore(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExtendHostCacheStoreAction.class) Closure c) { + def a = new org.zstack.sdk.ExtendHostCacheStoreAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -20213,8 +20291,8 @@ abstract class ApiHelper { } - def exportImageFromBackupStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExportImageFromBackupStorageAction.class) Closure c) { - def a = new org.zstack.sdk.ExportImageFromBackupStorageAction() + def exportBuildApp(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExportBuildAppAction.class) Closure c) { + def a = new org.zstack.sdk.ExportBuildAppAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20240,8 +20318,8 @@ abstract class ApiHelper { } - def exportNbdVolumes(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExportNbdVolumesAction.class) Closure c) { - def a = new org.zstack.sdk.ExportNbdVolumesAction() + def exportImageFromBackupStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExportImageFromBackupStorageAction.class) Closure c) { + def a = new org.zstack.sdk.ExportImageFromBackupStorageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20267,8 +20345,8 @@ abstract class ApiHelper { } - def exportVmOvaPackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExportVmOvaPackageAction.class) Closure c) { - def a = new org.zstack.sdk.ExportVmOvaPackageAction() + def exportNbdVolumes(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExportNbdVolumesAction.class) Closure c) { + def a = new org.zstack.sdk.ExportNbdVolumesAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20294,8 +20372,8 @@ abstract class ApiHelper { } - def expungeBaremetalInstance(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeBaremetalInstanceAction.class) Closure c) { - def a = new org.zstack.sdk.ExpungeBaremetalInstanceAction() + def exportVmOvaPackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExportVmOvaPackageAction.class) Closure c) { + def a = new org.zstack.sdk.ExportVmOvaPackageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20321,8 +20399,8 @@ abstract class ApiHelper { } - def expungeDataVolume(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeDataVolumeAction.class) Closure c) { - def a = new org.zstack.sdk.ExpungeDataVolumeAction() + def expungeBaremetalInstance(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeBaremetalInstanceAction.class) Closure c) { + def a = new org.zstack.sdk.ExpungeBaremetalInstanceAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20348,8 +20426,8 @@ abstract class ApiHelper { } - def expungeImage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeImageAction.class) Closure c) { - def a = new org.zstack.sdk.ExpungeImageAction() + def expungeDataVolume(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeDataVolumeAction.class) Closure c) { + def a = new org.zstack.sdk.ExpungeDataVolumeAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20375,8 +20453,8 @@ abstract class ApiHelper { } - def expungeImageGroup(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeImageGroupAction.class) Closure c) { - def a = new org.zstack.sdk.ExpungeImageGroupAction() + def expungeImage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeImageAction.class) Closure c) { + def a = new org.zstack.sdk.ExpungeImageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20402,8 +20480,8 @@ abstract class ApiHelper { } - def expungeVmInstance(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeVmInstanceAction.class) Closure c) { - def a = new org.zstack.sdk.ExpungeVmInstanceAction() + def expungeImageGroup(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeImageGroupAction.class) Closure c) { + def a = new org.zstack.sdk.ExpungeImageGroupAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20429,8 +20507,8 @@ abstract class ApiHelper { } - def expungeVmUserDefinedXmlHookScript(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeVmUserDefinedXmlHookScriptAction.class) Closure c) { - def a = new org.zstack.sdk.ExpungeVmUserDefinedXmlHookScriptAction() + def expungeVmInstance(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeVmInstanceAction.class) Closure c) { + def a = new org.zstack.sdk.ExpungeVmInstanceAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20456,8 +20534,8 @@ abstract class ApiHelper { } - def extendHostCacheStore(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExtendHostCacheStoreAction.class) Closure c) { - def a = new org.zstack.sdk.ExtendHostCacheStoreAction() + def expungeVmUserDefinedXmlHookScript(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeVmUserDefinedXmlHookScriptAction.class) Closure c) { + def a = new org.zstack.sdk.ExpungeVmUserDefinedXmlHookScriptAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -21380,20 +21458,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -32130,7 +32208,7 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + a.conditions = a.conditions.collect { it.toString() } @@ -32138,14 +32216,14 @@ abstract class ApiHelper { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -36677,6 +36755,35 @@ abstract class ApiHelper { } + def queryVmModelMount(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.QueryVmModelMountAction.class) Closure c) { + def a = new org.zstack.sdk.QueryVmModelMountAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + a.conditions = a.conditions.collect { it.toString() } + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def queryVmNic(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.QueryVmNicAction.class) Closure c) { def a = new org.zstack.sdk.QueryVmNicAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -37935,20 +38042,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -42444,20 +42551,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -45684,20 +45791,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -48351,6 +48458,33 @@ abstract class ApiHelper { } + def updateVmInstancePciDeviceSpecRef(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateVmInstancePciDeviceSpecRefAction.class) Closure c) { + def a = new org.zstack.sdk.UpdateVmInstancePciDeviceSpecRefAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def updateVmNetworkConfig(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.UpdateVmNetworkConfigAction.class) Closure c) { def a = new org.zstack.sdk.UpdateVmNetworkConfigAction() a.sessionId = Test.currentEnvSpec?.session?.uuid diff --git a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java index 273a5d2499c..0d464575634 100644 --- a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java +++ b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java @@ -16220,6 +16220,32 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_CRYPTO_SECURITYMACHINE_THIRDPARTY_PLUGIN_10006 = "ORG_ZSTACK_CRYPTO_SECURITYMACHINE_THIRDPARTY_PLUGIN_10006"; + public static final String ORG_ZSTACK_DGPU_10001 = "ORG_ZSTACK_DGPU_10001"; + + public static final String ORG_ZSTACK_DGPU_10002 = "ORG_ZSTACK_DGPU_10002"; + + public static final String ORG_ZSTACK_DGPU_10003 = "ORG_ZSTACK_DGPU_10003"; + + public static final String ORG_ZSTACK_DGPU_10004 = "ORG_ZSTACK_DGPU_10004"; + + public static final String ORG_ZSTACK_DGPU_10005 = "ORG_ZSTACK_DGPU_10005"; + + public static final String ORG_ZSTACK_DGPU_10006 = "ORG_ZSTACK_DGPU_10006"; + + public static final String ORG_ZSTACK_DGPU_10007 = "ORG_ZSTACK_DGPU_10007"; + + public static final String ORG_ZSTACK_DGPU_10008 = "ORG_ZSTACK_DGPU_10008"; + + public static final String ORG_ZSTACK_DGPU_10009 = "ORG_ZSTACK_DGPU_10009"; + + public static final String ORG_ZSTACK_DGPU_10010 = "ORG_ZSTACK_DGPU_10010"; + + public static final String ORG_ZSTACK_DGPU_10011 = "ORG_ZSTACK_DGPU_10011"; + + public static final String ORG_ZSTACK_DGPU_10012 = "ORG_ZSTACK_DGPU_10012"; + + public static final String ORG_ZSTACK_DGPU_10013 = "ORG_ZSTACK_DGPU_10013"; + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10001 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10001"; public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10002 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10002"; From 0dfe389e6d5560dbc8e899a4330de039c84e2791 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Wed, 24 Jun 2026 18:26:46 +0900 Subject: [PATCH 31/54] [search]: fix zql field query total Use entity alias for simple return-with-total counts so nullable selected fields do not turn totals into zero. Keep explicit count(field) and distinct(field) behavior unchanged. Add CountGroupByCase coverage for nullable first selected field. Resolves: ZSTAC-78110 Change-Id: I3b437bc686c345ba17baf256a338ce88209571f8 --- .../ast/visitors/plugin/SimpleCountPlugin.java | 16 ++++++++-------- .../test/integration/zql/CountGroupByCase.groovy | 8 ++++++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/search/src/main/java/org/zstack/zql/ast/visitors/plugin/SimpleCountPlugin.java b/search/src/main/java/org/zstack/zql/ast/visitors/plugin/SimpleCountPlugin.java index 6627311adb9..10ebab413b4 100644 --- a/search/src/main/java/org/zstack/zql/ast/visitors/plugin/SimpleCountPlugin.java +++ b/search/src/main/java/org/zstack/zql/ast/visitors/plugin/SimpleCountPlugin.java @@ -1,10 +1,10 @@ package org.zstack.zql.ast.visitors.plugin; -import org.apache.commons.lang.StringUtils; import org.zstack.header.zql.ASTNode; import java.util.List; -import java.util.stream.Collectors; + +import static org.zstack.zql.ast.visitors.constants.MySqlKeyword.isDistinct; public class SimpleCountPlugin extends AbstractQueryVisitorPlugin { public SimpleCountPlugin() { @@ -24,13 +24,13 @@ public String selectTarget() { String queryTarget; List fieldNames = super.targetFields(); - if (fieldNames.isEmpty()) { - queryTarget = entityAlias; - } else { + if (getClauseType() == ClauseType.COUNT || isDistinct(node.getTarget().getFunction())) { // TODO: Compatibility changes: hql count do not support multiple fields, even if distinct modified. - queryTarget = String.format("%s.%s", inventory.simpleInventoryName(), fieldNames.get(0)); + queryTarget = fieldNames.isEmpty() ? entityAlias : String.format("%s.%s", inventory.simpleInventoryName(), fieldNames.get(0)); + return String.format("count(%s)", String.format(functions(), queryTarget)); + } else { + queryTarget = entityAlias; + return String.format("count(%s)", queryTarget); } - - return String.format("count(%s)", String.format(functions(), queryTarget)); } } diff --git a/test/src/test/groovy/org/zstack/test/integration/zql/CountGroupByCase.groovy b/test/src/test/groovy/org/zstack/test/integration/zql/CountGroupByCase.groovy index 72cf1d6ebb8..e37785f7674 100644 --- a/test/src/test/groovy/org/zstack/test/integration/zql/CountGroupByCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/zql/CountGroupByCase.groovy @@ -36,6 +36,7 @@ class CountGroupByCase extends SubCase { env.create { testCountGroupBy() testQuery() + testFieldQueryTotalWithNullableFirstField() testGroupLimit() testEmpty() } @@ -80,6 +81,13 @@ class CountGroupByCase extends SubCase { assert ret2.total == 102 } + void testFieldQueryTotalWithNullableFirstField() { + def ret = ZQL.fromString("query instanceoffering.description,uuid return with (total) limit 100").getSingleResult() as ZQLQueryReturn + assert ret.inventories.size() == 100 + assert ret.total == 102 + assert ret.inventories.every { it.description == null } + } + void testGroupLimit() { def ret = ZQL.fromString("count instanceoffering group by name,cpuNum limit 2").getSingleResult() as ZQLQueryReturn assert ret.inventoryCounts.size() == 2 From 28e35002716ba60166d3a305713e50bf4bd4c9fd Mon Sep 17 00:00:00 2001 From: "hanyu.liang" Date: Thu, 25 Jun 2026 11:39:59 +0800 Subject: [PATCH 32/54] [scim]: add source tracking contract Merge the Cloud side of ZCF-4419 into one reviewable source tracking change. Add ResourceSourceRefVO schema/persistence, source constants, query API, RBAC contribution, inventory docs, and SDK classes for resources synchronized from external identity sources. Expose SCIM source fields on IAM2 and role inventories, keep role source refs lazy, and avoid brittle RoleVO simple-name filtering when applying source information. Add scim.receiver.enabled and receiver-disable cleanup through ScimResourceHandler.cleanupResources. Startup cleanup is compensated when the receiver is already disabled, cleanup and SCIM request handling are synchronized with a read/write lock, and GlobalConfig extension registration can self-heal after test/global-config re-linking. Tighten ResourceSourceRefVO persistence semantics with non-null source key columns, non-null uuid mapping, and create/update timestamp initialization. Verification: git diff --check ./runMavenProfile premium mvn -pl iam2,plugin-premium/iam2-script-plugin,test-premium -Djacoco.skip=true -Dtest=IAM2Test -DsubCaseCollectionStrategy=Designated -DcaseFilePath=/tmp/iam2_failed_cases.txt -DfailIfNoTests=false test mvn -pl plugin-premium/sso-plugin -Dtest=IAM2RopcLoginBackendTest test Resolves: ZCF-4419 Change-Id: Ibce13e7b792a16ba01f9c404ae46e2c1a83a1865 --- conf/db/upgrade/V5.5.28__schema.sql | 19 ++ conf/persistence.xml | 1 + conf/serviceConfig/rbac.xml | 21 ++- .../zstack/core/scim/ScimGlobalConfig.java | 4 + .../org/zstack/core/scim/ScimService.java | 175 ++++++++++++++---- .../header/identity/role/RoleInventory.java | 88 +++++++++ .../zstack/header/identity/role/RoleType.java | 3 +- .../zstack/header/identity/role/RoleVO.java | 17 ++ .../APIQueryResourceSourceRefMsg.java | 30 +++ ...IQueryResourceSourceRefMsgDoc_zh_cn.groovy | 30 +++ .../APIQueryResourceSourceRefReply.java | 19 ++ ...ueryResourceSourceRefReplyDoc_zh_cn.groovy | 32 ++++ .../org/zstack/header/resource/RBACInfo.java | 26 +++ .../resource/ResourceSourceConstant.java | 17 ++ .../resource/ResourceSourceRefInventory.java | 125 +++++++++++++ ...ResourceSourceRefInventoryDoc_zh_cn.groovy | 69 +++++++ .../header/resource/ResourceSourceRefVO.java | 134 ++++++++++++++ .../header/scim/ScimResourceHandler.java | 7 + .../identity/rbac/RBACApiInterceptor.java | 30 +++ sdk/src/main/java/SourceClassMap.java | 2 + .../sdk/QueryResourceSourceRefAction.java | 75 ++++++++ .../sdk/QueryResourceSourceRefResult.java | 22 +++ .../sdk/ResourceSourceRefInventory.java | 87 +++++++++ .../api/ConfigureIAM2ScimReceiverAction.java | 108 +++++++++++ .../api/ConfigureIAM2ScimReceiverResult.java | 14 ++ .../entity/IAM2OrganizationInventory.java | 48 +++++ .../sdk/iam2/entity/IAM2ProjectInventory.java | 48 +++++ .../entity/IAM2VirtualIDGroupInventory.java | 48 +++++ .../iam2/entity/IAM2VirtualIDInventory.java | 48 +++++ .../sdk/identity/role/RoleInventory.java | 48 +++++ .../zstack/sdk/identity/role/RoleType.java | 1 + 31 files changed, 1352 insertions(+), 44 deletions(-) create mode 100644 header/src/main/java/org/zstack/header/resource/APIQueryResourceSourceRefMsg.java create mode 100644 header/src/main/java/org/zstack/header/resource/APIQueryResourceSourceRefMsgDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/resource/APIQueryResourceSourceRefReply.java create mode 100644 header/src/main/java/org/zstack/header/resource/APIQueryResourceSourceRefReplyDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/resource/RBACInfo.java create mode 100644 header/src/main/java/org/zstack/header/resource/ResourceSourceConstant.java create mode 100644 header/src/main/java/org/zstack/header/resource/ResourceSourceRefInventory.java create mode 100644 header/src/main/java/org/zstack/header/resource/ResourceSourceRefInventoryDoc_zh_cn.groovy create mode 100644 header/src/main/java/org/zstack/header/resource/ResourceSourceRefVO.java create mode 100644 sdk/src/main/java/org/zstack/sdk/QueryResourceSourceRefAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/QueryResourceSourceRefResult.java create mode 100644 sdk/src/main/java/org/zstack/sdk/ResourceSourceRefInventory.java create mode 100644 sdk/src/main/java/org/zstack/sdk/iam2/api/ConfigureIAM2ScimReceiverAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/iam2/api/ConfigureIAM2ScimReceiverResult.java diff --git a/conf/db/upgrade/V5.5.28__schema.sql b/conf/db/upgrade/V5.5.28__schema.sql index 6a8a9e1e1b1..80ba45b1b82 100644 --- a/conf/db/upgrade/V5.5.28__schema.sql +++ b/conf/db/upgrade/V5.5.28__schema.sql @@ -146,3 +146,22 @@ CREATE TABLE IF NOT EXISTS `VolumeCacheVO` ( FOREIGN KEY (`poolUuid`) REFERENCES `HostCacheStoreVO` (`uuid`) ON DELETE SET NULL ) ENGINE = InnoDB DEFAULT CHARSET = utf8; + +-- ZCF-4419: Track resources synchronized from external identity sources. +CREATE TABLE IF NOT EXISTS `zstack`.`ResourceSourceRefVO` ( + `uuid` varchar(32) NOT NULL COMMENT 'uuid', + `resourceUuid` varchar(32) NOT NULL, + `resourceType` varchar(64) NOT NULL, + `sourceType` varchar(64) NOT NULL, + `sourceName` varchar(128) DEFAULT NULL, + `externalUuid` varchar(32) DEFAULT NULL, + `externalType` varchar(255) DEFAULT NULL, + `syncType` varchar(64) NOT NULL, + `lastOpDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00' ON UPDATE CURRENT_TIMESTAMP, + `createDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', + PRIMARY KEY (`uuid`), + UNIQUE KEY `ukResourceSourceRefVOResourceSourceSync` (`resourceUuid`, `resourceType`, `sourceType`, `syncType`), + KEY `idxResourceSourceRefVOSourceSyncResourceType` (`sourceType`, `syncType`, `resourceType`), + KEY `idxResourceSourceRefVOResourceSync` (`resourceType`, `resourceUuid`, `syncType`), + KEY `idxResourceSourceRefVOSyncType` (`syncType`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/conf/persistence.xml b/conf/persistence.xml index da9b18513d6..52c804b55dc 100755 --- a/conf/persistence.xml +++ b/conf/persistence.xml @@ -90,6 +90,7 @@ org.zstack.header.identity.SessionVO org.zstack.header.identity.AccountVO org.zstack.header.identity.AccountResourceRefVO + org.zstack.header.resource.ResourceSourceRefVO org.zstack.header.identity.UserVO org.zstack.header.identity.PolicyVO org.zstack.header.identity.UserPolicyRefVO diff --git a/conf/serviceConfig/rbac.xml b/conf/serviceConfig/rbac.xml index dd6061d80aa..db17591c436 100755 --- a/conf/serviceConfig/rbac.xml +++ b/conf/serviceConfig/rbac.xml @@ -15,14 +15,19 @@ org.zstack.header.identity.role.api.APIDeleteRoleMsg - - org.zstack.header.identity.role.api.APIQueryRoleMsg - query - - - - org.zstack.header.identity.role.api.APIChangeRoleStateMsg - + + org.zstack.header.identity.role.api.APIQueryRoleMsg + query + + + + org.zstack.header.resource.APIQueryResourceSourceRefMsg + query + + + + org.zstack.header.identity.role.api.APIChangeRoleStateMsg + org.zstack.header.identity.role.api.APIAttachPolicyToRoleMsg diff --git a/core/src/main/java/org/zstack/core/scim/ScimGlobalConfig.java b/core/src/main/java/org/zstack/core/scim/ScimGlobalConfig.java index 29cd225179c..3187e523f40 100644 --- a/core/src/main/java/org/zstack/core/scim/ScimGlobalConfig.java +++ b/core/src/main/java/org/zstack/core/scim/ScimGlobalConfig.java @@ -9,6 +9,10 @@ public class ScimGlobalConfig { public static final String CATEGORY = "scim"; + @GlobalConfigDef(type = Boolean.class, defaultValue = "false", + description = "Whether the SCIM receiver accepts incoming requests. Disabling it removes SCIM-synchronized resources.") + public static GlobalConfig RECEIVER_ENABLED = new GlobalConfig(CATEGORY, "receiver.enabled"); + @GlobalConfigValidation(notEmpty = false) @GlobalConfigDef(defaultValue = "", description = "Bearer token accepted by the SCIM receiver") public static GlobalConfig RECEIVER_TOKEN = new GlobalConfig(CATEGORY, "receiver.token"); diff --git a/core/src/main/java/org/zstack/core/scim/ScimService.java b/core/src/main/java/org/zstack/core/scim/ScimService.java index cfdf7b780f0..4df0c26d6f4 100644 --- a/core/src/main/java/org/zstack/core/scim/ScimService.java +++ b/core/src/main/java/org/zstack/core/scim/ScimService.java @@ -7,6 +7,10 @@ import org.zstack.core.db.DatabaseFacade; import org.zstack.core.db.GLock; import org.zstack.core.db.SQL; +import org.zstack.core.config.GlobalConfig; +import org.zstack.core.config.GlobalConfigUpdateExtensionPoint; +import org.zstack.core.thread.Task; +import org.zstack.core.thread.ThreadFacade; import org.zstack.header.scim.ScimConstant; import org.zstack.header.scim.ScimEventVO; import org.zstack.header.scim.ScimException; @@ -14,6 +18,9 @@ import org.zstack.header.scim.ScimPayload; import org.zstack.header.scim.ScimResourceHandler; import org.zstack.header.scim.ScimResult; +import org.zstack.header.resource.ResourceSourceConstant; +import org.zstack.utils.Utils; +import org.zstack.utils.logging.CLogger; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; @@ -27,8 +34,10 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantReadWriteLock; public class ScimService { + private static final CLogger logger = Utils.getLogger(ScimService.class); private static final ObjectMapper mapper = new ObjectMapper(); private static final int MAX_CLIENT_ID_LENGTH = 128; private static final int MAX_EVENT_ID_LENGTH = 255; @@ -45,45 +54,131 @@ public class ScimService { private DatabaseFacade dbf; @Autowired private ScimResourceHandler resourceHandler; + @Autowired + private ThreadFacade thdf; + private final GlobalConfigUpdateExtensionPoint cleanupWhenDisabledExtension = this::cleanupWhenDisabled; + private final ReentrantReadWriteLock receiverStateLock = new ReentrantReadWriteLock(); + private volatile GlobalConfig registeredReceiverEnabledConfig; + + public void init() { + ensureReceiverEnabledExtensionRegistered(); + if (!isEnabled(ScimGlobalConfig.RECEIVER_ENABLED.value())) { + submitStartupCleanup(); + } + } + + private void ensureReceiverEnabledExtensionRegisteredIfNeeded() { + GlobalConfig receiverEnabled = ScimGlobalConfig.RECEIVER_ENABLED; + // Lock-free fast path: concurrent misses are harmless because the synchronized slow path de-duplicates registration. + if (registeredReceiverEnabledConfig == receiverEnabled + && receiverEnabled.getLocalUpdateExtensions().contains(cleanupWhenDisabledExtension) + && receiverEnabled.getUpdateExtensions().contains(cleanupWhenDisabledExtension)) { + return; + } + ensureReceiverEnabledExtensionRegistered(); + } + + private synchronized void ensureReceiverEnabledExtensionRegistered() { + GlobalConfig receiverEnabled = ScimGlobalConfig.RECEIVER_ENABLED; + if (registeredReceiverEnabledConfig == receiverEnabled + && receiverEnabled.getLocalUpdateExtensions().contains(cleanupWhenDisabledExtension) + && receiverEnabled.getUpdateExtensions().contains(cleanupWhenDisabledExtension)) { + return; + } + + if (!receiverEnabled.getLocalUpdateExtensions().contains(cleanupWhenDisabledExtension)) { + receiverEnabled.installLocalUpdateExtension(cleanupWhenDisabledExtension); + } + if (!receiverEnabled.getUpdateExtensions().contains(cleanupWhenDisabledExtension)) { + receiverEnabled.installUpdateExtension(cleanupWhenDisabledExtension); + } + registeredReceiverEnabledConfig = receiverEnabled; + } + + private void submitStartupCleanup() { + thdf.submit(new Task() { + @Override + public String getName() { + return "scim-startup-cleanup"; + } + + @Override + public Void call() { + try { + cleanupScimResourcesIfPresent(); + } catch (RuntimeException e) { + logger.warn("failed to cleanup SCIM resources on startup", e); + } + return null; + } + }); + } + + private void cleanupWhenDisabled(GlobalConfig oldConfig, GlobalConfig newConfig) { + if (isEnabled(oldConfig.value()) && !isEnabled(newConfig.value())) { + cleanupScimResourcesIfPresent(); + } + } + + private void cleanupScimResourcesIfPresent() { + receiverStateLock.writeLock().lock(); + try { + Long count = SQL.New("select count(vo) from ResourceSourceRefVO vo where vo.syncType = :syncType", Long.class) + .param("syncType", ResourceSourceConstant.SYNC_TYPE_SCIM) + .find(); + if (count != null && count > 0) { + resourceHandler.cleanupResources(ResourceSourceConstant.SYNC_TYPE_SCIM); + } + } finally { + receiverStateLock.writeLock().unlock(); + } + } public ScimResult apply(HttpServletRequest request, String resourceType, String pathResourceId, String rawBody) { - String body = rawBody == null ? "" : rawBody; - verifyBearer(request); - String method = normalizeMethod(request); - validateMaxLength(resourceType, "resource type", MAX_RESOURCE_TYPE_LENGTH); - validateMaxLength(pathResourceId, "resource uuid", MAX_RESOURCE_ID_LENGTH); - String clientId = validateMaxLength(optionalHeader(request, ScimConstant.HEADER_CLIENT_ID, - ScimConstant.DEFAULT_CLIENT_ID), ScimConstant.HEADER_CLIENT_ID, MAX_CLIENT_ID_LENGTH); - String eventId = validateMaxLength(requiredHeader(request, ScimConstant.HEADER_EVENT_ID), - ScimConstant.HEADER_EVENT_ID, MAX_EVENT_ID_LENGTH); - long resourceVersion = parseResourceVersion(requiredHeader(request, ScimConstant.HEADER_RESOURCE_VERSION)); - String timestamp = requiredHeader(request, ScimConstant.HEADER_TIMESTAMP); - verifySignature(request, clientId, eventId, resourceVersion, timestamp, method, resourceType, pathResourceId, body); - - String canonicalType = resourceHandler.normalizeResourceType(resourceType); - validateMaxLength(canonicalType, "canonical resource type", MAX_RESOURCE_TYPE_LENGTH); - Map bodyMap = parseBody(body); - rejectSensitivePayloadKeys(bodyMap); - ScimPayload payload = mapper.convertValue(bodyMap, ScimPayload.class); - rejectSensitivePayloadKeys(payload.attributes); - String pathResourceUuid = normalizeOptionalUuid(pathResourceId, "path resource uuid"); - String payloadUuid = normalizeOptionalUuid(payload.uuid, "payload uuid"); - if (!isBlank(pathResourceUuid) && !isBlank(payloadUuid) && !pathResourceUuid.equals(payloadUuid)) { - throw new ScimException(400, "path resource uuid and payload uuid must match"); - } - String resourceId = validateMaxLength(firstNotBlank(pathResourceUuid, payloadUuid), - "resource uuid", MAX_RESOURCE_ID_LENGTH); - if (isBlank(resourceId)) { - throw new ScimException(400, "resource uuid is required"); - } - - ScimOperation operation = toOperation(method); - GLock lock = new GLock(lockName(clientId, canonicalType, resourceId), LOCK_TIMEOUT_SECONDS); - lock.lock(); + ensureReceiverEnabledExtensionRegisteredIfNeeded(); + receiverStateLock.readLock().lock(); try { - return applyLocked(clientId, eventId, canonicalType, resourceId, resourceVersion, operation, body, payload); + String body = rawBody == null ? "" : rawBody; + verifyEnabled(); + verifyBearer(request); + String method = normalizeMethod(request); + validateMaxLength(resourceType, "resource type", MAX_RESOURCE_TYPE_LENGTH); + validateMaxLength(pathResourceId, "resource uuid", MAX_RESOURCE_ID_LENGTH); + String clientId = validateMaxLength(optionalHeader(request, ScimConstant.HEADER_CLIENT_ID, + ScimConstant.DEFAULT_CLIENT_ID), ScimConstant.HEADER_CLIENT_ID, MAX_CLIENT_ID_LENGTH); + String eventId = validateMaxLength(requiredHeader(request, ScimConstant.HEADER_EVENT_ID), + ScimConstant.HEADER_EVENT_ID, MAX_EVENT_ID_LENGTH); + long resourceVersion = parseResourceVersion(requiredHeader(request, ScimConstant.HEADER_RESOURCE_VERSION)); + String timestamp = requiredHeader(request, ScimConstant.HEADER_TIMESTAMP); + verifySignature(request, clientId, eventId, resourceVersion, timestamp, method, resourceType, pathResourceId, body); + + String canonicalType = resourceHandler.normalizeResourceType(resourceType); + validateMaxLength(canonicalType, "canonical resource type", MAX_RESOURCE_TYPE_LENGTH); + Map bodyMap = parseBody(body); + rejectSensitivePayloadKeys(bodyMap); + ScimPayload payload = mapper.convertValue(bodyMap, ScimPayload.class); + rejectSensitivePayloadKeys(payload.attributes); + String pathResourceUuid = normalizeOptionalUuid(pathResourceId, "path resource uuid"); + String payloadUuid = normalizeOptionalUuid(payload.uuid, "payload uuid"); + if (!isBlank(pathResourceUuid) && !isBlank(payloadUuid) && !pathResourceUuid.equals(payloadUuid)) { + throw new ScimException(400, "path resource uuid and payload uuid must match"); + } + String resourceId = validateMaxLength(firstNotBlank(pathResourceUuid, payloadUuid), + "resource uuid", MAX_RESOURCE_ID_LENGTH); + if (isBlank(resourceId)) { + throw new ScimException(400, "resource uuid is required"); + } + + ScimOperation operation = toOperation(method); + GLock lock = new GLock(lockName(clientId, canonicalType, resourceId), LOCK_TIMEOUT_SECONDS); + lock.lock(); + try { + return applyLocked(clientId, eventId, canonicalType, resourceId, resourceVersion, operation, body, payload); + } finally { + lock.unlock(); + } } finally { - lock.unlock(); + receiverStateLock.readLock().unlock(); } } @@ -130,6 +225,16 @@ private void verifyBearer(HttpServletRequest request) { } } + private void verifyEnabled() { + if (!isEnabled(ScimGlobalConfig.RECEIVER_ENABLED.value())) { + throw new ScimException(403, "SCIM receiver is disabled"); + } + } + + private boolean isEnabled(String value) { + return Boolean.parseBoolean(value); + } + private String requiredHeader(HttpServletRequest request, String name) { String value = request.getHeader(name); if (isBlank(value)) { diff --git a/header/src/main/java/org/zstack/header/identity/role/RoleInventory.java b/header/src/main/java/org/zstack/header/identity/role/RoleInventory.java index b1070ec6710..76eab7a152b 100755 --- a/header/src/main/java/org/zstack/header/identity/role/RoleInventory.java +++ b/header/src/main/java/org/zstack/header/identity/role/RoleInventory.java @@ -1,5 +1,11 @@ package org.zstack.header.identity.role; +import org.hibernate.Hibernate; +import org.zstack.header.query.ExpandedQueries; +import org.zstack.header.query.ExpandedQuery; +import org.zstack.header.resource.ResourceSourceConstant; +import org.zstack.header.resource.ResourceSourceRefInventory; +import org.zstack.header.resource.ResourceSourceRefVO; import org.zstack.header.search.Inventory; import java.sql.Timestamp; @@ -8,6 +14,10 @@ import java.util.stream.Collectors; @Inventory(mappingVOClass = RoleVO.class) +@ExpandedQueries({ + @ExpandedQuery(expandedField = "sourceRef", inventoryClass = ResourceSourceRefInventory.class, + foreignKey = "uuid", expandedInventoryKey = "resourceUuid", hidden = true) +}) public class RoleInventory { private String uuid; private String name; @@ -19,6 +29,12 @@ public class RoleInventory { private Timestamp lastOpDate; private List statements; private List policies; + private String sourceCategory; + private String sourceType; + private String sourceName; + private String syncType; + private String externalUuid; + private String externalType; protected RoleInventory(RoleVO vo) { this.setCreateDate(vo.getCreateDate()); @@ -31,6 +47,7 @@ protected RoleInventory(RoleVO vo) { this.setIdentity(vo.getIdentity()); this.setStatements(RolePolicyStatementInventory.valueOf(vo.getStatements())); this.setPolicies(RolePolicyRefInventory.valueOf(vo.getPolicies())); + applySourceInfo(this, initializedSourceRefs(vo)); } public RoleInventory() { @@ -48,6 +65,7 @@ public static RoleInventory valueOf(RoleVO vo) { inv.lastOpDate = vo.getLastOpDate(); inv.statements = RolePolicyStatementInventory.valueOf(vo.getStatements()); inv.policies = RolePolicyRefInventory.valueOf(vo.getPolicies()); + applySourceInfo(inv, initializedSourceRefs(vo)); return inv; } @@ -134,4 +152,74 @@ public String getIdentity() { public void setIdentity(String identity) { this.identity = identity; } + + public String getSourceCategory() { + return sourceCategory; + } + + public void setSourceCategory(String sourceCategory) { + this.sourceCategory = sourceCategory; + } + + public String getSourceType() { + return sourceType; + } + + public void setSourceType(String sourceType) { + this.sourceType = sourceType; + } + + public String getSourceName() { + return sourceName; + } + + public void setSourceName(String sourceName) { + this.sourceName = sourceName; + } + + public String getSyncType() { + return syncType; + } + + public void setSyncType(String syncType) { + this.syncType = syncType; + } + + public String getExternalUuid() { + return externalUuid; + } + + public void setExternalUuid(String externalUuid) { + this.externalUuid = externalUuid; + } + + public String getExternalType() { + return externalType; + } + + public void setExternalType(String externalType) { + this.externalType = externalType; + } + + private static void applySourceInfo(RoleInventory inv, Collection refs) { + if (refs != null) { + for (ResourceSourceRefVO ref : refs) { + if (ResourceSourceConstant.SOURCE_TYPE_ZIAM.equals(ref.getSourceType()) && + ResourceSourceConstant.SYNC_TYPE_SCIM.equals(ref.getSyncType())) { + inv.sourceCategory = ResourceSourceConstant.SOURCE_CATEGORY_ZIAM_SCIM; + inv.sourceType = ref.getSourceType(); + inv.sourceName = ref.getSourceName(); + inv.syncType = ref.getSyncType(); + inv.externalUuid = ref.getExternalUuid(); + inv.externalType = ref.getExternalType(); + return; + } + } + } + inv.sourceCategory = ResourceSourceConstant.SOURCE_CATEGORY_LOCAL; + } + + private static Collection initializedSourceRefs(RoleVO vo) { + return vo.getSourceRefs() != null && Hibernate.isInitialized(vo.getSourceRefs()) ? vo.getSourceRefs() : null; + } } diff --git a/header/src/main/java/org/zstack/header/identity/role/RoleType.java b/header/src/main/java/org/zstack/header/identity/role/RoleType.java index e96f21dd174..d3e53a818bd 100755 --- a/header/src/main/java/org/zstack/header/identity/role/RoleType.java +++ b/header/src/main/java/org/zstack/header/identity/role/RoleType.java @@ -5,5 +5,6 @@ public enum RoleType { Customized, Predefined, CreatedBySystem, - PredefinedBySystem + PredefinedBySystem, + None } diff --git a/header/src/main/java/org/zstack/header/identity/role/RoleVO.java b/header/src/main/java/org/zstack/header/identity/role/RoleVO.java index 715ec47eea7..e48b1134d71 100755 --- a/header/src/main/java/org/zstack/header/identity/role/RoleVO.java +++ b/header/src/main/java/org/zstack/header/identity/role/RoleVO.java @@ -1,8 +1,11 @@ package org.zstack.header.identity.role; +import org.hibernate.annotations.BatchSize; +import org.hibernate.annotations.Where; import org.zstack.header.host.HostVO; import org.zstack.header.identity.OwnedByAccount; import org.zstack.header.identity.PolicyVO; +import org.zstack.header.resource.ResourceSourceRefVO; import org.zstack.header.vo.BaseResource; import org.zstack.header.vo.EntityGraph; import org.zstack.header.vo.ResourceVO; @@ -46,6 +49,12 @@ public class RoleVO extends ResourceVO implements OwnedByAccount { @JoinColumn(name = "roleUuid", insertable = false, updatable = false) private Set policies = new HashSet<>(); + @OneToMany(fetch = FetchType.EAGER) + @JoinColumn(name = "resourceUuid", referencedColumnName = "uuid", insertable = false, updatable = false) + @Where(clause = "resourceType = 'RoleVO'") + @BatchSize(size = 50) + private Set sourceRefs = new HashSet<>(); + @Transient private String accountUuid; @@ -113,6 +122,14 @@ public void setPolicies(Set policies) { this.policies = policies; } + public Set getSourceRefs() { + return sourceRefs; + } + + public void setSourceRefs(Set sourceRefs) { + this.sourceRefs = sourceRefs; + } + public RoleType getType() { return type; } diff --git a/header/src/main/java/org/zstack/header/resource/APIQueryResourceSourceRefMsg.java b/header/src/main/java/org/zstack/header/resource/APIQueryResourceSourceRefMsg.java new file mode 100644 index 00000000000..3470f7307db --- /dev/null +++ b/header/src/main/java/org/zstack/header/resource/APIQueryResourceSourceRefMsg.java @@ -0,0 +1,30 @@ +package org.zstack.header.resource; + +import org.springframework.http.HttpMethod; +import org.zstack.header.identity.AccountConstant; +import org.zstack.header.identity.Action; +import org.zstack.header.query.APIQueryMessage; +import org.zstack.header.query.AutoQuery; +import org.zstack.header.rest.RestRequest; + +import java.util.List; + +import static java.util.Arrays.asList; + +@AutoQuery(replyClass = APIQueryResourceSourceRefReply.class, inventoryClass = ResourceSourceRefInventory.class) +@Action(category = AccountConstant.ACTION_CATEGORY, names = {"read"}, adminOnly = true) +@RestRequest( + path = "/resources/source-refs", + method = HttpMethod.GET, + responseClass = APIQueryResourceSourceRefReply.class +) +public class APIQueryResourceSourceRefMsg extends APIQueryMessage { + public static List __example__() { + return asList( + "resourceUuid=" + uuid(), + "resourceType=RoleVO", + "sourceType=" + ResourceSourceConstant.SOURCE_TYPE_ZIAM, + "syncType=" + ResourceSourceConstant.SYNC_TYPE_SCIM + ); + } +} diff --git a/header/src/main/java/org/zstack/header/resource/APIQueryResourceSourceRefMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/resource/APIQueryResourceSourceRefMsgDoc_zh_cn.groovy new file mode 100644 index 00000000000..3584ebc0294 --- /dev/null +++ b/header/src/main/java/org/zstack/header/resource/APIQueryResourceSourceRefMsgDoc_zh_cn.groovy @@ -0,0 +1,30 @@ +package org.zstack.header.resource + +import org.zstack.header.resource.APIQueryResourceSourceRefReply +import org.zstack.header.query.APIQueryMessage + +doc { + title "QueryResourceSourceRef" + + category "rbac" + + desc """查询资源来源引用,用于识别资源是否由 ZIAM SCIM 等外部身份源同步管理。""" + + rest { + request { + url "GET /v1/resources/source-refs" + + header (Authorization: 'OAuth the-session-uuid') + + clz APIQueryResourceSourceRefMsg.class + + desc """按资源UUID、资源类型、来源类型或同步类型查询资源来源引用。""" + + params APIQueryMessage.class + } + + response { + clz APIQueryResourceSourceRefReply.class + } + } +} \ No newline at end of file diff --git a/header/src/main/java/org/zstack/header/resource/APIQueryResourceSourceRefReply.java b/header/src/main/java/org/zstack/header/resource/APIQueryResourceSourceRefReply.java new file mode 100644 index 00000000000..e55d8035038 --- /dev/null +++ b/header/src/main/java/org/zstack/header/resource/APIQueryResourceSourceRefReply.java @@ -0,0 +1,19 @@ +package org.zstack.header.resource; + +import org.zstack.header.query.APIQueryReply; +import org.zstack.header.rest.RestResponse; + +import java.util.List; + +@RestResponse(allTo = "inventories") +public class APIQueryResourceSourceRefReply extends APIQueryReply { + private List inventories; + + public List getInventories() { + return inventories; + } + + public void setInventories(List inventories) { + this.inventories = inventories; + } +} diff --git a/header/src/main/java/org/zstack/header/resource/APIQueryResourceSourceRefReplyDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/resource/APIQueryResourceSourceRefReplyDoc_zh_cn.groovy new file mode 100644 index 00000000000..e8945f9727b --- /dev/null +++ b/header/src/main/java/org/zstack/header/resource/APIQueryResourceSourceRefReplyDoc_zh_cn.groovy @@ -0,0 +1,32 @@ +package org.zstack.header.resource + +import org.zstack.header.resource.ResourceSourceRefInventory +import org.zstack.header.errorcode.ErrorCode + +doc { + + title "QueryResourceSourceRefReply" + + ref { + name "inventories" + path "org.zstack.header.resource.APIQueryResourceSourceRefReply.inventories" + desc "资源来源引用清单" + type "List" + since "5.5.28" + clz ResourceSourceRefInventory.class + } + field { + name "success" + desc "API调用是否成功" + type "boolean" + since "5.5.28" + } + ref { + name "error" + path "org.zstack.header.resource.APIQueryResourceSourceRefReply.error" + desc "错误码,若不为null,则表示操作失败, 操作成功时该字段为null",false + type "ErrorCode" + since "5.5.28" + clz ErrorCode.class + } +} diff --git a/header/src/main/java/org/zstack/header/resource/RBACInfo.java b/header/src/main/java/org/zstack/header/resource/RBACInfo.java new file mode 100644 index 00000000000..6ca21384860 --- /dev/null +++ b/header/src/main/java/org/zstack/header/resource/RBACInfo.java @@ -0,0 +1,26 @@ +package org.zstack.header.resource; + +import org.zstack.header.identity.rbac.RBACDescription; + +public class RBACInfo implements RBACDescription { + @Override + public void permissions() { + permissionBuilder() + .name("resource-source") + .adminOnlyAPIs(APIQueryResourceSourceRefMsg.class) + .targetResources(ResourceSourceRefVO.class) + .build(); + } + + @Override + public void contributeToRoles() { + } + + @Override + public void roles() { + } + + @Override + public void globalReadableResources() { + } +} diff --git a/header/src/main/java/org/zstack/header/resource/ResourceSourceConstant.java b/header/src/main/java/org/zstack/header/resource/ResourceSourceConstant.java new file mode 100644 index 00000000000..9866b5ab369 --- /dev/null +++ b/header/src/main/java/org/zstack/header/resource/ResourceSourceConstant.java @@ -0,0 +1,17 @@ +package org.zstack.header.resource; + +/** + * Constants shared by resource source tracking. SCIM receivers use these values + * to mark resources owned by upstream identity providers such as ZIAM. + */ +public class ResourceSourceConstant { + public static final String SOURCE_CATEGORY_LOCAL = "LOCAL"; + public static final String SOURCE_CATEGORY_UNIFIED_AUTH = "UNIFIED_AUTH"; + public static final String SOURCE_CATEGORY_ZIAM_SCIM = "ZIAM_SCIM"; + public static final String SOURCE_TYPE_ZIAM = "ZIAM"; + public static final String SOURCE_NAME_ZIAM = "ziam"; + public static final String SYNC_TYPE_SCIM = "SCIM"; + + private ResourceSourceConstant() { + } +} diff --git a/header/src/main/java/org/zstack/header/resource/ResourceSourceRefInventory.java b/header/src/main/java/org/zstack/header/resource/ResourceSourceRefInventory.java new file mode 100644 index 00000000000..443bf065525 --- /dev/null +++ b/header/src/main/java/org/zstack/header/resource/ResourceSourceRefInventory.java @@ -0,0 +1,125 @@ +package org.zstack.header.resource; + +import org.zstack.header.search.Inventory; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +@Inventory(mappingVOClass = ResourceSourceRefVO.class) +public class ResourceSourceRefInventory { + private String uuid; + private String resourceUuid; + private String resourceType; + private String sourceType; + private String sourceName; + private String externalUuid; + private String externalType; + private String syncType; + private Timestamp createDate; + private Timestamp lastOpDate; + + public static ResourceSourceRefInventory valueOf(ResourceSourceRefVO vo) { + ResourceSourceRefInventory inv = new ResourceSourceRefInventory(); + inv.uuid = vo.getUuid(); + inv.resourceUuid = vo.getResourceUuid(); + inv.resourceType = vo.getResourceType(); + inv.sourceType = vo.getSourceType(); + inv.sourceName = vo.getSourceName(); + inv.externalUuid = vo.getExternalUuid(); + inv.externalType = vo.getExternalType(); + inv.syncType = vo.getSyncType(); + inv.createDate = vo.getCreateDate(); + inv.lastOpDate = vo.getLastOpDate(); + return inv; + } + + public static List valueOf(Collection vos) { + List invs = new ArrayList<>(vos.size()); + for (ResourceSourceRefVO vo : vos) { + invs.add(valueOf(vo)); + } + return invs; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getResourceUuid() { + return resourceUuid; + } + + public void setResourceUuid(String resourceUuid) { + this.resourceUuid = resourceUuid; + } + + public String getResourceType() { + return resourceType; + } + + public void setResourceType(String resourceType) { + this.resourceType = resourceType; + } + + public String getSourceType() { + return sourceType; + } + + public void setSourceType(String sourceType) { + this.sourceType = sourceType; + } + + public String getSourceName() { + return sourceName; + } + + public void setSourceName(String sourceName) { + this.sourceName = sourceName; + } + + public String getExternalUuid() { + return externalUuid; + } + + public void setExternalUuid(String externalUuid) { + this.externalUuid = externalUuid; + } + + public String getExternalType() { + return externalType; + } + + public void setExternalType(String externalType) { + this.externalType = externalType; + } + + public String getSyncType() { + return syncType; + } + + public void setSyncType(String syncType) { + this.syncType = syncType; + } + + public Timestamp getCreateDate() { + return createDate; + } + + public void setCreateDate(Timestamp createDate) { + this.createDate = createDate; + } + + public Timestamp getLastOpDate() { + return lastOpDate; + } + + public void setLastOpDate(Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } +} diff --git a/header/src/main/java/org/zstack/header/resource/ResourceSourceRefInventoryDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/resource/ResourceSourceRefInventoryDoc_zh_cn.groovy new file mode 100644 index 00000000000..6a02b56830d --- /dev/null +++ b/header/src/main/java/org/zstack/header/resource/ResourceSourceRefInventoryDoc_zh_cn.groovy @@ -0,0 +1,69 @@ +package org.zstack.header.resource + +import java.sql.Timestamp + +doc { + + title "资源来源引用" + + field { + name "uuid" + desc "引用记录UUID" + type "String" + since "5.5.28" + } + field { + name "resourceUuid" + desc "资源UUID" + type "String" + since "5.5.28" + } + field { + name "resourceType" + desc "Cloud 侧资源类型,例如 IAM2VirtualIDVO、IAM2VirtualIDGroupVO、IAM2OrganizationVO、IAM2ProjectVO" + type "String" + since "5.5.28" + } + field { + name "sourceType" + desc "资源来源类型,ZIAM SCIM 同步资源固定为 ZIAM" + type "String" + since "5.5.28" + } + field { + name "sourceName" + desc "资源来源名称,ZIAM SCIM 同步资源固定为 ziam" + type "String" + since "5.5.28" + } + field { + name "externalUuid" + desc "上游身份源中的资源UUID" + type "String" + since "5.5.28" + } + field { + name "externalType" + desc "上游身份源中的资源类型" + type "String" + since "5.5.28" + } + field { + name "syncType" + desc "同步类型,SCIM 同步资源固定为 SCIM" + type "String" + since "5.5.28" + } + field { + name "createDate" + desc "创建时间" + type "Timestamp" + since "5.5.28" + } + field { + name "lastOpDate" + desc "最后一次修改时间" + type "Timestamp" + since "5.5.28" + } +} diff --git a/header/src/main/java/org/zstack/header/resource/ResourceSourceRefVO.java b/header/src/main/java/org/zstack/header/resource/ResourceSourceRefVO.java new file mode 100644 index 00000000000..c46cd9c7e7d --- /dev/null +++ b/header/src/main/java/org/zstack/header/resource/ResourceSourceRefVO.java @@ -0,0 +1,134 @@ +package org.zstack.header.resource; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.PrePersist; +import javax.persistence.PreUpdate; +import javax.persistence.Table; +import javax.persistence.UniqueConstraint; +import java.sql.Timestamp; + +@Entity +@Table(name = "ResourceSourceRefVO", uniqueConstraints = { + @UniqueConstraint(columnNames = {"resourceUuid", "resourceType", "sourceType", "syncType"}) +}) +public class ResourceSourceRefVO { + @Id + @Column(nullable = false, length = 32) + private String uuid; + @Column(nullable = false, length = 32) + private String resourceUuid; + @Column(nullable = false, length = 64) + private String resourceType; + @Column(nullable = false, length = 64) + private String sourceType; + @Column(length = 128) + private String sourceName; + @Column(length = 32) + private String externalUuid; + @Column(length = 255) + private String externalType; + @Column(nullable = false, length = 64) + private String syncType; + @Column(nullable = false) + private Timestamp createDate; + @Column(nullable = false) + private Timestamp lastOpDate; + + @PrePersist + private void prePersist() { + Timestamp now = new Timestamp(System.currentTimeMillis()); + if (createDate == null) { + createDate = now; + } + if (lastOpDate == null) { + lastOpDate = now; + } + } + + @PreUpdate + private void preUpdate() { + lastOpDate = new Timestamp(System.currentTimeMillis()); + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getResourceUuid() { + return resourceUuid; + } + + public void setResourceUuid(String resourceUuid) { + this.resourceUuid = resourceUuid; + } + + public String getResourceType() { + return resourceType; + } + + public void setResourceType(String resourceType) { + this.resourceType = resourceType; + } + + public String getSourceType() { + return sourceType; + } + + public void setSourceType(String sourceType) { + this.sourceType = sourceType; + } + + public String getSourceName() { + return sourceName; + } + + public void setSourceName(String sourceName) { + this.sourceName = sourceName; + } + + public String getExternalUuid() { + return externalUuid; + } + + public void setExternalUuid(String externalUuid) { + this.externalUuid = externalUuid; + } + + public String getExternalType() { + return externalType; + } + + public void setExternalType(String externalType) { + this.externalType = externalType; + } + + public String getSyncType() { + return syncType; + } + + public void setSyncType(String syncType) { + this.syncType = syncType; + } + + public Timestamp getCreateDate() { + return createDate; + } + + public void setCreateDate(Timestamp createDate) { + this.createDate = createDate; + } + + public Timestamp getLastOpDate() { + return lastOpDate; + } + + public void setLastOpDate(Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } +} diff --git a/header/src/main/java/org/zstack/header/scim/ScimResourceHandler.java b/header/src/main/java/org/zstack/header/scim/ScimResourceHandler.java index 1a46b4c501d..9418d69ec94 100644 --- a/header/src/main/java/org/zstack/header/scim/ScimResourceHandler.java +++ b/header/src/main/java/org/zstack/header/scim/ScimResourceHandler.java @@ -4,4 +4,11 @@ public interface ScimResourceHandler { String normalizeResourceType(String resourceType); void applyResource(ScimOperation operation, String resourceType, String resourceId, ScimPayload payload); + + /** + * Remove resources that were created for the given synchronization type. + * + * @param syncType synchronization type marker, for example SCIM + */ + void cleanupResources(String syncType); } diff --git a/identity/src/main/java/org/zstack/identity/rbac/RBACApiInterceptor.java b/identity/src/main/java/org/zstack/identity/rbac/RBACApiInterceptor.java index 911bacf4aaf..6a451153c2c 100755 --- a/identity/src/main/java/org/zstack/identity/rbac/RBACApiInterceptor.java +++ b/identity/src/main/java/org/zstack/identity/rbac/RBACApiInterceptor.java @@ -3,6 +3,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.zstack.core.componentloader.PluginRegistry; import org.zstack.core.db.Q; +import org.zstack.core.db.SQL; import org.zstack.header.apimediator.ApiMessageInterceptionException; import org.zstack.header.apimediator.ApiMessageInterceptor; import org.zstack.header.errorcode.OperationFailureException; @@ -17,7 +18,9 @@ import org.zstack.header.identity.role.api.APICreateRoleMsg; import org.zstack.header.identity.role.api.APIDeleteRoleMsg; import org.zstack.header.identity.role.api.APIUpdateRoleMsg; +import org.zstack.header.identity.role.api.RoleMessage; import org.zstack.header.message.APIMessage; +import org.zstack.header.resource.ResourceSourceConstant; import org.zstack.identity.CheckIfSessionCanOperationAdminPermission; import org.zstack.utils.gson.JSONObjectUtil; @@ -42,9 +45,36 @@ public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionExcepti validate((APICreateRoleMsg) msg); } + rejectScimRoleMutation(msg); return msg; } + private void rejectScimRoleMutation(APIMessage msg) { + if (!(msg instanceof RoleMessage) || msg instanceof APICreateRoleMsg) { + return; + } + + String roleUuid = ((RoleMessage) msg).getRoleUuid(); + if (roleUuid == null || roleUuid.trim().isEmpty()) { + return; + } + roleUuid = roleUuid.trim(); + + Long count = SQL.New("select count(vo) from ResourceSourceRefVO vo" + + " where vo.resourceUuid = :resourceUuid" + + " and vo.resourceType = :resourceType" + + " and vo.sourceType = :sourceType" + + " and vo.syncType = :syncType", Long.class) + .param("resourceUuid", roleUuid) + .param("resourceType", RoleVO.class.getSimpleName()) + .param("sourceType", ResourceSourceConstant.SOURCE_TYPE_ZIAM) + .param("syncType", ResourceSourceConstant.SYNC_TYPE_SCIM) + .find(); + if (count != null && count > 0) { + throw new ApiMessageInterceptionException(argerr("Role[uuid:%s] is managed by SCIM and cannot be modified locally", roleUuid)); + } + } + private void validate(APICreateRoleMsg msg) { if (msg.getIdentity() == null){ return; diff --git a/sdk/src/main/java/SourceClassMap.java b/sdk/src/main/java/SourceClassMap.java index 62a29adbf6d..9505f621283 100644 --- a/sdk/src/main/java/SourceClassMap.java +++ b/sdk/src/main/java/SourceClassMap.java @@ -356,6 +356,7 @@ public class SourceClassMap { put("org.zstack.header.protocol.Neighbor", "org.zstack.sdk.Neighbor"); put("org.zstack.header.protocol.NetworkRouterAreaRefInventory", "org.zstack.sdk.NetworkRouterAreaRefInventory"); put("org.zstack.header.protocol.RouterAreaInventory", "org.zstack.sdk.RouterAreaInventory"); + put("org.zstack.header.resource.ResourceSourceRefInventory", "org.zstack.sdk.ResourceSourceRefInventory"); put("org.zstack.header.scheduler.SchedulerJobGroupInventory", "org.zstack.sdk.SchedulerJobGroupInventory"); put("org.zstack.header.scheduler.SchedulerJobGroupJobRefInventory", "org.zstack.sdk.SchedulerJobGroupJobRefInventory"); put("org.zstack.header.scheduler.SchedulerJobGroupSchedulerTriggerRefInventory", "org.zstack.sdk.SchedulerJobGroupSchedulerTriggerRefInventory"); @@ -1462,6 +1463,7 @@ public class SourceClassMap { put("org.zstack.sdk.ResourceConfigStruct", "org.zstack.resourceconfig.ResourceConfigStruct"); put("org.zstack.sdk.ResourceExternalBackupInfo", "org.zstack.externalbackup.ResourceExternalBackupInfo"); put("org.zstack.sdk.ResourceInventory", "org.zstack.header.vo.ResourceInventory"); + put("org.zstack.sdk.ResourceSourceRefInventory", "org.zstack.header.resource.ResourceSourceRefInventory"); put("org.zstack.sdk.ResourceSpending", "org.zstack.billing.ResourceSpending"); put("org.zstack.sdk.ResourceStackInventory", "org.zstack.header.cloudformation.ResourceStackInventory"); put("org.zstack.sdk.ResourceStruct", "org.zstack.cloudformation.template.struct.ResourceStruct"); diff --git a/sdk/src/main/java/org/zstack/sdk/QueryResourceSourceRefAction.java b/sdk/src/main/java/org/zstack/sdk/QueryResourceSourceRefAction.java new file mode 100644 index 00000000000..1c25dc32d28 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/QueryResourceSourceRefAction.java @@ -0,0 +1,75 @@ +package org.zstack.sdk; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class QueryResourceSourceRefAction extends QueryAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.QueryResourceSourceRefResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.QueryResourceSourceRefResult value = res.getResult(org.zstack.sdk.QueryResourceSourceRefResult.class); + ret.value = value == null ? new org.zstack.sdk.QueryResourceSourceRefResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "GET"; + info.path = "/resources/source-refs"; + info.needSession = true; + info.needPoll = false; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/QueryResourceSourceRefResult.java b/sdk/src/main/java/org/zstack/sdk/QueryResourceSourceRefResult.java new file mode 100644 index 00000000000..6bd87012413 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/QueryResourceSourceRefResult.java @@ -0,0 +1,22 @@ +package org.zstack.sdk; + + + +public class QueryResourceSourceRefResult { + public java.util.List inventories; + public void setInventories(java.util.List inventories) { + this.inventories = inventories; + } + public java.util.List getInventories() { + return this.inventories; + } + + public java.lang.Long total; + public void setTotal(java.lang.Long total) { + this.total = total; + } + public java.lang.Long getTotal() { + return this.total; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/ResourceSourceRefInventory.java b/sdk/src/main/java/org/zstack/sdk/ResourceSourceRefInventory.java new file mode 100644 index 00000000000..0ebe46a3379 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/ResourceSourceRefInventory.java @@ -0,0 +1,87 @@ +package org.zstack.sdk; + + + +public class ResourceSourceRefInventory { + + public java.lang.String uuid; + public void setUuid(java.lang.String uuid) { + this.uuid = uuid; + } + public java.lang.String getUuid() { + return this.uuid; + } + + public java.lang.String resourceUuid; + public void setResourceUuid(java.lang.String resourceUuid) { + this.resourceUuid = resourceUuid; + } + public java.lang.String getResourceUuid() { + return this.resourceUuid; + } + + public java.lang.String resourceType; + public void setResourceType(java.lang.String resourceType) { + this.resourceType = resourceType; + } + public java.lang.String getResourceType() { + return this.resourceType; + } + + public java.lang.String sourceType; + public void setSourceType(java.lang.String sourceType) { + this.sourceType = sourceType; + } + public java.lang.String getSourceType() { + return this.sourceType; + } + + public java.lang.String sourceName; + public void setSourceName(java.lang.String sourceName) { + this.sourceName = sourceName; + } + public java.lang.String getSourceName() { + return this.sourceName; + } + + public java.lang.String externalUuid; + public void setExternalUuid(java.lang.String externalUuid) { + this.externalUuid = externalUuid; + } + public java.lang.String getExternalUuid() { + return this.externalUuid; + } + + public java.lang.String externalType; + public void setExternalType(java.lang.String externalType) { + this.externalType = externalType; + } + public java.lang.String getExternalType() { + return this.externalType; + } + + public java.lang.String syncType; + public void setSyncType(java.lang.String syncType) { + this.syncType = syncType; + } + public java.lang.String getSyncType() { + return this.syncType; + } + + public java.sql.Timestamp createDate; + public void setCreateDate(java.sql.Timestamp createDate) { + this.createDate = createDate; + } + public java.sql.Timestamp getCreateDate() { + return this.createDate; + } + + public java.sql.Timestamp lastOpDate; + public void setLastOpDate(java.sql.Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } + public java.sql.Timestamp getLastOpDate() { + return this.lastOpDate; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/iam2/api/ConfigureIAM2ScimReceiverAction.java b/sdk/src/main/java/org/zstack/sdk/iam2/api/ConfigureIAM2ScimReceiverAction.java new file mode 100644 index 00000000000..eb4d82f76e2 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/iam2/api/ConfigureIAM2ScimReceiverAction.java @@ -0,0 +1,108 @@ +package org.zstack.sdk.iam2.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class ConfigureIAM2ScimReceiverAction extends AbstractAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.iam2.api.ConfigureIAM2ScimReceiverResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.Boolean enabled; + + @Param(required = false, maxLength = 2048, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String token; + + @Param(required = false, maxLength = 2048, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String signatureSecret; + + @Param(required = false) + public java.util.List systemTags; + + @Param(required = false) + public java.util.List userTags; + + @Param(required = false) + public String sessionId; + + @Param(required = false) + public String accessKeyId; + + @Param(required = false) + public String accessKeySecret; + + @Param(required = false) + public String requestIp; + + @NonAPIParam + public long timeout = -1; + + @NonAPIParam + public long pollingInterval = -1; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.iam2.api.ConfigureIAM2ScimReceiverResult value = res.getResult(org.zstack.sdk.iam2.api.ConfigureIAM2ScimReceiverResult.class); + ret.value = value == null ? new org.zstack.sdk.iam2.api.ConfigureIAM2ScimReceiverResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "PUT"; + info.path = "/iam2/scim/receiver/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "configureIAM2ScimReceiver"; + info.morphTransform = "IAM2"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/iam2/api/ConfigureIAM2ScimReceiverResult.java b/sdk/src/main/java/org/zstack/sdk/iam2/api/ConfigureIAM2ScimReceiverResult.java new file mode 100644 index 00000000000..ef78403f1de --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/iam2/api/ConfigureIAM2ScimReceiverResult.java @@ -0,0 +1,14 @@ +package org.zstack.sdk.iam2.api; + + + +public class ConfigureIAM2ScimReceiverResult { + public boolean enabled; + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + public boolean getEnabled() { + return this.enabled; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/iam2/entity/IAM2OrganizationInventory.java b/sdk/src/main/java/org/zstack/sdk/iam2/entity/IAM2OrganizationInventory.java index 2fe5da94bd1..c1f029cca6d 100644 --- a/sdk/src/main/java/org/zstack/sdk/iam2/entity/IAM2OrganizationInventory.java +++ b/sdk/src/main/java/org/zstack/sdk/iam2/entity/IAM2OrganizationInventory.java @@ -93,4 +93,52 @@ public java.util.List getAttributes() { return this.attributes; } + public java.lang.String sourceCategory; + public void setSourceCategory(java.lang.String sourceCategory) { + this.sourceCategory = sourceCategory; + } + public java.lang.String getSourceCategory() { + return this.sourceCategory; + } + + public java.lang.String sourceType; + public void setSourceType(java.lang.String sourceType) { + this.sourceType = sourceType; + } + public java.lang.String getSourceType() { + return this.sourceType; + } + + public java.lang.String sourceName; + public void setSourceName(java.lang.String sourceName) { + this.sourceName = sourceName; + } + public java.lang.String getSourceName() { + return this.sourceName; + } + + public java.lang.String syncType; + public void setSyncType(java.lang.String syncType) { + this.syncType = syncType; + } + public java.lang.String getSyncType() { + return this.syncType; + } + + public java.lang.String externalUuid; + public void setExternalUuid(java.lang.String externalUuid) { + this.externalUuid = externalUuid; + } + public java.lang.String getExternalUuid() { + return this.externalUuid; + } + + public java.lang.String externalType; + public void setExternalType(java.lang.String externalType) { + this.externalType = externalType; + } + public java.lang.String getExternalType() { + return this.externalType; + } + } diff --git a/sdk/src/main/java/org/zstack/sdk/iam2/entity/IAM2ProjectInventory.java b/sdk/src/main/java/org/zstack/sdk/iam2/entity/IAM2ProjectInventory.java index f262d09a430..d1a5f28bdfe 100644 --- a/sdk/src/main/java/org/zstack/sdk/iam2/entity/IAM2ProjectInventory.java +++ b/sdk/src/main/java/org/zstack/sdk/iam2/entity/IAM2ProjectInventory.java @@ -60,6 +60,54 @@ public java.util.List getAttributes() { return this.attributes; } + public java.lang.String sourceCategory; + public void setSourceCategory(java.lang.String sourceCategory) { + this.sourceCategory = sourceCategory; + } + public java.lang.String getSourceCategory() { + return this.sourceCategory; + } + + public java.lang.String sourceType; + public void setSourceType(java.lang.String sourceType) { + this.sourceType = sourceType; + } + public java.lang.String getSourceType() { + return this.sourceType; + } + + public java.lang.String sourceName; + public void setSourceName(java.lang.String sourceName) { + this.sourceName = sourceName; + } + public java.lang.String getSourceName() { + return this.sourceName; + } + + public java.lang.String syncType; + public void setSyncType(java.lang.String syncType) { + this.syncType = syncType; + } + public java.lang.String getSyncType() { + return this.syncType; + } + + public java.lang.String externalUuid; + public void setExternalUuid(java.lang.String externalUuid) { + this.externalUuid = externalUuid; + } + public java.lang.String getExternalUuid() { + return this.externalUuid; + } + + public java.lang.String externalType; + public void setExternalType(java.lang.String externalType) { + this.externalType = externalType; + } + public java.lang.String getExternalType() { + return this.externalType; + } + public java.lang.String linkedAccountUuid; public void setLinkedAccountUuid(java.lang.String linkedAccountUuid) { this.linkedAccountUuid = linkedAccountUuid; diff --git a/sdk/src/main/java/org/zstack/sdk/iam2/entity/IAM2VirtualIDGroupInventory.java b/sdk/src/main/java/org/zstack/sdk/iam2/entity/IAM2VirtualIDGroupInventory.java index 0cf12cbb273..9243d694a22 100644 --- a/sdk/src/main/java/org/zstack/sdk/iam2/entity/IAM2VirtualIDGroupInventory.java +++ b/sdk/src/main/java/org/zstack/sdk/iam2/entity/IAM2VirtualIDGroupInventory.java @@ -68,4 +68,52 @@ public java.util.List getAttributes() { return this.attributes; } + public java.lang.String sourceCategory; + public void setSourceCategory(java.lang.String sourceCategory) { + this.sourceCategory = sourceCategory; + } + public java.lang.String getSourceCategory() { + return this.sourceCategory; + } + + public java.lang.String sourceType; + public void setSourceType(java.lang.String sourceType) { + this.sourceType = sourceType; + } + public java.lang.String getSourceType() { + return this.sourceType; + } + + public java.lang.String sourceName; + public void setSourceName(java.lang.String sourceName) { + this.sourceName = sourceName; + } + public java.lang.String getSourceName() { + return this.sourceName; + } + + public java.lang.String syncType; + public void setSyncType(java.lang.String syncType) { + this.syncType = syncType; + } + public java.lang.String getSyncType() { + return this.syncType; + } + + public java.lang.String externalUuid; + public void setExternalUuid(java.lang.String externalUuid) { + this.externalUuid = externalUuid; + } + public java.lang.String getExternalUuid() { + return this.externalUuid; + } + + public java.lang.String externalType; + public void setExternalType(java.lang.String externalType) { + this.externalType = externalType; + } + public java.lang.String getExternalType() { + return this.externalType; + } + } diff --git a/sdk/src/main/java/org/zstack/sdk/iam2/entity/IAM2VirtualIDInventory.java b/sdk/src/main/java/org/zstack/sdk/iam2/entity/IAM2VirtualIDInventory.java index b49096b1b3c..8c5080250c4 100644 --- a/sdk/src/main/java/org/zstack/sdk/iam2/entity/IAM2VirtualIDInventory.java +++ b/sdk/src/main/java/org/zstack/sdk/iam2/entity/IAM2VirtualIDInventory.java @@ -68,4 +68,52 @@ public java.util.List getAttributes() { return this.attributes; } + public java.lang.String sourceCategory; + public void setSourceCategory(java.lang.String sourceCategory) { + this.sourceCategory = sourceCategory; + } + public java.lang.String getSourceCategory() { + return this.sourceCategory; + } + + public java.lang.String sourceType; + public void setSourceType(java.lang.String sourceType) { + this.sourceType = sourceType; + } + public java.lang.String getSourceType() { + return this.sourceType; + } + + public java.lang.String sourceName; + public void setSourceName(java.lang.String sourceName) { + this.sourceName = sourceName; + } + public java.lang.String getSourceName() { + return this.sourceName; + } + + public java.lang.String syncType; + public void setSyncType(java.lang.String syncType) { + this.syncType = syncType; + } + public java.lang.String getSyncType() { + return this.syncType; + } + + public java.lang.String externalUuid; + public void setExternalUuid(java.lang.String externalUuid) { + this.externalUuid = externalUuid; + } + public java.lang.String getExternalUuid() { + return this.externalUuid; + } + + public java.lang.String externalType; + public void setExternalType(java.lang.String externalType) { + this.externalType = externalType; + } + public java.lang.String getExternalType() { + return this.externalType; + } + } diff --git a/sdk/src/main/java/org/zstack/sdk/identity/role/RoleInventory.java b/sdk/src/main/java/org/zstack/sdk/identity/role/RoleInventory.java index 912dd627115..8436483ad2e 100644 --- a/sdk/src/main/java/org/zstack/sdk/identity/role/RoleInventory.java +++ b/sdk/src/main/java/org/zstack/sdk/identity/role/RoleInventory.java @@ -85,4 +85,52 @@ public java.util.List getPolicies() { return this.policies; } + public java.lang.String sourceCategory; + public void setSourceCategory(java.lang.String sourceCategory) { + this.sourceCategory = sourceCategory; + } + public java.lang.String getSourceCategory() { + return this.sourceCategory; + } + + public java.lang.String sourceType; + public void setSourceType(java.lang.String sourceType) { + this.sourceType = sourceType; + } + public java.lang.String getSourceType() { + return this.sourceType; + } + + public java.lang.String sourceName; + public void setSourceName(java.lang.String sourceName) { + this.sourceName = sourceName; + } + public java.lang.String getSourceName() { + return this.sourceName; + } + + public java.lang.String syncType; + public void setSyncType(java.lang.String syncType) { + this.syncType = syncType; + } + public java.lang.String getSyncType() { + return this.syncType; + } + + public java.lang.String externalUuid; + public void setExternalUuid(java.lang.String externalUuid) { + this.externalUuid = externalUuid; + } + public java.lang.String getExternalUuid() { + return this.externalUuid; + } + + public java.lang.String externalType; + public void setExternalType(java.lang.String externalType) { + this.externalType = externalType; + } + public java.lang.String getExternalType() { + return this.externalType; + } + } diff --git a/sdk/src/main/java/org/zstack/sdk/identity/role/RoleType.java b/sdk/src/main/java/org/zstack/sdk/identity/role/RoleType.java index 2ce36d8842a..270d338c0d3 100644 --- a/sdk/src/main/java/org/zstack/sdk/identity/role/RoleType.java +++ b/sdk/src/main/java/org/zstack/sdk/identity/role/RoleType.java @@ -6,4 +6,5 @@ public enum RoleType { Predefined, CreatedBySystem, PredefinedBySystem, + None, } From 926c4c14ded8ee2a0b96ffdededa693b9fd0622d Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Sun, 28 Jun 2026 11:25:02 +0800 Subject: [PATCH 33/54] [mgt-ipv6]: support IPv6 management network Squash IPv6 management network feature changes onto latest 5.5.28. Resolves: ZSTAC-79206 Change-Id: I4352b96cb247538cfd59ab5da4f8b1cf16ec570e --- .../compute/allocator/TagAllocatorFlow.java | 3 +- .../compute/host/HostApiInterceptor.java | 67 +- .../compute/vm/VmCreateOnHypervisorFlow.java | 4 +- ...InstantiateResourceForChangeImageFlow.java | 8 +- .../vm/VmInstantiateResourcePostFlow.java | 8 +- .../vm/VmInstantiateResourcePreFlow.java | 8 +- .../compute/vm/VmReleaseResourceFlow.java | 7 +- ...ManagementNetworkIpVersionManagerImpl.java | 145 +++++ .../zstack/compute/zone/ZoneManagerImpl.java | 45 ++ .../zstack/compute/zone/ZoneSystemTags.java | 5 + conf/db/upgrade/V5.5.28__schema.sql | 18 + conf/deploydb.sh | 23 +- conf/deployuidb.sh | 26 +- conf/springConfigXml/DatabaseFacade.xml | 2 +- .../springConfigXml/PrimaryStorageManager.xml | 6 + conf/springConfigXml/SftpBackupStorage.xml | 9 +- conf/springConfigXml/ZoneManager.xml | 16 +- conf/springConfigXml/ceph.xml | 1 + conf/zstack.properties | 3 +- conf/zstack.xml | 1 - .../zstack/console/ConsoleApiInterceptor.java | 17 +- .../zstack/console/ConsoleManagerImpl.java | 110 +++- .../org/zstack/console/ConsoleProxyBase.java | 10 +- .../ManagementServerConsoleProxyBackend.java | 63 +- .../org/zstack/core/CoreGlobalProperty.java | 4 + .../main/java/org/zstack/core/Platform.java | 612 ++++++++++++++++-- .../zstack/core/agent/AgentManagerImpl.java | 8 +- .../zstack/core/ansible/AnsibleRunner.java | 11 +- .../core/ansible/CallBackNetworkChecker.java | 17 +- .../zstack/core/cloudbus/CloudBusImpl3.java | 13 +- .../org/zstack/core/config/GlobalConfig.java | 5 + .../core/config/GlobalConfigConstant.java | 2 + .../core/config/GlobalConfigFacadeImpl.java | 6 +- .../zstack/core/db/DatabaseFacadeImpl.java | 2 +- .../main/java/org/zstack/core/db/GLock.java | 7 + core/src/main/java/org/zstack/core/db/Q.java | 4 +- .../org/zstack/core/db/SimpleQueryImpl.java | 6 + .../org/zstack/core/rest/RESTFacadeImpl.java | 46 +- .../core/search/SearchBackendConstant.java | 7 + .../search/ZStackZSha2JGroupsBackend.java | 12 + .../core/search/ZStackZSha2NodeSelector.java | 21 + .../core/timeout/ApiTimeoutManagerImpl.java | 11 +- .../APIUpdateConsoleProxyAgentMsg.java | 22 + ...UpdateConsoleProxyAgentMsgDoc_zh_cn.groovy | 18 + .../console/ConsoleProxyAgentInventory.java | 20 + .../header/console/ConsoleProxyAgentVO.java | 22 + .../console/UpdateConsoleProxyAgentMsg.java | 18 + .../org/zstack/header/host/APIAddHostMsg.java | 4 +- .../host/APIUpdateHostMsgDoc_zh_cn.groovy | 2 +- .../org/zstack/header/rest/RESTFacade.java | 8 + .../zstack/header/zone/APICreateZoneMsg.java | 2 + .../ManagementNetworkIpVersionManager.java | 56 ++ ...etworkIpVersionResourceExtensionPoint.java | 5 + .../network/l3/L3NetworkApiInterceptor.java | 29 +- .../zstack/appliancevm/ApplianceVmBase.java | 14 +- .../appliancevm/ApplianceVmConstant.java | 1 + .../appliancevm/ApplianceVmFacadeImpl.java | 69 +- .../storage/ceph/CephApiInterceptor.java | 172 ++++- .../java/org/zstack/storage/ceph/MonUri.java | 11 + .../CephBackupStorageMetaDataMaker.java | 9 +- .../ceph/backup/CephBackupStorageMonBase.java | 3 +- .../ceph/primary/CephPrimaryStorageBase.java | 7 +- .../primary/CephPrimaryStorageMonBase.java | 3 +- .../network/service/flat/FlatDhcpBackend.java | 2 + .../kvm/APIAddKVMHostMsgDoc_zh_cn.groovy | 2 +- .../kvm/APIUpdateKVMHostMsgDoc_zh_cn.groovy | 2 +- .../kvm/KVMConsoleHypervisorBackend.java | 7 +- .../src/main/java/org/zstack/kvm/KVMHost.java | 108 ++-- .../java/org/zstack/kvm/KVMHostFactory.java | 35 +- .../java/org/zstack/kvm/KVMHostUtils.java | 5 + .../zstack/kvm/KvmHostIpmiPowerExecutor.java | 8 +- .../kvm/VmCpuVendorKvmStartVmExtension.java | 13 +- .../primary/local/LocalStorageConstants.java | 3 +- .../primary/nfs/NfsApiParamChecker.java | 41 +- .../backup/sftp/SftpBackupStorage.java | 3 +- .../sftp/SftpBackupStorageApiInterceptor.java | 81 ++- .../sftp/SftpBackupStorageMetaDataMaker.java | 3 +- .../service/virtualrouter/VirtualRouter.java | 6 +- .../VirtualRouterApiInterceptor.java | 5 - .../virtualrouter/VirtualRouterManager.java | 9 + .../VirtualRouterManagerImpl.java | 79 +-- .../VirtualRouterDeployAgentFlow.java | 4 +- .../virtualrouter/vyos/VyosConnectFlow.java | 2 +- .../APICreateVxlanPoolRemoteVtepMsg.java | 2 +- .../APIDeleteVxlanPoolRemoteVtepMsg.java | 2 +- .../VxlanNetworkPoolConstant.java | 1 + .../VxlanPoolApiInterceptor.java | 40 +- .../java/org/zstack/storage/zbs/MdsUri.java | 5 +- .../org/zstack/storage/zbs/ZbsAgentUrl.java | 26 +- .../ManagementNodeManagerImpl.java | 19 + .../zstack/resourceconfig/ResourceConfig.java | 5 + .../ResourceConfigFacadeImpl.java | 8 +- .../sdk/ConsoleProxyAgentInventory.java | 16 + .../sdk/CreateVxlanPoolRemoteVtepAction.java | 2 +- .../sdk/DeleteVxlanPoolRemoteVtepAction.java | 2 +- .../SetIpOnHostNetworkInterfaceAction.java | 3 + .../sdk/UpdateConsoleProxyAgentAction.java | 6 + ...ementNetworkIpVersionStorageExtension.java | 68 ++ .../backup/BackupStorageApiInterceptor.java | 14 + .../primary/PrimaryStorageApiInterceptor.java | 50 ++ .../PrimaryStorageFeatureAllocatorFlow.java | 4 +- .../PrimaryStorageTagAllocatorFlow.java | 7 +- .../host/HostApiInterceptorIpv6Case.groovy | 33 + .../zstack/kvm/KVMHostFactoryIpv6Case.groovy | 21 + .../appliancevm/ApplianceVmIpv6Case.groovy | 85 +++ .../console/ConsoleProxyCase.groovy | 58 ++ .../core/ManagementNetworkIpv6Case.groovy | 597 +++++++++++++++++ .../test/integration/core/MustFailCase.groovy | 2 + .../kvm/host/KvmHostIpv6Case.groovy | 149 +++++ .../l3network/ipv6/IPv6DhcpCase.groovy | 4 +- .../l3network/ipv6/Ipv6RangeCase.groovy | 56 +- .../AddRemoteVxlanVtepIpCase.groovy | 141 ++-- .../vxlanNetwork/AddVxlanVtepIpCase.groovy | 77 +++ ...tworkIpVersionStorageConstraintCase.groovy | 394 +++++++++++ .../addon/zbs/ZbsPrimaryStorageCase.groovy | 33 + .../unittest/utils/NetworkUtilsCase.groovy | 27 + .../org/zstack/test/kvm/KVMHostUtilsTest.java | 7 + .../unitTestSuiteXml/BackupStorageManager.xml | 9 +- .../main/java/org/zstack/utils/TagUtils.java | 117 +++- .../java/org/zstack/utils/URLBuilder.java | 7 +- .../CloudOperationsErrorCode.java | 98 +++ .../zstack/utils/network/IPv6Constants.java | 1 + .../utils/network/IPv6NetworkUtils.java | 74 +++ .../ManagementNetworkIpVersionUtils.java | 105 +++ .../zstack/utils/network/NetworkUtils.java | 38 +- .../main/java/org/zstack/utils/ssh/Ssh.java | 5 +- .../java/org/zstack/utils/ssh/SshShell.java | 31 +- .../org/zstack/utils/zsha2/ZSha2Helper.java | 2 +- 128 files changed, 4295 insertions(+), 468 deletions(-) create mode 100644 compute/src/main/java/org/zstack/compute/zone/ManagementNetworkIpVersionManagerImpl.java create mode 100644 core/src/main/java/org/zstack/core/search/SearchBackendConstant.java create mode 100644 core/src/main/java/org/zstack/core/search/ZStackZSha2JGroupsBackend.java create mode 100644 core/src/main/java/org/zstack/core/search/ZStackZSha2NodeSelector.java create mode 100644 header/src/main/java/org/zstack/header/zone/ManagementNetworkIpVersionManager.java create mode 100644 header/src/main/java/org/zstack/header/zone/ManagementNetworkIpVersionResourceExtensionPoint.java create mode 100644 storage/src/main/java/org/zstack/storage/ManagementNetworkIpVersionStorageExtension.java create mode 100644 test/src/test/groovy/org/zstack/compute/host/HostApiInterceptorIpv6Case.groovy create mode 100644 test/src/test/groovy/org/zstack/kvm/KVMHostFactoryIpv6Case.groovy create mode 100644 test/src/test/groovy/org/zstack/test/integration/appliancevm/ApplianceVmIpv6Case.groovy create mode 100644 test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy create mode 100644 test/src/test/groovy/org/zstack/test/integration/kvm/host/KvmHostIpv6Case.groovy create mode 100644 test/src/test/groovy/org/zstack/test/integration/storage/ManagementNetworkIpVersionStorageConstraintCase.groovy create mode 100644 utils/src/main/java/org/zstack/utils/network/ManagementNetworkIpVersionUtils.java diff --git a/compute/src/main/java/org/zstack/compute/allocator/TagAllocatorFlow.java b/compute/src/main/java/org/zstack/compute/allocator/TagAllocatorFlow.java index 0d046d2cc7e..c98bb3403bf 100755 --- a/compute/src/main/java/org/zstack/compute/allocator/TagAllocatorFlow.java +++ b/compute/src/main/java/org/zstack/compute/allocator/TagAllocatorFlow.java @@ -40,7 +40,7 @@ public class TagAllocatorFlow extends AbstractHostAllocatorFlow { private List instanceOfferingExtensions; private List diskOfferingExtensions; - public TagAllocatorFlow() { + private void loadExtensions() { instanceOfferingExtensions = pluginRgty.getExtensionList(InstanceOfferingTagAllocatorExtensionPoint.class); diskOfferingExtensions = pluginRgty.getExtensionList(DiskOfferingTagAllocatorExtensionPoint.class); } @@ -48,6 +48,7 @@ public TagAllocatorFlow() { @Override public void allocate() { throwExceptionIfIAmTheFirstFlow(); + loadExtensions(); if (!instanceOfferingExtensions.isEmpty()) { SimpleQuery q = dbf.createQuery(SystemTagVO.class); diff --git a/compute/src/main/java/org/zstack/compute/host/HostApiInterceptor.java b/compute/src/main/java/org/zstack/compute/host/HostApiInterceptor.java index 0bbbe166d1c..60e19d32371 100755 --- a/compute/src/main/java/org/zstack/compute/host/HostApiInterceptor.java +++ b/compute/src/main/java/org/zstack/compute/host/HostApiInterceptor.java @@ -11,10 +11,14 @@ import org.zstack.header.apimediator.ApiMessageInterceptionException; import org.zstack.header.apimediator.ApiMessageInterceptor; import org.zstack.header.apimediator.StopRoutingException; +import org.zstack.header.cluster.ClusterVO; +import org.zstack.header.cluster.ClusterVO_; import org.zstack.header.host.*; import org.zstack.header.message.APIMessage; +import org.zstack.header.zone.ManagementNetworkIpVersionManager; import org.zstack.utils.ShellResult; import org.zstack.utils.ShellUtils; +import org.zstack.utils.network.IPv6NetworkUtils; import org.zstack.utils.network.NetworkUtils; import static org.zstack.core.Platform.argerr; @@ -28,12 +32,19 @@ * To change this template use File | Settings | File Templates. */ public class HostApiInterceptor implements ApiMessageInterceptor { + private static final String INVALID_MANAGEMENT_IP_ERROR = + "managementIp[%s] is not a valid IPv4 address, IPv6 address, or hostname"; + private static final String RESERVED_MANAGEMENT_IPV6_ERROR = + "managementIp[%s] is an IPv6 address that cannot be used as a management address"; + @Autowired private CloudBus bus; @Autowired private ErrorFacade errf; @Autowired private DatabaseFacade dbf; + @Autowired + private ManagementNetworkIpVersionManager managementNetworkIpVersionManager; private void setServiceId(APIMessage msg) { if (msg instanceof HostMessage) { @@ -112,18 +123,70 @@ private void validate(APIDeleteHostMsg msg) { private void validate(APIUpdateHostMsg msg) { if (msg.getManagementIp() != null) { + msg.setManagementIp(validateManagementEndpoint(msg.getManagementIp())); + SimpleQuery q = dbf.createQuery(HostVO.class); q.add(HostVO_.managementIp, Op.EQ, msg.getManagementIp()); if (q.isExists()) { throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_HOST_10112, "there has been a host having managementIp[%s]", msg.getManagementIp())); } + + String zoneUuid = Q.New(HostVO.class) + .select(HostVO_.zoneUuid) + .eq(HostVO_.uuid, msg.getUuid()) + .findValue(); + managementNetworkIpVersionManager.validateEndpointInZone(zoneUuid, msg.getManagementIp(), + "host", msg.getUuid(), ORG_ZSTACK_COMPUTE_HOST_10130); } } private void validate(APIAddHostMsg msg) { - if (!NetworkUtils.isIpv4Address(msg.getManagementIp()) && !NetworkUtils.isHostname(msg.getManagementIp())) { - throw new ApiMessageInterceptionException(argerr(ORG_ZSTACK_COMPUTE_HOST_10113, "managementIp[%s] is neither an IPv4 address nor a valid hostname", msg.getManagementIp())); + validateManagementEndpoint(msg); + String zoneUuid = Q.New(ClusterVO.class) + .select(ClusterVO_.zoneUuid) + .eq(ClusterVO_.uuid, msg.getClusterUuid()) + .findValue(); + managementNetworkIpVersionManager.validateEndpointInZone(zoneUuid, msg.getManagementIp(), + "host", msg.getName(), ORG_ZSTACK_COMPUTE_HOST_10130); + } + + static void validateManagementEndpoint(APIAddHostMsg msg) { + String managementIp = msg.getManagementIp(); + msg.setManagementIp(validateManagementEndpoint(managementIp)); + } + + static String validateManagementEndpoint(String managementIp) { + if (IPv6NetworkUtils.isIpv6Address(managementIp)) { + if (!IPv6NetworkUtils.isValidManagementIpv6Address(managementIp)) { + throw new ApiMessageInterceptionException(argerr( + ORG_ZSTACK_COMPUTE_HOST_10129, + RESERVED_MANAGEMENT_IPV6_ERROR, + managementIp)); + } + } else if (!isValidManagementEndpoint(managementIp)) { + throw new ApiMessageInterceptionException(argerr( + ORG_ZSTACK_COMPUTE_HOST_10128, + INVALID_MANAGEMENT_IP_ERROR, + managementIp)); } + + if (IPv6NetworkUtils.isIpv6Address(managementIp)) { + return IPv6NetworkUtils.getIpv6AddressCanonicalString(managementIp); + } + + return managementIp; + } + + static String getManagementEndpointValidationErrorCode(String managementIp) { + if (IPv6NetworkUtils.isIpv6Address(managementIp)) { + return IPv6NetworkUtils.isValidManagementIpv6Address(managementIp) ? null : ORG_ZSTACK_COMPUTE_HOST_10129; + } + + return isValidManagementEndpoint(managementIp) ? null : ORG_ZSTACK_COMPUTE_HOST_10128; + } + + private static boolean isValidManagementEndpoint(String endpoint) { + return IPv6NetworkUtils.isValidManagementEndpoint(endpoint); } private void validate(APIChangeHostStateMsg msg){ diff --git a/compute/src/main/java/org/zstack/compute/vm/VmCreateOnHypervisorFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmCreateOnHypervisorFlow.java index 5b054dede75..6a40c15c34b 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmCreateOnHypervisorFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmCreateOnHypervisorFlow.java @@ -33,10 +33,8 @@ public class VmCreateOnHypervisorFlow implements Flow { @Autowired private EventFacade evtf; - private final List exts = pluginRgty.getExtensionList(VmBeforeCreateOnHypervisorExtensionPoint.class); - private void fireExtensions(VmInstanceSpec spec) { - for (VmBeforeCreateOnHypervisorExtensionPoint ext : exts) { + for (VmBeforeCreateOnHypervisorExtensionPoint ext : pluginRgty.getExtensionList(VmBeforeCreateOnHypervisorExtensionPoint.class)) { ext.beforeCreateVmOnHypervisor(spec); } } diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstantiateResourceForChangeImageFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmInstantiateResourceForChangeImageFlow.java index bbdb2978036..dbf1b574e35 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstantiateResourceForChangeImageFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstantiateResourceForChangeImageFlow.java @@ -25,8 +25,9 @@ public class VmInstantiateResourceForChangeImageFlow implements Flow { @Autowired private PluginRegistry pluginRgty; - private final List extensions = pluginRgty.getExtensionList(ChangeVmImageExtensionPoint.class); - + private List getExtensions() { + return pluginRgty.getExtensionList(ChangeVmImageExtensionPoint.class); + } private void runExtensions(final Iterator it, final VmInstanceSpec spec, final FlowTrigger chain) { if (!it.hasNext()) { @@ -53,6 +54,7 @@ public void fail(ErrorCode errorCode) { @Override public void run(FlowTrigger chain, Map data) { VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString()); + List extensions = getExtensions(); for (ChangeVmImageExtensionPoint extp : extensions) { try { extp.preBeforeInstantiateVmResource(spec); @@ -89,6 +91,6 @@ public void fail(ErrorCode errorCode) { @Override public void rollback(FlowRollback chain, Map data) { VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString()); - rollbackExtensions(extensions.iterator(), spec, chain); + rollbackExtensions(getExtensions().iterator(), spec, chain); } } diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstantiateResourcePostFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmInstantiateResourcePostFlow.java index e6a7f14c34d..a5f8e46bd54 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstantiateResourcePostFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstantiateResourcePostFlow.java @@ -28,11 +28,13 @@ public class VmInstantiateResourcePostFlow implements Flow { @Autowired private PluginRegistry pluginRgty; - private final List extensions = pluginRgty.getExtensionList(PostVmInstantiateResourceExtensionPoint.class); - + private List getExtensions() { + return pluginRgty.getExtensionList(PostVmInstantiateResourceExtensionPoint.class); + } public void run(FlowTrigger trigger, Map data) { VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString()); + List extensions = getExtensions(); for (PostVmInstantiateResourceExtensionPoint ext : extensions) { ext.postBeforeInstantiateVmResource(spec); } @@ -64,7 +66,7 @@ public void fail(ErrorCode errorCode) { @Override public void rollback(FlowRollback trigger, Map data) { VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString()); - rollbackExtensions(extensions.iterator(), spec, trigger); + rollbackExtensions(getExtensions().iterator(), spec, trigger); } private void rollbackExtensions(final Iterator iterator, final VmInstanceSpec spec, final FlowRollback trigger) { diff --git a/compute/src/main/java/org/zstack/compute/vm/VmInstantiateResourcePreFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmInstantiateResourcePreFlow.java index 4c7b6eee38f..4f4f7e6ff63 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmInstantiateResourcePreFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmInstantiateResourcePreFlow.java @@ -29,8 +29,9 @@ public class VmInstantiateResourcePreFlow implements Flow { @Autowired private PluginRegistry pluginRgty; - private final List extensions = pluginRgty.getExtensionList(PreVmInstantiateResourceExtensionPoint.class); - + private List getExtensions() { + return pluginRgty.getExtensionList(PreVmInstantiateResourceExtensionPoint.class); + } private void runExtensions(final Iterator it, final VmInstanceSpec spec, final FlowTrigger chain) { if (!it.hasNext()) { @@ -61,6 +62,7 @@ public void fail(ErrorCode errorCode) { @Override public void run(FlowTrigger chain, Map data) { VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString()); + List extensions = getExtensions(); for (PreVmInstantiateResourceExtensionPoint extp : extensions) { try { extp.preBeforeInstantiateVmResource(spec); @@ -97,6 +99,6 @@ public void fail(ErrorCode errorCode) { @Override public void rollback(FlowRollback chain, Map data) { VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString()); - rollbackExtensions(extensions.iterator(), spec, chain); + rollbackExtensions(getExtensions().iterator(), spec, chain); } } diff --git a/compute/src/main/java/org/zstack/compute/vm/VmReleaseResourceFlow.java b/compute/src/main/java/org/zstack/compute/vm/VmReleaseResourceFlow.java index a57b93acde1..119ffdb74f5 100755 --- a/compute/src/main/java/org/zstack/compute/vm/VmReleaseResourceFlow.java +++ b/compute/src/main/java/org/zstack/compute/vm/VmReleaseResourceFlow.java @@ -26,9 +26,10 @@ public class VmReleaseResourceFlow implements Flow { @Autowired private PluginRegistry pluginRgty; - - private final List extensions = pluginRgty.getExtensionList(VmReleaseResourceExtensionPoint.class); + private List getExtensions() { + return pluginRgty.getExtensionList(VmReleaseResourceExtensionPoint.class); + } private void fireExtensions(final Iterator it, final VmInstanceSpec spec, final Map ctx, final FlowTrigger chain) { if (!it.hasNext()) { @@ -53,7 +54,7 @@ public void fail(ErrorCode errorCode) { @Override public void run(FlowTrigger chain, Map data) { VmInstanceSpec spec = (VmInstanceSpec) data.get(VmInstanceConstant.Params.VmInstanceSpec.toString()); - fireExtensions(extensions.iterator(), spec, data, chain); + fireExtensions(getExtensions().iterator(), spec, data, chain); } @Override diff --git a/compute/src/main/java/org/zstack/compute/zone/ManagementNetworkIpVersionManagerImpl.java b/compute/src/main/java/org/zstack/compute/zone/ManagementNetworkIpVersionManagerImpl.java new file mode 100644 index 00000000000..7117791bfb4 --- /dev/null +++ b/compute/src/main/java/org/zstack/compute/zone/ManagementNetworkIpVersionManagerImpl.java @@ -0,0 +1,145 @@ +package org.zstack.compute.zone; + +import org.springframework.beans.factory.annotation.Autowired; +import org.zstack.core.componentloader.PluginRegistry; +import org.zstack.core.db.DatabaseFacade; +import org.zstack.core.db.Q; +import org.zstack.header.apimediator.ApiMessageInterceptionException; +import org.zstack.header.host.HostVO; +import org.zstack.header.host.HostVO_; +import org.zstack.header.tag.SystemTagInventory; +import org.zstack.header.zone.ManagementNetworkIpVersionManager; +import org.zstack.header.zone.ManagementNetworkIpVersionResourceExtensionPoint; +import org.zstack.header.zone.ZoneVO; +import org.zstack.utils.network.ManagementNetworkIpVersionUtils; + +import java.util.List; +import java.util.Map; + +import static org.zstack.core.Platform.argerr; +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_COMPUTE_ZONE_10004; +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_COMPUTE_ZONE_10005; +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_COMPUTE_ZONE_10006; +import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.ORG_ZSTACK_COMPUTE_ZONE_10007; + +public class ManagementNetworkIpVersionManagerImpl implements ManagementNetworkIpVersionManager { + private static final String DEFAULT_ZONE_IP_VERSION = ManagementNetworkIpVersionUtils.IPV4; + private static final String HOST_RESOURCE_TYPE = "host"; + private static final String MISMATCH_ERROR = + "%s[%s] uses %s endpoint[%s], but zone[uuid:%s] management network ipVersion is %s"; + + @Autowired + private DatabaseFacade dbf; + @Autowired + private PluginRegistry pluginRgty; + + @Override + public void validateEndpointInZone(String zoneUuid, String endpoint, String resourceType, String resourceIdentity, String errorCode) { + if (zoneUuid == null || endpoint == null) { + return; + } + + validateEndpointMatchesIpVersion(zoneUuid, getZoneIpVersion(zoneUuid), endpoint, resourceType, resourceIdentity, errorCode); + } + + @Override + public void validateEndpointMatchesIpVersion(String zoneUuid, String ipVersion, String endpoint, + String resourceType, String resourceIdentity, String errorCode) { + if (zoneUuid == null || ipVersion == null || endpoint == null) { + return; + } + + if (ManagementNetworkIpVersionUtils.isIpv6LinkLocalEndpoint(endpoint)) { + throw new ApiMessageInterceptionException(argerr( + ORG_ZSTACK_COMPUTE_ZONE_10007, + "%s[%s] uses IPv6 link-local endpoint[%s], which is not supported for zone[uuid:%s] management network", + resourceType, resourceIdentity, endpoint, zoneUuid)); + } + + String endpointIpVersion = ManagementNetworkIpVersionUtils.getEndpointIpVersion(endpoint); + if (endpointIpVersion == null || endpointIpVersion.equals(ipVersion)) { + return; + } + + throw new ApiMessageInterceptionException(argerr(errorCode, MISMATCH_ERROR, + resourceType, resourceIdentity, endpointIpVersion, endpoint, zoneUuid, ipVersion)); + } + + @Override + public String getZoneIpVersion(String zoneUuid) { + Map tokens = ZoneSystemTags.MANAGEMENT_NETWORK_IP_VERSION.getTokensByResourceUuid(zoneUuid); + if (tokens == null) { + return DEFAULT_ZONE_IP_VERSION; + } + + return ManagementNetworkIpVersionUtils.normalizeIpVersion( + tokens.get(ZoneSystemTags.MANAGEMENT_NETWORK_IP_VERSION_TOKEN)); + } + + @Override + public void validateZoneCompatibleWithExistingResources(String zoneUuid, String ipVersion) { + validateExistingHosts(zoneUuid, ipVersion); + for (ManagementNetworkIpVersionResourceExtensionPoint ext : + pluginRgty.getExtensionList(ManagementNetworkIpVersionResourceExtensionPoint.class)) { + ext.validateExistingResourcesInZone(zoneUuid, ipVersion); + } + } + + public void validateZoneIpVersionTag(String resourceUuid, String systemTag) { + validateZoneIpVersionTag(resourceUuid, systemTag, null); + } + + public void validateZoneIpVersionTag(String resourceUuid, String systemTag, String excludedTagUuid) { + validateZoneIpVersionTagValue(resourceUuid, systemTag); + validateZoneHasNoOtherIpVersionTag(resourceUuid, excludedTagUuid); + } + + public void validateZoneIpVersionTagValue(String resourceUuid, String systemTag) { + if (!ZoneSystemTags.MANAGEMENT_NETWORK_IP_VERSION.isMatch(systemTag)) { + return; + } + + if (!dbf.isExist(resourceUuid, ZoneVO.class)) { + return; + } + + String ipVersion = ZoneSystemTags.MANAGEMENT_NETWORK_IP_VERSION.getTokenByTag( + systemTag, + ZoneSystemTags.MANAGEMENT_NETWORK_IP_VERSION_TOKEN); + String normalizedIpVersion = ManagementNetworkIpVersionUtils.normalizeIpVersion(ipVersion); + if (normalizedIpVersion == null) { + throw new ApiMessageInterceptionException(argerr( + ORG_ZSTACK_COMPUTE_ZONE_10004, + "management network ipVersion[%s] is invalid, allowed values are [%s, %s]", + ipVersion, + ManagementNetworkIpVersionUtils.IPV4, + ManagementNetworkIpVersionUtils.IPV6)); + } + + validateZoneCompatibleWithExistingResources(resourceUuid, normalizedIpVersion); + } + + private void validateZoneHasNoOtherIpVersionTag(String zoneUuid, String excludedTagUuid) { + for (SystemTagInventory tag : ZoneSystemTags.MANAGEMENT_NETWORK_IP_VERSION.getTagInventories(zoneUuid)) { + if (excludedTagUuid != null && excludedTagUuid.equals(tag.getUuid())) { + continue; + } + + throw new ApiMessageInterceptionException(argerr( + ORG_ZSTACK_COMPUTE_ZONE_10006, + "zone[uuid:%s] already has management network ipVersion system tag[%s], update or delete it before creating another one", + zoneUuid, tag.getTag())); + } + } + + private void validateExistingHosts(String zoneUuid, String ipVersion) { + List hosts = Q.New(HostVO.class) + .eq(HostVO_.zoneUuid, zoneUuid) + .list(); + + for (HostVO host : hosts) { + validateEndpointMatchesIpVersion(zoneUuid, ipVersion, host.getManagementIp(), + HOST_RESOURCE_TYPE, host.getUuid(), ORG_ZSTACK_COMPUTE_ZONE_10005); + } + } +} diff --git a/compute/src/main/java/org/zstack/compute/zone/ZoneManagerImpl.java b/compute/src/main/java/org/zstack/compute/zone/ZoneManagerImpl.java index e450fa75f9b..1537ed30318 100755 --- a/compute/src/main/java/org/zstack/compute/zone/ZoneManagerImpl.java +++ b/compute/src/main/java/org/zstack/compute/zone/ZoneManagerImpl.java @@ -20,9 +20,13 @@ import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.message.APIMessage; import org.zstack.header.message.Message; +import org.zstack.header.tag.AbstractSystemTagOperationJudger; +import org.zstack.header.tag.SystemTagInventory; +import org.zstack.header.tag.SystemTagValidator; import org.zstack.header.zone.*; import org.zstack.search.SearchQuery; import org.zstack.tag.TagManager; +import org.zstack.utils.network.ManagementNetworkIpVersionUtils; import org.zstack.utils.ObjectUtils; import org.zstack.utils.Utils; import org.zstack.utils.logging.CLogger; @@ -51,9 +55,13 @@ public class ZoneManagerImpl extends AbstractService implements ZoneManager { private TagManager tagMgr; @Autowired private ThreadFacade thdf; + @Autowired + private ManagementNetworkIpVersionManagerImpl managementNetworkIpVersionManager; private Map zoneFactories = Collections.synchronizedMap(new HashMap()); private static final Set allowedMessageAfterSoftDeletion = new HashSet(); + private static final String DEFAULT_MANAGEMENT_NETWORK_IP_VERSION_TAG = + String.format("managementNetwork::ipVersion::%s", ManagementNetworkIpVersionUtils.IPV4); static { allowedMessageAfterSoftDeletion.add(ZoneDeletionMsg.class); @@ -156,10 +164,22 @@ protected void scripts() { }.execute(); tagMgr.createTagsFromAPICreateMessage(msg, finalVO.getUuid(), ZoneVO.class.getSimpleName()); + createDefaultManagementNetworkIpVersionTagIfAbsent(finalVO.getUuid()); return ZoneInventory.valueOf(finalVO); } + private void createDefaultManagementNetworkIpVersionTagIfAbsent(String zoneUuid) { + if (ZoneSystemTags.MANAGEMENT_NETWORK_IP_VERSION.getTagInventory(zoneUuid) != null) { + return; + } + + tagMgr.createNonInherentSystemTag( + zoneUuid, + DEFAULT_MANAGEMENT_NETWORK_IP_VERSION_TAG, + ZoneVO.class.getSimpleName()); + } + private void createZone(APICreateZoneMsg msg, ReturnValueCompletion completion) { if (msg.getDefault() == null || !msg.getDefault()) { completion.success(createZoneFromApiMessage(msg)); @@ -225,6 +245,31 @@ private void populateExtensions() { @Override public boolean start() { populateExtensions(); + ZoneSystemTags.MANAGEMENT_NETWORK_IP_VERSION.installValidator(new SystemTagValidator() { + @Override + public void validateSystemTag(String resourceUuid, Class resourceType, String systemTag) { + managementNetworkIpVersionManager.validateZoneIpVersionTagValue(resourceUuid, systemTag); + } + }); + ZoneSystemTags.MANAGEMENT_NETWORK_IP_VERSION.installJudger(new AbstractSystemTagOperationJudger() { + @Override + public void tagPreCreated(SystemTagInventory tag) { + managementNetworkIpVersionManager.validateZoneIpVersionTag( + tag.getResourceUuid(), tag.getTag()); + } + + @Override + public void tagPreUpdated(SystemTagInventory old, SystemTagInventory newTag) { + managementNetworkIpVersionManager.validateZoneIpVersionTag( + newTag.getResourceUuid(), newTag.getTag(), old.getUuid()); + } + + @Override + public void tagPreDeleted(SystemTagInventory tag) { + managementNetworkIpVersionManager.validateZoneCompatibleWithExistingResources( + tag.getResourceUuid(), ManagementNetworkIpVersionUtils.IPV4); + } + }); return true; } diff --git a/compute/src/main/java/org/zstack/compute/zone/ZoneSystemTags.java b/compute/src/main/java/org/zstack/compute/zone/ZoneSystemTags.java index e2d68ff9bd8..8de485355e1 100755 --- a/compute/src/main/java/org/zstack/compute/zone/ZoneSystemTags.java +++ b/compute/src/main/java/org/zstack/compute/zone/ZoneSystemTags.java @@ -10,4 +10,9 @@ public class ZoneSystemTags { public static PatternedSystemTag HOST_RESERVED_CPU_CAPACITY = new PatternedSystemTag("host::reservedCpu::{capacity}", ZoneVO.class); public static PatternedSystemTag HOST_RESERVED_MEMORY_CAPACITY = new PatternedSystemTag("host::reservedMemory::{capacity}", ZoneVO.class); + public static final String MANAGEMENT_NETWORK_IP_VERSION_TOKEN = "ipVersion"; + public static PatternedSystemTag MANAGEMENT_NETWORK_IP_VERSION = new PatternedSystemTag( + String.format("managementNetwork::ipVersion::{%s}", MANAGEMENT_NETWORK_IP_VERSION_TOKEN), + ZoneVO.class + ); } diff --git a/conf/db/upgrade/V5.5.28__schema.sql b/conf/db/upgrade/V5.5.28__schema.sql index 80ba45b1b82..43958fc4b21 100644 --- a/conf/db/upgrade/V5.5.28__schema.sql +++ b/conf/db/upgrade/V5.5.28__schema.sql @@ -165,3 +165,21 @@ CREATE TABLE IF NOT EXISTS `zstack`.`ResourceSourceRefVO` ( KEY `idxResourceSourceRefVOResourceSync` (`resourceType`, `resourceUuid`, `syncType`), KEY `idxResourceSourceRefVOSyncType` (`syncType`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +ALTER TABLE `zstack`.`VtepVO` MODIFY COLUMN `vtepIp` varchar(128) NOT NULL; +ALTER TABLE `zstack`.`RemoteVtepVO` MODIFY COLUMN `vtepIp` varchar(128) NOT NULL; + +INSERT INTO `zstack`.`SystemTagVO` (`uuid`, `resourceUuid`, `resourceType`, `inherent`, `type`, `tag`, `createDate`, `lastOpDate`) +SELECT REPLACE(UUID(), '-', ''), z.uuid, 'ZoneVO', 0, 'System', 'managementNetwork::ipVersion::ipv4', CURRENT_TIMESTAMP(), CURRENT_TIMESTAMP() +FROM `zstack`.`ZoneVO` z +WHERE NOT EXISTS ( + SELECT 1 + FROM `zstack`.`SystemTagVO` st + WHERE st.resourceUuid = z.uuid + AND st.resourceType = 'ZoneVO' + AND st.type = 'System' + AND st.tag LIKE 'managementNetwork::ipVersion::%' +); + +ALTER TABLE `zstack`.`ConsoleProxyAgentVO` ADD COLUMN `consoleProxyOverriddenIpv4` varchar(255) DEFAULT NULL; +ALTER TABLE `zstack`.`ConsoleProxyAgentVO` ADD COLUMN `consoleProxyOverriddenIpv6` varchar(255) DEFAULT NULL; diff --git a/conf/deploydb.sh b/conf/deploydb.sh index 033da079954..c0d27e12d41 100755 --- a/conf/deploydb.sh +++ b/conf/deploydb.sh @@ -8,6 +8,16 @@ password="$2" host="$3" port="$4" zstack_user_password="$5" +mysql_host="$host" + +case "$mysql_host" in + \[*\]) mysql_host="${mysql_host#\[}"; mysql_host="${mysql_host%\]}" ;; +esac + +jdbc_host="$mysql_host" +case "$jdbc_host" in + *:*) jdbc_host="[$jdbc_host]" ;; +esac base=`dirname $0` @@ -30,7 +40,7 @@ if command -v greatdb &> /dev/null; then fi mysql_run() { - $MYSQL --user=$user --password=$password --host=$host --port=$port "$@" + $MYSQL --user=$user --password=$password --host=$mysql_host --port=$port "$@" } if command -v greatdb &> /dev/null; then @@ -67,7 +77,7 @@ mkdir -p $flyway_sql cp $base/db/V0.6__schema.sql $flyway_sql cp $base/db/upgrade/* $flyway_sql -url="jdbc:mysql://$host:$port/zstack" +url="jdbc:mysql://$jdbc_host:$port/zstack" bash $flyway -user=$user -password=$password -url=$url clean @@ -81,7 +91,7 @@ eval "rm -f $flyway_sql/*" cp $base/db/V0.6__schema_buildin_httpserver.sql $flyway_sql -url="jdbc:mysql://$host:$port/zstack_rest" +url="jdbc:mysql://$jdbc_host:$port/zstack_rest" bash $flyway -user=$user -password=$password -url=$url clean bash $flyway -user=$user -password=$password -url=$url migrate @@ -92,7 +102,7 @@ hostname=`hostname` [ -z $zstack_user_password ] && zstack_user_password='' if command -v greatdb &> /dev/null; then - $MYSQL --user=$user --password=$password --host=$host --port=$port << EOF + $MYSQL --user=$user --password=$password --host=$mysql_host --port=$port << EOF drop user if exists zstack; drop user if exists zstack_rest; create user if not exists 'zstack'@'localhost' identified by "$zstack_user_password"; @@ -110,7 +120,7 @@ EOF else db_version=`$MYSQL --version | awk '/Distrib/{print $5}' |awk -F'.' '{print $1}'` if [ $db_version -ge 10 ];then - $MYSQL --user=$user --password=$password --host=$host --port=$port << EOF + $MYSQL --user=$user --password=$password --host=$mysql_host --port=$port << EOF drop user if exists zstack; drop user if exists zstack_rest; create user 'zstack' identified by "$zstack_user_password"; @@ -122,7 +132,7 @@ else flush privileges; EOF else - $MYSQL --user=$user --password=$password --host=$host --port=$port << EOF + $MYSQL --user=$user --password=$password --host=$mysql_host --port=$port << EOF grant usage on *.* to 'zstack'@'localhost'; grant usage on *.* to 'zstack'@'%'; drop user zstack; @@ -137,4 +147,3 @@ EOF EOF fi fi - diff --git a/conf/deployuidb.sh b/conf/deployuidb.sh index 16a7e659576..cba144bdcad 100755 --- a/conf/deployuidb.sh +++ b/conf/deployuidb.sh @@ -6,6 +6,16 @@ password="$2" host="$3" port="$4" zstack_ui_db_password="$5" +mysql_host="$host" + +case "$mysql_host" in + \[*\]) mysql_host="${mysql_host#\[}"; mysql_host="${mysql_host%\]}" ;; +esac + +jdbc_host="$mysql_host" +case "$jdbc_host" in + *:*) jdbc_host="[$jdbc_host]" ;; +esac MYSQL='mysql' @@ -27,18 +37,18 @@ flyway_sql="$base/tools/flyway-3.2.1/sql/" # give grant option to the new management ip after `zstack-ctl change_ip` if command -v greatdb &> /dev/null; then $MYSQL --user=$user --password=$password --port=$port << EOF - grant all privileges on *.* to 'root'@"$host" with grant option; + grant all privileges on *.* to 'root'@"$mysql_host" with grant option; FLUSH PRIVILEGES; EOF else $MYSQL --user=$user --password=$password --port=$port << EOF - grant all privileges on *.* to 'root'@"$host" identified by "$password" with grant option; + grant all privileges on *.* to 'root'@"$mysql_host" identified by "$password" with grant option; FLUSH PRIVILEGES; EOF fi if command -v greatdb &> /dev/null; then - $MYSQL --user=$user --password=$password --host=$host --port=$port << EOF + $MYSQL --user=$user --password=$password --host=$mysql_host --port=$port << EOF grant usage on *.* to 'root'@'localhost'; grant usage on *.* to 'root'@'%'; DROP DATABASE IF EXISTS zstack_ui; @@ -50,7 +60,7 @@ if command -v greatdb &> /dev/null; then flush privileges; EOF else - $MYSQL --user=$user --password=$password --host=$host --port=$port << EOF + $MYSQL --user=$user --password=$password --host=$mysql_host --port=$port << EOF grant usage on *.* to 'root'@'localhost'; grant usage on *.* to 'root'@'%'; DROP DATABASE IF EXISTS zstack_ui; @@ -67,7 +77,7 @@ mkdir -p $flyway_sql ui_schema_path=`echo ~zstack`"/zstack-ui/tmp/WEB-INF/classes/db/migration/" if [ -d $ui_schema_path ]; then cp $ui_schema_path/* $flyway_sql - url="jdbc:mysql://$host:$port/zstack_ui" + url="jdbc:mysql://$jdbc_host:$port/zstack_ui" bash $flyway -user=$user -password=$password -url=$url clean bash $flyway -user=$user -password=$password -url=$url migrate eval "rm -f $flyway_sql/*" @@ -76,7 +86,7 @@ fi hostname=`hostname` if command -v greatdb &> /dev/null; then - $MYSQL --user=$user --password=$password --host=$host --port=$port << EOF + $MYSQL --user=$user --password=$password --host=$mysql_host --port=$port << EOF drop user if exists zstack_ui; create user 'zstack_ui' identified by "$zstack_ui_db_password"; create user if not exists 'zstack_ui'@'localhost' identified by "$zstack_ui_db_password"; @@ -88,7 +98,7 @@ EOF else db_version=`$MYSQL --version | awk '/Distrib/{print $5}' |awk -F'.' '{print $1}'` if [ $db_version -ge 10 ];then - $MYSQL --user=$user --password=$password --host=$host --port=$port << EOF + $MYSQL --user=$user --password=$password --host=$mysql_host --port=$port << EOF drop user if exists zstack_ui; create user 'zstack_ui' identified by "$zstack_ui_db_password"; grant all privileges on zstack_ui.* to zstack_ui@'localhost' identified by "$zstack_ui_db_password"; @@ -96,7 +106,7 @@ else flush privileges; EOF else - $MYSQL --user=$user --password=$password --host=$host --port=$port << EOF + $MYSQL --user=$user --password=$password --host=$mysql_host --port=$port << EOF grant usage on *.* to 'zstack_ui'@'localhost'; grant usage on *.* to 'zstack_ui'@'%'; drop user zstack_ui; diff --git a/conf/springConfigXml/DatabaseFacade.xml b/conf/springConfigXml/DatabaseFacade.xml index d7afee98688..44f93eae20b 100755 --- a/conf/springConfigXml/DatabaseFacade.xml +++ b/conf/springConfigXml/DatabaseFacade.xml @@ -78,7 +78,7 @@ infinispan searchConfig/default-infinispan.xml ${Exclusive.indexUse:false} - jgroups + ${Search.backend:jgroups} searchConfig/jgroup-backend-tcp.xml \n") } - + return out } else { return errorOut(a.call()) @@ -3695,20 +3695,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -5747,19 +5747,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -9823,20 +9824,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -15034,20 +15035,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -19948,19 +19949,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -20136,19 +20138,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -20264,26 +20267,26 @@ abstract class ApiHelper { } - def extendHostCacheStore(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExtendHostCacheStoreAction.class) Closure c) { - def a = new org.zstack.sdk.ExtendHostCacheStoreAction() + def exportBuildApp(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExportBuildAppAction.class) Closure c) { + def a = new org.zstack.sdk.ExportBuildAppAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -20291,8 +20294,8 @@ abstract class ApiHelper { } - def exportBuildApp(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExportBuildAppAction.class) Closure c) { - def a = new org.zstack.sdk.ExportBuildAppAction() + def exportImageFromBackupStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExportImageFromBackupStorageAction.class) Closure c) { + def a = new org.zstack.sdk.ExportImageFromBackupStorageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20318,8 +20321,8 @@ abstract class ApiHelper { } - def exportImageFromBackupStorage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExportImageFromBackupStorageAction.class) Closure c) { - def a = new org.zstack.sdk.ExportImageFromBackupStorageAction() + def exportNbdVolumes(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExportNbdVolumesAction.class) Closure c) { + def a = new org.zstack.sdk.ExportNbdVolumesAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20345,8 +20348,8 @@ abstract class ApiHelper { } - def exportNbdVolumes(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExportNbdVolumesAction.class) Closure c) { - def a = new org.zstack.sdk.ExportNbdVolumesAction() + def exportVmOvaPackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExportVmOvaPackageAction.class) Closure c) { + def a = new org.zstack.sdk.ExportVmOvaPackageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20372,8 +20375,8 @@ abstract class ApiHelper { } - def exportVmOvaPackage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExportVmOvaPackageAction.class) Closure c) { - def a = new org.zstack.sdk.ExportVmOvaPackageAction() + def expungeBaremetalInstance(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeBaremetalInstanceAction.class) Closure c) { + def a = new org.zstack.sdk.ExpungeBaremetalInstanceAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20399,8 +20402,8 @@ abstract class ApiHelper { } - def expungeBaremetalInstance(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeBaremetalInstanceAction.class) Closure c) { - def a = new org.zstack.sdk.ExpungeBaremetalInstanceAction() + def expungeDataVolume(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeDataVolumeAction.class) Closure c) { + def a = new org.zstack.sdk.ExpungeDataVolumeAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20426,8 +20429,8 @@ abstract class ApiHelper { } - def expungeDataVolume(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeDataVolumeAction.class) Closure c) { - def a = new org.zstack.sdk.ExpungeDataVolumeAction() + def expungeImage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeImageAction.class) Closure c) { + def a = new org.zstack.sdk.ExpungeImageAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20453,8 +20456,8 @@ abstract class ApiHelper { } - def expungeImage(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeImageAction.class) Closure c) { - def a = new org.zstack.sdk.ExpungeImageAction() + def expungeImageGroup(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeImageGroupAction.class) Closure c) { + def a = new org.zstack.sdk.ExpungeImageGroupAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20480,8 +20483,8 @@ abstract class ApiHelper { } - def expungeImageGroup(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeImageGroupAction.class) Closure c) { - def a = new org.zstack.sdk.ExpungeImageGroupAction() + def expungeVmInstance(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeVmInstanceAction.class) Closure c) { + def a = new org.zstack.sdk.ExpungeVmInstanceAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20507,8 +20510,8 @@ abstract class ApiHelper { } - def expungeVmInstance(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeVmInstanceAction.class) Closure c) { - def a = new org.zstack.sdk.ExpungeVmInstanceAction() + def expungeVmUserDefinedXmlHookScript(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeVmUserDefinedXmlHookScriptAction.class) Closure c) { + def a = new org.zstack.sdk.ExpungeVmUserDefinedXmlHookScriptAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -20534,8 +20537,8 @@ abstract class ApiHelper { } - def expungeVmUserDefinedXmlHookScript(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExpungeVmUserDefinedXmlHookScriptAction.class) Closure c) { - def a = new org.zstack.sdk.ExpungeVmUserDefinedXmlHookScriptAction() + def extendHostCacheStore(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.ExtendHostCacheStoreAction.class) Closure c) { + def a = new org.zstack.sdk.ExtendHostCacheStoreAction() a.sessionId = Test.currentEnvSpec?.session?.uuid c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a @@ -21458,20 +21461,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -23884,24 +23887,24 @@ abstract class ApiHelper { def getLicenseAuthorizationInfo(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.GetLicenseAuthorizationInfoAction.class) Closure c) { def a = new org.zstack.sdk.GetLicenseAuthorizationInfoAction() - + c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -23915,20 +23918,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -32208,7 +32211,7 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + a.conditions = a.conditions.collect { it.toString() } @@ -32216,14 +32219,14 @@ abstract class ApiHelper { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -35131,6 +35134,35 @@ abstract class ApiHelper { } + def queryResourceSourceRef(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.QueryResourceSourceRefAction.class) Closure c) { + def a = new org.zstack.sdk.QueryResourceSourceRefAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + a.conditions = a.conditions.collect { it.toString() } + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def queryResourceStack(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.QueryResourceStackAction.class) Closure c) { def a = new org.zstack.sdk.QueryResourceStackAction() a.sessionId = Test.currentEnvSpec?.session?.uuid @@ -38042,20 +38074,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -42551,20 +42583,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -45791,20 +45823,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -48410,20 +48442,20 @@ abstract class ApiHelper { c.resolveStrategy = Closure.OWNER_FIRST c.delegate = a c() - + if (System.getProperty("apipath") != null) { if (a.apiId == null) { a.apiId = Platform.uuid } - + def tracker = new ApiPathTracker(a.apiId) def out = errorOut(a.call()) def path = tracker.getApiPath() if (!path.isEmpty()) { Test.apiPaths[a.class.name] = path.join(" --->\n") } - + return out } else { return errorOut(a.call()) @@ -50466,6 +50498,33 @@ abstract class ApiHelper { } + def configureIAM2ScimReceiver(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.iam2.api.ConfigureIAM2ScimReceiverAction.class) Closure c) { + def a = new org.zstack.sdk.iam2.api.ConfigureIAM2ScimReceiverAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def createIAM2Organization(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.iam2.api.CreateIAM2OrganizationAction.class) Closure c) { def a = new org.zstack.sdk.iam2.api.CreateIAM2OrganizationAction() a.sessionId = Test.currentEnvSpec?.session?.uuid From 94eec437110ab2b12f823d718cb9733b276a3aab Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Tue, 30 Jun 2026 00:36:25 +0900 Subject: [PATCH 42/54] [mgt-ipv6]: fix ipv6 regression cases Fix console proxy partial update semantics.\n\nFix CloudBus context path URL normalization.\n\nFix KVM IPv6 error code assertions. Change-Id: I5b005670f63f34a1868a79b69f01f868dd04891a --- .../java/org/zstack/console/ConsoleApiInterceptor.java | 4 ---- .../java/org/zstack/core/cloudbus/CloudBusImpl3.java | 9 +++++++-- .../integration/core/ManagementNetworkIpv6Case.groovy | 2 ++ .../test/integration/kvm/host/KvmHostIpv6Case.groovy | 10 +++++++--- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/console/src/main/java/org/zstack/console/ConsoleApiInterceptor.java b/console/src/main/java/org/zstack/console/ConsoleApiInterceptor.java index a90c5e1b575..f1579143fbf 100755 --- a/console/src/main/java/org/zstack/console/ConsoleApiInterceptor.java +++ b/console/src/main/java/org/zstack/console/ConsoleApiInterceptor.java @@ -66,11 +66,7 @@ private void validate(APIRequestConsoleAccessMsg msg) { } private void validate(APIUpdateConsoleProxyAgentMsg msg) { - // consoleProxyOverriddenIp default value is 0.0.0.0 msg.setConsoleProxyOverriddenIp(normalizeOptionalConsoleProxyHost(msg.getConsoleProxyOverriddenIp())); - if (msg.getConsoleProxyOverriddenIp() == null) { - msg.setConsoleProxyOverriddenIp("0.0.0.0"); - } msg.setConsoleProxyOverriddenIpv4(normalizeOptionalConsoleProxyHost(msg.getConsoleProxyOverriddenIpv4())); msg.setConsoleProxyOverriddenIpv6(normalizeOptionalConsoleProxyHost(msg.getConsoleProxyOverriddenIpv6())); } diff --git a/core/src/main/java/org/zstack/core/cloudbus/CloudBusImpl3.java b/core/src/main/java/org/zstack/core/cloudbus/CloudBusImpl3.java index 5d713331fcf..03179c4994e 100755 --- a/core/src/main/java/org/zstack/core/cloudbus/CloudBusImpl3.java +++ b/core/src/main/java/org/zstack/core/cloudbus/CloudBusImpl3.java @@ -6,6 +6,7 @@ import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.sentry.Sentry; +import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.ThreadContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpEntity; @@ -147,8 +148,12 @@ public class CloudBusImpl3 implements CloudBus, CloudBusIN { public static String buildCloudBusUrl(String ip, int port, String contextPath) { String host = IPv6NetworkUtils.formatHostForUrl(ip); - return contextPath.isEmpty() ? String.format(HTTP_URL_FORMAT, host, port, HTTP_BASE_URL) : - String.format(HTTP_CONTEXT_URL_FORMAT, host, port, contextPath, HTTP_BASE_URL); + if (StringUtils.isBlank(contextPath)) { + return String.format(HTTP_URL_FORMAT, host, port, HTTP_BASE_URL); + } + + String normalizedContextPath = StringUtils.stripEnd(StringUtils.stripStart(contextPath, "/"), "/"); + return String.format(HTTP_CONTEXT_URL_FORMAT, host, port, normalizedContextPath, HTTP_BASE_URL); } private TelemetryFacade telemetryFacade; diff --git a/test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy b/test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy index 48cca16068a..3d8f64a1abe 100644 --- a/test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/core/ManagementNetworkIpv6Case.groovy @@ -197,6 +197,8 @@ class ManagementNetworkIpv6Case extends SubCase { void testCoreManagementUrlsIpv6() { assert CloudBusImpl3.buildCloudBusUrl(IPV6, REST_PORT, "") == "http://[2001:db8::1]:8080/cloudbus" assert CloudBusImpl3.buildCloudBusUrl(IPV6, REST_PORT, "zstack") == "http://[2001:db8::1]:8080/zstack/cloudbus" + assert CloudBusImpl3.buildCloudBusUrl(IPV6, REST_PORT, "/zstack") == "http://[2001:db8::1]:8080/zstack/cloudbus" + assert CloudBusImpl3.buildCloudBusUrl(IPV6, REST_PORT, "/zstack/") == "http://[2001:db8::1]:8080/zstack/cloudbus" assert AgentManagerImpl.buildAgentUrl(IPV6, REST_PORT, "/agent/echo") == "http://[2001:db8::1]:8080/agent/echo" assert AnsibleRunner.buildPipUrl(IPV6, REST_PORT) == "http://[2001:db8::1]:8080/zstack/static/pypi/simple" } diff --git a/test/src/test/groovy/org/zstack/test/integration/kvm/host/KvmHostIpv6Case.groovy b/test/src/test/groovy/org/zstack/test/integration/kvm/host/KvmHostIpv6Case.groovy index 3c3aba7d7b2..f825c4c9747 100644 --- a/test/src/test/groovy/org/zstack/test/integration/kvm/host/KvmHostIpv6Case.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/kvm/host/KvmHostIpv6Case.groovy @@ -4,6 +4,7 @@ import org.zstack.core.Platform import org.zstack.core.cloudbus.CloudBus import org.zstack.core.db.DatabaseFacade import org.zstack.core.db.Q +import org.zstack.header.errorcode.SysErrors import org.zstack.header.host.ConnectHostMsg import org.zstack.header.host.ConnectHostReply import org.zstack.header.host.CpuArchitecture @@ -117,7 +118,8 @@ class KvmHostIpv6Case extends SubCase { def res = action.call() assert res.error != null - assert res.error.code == ORG_ZSTACK_COMPUTE_HOST_10128 + assert res.error.code == SysErrors.INVALID_ARGUMENT_ERROR.toString() + assert res.error.globalErrorCode == ORG_ZSTACK_COMPUTE_HOST_10128 assert Q.New(HostVO.class).count() == before action = new AddKVMHostAction() @@ -131,7 +133,8 @@ class KvmHostIpv6Case extends SubCase { res = action.call() assert res.error != null - assert res.error.code == ORG_ZSTACK_COMPUTE_HOST_10129 + assert res.error.code == SysErrors.INVALID_ARGUMENT_ERROR.toString() + assert res.error.globalErrorCode == ORG_ZSTACK_COMPUTE_HOST_10129 assert Q.New(HostVO.class).count() == before action = new AddKVMHostAction() @@ -145,7 +148,8 @@ class KvmHostIpv6Case extends SubCase { res = action.call() assert res.error != null - assert res.error.code == ORG_ZSTACK_COMPUTE_HOST_10129 + assert res.error.code == SysErrors.INVALID_ARGUMENT_ERROR.toString() + assert res.error.globalErrorCode == ORG_ZSTACK_COMPUTE_HOST_10129 assert Q.New(HostVO.class).count() == before } } From 26aacf97c4030dafa85d1b4ecb36a99fbb8be6c8 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Tue, 30 Jun 2026 01:34:34 +0900 Subject: [PATCH 43/54] [mgt-ipv6]: sync console proxy api doc Mark consoleProxyOverriddenIp optional in zh_cn API doc.\n\nThis matches APIUpdateConsoleProxyAgentMsg and SDK metadata. Resolves: ZSTAC-79206 Change-Id: Id4a5f477218d7a448bd7834d80420978806bb0b2 --- .../console/APIUpdateConsoleProxyAgentMsgDoc_zh_cn.groovy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/header/src/main/java/org/zstack/header/console/APIUpdateConsoleProxyAgentMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/console/APIUpdateConsoleProxyAgentMsgDoc_zh_cn.groovy index 18356acfaaf..dc74fa68c9a 100644 --- a/header/src/main/java/org/zstack/header/console/APIUpdateConsoleProxyAgentMsgDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/console/APIUpdateConsoleProxyAgentMsgDoc_zh_cn.groovy @@ -36,7 +36,7 @@ doc { desc "新的控制台代理IP" location "body" type "String" - optional false + optional true since "2.3" } column { @@ -91,4 +91,4 @@ doc { clz APIUpdateConsoleProxyAgentEvent.class } } -} \ No newline at end of file +} From 32a3de0baa19b4b47e2b83c9726339ded5aa7848 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Tue, 30 Jun 2026 02:17:10 +0900 Subject: [PATCH 44/54] [mgt-ipv6]: match api doc template eof Keep APIUpdateConsoleProxyAgentMsgDoc_zh_cn.groovy byte-identical with the generated template. Resolves: ZSTAC-79206 Change-Id: I0097f2d6c20539921bd485b316bfe3446e5a5582 --- .../console/APIUpdateConsoleProxyAgentMsgDoc_zh_cn.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/header/src/main/java/org/zstack/header/console/APIUpdateConsoleProxyAgentMsgDoc_zh_cn.groovy b/header/src/main/java/org/zstack/header/console/APIUpdateConsoleProxyAgentMsgDoc_zh_cn.groovy index dc74fa68c9a..82816289602 100644 --- a/header/src/main/java/org/zstack/header/console/APIUpdateConsoleProxyAgentMsgDoc_zh_cn.groovy +++ b/header/src/main/java/org/zstack/header/console/APIUpdateConsoleProxyAgentMsgDoc_zh_cn.groovy @@ -91,4 +91,4 @@ doc { clz APIUpdateConsoleProxyAgentEvent.class } } -} +} \ No newline at end of file From ad60d2520b5b251f64ede28bfdb671eb05f90587 Mon Sep 17 00:00:00 2001 From: "shan.wu" Date: Tue, 30 Jun 2026 10:39:04 +0800 Subject: [PATCH 45/54] [migration]: Fix storage migration host filters Introduce MigrateStorage host allocation semantics for storage migration so running VMs get KVM host property checks while stopped VMs skip runtime component version filtering. Resolves: ZSTAC-75332 Change-Id: I6c393d7ec962d71fb5437d7a41d0891deb7abae7 --- .../AttachedVolumePrimaryStorageAllocatorFlow.java | 2 +- .../allocator/HostPrimaryStorageAllocatorFlow.java | 6 +++--- .../java/org/zstack/header/vm/VmInstanceConstant.java | 1 + .../zstack/kvm/KVMHostAllocatorFilterExtensionPoint.java | 8 +++++++- .../primary/local/LocalStorageAllocatorFactory.java | 2 +- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/compute/src/main/java/org/zstack/compute/allocator/AttachedVolumePrimaryStorageAllocatorFlow.java b/compute/src/main/java/org/zstack/compute/allocator/AttachedVolumePrimaryStorageAllocatorFlow.java index e9ffde6e721..b999b06387e 100755 --- a/compute/src/main/java/org/zstack/compute/allocator/AttachedVolumePrimaryStorageAllocatorFlow.java +++ b/compute/src/main/java/org/zstack/compute/allocator/AttachedVolumePrimaryStorageAllocatorFlow.java @@ -32,7 +32,7 @@ public class AttachedVolumePrimaryStorageAllocatorFlow extends AbstractHostAlloc public void allocate() { throwExceptionIfIAmTheFirstFlow(); - if (VmOperation.NewCreate.toString().equals(spec.getVmOperation())) { + if (VmOperation.NewCreate.toString().equals(spec.getVmOperation()) || VmOperation.MigrateStorage.toString().equals(spec.getVmOperation())) { next(candidates); return; } diff --git a/compute/src/main/java/org/zstack/compute/allocator/HostPrimaryStorageAllocatorFlow.java b/compute/src/main/java/org/zstack/compute/allocator/HostPrimaryStorageAllocatorFlow.java index c229cde82ad..35b5c713362 100755 --- a/compute/src/main/java/org/zstack/compute/allocator/HostPrimaryStorageAllocatorFlow.java +++ b/compute/src/main/java/org/zstack/compute/allocator/HostPrimaryStorageAllocatorFlow.java @@ -142,11 +142,11 @@ private List filterHostHavingAccessiblePrimaryStorage(List huuid private List allocateFromCandidates() { List huuids = getHostUuidsFromCandidates(); Set requiredPsUuids = spec.getRequiredPrimaryStorageUuids(); - if (!VmOperation.NewCreate.toString().equals(spec.getVmOperation())) { - return filterHostHavingAccessiblePrimaryStorage(huuids, spec); - } else { + if (VmOperation.NewCreate.toString().equals(spec.getVmOperation()) || VmOperation.MigrateStorage.toString().equals(spec.getVmOperation())) { huuids = filterHostHavingAccessiblePrimaryStorage(huuids, spec) .stream().map(ResourceVO::getUuid).collect(Collectors.toList()); + } else { + return filterHostHavingAccessiblePrimaryStorage(huuids, spec); } if (huuids.isEmpty()) { diff --git a/header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java b/header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java index 61cfe494fab..88ade64b874 100755 --- a/header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java +++ b/header/src/main/java/org/zstack/header/vm/VmInstanceConstant.java @@ -62,6 +62,7 @@ enum VmOperation { Reboot, Destroy, Migrate, + MigrateStorage, AttachVolume, AttachNic, ChangeNicNetwork, diff --git a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostAllocatorFilterExtensionPoint.java b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostAllocatorFilterExtensionPoint.java index 5c12980bb50..4022b7eedb9 100644 --- a/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostAllocatorFilterExtensionPoint.java +++ b/plugin/kvm/src/main/java/org/zstack/kvm/KVMHostAllocatorFilterExtensionPoint.java @@ -5,6 +5,8 @@ import org.zstack.header.allocator.HostAllocatorSpec; import org.zstack.header.host.HostVO; import org.zstack.header.vm.VmInstanceConstant; +import org.zstack.header.vm.VmInstanceConstant.VmOperation; +import org.zstack.header.vm.VmInstanceState; import org.zstack.utils.Utils; import org.zstack.utils.logging.CLogger; @@ -171,7 +173,11 @@ public List filterHostCandidates(List candidates, HostAllocatorS return candidates; } - if (!VmInstanceConstant.VmOperation.Migrate.toString().equals(spec.getVmOperation())) { + if (!VmOperation.Migrate.toString().equals(spec.getVmOperation()) && !VmOperation.MigrateStorage.toString().equals(spec.getVmOperation())) { + return candidates; + } + + if (VmInstanceState.Stopped.toString().equals(spec.getVmInstance().getState())) { return candidates; } diff --git a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageAllocatorFactory.java b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageAllocatorFactory.java index 785b4092144..ca3fd08dc32 100755 --- a/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageAllocatorFactory.java +++ b/plugin/localstorage/src/main/java/org/zstack/storage/primary/local/LocalStorageAllocatorFactory.java @@ -109,7 +109,7 @@ public boolean stop() { public List filterHostCandidates(List candidates, HostAllocatorSpec spec) { long reservedCapacity = SizeUtils.sizeStringToBytes(PrimaryStorageGlobalConfig.RESERVED_CAPACITY.value()); - if (VmOperation.NewCreate.toString().equals(spec.getVmOperation())) { + if (VmOperation.NewCreate.toString().equals(spec.getVmOperation()) || VmOperation.MigrateStorage.toString().equals(spec.getVmOperation())) { List huuids = getNeedCheckHostLocalStorageList(candidates, spec); if (huuids.isEmpty()) { return candidates; From 3e5f9d32f1c0cb3ee8cfd055e2fc87653303cb7d Mon Sep 17 00:00:00 2001 From: "yaohua.wu" Date: Tue, 30 Jun 2026 10:36:26 +0800 Subject: [PATCH 46/54] [zwatch]: backport alarm optimize api to 5.5.28 1. Why? 5.5.28 needs the alarm optimize API and schema changes from the feature branch so premium runtime changes can build and run. 2. How? Add recovery rule fields, alarm resource state schema, SDK inventories, query actions, and testlib helper entry points generated for zwatch. 3. Side effects? Adds V5.5.28 schema changes for AlarmVO and AlarmResourceStateVO. # Summary of changes (by module): - conf: add alarm recovery and resource state schema. - sdk: add alarm resource state SDK and query action. - testlib: add queryAlarmResourceState helper. Related: ZSTAC-86212 Change-Id: Ibd449cb2eac2be8b0e2c661547b989d65eac5943 --- conf/db/upgrade/V5.5.28__schema.sql | 19 +++++ sdk/src/main/java/SourceClassMap.java | 2 + .../sdk/zwatch/alarm/AlarmInventory.java | 16 ++++ .../alarm/AlarmResourceStateInventory.java | 79 +++++++++++++++++++ .../sdk/zwatch/alarm/CreateAlarmAction.java | 6 ++ .../alarm/QueryAlarmResourceStateAction.java | 75 ++++++++++++++++++ .../alarm/QueryAlarmResourceStateResult.java | 22 ++++++ .../sdk/zwatch/alarm/UpdateAlarmAction.java | 6 ++ .../java/org/zstack/testlib/ApiHelper.groovy | 29 +++++++ 9 files changed, 254 insertions(+) create mode 100644 sdk/src/main/java/org/zstack/sdk/zwatch/alarm/AlarmResourceStateInventory.java create mode 100644 sdk/src/main/java/org/zstack/sdk/zwatch/alarm/QueryAlarmResourceStateAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/zwatch/alarm/QueryAlarmResourceStateResult.java diff --git a/conf/db/upgrade/V5.5.28__schema.sql b/conf/db/upgrade/V5.5.28__schema.sql index d25afe0ba7e..bbf92615bfd 100644 --- a/conf/db/upgrade/V5.5.28__schema.sql +++ b/conf/db/upgrade/V5.5.28__schema.sql @@ -200,3 +200,22 @@ WHERE NOT EXISTS ( ALTER TABLE `zstack`.`ConsoleProxyAgentVO` ADD COLUMN `consoleProxyOverriddenIpv4` varchar(255) DEFAULT NULL; ALTER TABLE `zstack`.`ConsoleProxyAgentVO` ADD COLUMN `consoleProxyOverriddenIpv6` varchar(255) DEFAULT NULL; + +ALTER TABLE `zstack`.`AlarmVO` ADD COLUMN `recoveryDuration` int unsigned DEFAULT NULL; +ALTER TABLE `zstack`.`AlarmVO` ADD COLUMN `recoveryThreshold` int unsigned DEFAULT NULL; + +CREATE TABLE IF NOT EXISTS `zstack`.`AlarmResourceStateVO` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `alarmUuid` varchar(32) NOT NULL, + `identifyLabel` varchar(191) NOT NULL, + `resourceUuid` varchar(32) DEFAULT NULL, + `resourceType` varchar(256) DEFAULT NULL, + `status` varchar(32) NOT NULL, + `lastStatusChangeTime` bigint(20) DEFAULT NULL, + `lastOpDate` timestamp ON UPDATE CURRENT_TIMESTAMP, + `createDate` timestamp, + PRIMARY KEY (`id`), + UNIQUE KEY `ukAlarmUuidIdentifyLabel` (`alarmUuid`, `identifyLabel`, `resourceUuid`), + KEY `idxAlarmResourceStateVOresourceUuid` (`resourceUuid`), + CONSTRAINT `fkAlarmResourceStateVOAlarmVO` FOREIGN KEY (`alarmUuid`) REFERENCES `AlarmVO` (`uuid`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/sdk/src/main/java/SourceClassMap.java b/sdk/src/main/java/SourceClassMap.java index 9505f621283..af02fcb2b89 100644 --- a/sdk/src/main/java/SourceClassMap.java +++ b/sdk/src/main/java/SourceClassMap.java @@ -863,6 +863,7 @@ public class SourceClassMap { put("org.zstack.zwatch.alarm.AlarmDataAckInventory", "org.zstack.sdk.zwatch.alarm.AlarmDataAckInventory"); put("org.zstack.zwatch.alarm.AlarmInventory", "org.zstack.sdk.zwatch.alarm.AlarmInventory"); put("org.zstack.zwatch.alarm.AlarmLabelInventory", "org.zstack.sdk.zwatch.alarm.AlarmLabelInventory"); + put("org.zstack.zwatch.alarm.AlarmResourceStateInventory", "org.zstack.sdk.zwatch.alarm.AlarmResourceStateInventory"); put("org.zstack.zwatch.alarm.AlarmState", "org.zstack.sdk.zwatch.alarm.AlarmState"); put("org.zstack.zwatch.alarm.AlarmStatus", "org.zstack.sdk.zwatch.alarm.AlarmStatus"); put("org.zstack.zwatch.alarm.AlertDataAckInventory", "org.zstack.sdk.zwatch.alarm.AlertDataAckInventory"); @@ -1781,6 +1782,7 @@ public class SourceClassMap { put("org.zstack.sdk.zwatch.alarm.AlarmDataAckInventory", "org.zstack.zwatch.alarm.AlarmDataAckInventory"); put("org.zstack.sdk.zwatch.alarm.AlarmInventory", "org.zstack.zwatch.alarm.AlarmInventory"); put("org.zstack.sdk.zwatch.alarm.AlarmLabelInventory", "org.zstack.zwatch.alarm.AlarmLabelInventory"); + put("org.zstack.sdk.zwatch.alarm.AlarmResourceStateInventory", "org.zstack.zwatch.alarm.AlarmResourceStateInventory"); put("org.zstack.sdk.zwatch.alarm.AlarmState", "org.zstack.zwatch.alarm.AlarmState"); put("org.zstack.sdk.zwatch.alarm.AlarmStatus", "org.zstack.zwatch.alarm.AlarmStatus"); put("org.zstack.sdk.zwatch.alarm.AlertDataAckInventory", "org.zstack.zwatch.alarm.AlertDataAckInventory"); diff --git a/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/AlarmInventory.java b/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/AlarmInventory.java index cf641122592..d95ed32f62a 100644 --- a/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/AlarmInventory.java +++ b/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/AlarmInventory.java @@ -110,6 +110,22 @@ public boolean getEnableRecovery() { return this.enableRecovery; } + public java.lang.Integer recoveryDuration; + public void setRecoveryDuration(java.lang.Integer recoveryDuration) { + this.recoveryDuration = recoveryDuration; + } + public java.lang.Integer getRecoveryDuration() { + return this.recoveryDuration; + } + + public java.lang.Integer recoveryThreshold; + public void setRecoveryThreshold(java.lang.Integer recoveryThreshold) { + this.recoveryThreshold = recoveryThreshold; + } + public java.lang.Integer getRecoveryThreshold() { + return this.recoveryThreshold; + } + public java.sql.Timestamp createDate; public void setCreateDate(java.sql.Timestamp createDate) { this.createDate = createDate; diff --git a/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/AlarmResourceStateInventory.java b/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/AlarmResourceStateInventory.java new file mode 100644 index 00000000000..5427b6a9856 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/AlarmResourceStateInventory.java @@ -0,0 +1,79 @@ +package org.zstack.sdk.zwatch.alarm; + +import org.zstack.sdk.zwatch.alarm.AlarmStatus; + +public class AlarmResourceStateInventory { + + public long id; + public void setId(long id) { + this.id = id; + } + public long getId() { + return this.id; + } + + public java.lang.String alarmUuid; + public void setAlarmUuid(java.lang.String alarmUuid) { + this.alarmUuid = alarmUuid; + } + public java.lang.String getAlarmUuid() { + return this.alarmUuid; + } + + public java.lang.String identifyLabel; + public void setIdentifyLabel(java.lang.String identifyLabel) { + this.identifyLabel = identifyLabel; + } + public java.lang.String getIdentifyLabel() { + return this.identifyLabel; + } + + public java.lang.String resourceUuid; + public void setResourceUuid(java.lang.String resourceUuid) { + this.resourceUuid = resourceUuid; + } + public java.lang.String getResourceUuid() { + return this.resourceUuid; + } + + public java.lang.String resourceType; + public void setResourceType(java.lang.String resourceType) { + this.resourceType = resourceType; + } + public java.lang.String getResourceType() { + return this.resourceType; + } + + public AlarmStatus status; + public void setStatus(AlarmStatus status) { + this.status = status; + } + public AlarmStatus getStatus() { + return this.status; + } + + public java.lang.Long lastStatusChangeTime; + public void setLastStatusChangeTime(java.lang.Long lastStatusChangeTime) { + this.lastStatusChangeTime = lastStatusChangeTime; + } + public java.lang.Long getLastStatusChangeTime() { + return this.lastStatusChangeTime; + } + + public java.sql.Timestamp createDate; + public void setCreateDate(java.sql.Timestamp createDate) { + this.createDate = createDate; + } + public java.sql.Timestamp getCreateDate() { + return this.createDate; + } + + public java.sql.Timestamp lastOpDate; + public void setLastOpDate(java.sql.Timestamp lastOpDate) { + this.lastOpDate = lastOpDate; + } + public java.sql.Timestamp getLastOpDate() { + return this.lastOpDate; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/CreateAlarmAction.java b/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/CreateAlarmAction.java index e61033eabf4..7791c064579 100644 --- a/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/CreateAlarmAction.java +++ b/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/CreateAlarmAction.java @@ -64,6 +64,12 @@ public Result throwExceptionIfError() { @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.lang.Boolean enableRecovery = false; + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, numberRange = {1L,31536000L}, noTrim = false) + public java.lang.Integer recoveryDuration; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, numberRange = {1L,100L}, noTrim = false) + public java.lang.Integer recoveryThreshold; + @Param(required = false, validValues = {"Emergent","Important","Normal"}, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.lang.String emergencyLevel = "Important"; diff --git a/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/QueryAlarmResourceStateAction.java b/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/QueryAlarmResourceStateAction.java new file mode 100644 index 00000000000..55937ebf279 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/QueryAlarmResourceStateAction.java @@ -0,0 +1,75 @@ +package org.zstack.sdk.zwatch.alarm; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class QueryAlarmResourceStateAction extends QueryAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.zwatch.alarm.QueryAlarmResourceStateResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.zwatch.alarm.QueryAlarmResourceStateResult value = res.getResult(org.zstack.sdk.zwatch.alarm.QueryAlarmResourceStateResult.class); + ret.value = value == null ? new org.zstack.sdk.zwatch.alarm.QueryAlarmResourceStateResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "GET"; + info.path = "/zwatch/alarms/resource-states"; + info.needSession = true; + info.needPoll = false; + info.parameterName = ""; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/QueryAlarmResourceStateResult.java b/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/QueryAlarmResourceStateResult.java new file mode 100644 index 00000000000..82b25a72da3 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/QueryAlarmResourceStateResult.java @@ -0,0 +1,22 @@ +package org.zstack.sdk.zwatch.alarm; + + + +public class QueryAlarmResourceStateResult { + public java.util.List inventories; + public void setInventories(java.util.List inventories) { + this.inventories = inventories; + } + public java.util.List getInventories() { + return this.inventories; + } + + public java.lang.Long total; + public void setTotal(java.lang.Long total) { + this.total = total; + } + public java.lang.Long getTotal() { + return this.total; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/UpdateAlarmAction.java b/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/UpdateAlarmAction.java index 29656c5c256..3b4fe0ebbca 100644 --- a/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/UpdateAlarmAction.java +++ b/sdk/src/main/java/org/zstack/sdk/zwatch/alarm/UpdateAlarmAction.java @@ -52,6 +52,12 @@ public Result throwExceptionIfError() { @Param(required = false, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.lang.Boolean enableRecovery; + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, numberRange = {1L,31536000L}, noTrim = false) + public java.lang.Integer recoveryDuration; + + @Param(required = false, nonempty = false, nullElements = false, emptyString = true, numberRange = {1L,100L}, noTrim = false) + public java.lang.Integer recoveryThreshold; + @Param(required = false, validValues = {"Emergent","Important","Normal"}, nonempty = false, nullElements = false, emptyString = true, noTrim = false) public java.lang.String emergencyLevel; diff --git a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy index 170fe18cd98..6c5125898af 100644 --- a/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy +++ b/testlib/src/main/java/org/zstack/testlib/ApiHelper.groovy @@ -56092,6 +56092,35 @@ abstract class ApiHelper { } + def queryAlarmResourceState(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zwatch.alarm.QueryAlarmResourceStateAction.class) Closure c) { + def a = new org.zstack.sdk.zwatch.alarm.QueryAlarmResourceStateAction() + a.sessionId = Test.currentEnvSpec?.session?.uuid + c.resolveStrategy = Closure.OWNER_FIRST + c.delegate = a + c() + + a.conditions = a.conditions.collect { it.toString() } + + + if (System.getProperty("apipath") != null) { + if (a.apiId == null) { + a.apiId = Platform.uuid + } + + def tracker = new ApiPathTracker(a.apiId) + def out = errorOut(a.call()) + def path = tracker.getApiPath() + if (!path.isEmpty()) { + Test.apiPaths[a.class.name] = path.join(" --->\n") + } + + return out + } else { + return errorOut(a.call()) + } + } + + def queryAlertDataAck(@DelegatesTo(strategy = Closure.OWNER_FIRST, value = org.zstack.sdk.zwatch.alarm.QueryAlertDataAckAction.class) Closure c) { def a = new org.zstack.sdk.zwatch.alarm.QueryAlertDataAckAction() a.sessionId = Test.currentEnvSpec?.session?.uuid From 0331cd48e0fa0bf439b595bd5f0362a4a272b97e Mon Sep 17 00:00:00 2001 From: "hanyu.liang" Date: Wed, 18 Mar 2026 23:58:35 -0700 Subject: [PATCH 47/54] [plugin]: add newDriverInstance for isolated drivers Add newDriverInstance() method to PluginManager interface and implementation. Unlike getPlugin() which returns a shared singleton, this method creates a fresh instance each time via reflection to avoid concurrent state corruption when multiple callers need isolated driver configurations. Resolves: ZSTAC-82729 Change-Id: Ife8c01012dc38780d91f2b67c04c7a1ac8f9bd1f --- .../org/zstack/core/plugin/PluginManager.java | 11 +++++++++++ .../org/zstack/core/plugin/PluginManagerImpl.java | 15 +++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/core/src/main/java/org/zstack/core/plugin/PluginManager.java b/core/src/main/java/org/zstack/core/plugin/PluginManager.java index 4e1b78c33d2..bb2b2abd3b5 100644 --- a/core/src/main/java/org/zstack/core/plugin/PluginManager.java +++ b/core/src/main/java/org/zstack/core/plugin/PluginManager.java @@ -25,4 +25,15 @@ public interface PluginManager { // get plugin class with type T getPlugin(Class pluginClass, String type); + + /** + * Create a new independent instance of a plugin driver. + * Unlike getPlugin() which returns a shared singleton, this method creates + * a fresh instance each time to avoid concurrent state corruption when + * multiple callers need isolated driver configurations. + * + * @param pluginUuid the plugin product key / UUID + * @return a new instance of the plugin driver + */ + T newDriverInstance(String pluginUuid); } diff --git a/core/src/main/java/org/zstack/core/plugin/PluginManagerImpl.java b/core/src/main/java/org/zstack/core/plugin/PluginManagerImpl.java index 619d960edae..dd69b899419 100644 --- a/core/src/main/java/org/zstack/core/plugin/PluginManagerImpl.java +++ b/core/src/main/java/org/zstack/core/plugin/PluginManagerImpl.java @@ -299,6 +299,21 @@ public T getPlugin(Class plugin .orElse(null); } + @Override + public T newDriverInstance(String pluginProductKey) { + PluginDriver singleton = pluginInstances.get(pluginProductKey); + if (singleton == null) { + throw new CloudRuntimeException(String.format("Unsupported plugin %s", pluginProductKey)); + } + + try { + return (T) singleton.getClass().getConstructor().newInstance(); + } catch (Exception e) { + throw new CloudRuntimeException( + String.format("Failed to create new instance of plugin %s", pluginProductKey), e); + } + } + @Override public void handleMessage(Message msg) { if (msg instanceof APIRefreshPluginDriversMsg) { From 60bd4ea5d3583cc2449fe5715b9f1a2e0b1a3f91 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Tue, 30 Jun 2026 15:35:52 +0900 Subject: [PATCH 48/54] [management-node]: recreate mn record idempotently Clear persistence context after bulk deleting stale ManagementNodeVO before persisting the replacement startup record. Resolves: ZSTAC-86384 Change-Id: Ibd20a5ffdf4b4b083d27641328f00f434ae7cb89 --- .../ManagementNodeManagerImpl.java | 54 ++++++++++--------- .../portal/ManagementNodeHeartbeatCase.groovy | 25 +++++++++ 2 files changed, 55 insertions(+), 24 deletions(-) diff --git a/portal/src/main/java/org/zstack/portal/managementnode/ManagementNodeManagerImpl.java b/portal/src/main/java/org/zstack/portal/managementnode/ManagementNodeManagerImpl.java index 41d90a3675c..4435fb45d29 100755 --- a/portal/src/main/java/org/zstack/portal/managementnode/ManagementNodeManagerImpl.java +++ b/portal/src/main/java/org/zstack/portal/managementnode/ManagementNodeManagerImpl.java @@ -517,30 +517,7 @@ public void rollback(FlowRollback trigger, Map data) { @Override public void run(FlowTrigger trigger, Map data) { - new SQLBatch() { - @Override - protected void scripts() { - String ip = Platform.getManagementServerIp(); - String uuid = Platform.getManagementServerId(); - - ManagementNodeVO existing = findByUuid(uuid, ManagementNodeVO.class); - if (ManagementNodeManagerImpl.this.isDuplicatedActiveManagementServerId(existing, ip, databaseFacade.getCurrentSqlTime())) { - throw new CloudRuntimeException(String.format( - "duplicated management server id[%s] detected, current management.server.ip[%s], existing management node hostName[%s]. " + - "Please remove %s on the cloned or peer management node and restart it", - uuid, ip, existing.getHostName(), Platform.getManagementServerIdStateFile().getAbsolutePath())); - } - - sql(ManagementNodeVO.class).eq(ManagementNodeVO_.uuid, uuid).hardDelete(); - - ManagementNodeVO vo = new ManagementNodeVO(); - vo.setHostName(ip); - vo.setUuid(uuid); - persist(vo); - reload(vo); - node = vo; - } - }.execute(); + recreateManagementNodeRecord(Platform.getManagementServerIp(), Platform.getManagementServerId()); trigger.next(); } @@ -686,6 +663,35 @@ public void handle(ErrorCode errCode, Map data) { return true; } + ManagementNodeVO recreateManagementNodeRecord(String ip, String uuid) { + final ManagementNodeVO[] ret = new ManagementNodeVO[1]; + new SQLBatch() { + @Override + protected void scripts() { + ManagementNodeVO existing = findByUuid(uuid, ManagementNodeVO.class); + if (ManagementNodeManagerImpl.this.isDuplicatedActiveManagementServerId(existing, ip, databaseFacade.getCurrentSqlTime())) { + throw new CloudRuntimeException(String.format( + "duplicated management server id[%s] detected, current management.server.ip[%s], existing management node hostName[%s]. " + + "Please remove %s on the cloned or peer management node and restart it", + uuid, ip, existing.getHostName(), Platform.getManagementServerIdStateFile().getAbsolutePath())); + } + + sql(ManagementNodeVO.class).eq(ManagementNodeVO_.uuid, uuid).hardDelete(); + databaseFacade.getEntityManager().clear(); + + ManagementNodeVO vo = new ManagementNodeVO(); + vo.setHostName(ip); + vo.setUuid(uuid); + persist(vo); + reload(vo); + ret[0] = vo; + } + }.execute(); + + node = ret[0]; + return node; + } + private void setupHeartbeat() { ManagementNodeGlobalConfig.NODE_HEARTBEAT_INTERVAL.installUpdateExtension(new GlobalConfigUpdateExtensionPoint() { @Override diff --git a/test/src/test/groovy/org/zstack/test/integration/portal/ManagementNodeHeartbeatCase.groovy b/test/src/test/groovy/org/zstack/test/integration/portal/ManagementNodeHeartbeatCase.groovy index a6940fab90e..5a2802d954f 100644 --- a/test/src/test/groovy/org/zstack/test/integration/portal/ManagementNodeHeartbeatCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/portal/ManagementNodeHeartbeatCase.groovy @@ -7,6 +7,7 @@ import org.zstack.header.managementnode.ManagementNodeState import org.zstack.header.managementnode.ManagementNodeVO import org.zstack.header.managementnode.ManagementNodeVO_ import org.zstack.portal.managementnode.ManagementNodeGlobalConfig +import org.zstack.portal.managementnode.ManagementNodeManagerImpl import org.zstack.portal.managementnode.PortalGlobalProperty import org.zstack.testlib.SubCase @@ -36,6 +37,7 @@ class ManagementNodeHeartbeatCase extends SubCase { dbf = bean(DatabaseFacade.class) testUnexpectedManagementNodeRecord() + testRecreateManagementNodeRecordWithManagedExistingRecord() } void prepareInvalidRecords() { @@ -97,4 +99,27 @@ class ManagementNodeHeartbeatCase extends SubCase { PortalGlobalProperty.MAX_HEARTBEAT_FAILURE = 5 } + + void testRecreateManagementNodeRecordWithManagedExistingRecord() { + String uuid = Platform.uuid + String ip = "127.0.0.1" + + ManagementNodeVO vo = new ManagementNodeVO() + vo.setUuid(uuid) + vo.setHostName(ip) + vo.setState(ManagementNodeState.JOINING) + dbf.persist(vo) + + def method = ManagementNodeManagerImpl.class.getDeclaredMethod("recreateManagementNodeRecord", String.class, String.class) + method.setAccessible(true) + method.invoke(bean(ManagementNodeManagerImpl.class), ip, uuid) + + List nodes = Q.New(ManagementNodeVO.class) + .eq(ManagementNodeVO_.uuid, uuid) + .list() + assert nodes.size() == 1 + assert nodes[0].hostName == ip + + dbf.remove(nodes[0]) + } } From ca3fe4b659b321408bd09117450fcbfc9494f9c8 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Tue, 30 Jun 2026 16:39:09 +0900 Subject: [PATCH 49/54] [management-node]: stabilize heartbeat cleanup case Avoid asserting intermediate heartbeat cleanup order in stability test. Wait for final cleanup state and always restore global test property. Resolves: ZSTAC-86384 Change-Id: I1561f914c6ca2c71d934ba97d50fdb60166d1db4 --- .../portal/ManagementNodeHeartbeatCase.groovy | 46 ++++++------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/test/src/test/groovy/org/zstack/test/integration/portal/ManagementNodeHeartbeatCase.groovy b/test/src/test/groovy/org/zstack/test/integration/portal/ManagementNodeHeartbeatCase.groovy index 5a2802d954f..f38001002a8 100644 --- a/test/src/test/groovy/org/zstack/test/integration/portal/ManagementNodeHeartbeatCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/portal/ManagementNodeHeartbeatCase.groovy @@ -13,7 +13,6 @@ import org.zstack.testlib.SubCase import java.sql.Timestamp import java.time.LocalDateTime -import java.util.concurrent.TimeUnit class ManagementNodeHeartbeatCase extends SubCase { @@ -64,40 +63,21 @@ class ManagementNodeHeartbeatCase extends SubCase { ManagementNodeGlobalConfig.NODE_HEARTBEAT_INTERVAL.updateValue(1) PortalGlobalProperty.MAX_HEARTBEAT_FAILURE = 2 - int heartbeatFailureTimeout = ManagementNodeGlobalConfig.NODE_HEARTBEAT_INTERVAL.value(Integer.class) * PortalGlobalProperty.MAX_HEARTBEAT_FAILURE - int heartbeatUpdateDelay = 1 * ManagementNodeGlobalConfig.NODE_HEARTBEAT_INTERVAL.value(Integer.class) - - int waitBeforeClean = heartbeatFailureTimeout + heartbeatUpdateDelay int failureInterval = ManagementNodeGlobalConfig.NODE_HEARTBEAT_INTERVAL.value(Integer.class) - // wait a interval before all nodes failed - sleep(TimeUnit.SECONDS.toMillis(waitBeforeClean - failureInterval)) - long count = dbf.count(ManagementNodeVO.class) - assert count == 2 - - // confirm 127.0.0.222 is cleaned at first - count = Q.New(ManagementNodeVO.class) - .notEq(ManagementNodeVO_.hostName, '127.0.0.222') - .count() - assert count == 2 - - // wait one more interval to wait 127.0.0.111 cleaned - // exceed 3s will be added in suspects - // next heartbeat will be clean - // but hb timestamp is second precision, so it will not be added until 4s. - // and cleaned after 5s - // so we need to wait more than 5s - sleep(TimeUnit.SECONDS.toMillis(failureInterval * 3) + 500) - count = dbf.count(ManagementNodeVO.class) - assert count == 1 - - // confirm 127.0.0.111 is cleaned - count = Q.New(ManagementNodeVO.class) - .notEq(ManagementNodeVO_.hostName, '127.0.0.111') - .count() - assert count == 1 - - PortalGlobalProperty.MAX_HEARTBEAT_FAILURE = 5 + try { + retryInSecs(failureInterval * 8, failureInterval) { + long count = dbf.count(ManagementNodeVO.class) + assert count == 1 + + count = Q.New(ManagementNodeVO.class) + .notIn(ManagementNodeVO_.hostName, ['127.0.0.111', '127.0.0.222']) + .count() + assert count == 1 + } + } finally { + PortalGlobalProperty.MAX_HEARTBEAT_FAILURE = 5 + } } void testRecreateManagementNodeRecordWithManagedExistingRecord() { From 1f65e4e5a8a547176cab7c8ed029e3288765d9f6 Mon Sep 17 00:00:00 2001 From: "haidong.pang" Date: Mon, 29 Jun 2026 17:45:03 +0800 Subject: [PATCH 50/54] [zbs]: try next mds on sync failure Catch sync REST failures in the ZBS MDS HTTP caller so getActiveClients can try the next connected MDS when the selected MDS is unreachable. Add a regression test for the volume clients path where the first MDS throws an operation failure and the second returns active clients. Resolves: ZSTAC-80595 Change-Id: Id891693b033ac3d4588243d2dd25750f63110db6 --- .../org/zstack/storage/zbs/ZbsMdsBase.java | 9 +++++- .../addon/zbs/ZbsPrimaryStorageCase.groovy | 32 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsMdsBase.java b/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsMdsBase.java index 7016205561c..aa9087bdc5e 100644 --- a/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsMdsBase.java +++ b/plugin/zbs/src/main/java/org/zstack/storage/zbs/ZbsMdsBase.java @@ -10,6 +10,7 @@ import org.zstack.header.errorcode.OperationFailureException; import org.zstack.header.rest.JsonAsyncRESTCallback; import org.zstack.header.rest.RESTFacade; +import org.zstack.utils.gson.JSONObjectUtil; import org.zstack.utils.ssh.Ssh; import org.zstack.utils.ssh.SshException; import org.zstack.utils.ssh.SshResult; @@ -76,7 +77,13 @@ protected void checkStorageHealth() { } public T syncCall(final String path, final Object cmd, final Class retClass, TimeUnit unit, long timeout) { - return restf.syncJsonPost(makeHttpPath(self.getAddr(), path), cmd, retClass, unit, timeout); + try { + return restf.syncJsonPost(makeHttpPath(self.getAddr(), path), cmd, retClass, unit, timeout); + } catch (OperationFailureException e) { + T ret = JSONObjectUtil.toObject("{}", retClass); + ret.setError(e.getErrorCode().getDetails()); + return ret; + } } public void httpCall(final String path, final Object cmd, final Class retClass, final ReturnValueCompletion completion) { 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 089a8ac2c1c..a9799056414 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 @@ -6,6 +6,8 @@ import org.zstack.core.cloudbus.EventCallback import org.zstack.core.cloudbus.EventFacade import org.zstack.core.db.DatabaseFacade import org.zstack.core.db.Q +import org.zstack.header.errorcode.ErrorCode +import org.zstack.header.errorcode.OperationFailureException import org.zstack.header.message.MessageReply import org.zstack.header.storage.primary.GetVolumeBackingChainFromPrimaryStorageMsg import org.zstack.header.storage.primary.GetVolumeBackingChainFromPrimaryStorageReply @@ -194,6 +196,7 @@ class ZbsPrimaryStorageCase extends SubCase { testMdsPing() testCheckHostStorageConnection() testNegativeScenario() + testDeleteVolumeTriesNextMdsAfterSyncFailure() testAddExternalPrimaryStorageWithMalformedJsonRejectedByInterceptor() testDataVolumeNegativeScenario() testDecodeMdsUriWithSpecialPassword() @@ -897,6 +900,35 @@ class ZbsPrimaryStorageCase extends SubCase { } } + void testDeleteVolumeTriesNextMdsAfterSyncFailure() { + AtomicInteger getVolumeClientsCallCount = new AtomicInteger(0) + List getVolumeClientsMds = Collections.synchronizedList(new ArrayList<>()) + env.simulator(ZbsStorageController.GET_VOLUME_CLIENTS_PATH) { HttpEntity e, EnvSpec spec -> + def cmd = JSONObjectUtil.toObject(e.body, ZbsStorageController.GetVolumeClientsCmd.class) + getVolumeClientsMds.add(cmd.addr) + if (getVolumeClientsCallCount.incrementAndGet() == 1) { + throw new OperationFailureException(new ErrorCode("TEST.1000", "test error", "simulated MDS network failure")) + } + + return new ZbsStorageController.GetVolumeClientsRsp() + } + + VolumeInventory volume = createDataVolume { + name = "delete-after-mds-sync-failure" + diskOfferingUuid = diskOffering.uuid + primaryStorageUuid = ps.uuid + } as VolumeInventory + + deleteVolume(volume.uuid) + + assert getVolumeClientsCallCount.get() == 2 : + "deleteVolume expunge should retry GET_VOLUME_CLIENTS on next MDS after sync failure: expectedCalls=2 actualCalls=${getVolumeClientsCallCount.get()} mds=${getVolumeClientsMds}" + assert getVolumeClientsMds.toSet().size() == 2 : + "deleteVolume expunge should use two different MDS nodes after first sync failure: expectedDistinctMds=2 actualMds=${getVolumeClientsMds}" + + env.cleanSimulatorHandlers() + } + void testNegativeScenario() { expect(AssertionError.class) { addExternalPrimaryStorage { From 4313cc32d311caf8aee5729977977628208b56d7 Mon Sep 17 00:00:00 2001 From: "hanyu.liang" Date: Tue, 30 Jun 2026 16:14:17 +0800 Subject: [PATCH 51/54] [scim]: remove receiver version gate SCIM events can be rejected when the sender misses the latest receiver-side resource version. Drop receiver-side version comparison and keep event application idempotent through resource identity handling. Existing SCIM payloads continue to apply without requiring version negotiation. DBImpact Related: ZCF-4469 Change-Id: Id5c617ebfc5c80631aa7e608b0651ea28c6fcad5 --- conf/db/upgrade/V5.5.28__schema.sql | 4 +- .../org/zstack/core/scim/ScimService.java | 81 +++---------------- .../org/zstack/header/scim/ScimConstant.java | 1 - .../org/zstack/header/scim/ScimEventVO.java | 10 --- .../org/zstack/header/scim/ScimResult.java | 9 +-- 5 files changed, 14 insertions(+), 91 deletions(-) diff --git a/conf/db/upgrade/V5.5.28__schema.sql b/conf/db/upgrade/V5.5.28__schema.sql index 869377eddcd..95db09ddf99 100644 --- a/conf/db/upgrade/V5.5.28__schema.sql +++ b/conf/db/upgrade/V5.5.28__schema.sql @@ -89,7 +89,6 @@ CREATE TABLE IF NOT EXISTS `zstack`.`ScimEventVO` ( `eventId` varchar(255) NOT NULL, `resourceType` varchar(64) NOT NULL, `resourceId` varchar(255) NOT NULL, - `resourceVersion` bigint NOT NULL, `operation` varchar(32) NOT NULL, `status` varchar(32) NOT NULL, `payloadHash` varchar(128) DEFAULT NULL, @@ -97,8 +96,7 @@ CREATE TABLE IF NOT EXISTS `zstack`.`ScimEventVO` ( `lastOpDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00' ON UPDATE CURRENT_TIMESTAMP, `createDate` timestamp NOT NULL DEFAULT '2000-01-01 00:00:00', PRIMARY KEY (`uuid`), - UNIQUE KEY `ukScimEventVOClientEvent` (`clientId`, `eventId`), - KEY `idxScimEventVOResourceVersion` (`clientId`, `resourceType`, `resourceId`, `resourceVersion`) + UNIQUE KEY `ukScimEventVOClientEvent` (`clientId`, `eventId`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; CREATE TABLE IF NOT EXISTS `HostCacheStoreVO` ( diff --git a/core/src/main/java/org/zstack/core/scim/ScimService.java b/core/src/main/java/org/zstack/core/scim/ScimService.java index 4df0c26d6f4..657a0818640 100644 --- a/core/src/main/java/org/zstack/core/scim/ScimService.java +++ b/core/src/main/java/org/zstack/core/scim/ScimService.java @@ -9,8 +9,6 @@ import org.zstack.core.db.SQL; import org.zstack.core.config.GlobalConfig; import org.zstack.core.config.GlobalConfigUpdateExtensionPoint; -import org.zstack.core.thread.Task; -import org.zstack.core.thread.ThreadFacade; import org.zstack.header.scim.ScimConstant; import org.zstack.header.scim.ScimEventVO; import org.zstack.header.scim.ScimException; @@ -54,17 +52,12 @@ public class ScimService { private DatabaseFacade dbf; @Autowired private ScimResourceHandler resourceHandler; - @Autowired - private ThreadFacade thdf; private final GlobalConfigUpdateExtensionPoint cleanupWhenDisabledExtension = this::cleanupWhenDisabled; private final ReentrantReadWriteLock receiverStateLock = new ReentrantReadWriteLock(); private volatile GlobalConfig registeredReceiverEnabledConfig; public void init() { ensureReceiverEnabledExtensionRegistered(); - if (!isEnabled(ScimGlobalConfig.RECEIVER_ENABLED.value())) { - submitStartupCleanup(); - } } private void ensureReceiverEnabledExtensionRegisteredIfNeeded() { @@ -95,25 +88,6 @@ private synchronized void ensureReceiverEnabledExtensionRegistered() { registeredReceiverEnabledConfig = receiverEnabled; } - private void submitStartupCleanup() { - thdf.submit(new Task() { - @Override - public String getName() { - return "scim-startup-cleanup"; - } - - @Override - public Void call() { - try { - cleanupScimResourcesIfPresent(); - } catch (RuntimeException e) { - logger.warn("failed to cleanup SCIM resources on startup", e); - } - return null; - } - }); - } - private void cleanupWhenDisabled(GlobalConfig oldConfig, GlobalConfig newConfig) { if (isEnabled(oldConfig.value()) && !isEnabled(newConfig.value())) { cleanupScimResourcesIfPresent(); @@ -148,9 +122,8 @@ public ScimResult apply(HttpServletRequest request, String resourceType, String ScimConstant.DEFAULT_CLIENT_ID), ScimConstant.HEADER_CLIENT_ID, MAX_CLIENT_ID_LENGTH); String eventId = validateMaxLength(requiredHeader(request, ScimConstant.HEADER_EVENT_ID), ScimConstant.HEADER_EVENT_ID, MAX_EVENT_ID_LENGTH); - long resourceVersion = parseResourceVersion(requiredHeader(request, ScimConstant.HEADER_RESOURCE_VERSION)); String timestamp = requiredHeader(request, ScimConstant.HEADER_TIMESTAMP); - verifySignature(request, clientId, eventId, resourceVersion, timestamp, method, resourceType, pathResourceId, body); + verifySignature(request, clientId, eventId, timestamp, method, resourceType, pathResourceId, body); String canonicalType = resourceHandler.normalizeResourceType(resourceType); validateMaxLength(canonicalType, "canonical resource type", MAX_RESOURCE_TYPE_LENGTH); @@ -173,7 +146,7 @@ public ScimResult apply(HttpServletRequest request, String resourceType, String GLock lock = new GLock(lockName(clientId, canonicalType, resourceId), LOCK_TIMEOUT_SECONDS); lock.lock(); try { - return applyLocked(clientId, eventId, canonicalType, resourceId, resourceVersion, operation, body, payload); + return applyLocked(clientId, eventId, canonicalType, resourceId, operation, body, payload); } finally { lock.unlock(); } @@ -183,19 +156,13 @@ public ScimResult apply(HttpServletRequest request, String resourceType, String } private ScimResult applyLocked(String clientId, String eventId, String resourceType, String resourceId, - long resourceVersion, ScimOperation operation, String body, ScimPayload payload) { + ScimOperation operation, String body, ScimPayload payload) { ScimEventVO existing = findEvent(clientId, eventId); if (existing != null) { return resultForExistingEvent(existing); } - Long latestVersion = latestAcceptedVersion(clientId, resourceType, resourceId); - if (latestVersion != null && resourceVersion <= latestVersion) { - throw new ScimException(409, - String.format("resource version[%s] is not newer than current[%s]", resourceVersion, latestVersion)); - } - - ScimEventVO event = reserveEvent(clientId, eventId, resourceType, resourceId, resourceVersion, operation, body); + ScimEventVO event = reserveEvent(clientId, eventId, resourceType, resourceId, operation, body); if (!ScimConstant.EVENT_STATUS_PENDING.equals(event.getStatus())) { return resultForExistingEvent(event); } @@ -203,7 +170,7 @@ private ScimResult applyLocked(String clientId, String eventId, String resourceT try { resourceHandler.applyResource(operation, resourceType, resourceId, payload); markEventApplied(event); - return ScimResult.applied(clientId, eventId, resourceType, resourceId, resourceVersion, operation.name()); + return ScimResult.applied(clientId, eventId, resourceType, resourceId, operation.name()); } catch (RuntimeException e) { markEventFailed(event, e); throw e; @@ -271,19 +238,7 @@ private ScimOperation toOperation(String method) { } } - private long parseResourceVersion(String value) { - try { - long version = Long.parseLong(value); - if (version <= 0) { - throw new ScimException(400, "resource version must be positive"); - } - return version; - } catch (NumberFormatException e) { - throw new ScimException(400, "invalid resource version"); - } - } - - private void verifySignature(HttpServletRequest request, String clientId, String eventId, long resourceVersion, + private void verifySignature(HttpServletRequest request, String clientId, String eventId, String timestamp, String method, String resourceType, String pathResourceId, String body) { String secret = ScimGlobalConfig.RECEIVER_SIGNATURE_SECRET.value(); if (isBlank(secret)) { @@ -293,7 +248,7 @@ private void verifySignature(HttpServletRequest request, String clientId, String if (isBlank(actual)) { throw new ScimException(401, "missing SCIM signature"); } - String base = signatureBase(clientId, method, resourceType, pathResourceId, eventId, resourceVersion, timestamp, body); + String base = signatureBase(clientId, method, resourceType, pathResourceId, eventId, timestamp, body); String expected = "sha256=" + hmacSha256(secret, base); if (!constantTimeEquals(expected, actual.trim())) { throw new ScimException(401, "invalid SCIM signature"); @@ -335,29 +290,14 @@ private ScimEventVO findEvent(String clientId, String eventId) { .find(); } - private Long latestAcceptedVersion(String clientId, String resourceType, String resourceId) { - return SQL.New("select max(vo.resourceVersion) from ScimEventVO vo" + - " where vo.clientId = :clientId" + - " and vo.resourceType = :resourceType" + - " and vo.resourceId = :resourceId" + - " and (vo.status = :pending or vo.status = :applied)", Long.class) - .param("clientId", clientId) - .param("resourceType", resourceType) - .param("resourceId", resourceId) - .param("pending", ScimConstant.EVENT_STATUS_PENDING) - .param("applied", ScimConstant.EVENT_STATUS_APPLIED) - .find(); - } - private ScimEventVO reserveEvent(String clientId, String eventId, String resourceType, String resourceId, - long version, ScimOperation operation, String body) { + ScimOperation operation, String body) { ScimEventVO vo = new ScimEventVO(); vo.setUuid(Platform.getUuid()); vo.setClientId(clientId); vo.setEventId(eventId); vo.setResourceType(resourceType); vo.setResourceId(resourceId); - vo.setResourceVersion(version); vo.setOperation(operation.name()); vo.setStatus(ScimConstant.EVENT_STATUS_PENDING); vo.setPayloadHash(sha256(body)); @@ -393,7 +333,7 @@ private void markEventFailed(ScimEventVO event, RuntimeException e) { private ScimResult resultForExistingEvent(ScimEventVO event) { if (ScimConstant.EVENT_STATUS_APPLIED.equals(event.getStatus())) { return ScimResult.duplicate(event.getClientId(), event.getEventId(), event.getResourceType(), - event.getResourceId(), event.getResourceVersion()); + event.getResourceId()); } if (ScimConstant.EVENT_STATUS_PENDING.equals(event.getStatus())) { throw new ScimException(409, String.format("SCIM event[%s] is being processed", event.getEventId())); @@ -495,13 +435,12 @@ private String trimError(RuntimeException e) { } private String signatureBase(String clientId, String method, String resourceType, String pathResourceId, - String eventId, long resourceVersion, String timestamp, String body) { + String eventId, String timestamp, String body) { return clientId + "\n" + method.toUpperCase(Locale.ROOT) + "\n" + nullToEmpty(resourceType) + "\n" + signaturePathResourceId(method, pathResourceId) + "\n" + eventId + "\n" + - resourceVersion + "\n" + timestamp.trim() + "\n" + body; } diff --git a/header/src/main/java/org/zstack/header/scim/ScimConstant.java b/header/src/main/java/org/zstack/header/scim/ScimConstant.java index ffe5c45d54b..919fdba9ee8 100644 --- a/header/src/main/java/org/zstack/header/scim/ScimConstant.java +++ b/header/src/main/java/org/zstack/header/scim/ScimConstant.java @@ -3,7 +3,6 @@ public class ScimConstant { public static final String HEADER_CLIENT_ID = "X-SCIM-Client-UUID"; public static final String HEADER_EVENT_ID = "X-SCIM-Event-UUID"; - public static final String HEADER_RESOURCE_VERSION = "X-SCIM-Resource-Version"; public static final String HEADER_TIMESTAMP = "X-SCIM-Timestamp"; public static final String HEADER_SIGNATURE = "X-SCIM-Signature"; public static final String DEFAULT_CLIENT_ID = "default"; diff --git a/header/src/main/java/org/zstack/header/scim/ScimEventVO.java b/header/src/main/java/org/zstack/header/scim/ScimEventVO.java index f97b6985456..49b291ff253 100644 --- a/header/src/main/java/org/zstack/header/scim/ScimEventVO.java +++ b/header/src/main/java/org/zstack/header/scim/ScimEventVO.java @@ -23,8 +23,6 @@ public class ScimEventVO { @Column private String resourceId; @Column - private long resourceVersion; - @Column private String operation; @Column private String status; @@ -82,14 +80,6 @@ public void setResourceId(String resourceId) { this.resourceId = resourceId; } - public long getResourceVersion() { - return resourceVersion; - } - - public void setResourceVersion(long resourceVersion) { - this.resourceVersion = resourceVersion; - } - public String getOperation() { return operation; } diff --git a/header/src/main/java/org/zstack/header/scim/ScimResult.java b/header/src/main/java/org/zstack/header/scim/ScimResult.java index 9da41346705..08080358079 100644 --- a/header/src/main/java/org/zstack/header/scim/ScimResult.java +++ b/header/src/main/java/org/zstack/header/scim/ScimResult.java @@ -5,25 +5,22 @@ public class ScimResult { public String eventId; public String resourceType; public String resourceId; - public long resourceVersion; public String operation; public boolean duplicate; public static ScimResult applied(String clientId, String eventId, String resourceType, String resourceId, - long version, String operation) { + String operation) { ScimResult result = new ScimResult(); result.clientId = clientId; result.eventId = eventId; result.resourceType = resourceType; result.resourceId = resourceId; - result.resourceVersion = version; result.operation = operation; return result; } - public static ScimResult duplicate(String clientId, String eventId, String resourceType, String resourceId, - long version) { - ScimResult result = applied(clientId, eventId, resourceType, resourceId, version, "DUPLICATE"); + public static ScimResult duplicate(String clientId, String eventId, String resourceType, String resourceId) { + ScimResult result = applied(clientId, eventId, resourceType, resourceId, "DUPLICATE"); result.duplicate = true; return result; } From 964b288c006a156a62acdc21c5192971e638bbb2 Mon Sep 17 00:00:00 2001 From: "hanyu.liang" Date: Tue, 30 Jun 2026 16:14:17 +0800 Subject: [PATCH 52/54] [identity]: allow ZIAM role policy updates ZIAM synchronized custom roles need to accept permission changes from Cloud. Route role statement mutations through an extension point. SCIM-origin roles can validate allowed local updates instead of being rejected by the generic RBAC interceptor. Related: ZCF-4469 Change-Id: If3f7e0e997251cbfd09dbd1089ac6fa18672e03b --- .../identity/rbac/RBACApiInterceptor.java | 38 +----- .../org/zstack/identity/rbac/RoleBase.java | 8 ++ .../rbac/RoleMutationExtensionPoint.java | 8 ++ .../sdk/iam2/api/SetIAM2RoleScopeAction.java | 111 ++++++++++++++++++ .../sdk/iam2/api/SetIAM2RoleScopeResult.java | 22 ++++ 5 files changed, 150 insertions(+), 37 deletions(-) create mode 100644 identity/src/main/java/org/zstack/identity/rbac/RoleMutationExtensionPoint.java create mode 100644 sdk/src/main/java/org/zstack/sdk/iam2/api/SetIAM2RoleScopeAction.java create mode 100644 sdk/src/main/java/org/zstack/sdk/iam2/api/SetIAM2RoleScopeResult.java diff --git a/identity/src/main/java/org/zstack/identity/rbac/RBACApiInterceptor.java b/identity/src/main/java/org/zstack/identity/rbac/RBACApiInterceptor.java index 6a451153c2c..e9601e59b0e 100755 --- a/identity/src/main/java/org/zstack/identity/rbac/RBACApiInterceptor.java +++ b/identity/src/main/java/org/zstack/identity/rbac/RBACApiInterceptor.java @@ -1,9 +1,6 @@ package org.zstack.identity.rbac; -import org.springframework.beans.factory.annotation.Autowired; -import org.zstack.core.componentloader.PluginRegistry; import org.zstack.core.db.Q; -import org.zstack.core.db.SQL; import org.zstack.header.apimediator.ApiMessageInterceptionException; import org.zstack.header.apimediator.ApiMessageInterceptor; import org.zstack.header.errorcode.OperationFailureException; @@ -18,9 +15,7 @@ import org.zstack.header.identity.role.api.APICreateRoleMsg; import org.zstack.header.identity.role.api.APIDeleteRoleMsg; import org.zstack.header.identity.role.api.APIUpdateRoleMsg; -import org.zstack.header.identity.role.api.RoleMessage; import org.zstack.header.message.APIMessage; -import org.zstack.header.resource.ResourceSourceConstant; import org.zstack.identity.CheckIfSessionCanOperationAdminPermission; import org.zstack.utils.gson.JSONObjectUtil; @@ -30,9 +25,6 @@ import static org.zstack.utils.clouderrorcode.CloudOperationsErrorCode.*; public class RBACApiInterceptor implements ApiMessageInterceptor { - @Autowired - private PluginRegistry pluginRgty; - @Override public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionException { if (msg instanceof APIDeleteRoleMsg) { @@ -43,36 +35,8 @@ public APIMessage intercept(APIMessage msg) throws ApiMessageInterceptionExcepti validate((APIAddPolicyStatementsToRoleMsg) msg); } else if (msg instanceof APICreateRoleMsg) { validate((APICreateRoleMsg) msg); - } - - rejectScimRoleMutation(msg); - return msg; - } - - private void rejectScimRoleMutation(APIMessage msg) { - if (!(msg instanceof RoleMessage) || msg instanceof APICreateRoleMsg) { - return; - } - - String roleUuid = ((RoleMessage) msg).getRoleUuid(); - if (roleUuid == null || roleUuid.trim().isEmpty()) { - return; - } - roleUuid = roleUuid.trim(); - - Long count = SQL.New("select count(vo) from ResourceSourceRefVO vo" + - " where vo.resourceUuid = :resourceUuid" + - " and vo.resourceType = :resourceType" + - " and vo.sourceType = :sourceType" + - " and vo.syncType = :syncType", Long.class) - .param("resourceUuid", roleUuid) - .param("resourceType", RoleVO.class.getSimpleName()) - .param("sourceType", ResourceSourceConstant.SOURCE_TYPE_ZIAM) - .param("syncType", ResourceSourceConstant.SYNC_TYPE_SCIM) - .find(); - if (count != null && count > 0) { - throw new ApiMessageInterceptionException(argerr("Role[uuid:%s] is managed by SCIM and cannot be modified locally", roleUuid)); } + return msg; } private void validate(APICreateRoleMsg msg) { diff --git a/identity/src/main/java/org/zstack/identity/rbac/RoleBase.java b/identity/src/main/java/org/zstack/identity/rbac/RoleBase.java index 291d68494c2..639f80438db 100755 --- a/identity/src/main/java/org/zstack/identity/rbac/RoleBase.java +++ b/identity/src/main/java/org/zstack/identity/rbac/RoleBase.java @@ -55,6 +55,8 @@ private void handleLocalMessage(Message msg) { } private void handleApiMessage(APIMessage msg) { + beforeRoleMutation((RoleMessage) msg); + if (msg instanceof APIDeleteRoleMsg) { handle((APIDeleteRoleMsg) msg); } else if (msg instanceof APIAttachRoleToAccountMsg) { @@ -78,6 +80,12 @@ private void handleApiMessage(APIMessage msg) { } } + private void beforeRoleMutation(RoleMessage msg) { + for (RoleMutationExtensionPoint ext : pluginRgty.getExtensionList(RoleMutationExtensionPoint.class)) { + ext.beforeRoleMutation(self, msg); + } + } + private void handle(APIUpdateRoleMsg msg) { APIUpdateRoleEvent evt = new APIUpdateRoleEvent(msg.getId()); RoleInventory inv = new SQLBatchWithReturn() { diff --git a/identity/src/main/java/org/zstack/identity/rbac/RoleMutationExtensionPoint.java b/identity/src/main/java/org/zstack/identity/rbac/RoleMutationExtensionPoint.java new file mode 100644 index 00000000000..d23bb7c57a1 --- /dev/null +++ b/identity/src/main/java/org/zstack/identity/rbac/RoleMutationExtensionPoint.java @@ -0,0 +1,8 @@ +package org.zstack.identity.rbac; + +import org.zstack.header.identity.role.RoleVO; +import org.zstack.header.identity.role.api.RoleMessage; + +public interface RoleMutationExtensionPoint { + void beforeRoleMutation(RoleVO role, RoleMessage msg); +} diff --git a/sdk/src/main/java/org/zstack/sdk/iam2/api/SetIAM2RoleScopeAction.java b/sdk/src/main/java/org/zstack/sdk/iam2/api/SetIAM2RoleScopeAction.java new file mode 100644 index 00000000000..dd999753974 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/iam2/api/SetIAM2RoleScopeAction.java @@ -0,0 +1,111 @@ +package org.zstack.sdk.iam2.api; + +import java.util.HashMap; +import java.util.Map; +import org.zstack.sdk.*; + +public class SetIAM2RoleScopeAction extends AbstractAction { + + private static final HashMap parameterMap = new HashMap<>(); + + private static final HashMap nonAPIParameterMap = new HashMap<>(); + + public static class Result { + public ErrorCode error; + public org.zstack.sdk.iam2.api.SetIAM2RoleScopeResult value; + + public Result throwExceptionIfError() { + if (error != null) { + throw new ApiException( + String.format("error[code: %s, description: %s, details: %s, globalErrorCode: %s]", error.code, error.description, error.details, error.globalErrorCode) + ); + } + + return this; + } + } + + @Param(required = false) + public java.lang.String SCOPE_PLATFORM = "platform"; + + @Param(required = false) + public java.lang.String SCOPE_PROJECT = "project"; + + @Param(required = true, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String uuid; + + @Param(required = true, validValues = {"platform","project"}, nonempty = false, nullElements = false, emptyString = true, noTrim = false) + public java.lang.String scope; + + @Param(required = false) + public java.util.List systemTags; + + @Param(required = false) + public java.util.List userTags; + + @Param(required = false) + public String sessionId; + + @Param(required = false) + public String accessKeyId; + + @Param(required = false) + public String accessKeySecret; + + @Param(required = false) + public String requestIp; + + @NonAPIParam + public long timeout = -1; + + @NonAPIParam + public long pollingInterval = -1; + + + private Result makeResult(ApiResult res) { + Result ret = new Result(); + if (res.error != null) { + ret.error = res.error; + return ret; + } + + org.zstack.sdk.iam2.api.SetIAM2RoleScopeResult value = res.getResult(org.zstack.sdk.iam2.api.SetIAM2RoleScopeResult.class); + ret.value = value == null ? new org.zstack.sdk.iam2.api.SetIAM2RoleScopeResult() : value; + + return ret; + } + + public Result call() { + ApiResult res = ZSClient.call(this); + return makeResult(res); + } + + public void call(final Completion completion) { + ZSClient.call(this, new InternalCompletion() { + @Override + public void complete(ApiResult res) { + completion.complete(makeResult(res)); + } + }); + } + + protected Map getParameterMap() { + return parameterMap; + } + + protected Map getNonAPIParameterMap() { + return nonAPIParameterMap; + } + + protected RestInfo getRestInfo() { + RestInfo info = new RestInfo(); + info.httpMethod = "PUT"; + info.path = "/iam2/roles/{uuid}/scope/actions"; + info.needSession = true; + info.needPoll = true; + info.parameterName = "setIAM2RoleScope"; + info.morphTransform = "IAM2"; + return info; + } + +} diff --git a/sdk/src/main/java/org/zstack/sdk/iam2/api/SetIAM2RoleScopeResult.java b/sdk/src/main/java/org/zstack/sdk/iam2/api/SetIAM2RoleScopeResult.java new file mode 100644 index 00000000000..13f5f404aa5 --- /dev/null +++ b/sdk/src/main/java/org/zstack/sdk/iam2/api/SetIAM2RoleScopeResult.java @@ -0,0 +1,22 @@ +package org.zstack.sdk.iam2.api; + + + +public class SetIAM2RoleScopeResult { + public java.lang.String roleUuid; + public void setRoleUuid(java.lang.String roleUuid) { + this.roleUuid = roleUuid; + } + public java.lang.String getRoleUuid() { + return this.roleUuid; + } + + public java.lang.String scope; + public void setScope(java.lang.String scope) { + this.scope = scope; + } + public java.lang.String getScope() { + return this.scope; + } + +} From 2f40cc2764c8b68f11a27121a9c1980c2d67f5d0 Mon Sep 17 00:00:00 2001 From: "shixin.ruan" Date: Tue, 30 Jun 2026 17:05:57 +0800 Subject: [PATCH 53/54] [management-node]: keep heartbeat case node state intact Do not pollute ManagementNodeManagerImpl.node with a temporary management node record during the recreate-record test. The stability run reuses the same JVM. The temporary node made the next heartbeat loop stop itself and left invalid records uncollected. Resolves: ZSTAC-86384 Change-Id: I2f1096c3fa897c9d9af50e71744bb4762b70920a --- .../portal/ManagementNodeHeartbeatCase.groovy | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/test/src/test/groovy/org/zstack/test/integration/portal/ManagementNodeHeartbeatCase.groovy b/test/src/test/groovy/org/zstack/test/integration/portal/ManagementNodeHeartbeatCase.groovy index f38001002a8..7df36c5fc37 100644 --- a/test/src/test/groovy/org/zstack/test/integration/portal/ManagementNodeHeartbeatCase.groovy +++ b/test/src/test/groovy/org/zstack/test/integration/portal/ManagementNodeHeartbeatCase.groovy @@ -81,14 +81,14 @@ class ManagementNodeHeartbeatCase extends SubCase { } void testRecreateManagementNodeRecordWithManagedExistingRecord() { - String uuid = Platform.uuid - String ip = "127.0.0.1" - - ManagementNodeVO vo = new ManagementNodeVO() - vo.setUuid(uuid) - vo.setHostName(ip) - vo.setState(ManagementNodeState.JOINING) - dbf.persist(vo) + String uuid = Platform.getManagementServerId() + String ip = Platform.getManagementServerIp() + ManagementNodeVO original = Q.New(ManagementNodeVO.class) + .eq(ManagementNodeVO_.uuid, uuid) + .find() + ManagementNodeState state = original.state + Timestamp heartBeat = original.heartBeat + int port = original.port def method = ManagementNodeManagerImpl.class.getDeclaredMethod("recreateManagementNodeRecord", String.class, String.class) method.setAccessible(true) @@ -100,6 +100,9 @@ class ManagementNodeHeartbeatCase extends SubCase { assert nodes.size() == 1 assert nodes[0].hostName == ip - dbf.remove(nodes[0]) + nodes[0].state = state + nodes[0].heartBeat = heartBeat + nodes[0].port = port + dbf.update(nodes[0]) } } From 4c4dbf895f5347f885672ff5b77d90171a5c9d16 Mon Sep 17 00:00:00 2001 From: "xinhao.huang" Date: Wed, 24 Jun 2026 16:15:00 +0800 Subject: [PATCH 54/54] [pciDevice]: add share error code Add a distinct PCI device access error code. Test: ./runMavenProfile premium Test: PremiumTestCaseStabilityTest TestGpuDeviceShareTypeCase Resolves: ZSTAC-85055 Change-Id: I7cc18aaba38631696ff3f928f2d9d1dfc75add3d --- .../zstack/utils/clouderrorcode/CloudOperationsErrorCode.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java index b848d811523..1c98f41a281 100644 --- a/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java +++ b/utils/src/main/java/org/zstack/utils/clouderrorcode/CloudOperationsErrorCode.java @@ -11807,6 +11807,8 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_PCIDEVICE_10077 = "ORG_ZSTACK_PCIDEVICE_10077"; + public static final String ORG_ZSTACK_PCIDEVICE_10078 = "ORG_ZSTACK_PCIDEVICE_10078"; + public static final String ORG_ZSTACK_CAS_DRIVER_DONGHAI_10000 = "ORG_ZSTACK_CAS_DRIVER_DONGHAI_10000"; public static final String ORG_ZSTACK_CAS_DRIVER_DONGHAI_10001 = "ORG_ZSTACK_CAS_DRIVER_DONGHAI_10001"; @@ -16360,6 +16362,8 @@ public class CloudOperationsErrorCode { public static final String ORG_ZSTACK_DGPU_10013 = "ORG_ZSTACK_DGPU_10013"; + public static final String ORG_ZSTACK_DGPU_10014 = "ORG_ZSTACK_DGPU_10014"; + public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10001 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10001"; public static final String ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10002 = "ORG_ZSTACK_STORAGE_LOCALVOLUMECACHE_10002";