From 1c47f935e238acd370262b68066649d51578e7ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Erki=20Ma=CC=88rks?= Date: Thu, 7 May 2026 11:33:15 +0300 Subject: [PATCH] Add xenserver.create.full.clone global setting Adds a StoragePool-scoped boolean ConfigKey mirroring vmware.create.full.clone so XenServer-backed VMs can be deployed as full VDI copies (VDI.copy) instead of always using linked clones (VDI.clone). Default false preserves today's behavior. The per-pool flag flows into the existing PrimaryDataStoreTO.fullCloneFlag through a new dispatch method addFullCloneAndDiskprovisiongStrictnessFlagOnDest that switches on hypervisor type. --- .../com/cloud/storage/StorageManager.java | 8 +++ .../motion/AncientDataMotionStrategy.java | 51 ++++++++++++++++--- .../motion/AncientDataMotionStrategyTest.java | 8 +++ .../resource/XenServerStorageProcessor.java | 9 +++- .../com/cloud/storage/StorageManagerImpl.java | 1 + 5 files changed, 69 insertions(+), 8 deletions(-) diff --git a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java index 3c62738f9ed5..d6604cffc40a 100644 --- a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java +++ b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java @@ -195,6 +195,14 @@ public interface StorageManager extends StorageService { true, ConfigKey.Scope.StoragePool, null); + ConfigKey XenserverCreateCloneFull = new ConfigKey<>(Boolean.class, + "xenserver.create.full.clone", + "Storage", + "false", + "If set to true, creates VMs as full clones on XenServer hypervisor (uses VDI.copy instead of VDI.clone, removing the linked-clone parent relationship).", + true, + ConfigKey.Scope.StoragePool, + null); ConfigKey VmwareAllowParallelExecution = new ConfigKey<>(Boolean.class, "vmware.allow.parallel.command.execution", "Advanced", diff --git a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java index 8145158dfa40..c5654e13db1f 100644 --- a/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java +++ b/engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java @@ -187,7 +187,7 @@ protected Answer copyObject(DataObject srcData, DataObject destData, Host destHo srcForCopy = cacheData = cacheMgr.createCacheObject(srcData, destScope); } - CopyCommand cmd = new CopyCommand(srcForCopy.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(destData.getTO()), primaryStorageDownloadWait, + CopyCommand cmd = new CopyCommand(srcForCopy.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnDest(destData.getTO()), primaryStorageDownloadWait, VirtualMachineManager.ExecuteInSequence.value()); EndPoint ep = destHost != null ? RemoteHostEndPoint.getHypervisorHostEndPoint(destHost) : selector.select(srcForCopy, destData); if (ep == null) { @@ -253,6 +253,43 @@ protected DataTO addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(DataTO return dataTO; } + /** + * Adds {@code 'xenserver.create.full.clone'} value for a given primary storage, whose HV is XenServer, on datastore's {@code fullCloneFlag} field + * @param dataTO Dest data store TO + * @return dataTO including fullCloneFlag, if provided + */ + protected DataTO addFullCloneAndDiskprovisiongStrictnessFlagOnXenServerDest(DataTO dataTO) { + if (dataTO != null && dataTO.getHypervisorType().equals(Hypervisor.HypervisorType.XenServer)){ + DataStoreTO dataStoreTO = dataTO.getDataStore(); + if (dataStoreTO != null && dataStoreTO instanceof PrimaryDataStoreTO){ + PrimaryDataStoreTO primaryDataStoreTO = (PrimaryDataStoreTO) dataStoreTO; + primaryDataStoreTO.setFullCloneFlag(StorageManager.XenserverCreateCloneFull.valueIn(primaryDataStoreTO.getId())); + } + } + return dataTO; + } + + /** + * Dispatches to the per-hypervisor {@code addFullCloneAndDiskprovisiongStrictnessFlagOn*Dest} helper + * based on {@code dataTO.getHypervisorType()}. Returns {@code dataTO} unchanged for hypervisors + * that do not have a full-clone toggle. + * @param dataTO Dest data store TO + * @return dataTO including fullCloneFlag, if provided + */ + protected DataTO addFullCloneAndDiskprovisiongStrictnessFlagOnDest(DataTO dataTO) { + if (dataTO == null) { + return dataTO; + } + switch (dataTO.getHypervisorType()) { + case VMware: + return addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(dataTO); + case XenServer: + return addFullCloneAndDiskprovisiongStrictnessFlagOnXenServerDest(dataTO); + default: + return dataTO; + } + } + protected Answer copyObject(DataObject srcData, DataObject destData) { return copyObject(srcData, destData, null); } @@ -309,7 +346,7 @@ protected Answer copyVolumeFromSnapshot(DataObject snapObj, DataObject volObj) { ep = selector.select(srcData, volObj); } - CopyCommand cmd = new CopyCommand(srcData.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(volObj.getTO()), _createVolumeFromSnapshotWait, VirtualMachineManager.ExecuteInSequence.value()); + CopyCommand cmd = new CopyCommand(srcData.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnDest(volObj.getTO()), _createVolumeFromSnapshotWait, VirtualMachineManager.ExecuteInSequence.value()); Answer answer = null; if (ep == null) { @@ -332,7 +369,7 @@ protected Answer copyVolumeFromSnapshot(DataObject snapObj, DataObject volObj) { } protected Answer cloneVolume(DataObject template, DataObject volume) { - CopyCommand cmd = new CopyCommand(template.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(volume.getTO()), 0, VirtualMachineManager.ExecuteInSequence.value()); + CopyCommand cmd = new CopyCommand(template.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnDest(volume.getTO()), 0, VirtualMachineManager.ExecuteInSequence.value()); try { EndPoint ep = selector.select(volume, anyVolumeRequiresEncryption(volume)); Answer answer = null; @@ -416,7 +453,7 @@ protected Answer copyVolumeBetweenPools(DataObject srcData, DataObject destData) objOnImageStore.processEvent(Event.CopyingRequested); - CopyCommand cmd = new CopyCommand(objOnImageStore.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(destData.getTO()), _copyvolumewait, VirtualMachineManager.ExecuteInSequence.value()); + CopyCommand cmd = new CopyCommand(objOnImageStore.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnDest(destData.getTO()), _copyvolumewait, VirtualMachineManager.ExecuteInSequence.value()); EndPoint ep = selector.select(objOnImageStore, destData, encryptionRequired); if (ep == null) { String errMsg = String.format(NO_REMOTE_ENDPOINT_WITH_ENCRYPTION, encryptionRequired); @@ -660,7 +697,7 @@ protected Answer createTemplateFromSnapshot(DataObject srcData, DataObject destD ep = selector.select(srcData, destData); } - CopyCommand cmd = new CopyCommand(srcData.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(destData.getTO()), _createprivatetemplatefromsnapshotwait, VirtualMachineManager.ExecuteInSequence.value()); + CopyCommand cmd = new CopyCommand(srcData.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnDest(destData.getTO()), _createprivatetemplatefromsnapshotwait, VirtualMachineManager.ExecuteInSequence.value()); Answer answer = null; if (ep == null) { logger.error(NO_REMOTE_ENDPOINT_SSVM); @@ -698,7 +735,7 @@ protected Answer copySnapshot(DataObject srcData, DataObject destData) { Scope selectedScope = pickCacheScopeForCopy(srcData, destData); cacheData = cacheMgr.getCacheObject(srcData, selectedScope); - CopyCommand cmd = new CopyCommand(srcData.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(destData.getTO()), _backupsnapshotwait, VirtualMachineManager.ExecuteInSequence.value()); + CopyCommand cmd = new CopyCommand(srcData.getTO(), addFullCloneAndDiskprovisiongStrictnessFlagOnDest(destData.getTO()), _backupsnapshotwait, VirtualMachineManager.ExecuteInSequence.value()); cmd.setCacheTO(cacheData.getTO()); cmd.setOptions(options); EndPoint ep = selector.select(srcData, destData, encryptionRequired); @@ -709,7 +746,7 @@ protected Answer copySnapshot(DataObject srcData, DataObject destData) { answer = ep.sendMessage(cmd); } } else { - addFullCloneAndDiskprovisiongStrictnessFlagOnVMwareDest(destData.getTO()); + addFullCloneAndDiskprovisiongStrictnessFlagOnDest(destData.getTO()); CopyCommand cmd = new CopyCommand(srcData.getTO(), destData.getTO(), _backupsnapshotwait, VirtualMachineManager.ExecuteInSequence.value()); cmd.setOptions(options); EndPoint ep = selector.select(srcData, destData, StorageAction.BACKUPSNAPSHOT, encryptionRequired); diff --git a/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategyTest.java b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategyTest.java index e167cc0a9653..7e300f2608f0 100755 --- a/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategyTest.java +++ b/engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategyTest.java @@ -101,6 +101,14 @@ public void testAddFullCloneFlagOnVMwareDest(){ verify(dataStoreTO).setFullCloneFlag(FULL_CLONE_FLAG); } + @Test + public void testAddFullCloneFlagOnXenServerDest() throws IllegalAccessException, NoSuchFieldException { + overrideDefaultConfigValue(StorageManager.XenserverCreateCloneFull, String.valueOf(FULL_CLONE_FLAG)); + when(dataTO.getHypervisorType()).thenReturn(HypervisorType.XenServer); + strategy.addFullCloneAndDiskprovisiongStrictnessFlagOnXenServerDest(dataTO); + verify(dataStoreTO).setFullCloneFlag(FULL_CLONE_FLAG); + } + @Test public void testAddFullCloneFlagOnNotVmwareDest(){ verify(dataStoreTO, never()).setFullCloneFlag(any(Boolean.class)); diff --git a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java index c9e6118340cc..a1d27b65abac 100644 --- a/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java +++ b/plugins/hypervisors/xenserver/src/main/java/com/cloud/hypervisor/xenserver/resource/XenServerStorageProcessor.java @@ -859,12 +859,19 @@ public Answer cloneVolumeFromBaseTemplate(final CopyCommand cmd) { final DataTO srcData = cmd.getSrcTO(); final DataTO destData = cmd.getDestTO(); final VolumeObjectTO volume = (VolumeObjectTO) destData; + final DataStoreTO destStore = volume.getDataStore(); + final boolean fullClone = destStore instanceof PrimaryDataStoreTO + && Boolean.TRUE.equals(((PrimaryDataStoreTO) destStore).isFullCloneFlag()); VDI vdi = null; try { VDI tmpltvdi = null; tmpltvdi = getVDIbyUuid(conn, srcData.getPath()); - vdi = tmpltvdi.createClone(conn, new HashMap()); + if (fullClone) { + vdi = tmpltvdi.copy(conn, tmpltvdi.getSR(conn)); + } else { + vdi = tmpltvdi.createClone(conn, new HashMap()); + } Long virtualSize = vdi.getVirtualSize(conn); if (volume.getSize() > virtualSize) { logger.debug("Overriding provided Template's size with new size " + toHumanReadableSize(volume.getSize()) + " for volume: " + volume.getName()); diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java index 7c501c78beeb..e4c37fd9e941 100644 --- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java +++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java @@ -4616,6 +4616,7 @@ public ConfigKey[] getConfigKeys() { SecStorageVMAutoScaleDown, MountDisabledStoragePool, VmwareCreateCloneFull, + XenserverCreateCloneFull, VmwareAllowParallelExecution, DataStoreDownloadFollowRedirects, AllowVolumeReSizeBeyondAllocation,