@@ -1406,7 +1406,7 @@ public KVMPhysicalDisk createPhysicalDisk(String name, KVMStoragePool pool,
14061406 return (dataPool == null ) ? createPhysicalDiskByLibVirt (name , pool , PhysicalDiskFormat .RAW , provisioningType , size ) :
14071407 createPhysicalDiskByQemuImg (name , pool , PhysicalDiskFormat .RAW , provisioningType , size , passphrase );
14081408 } else if (StoragePoolType .CLVM_NG .equals (poolType )) {
1409- return createClvmNgDiskWithBacking (name , 0 , size , null , pool );
1409+ return createClvmNgDiskWithBacking (name , 0 , size , null , pool , provisioningType );
14101410 } else if (StoragePoolType .NetworkFilesystem .equals (poolType ) || StoragePoolType .Filesystem .equals (poolType )) {
14111411 switch (format ) {
14121412 case QCOW2 :
@@ -1578,6 +1578,8 @@ private void ensureClvmNgBackingFileAccessible(String volumeName, KVMStoragePool
15781578 }
15791579 }
15801580 }
1581+ } else {
1582+ logger .debug ("Volume {} does not have a backing file (full clone)" , volumePath );
15811583 }
15821584 } else {
15831585 logger .debug ("Volume {} does not have a backing file (full clone)" , volumePath );
@@ -1828,7 +1830,7 @@ public KVMPhysicalDisk createDiskFromTemplate(KVMPhysicalDisk template,
18281830 logger .info ("Creating CLVM_NG volume {} with backing file from template {}" , newUuid , template .getName ());
18291831 String backingFile = getClvmBackingFile (template , destPool );
18301832
1831- disk = createClvmNgDiskWithBacking (newUuid , timeout , size , backingFile , destPool );
1833+ disk = createClvmNgDiskWithBacking (newUuid , timeout , size , backingFile , destPool , provisioningType );
18321834 return disk ;
18331835 }
18341836 List <QemuObject > passphraseObjects = new ArrayList <>();
@@ -2060,9 +2062,7 @@ private KVMPhysicalDisk createDiskFromTemplateOnRBD(KVMPhysicalDisk template,
20602062 rbd .close (destImage );
20612063 } else {
20622064 logger .debug ("The source image " + srcPool .getSourceDir () + "/" + template .getName ()
2063- + " is RBD format 2. We will perform a RBD clone using snapshot "
2064- + rbdTemplateSnapName );
2065- /* The source image is format 2, we can do a RBD snapshot+clone (layering) */
2065+ + " is RBD format 2. We will perform a RBD snapshot+clone (layering)" );
20662066
20672067
20682068 logger .debug ("Checking if RBD snapshot " + srcPool .getSourceDir () + "/" + template .getName ()
@@ -2553,51 +2553,54 @@ private long getVgPhysicalExtentSize(String vgName) {
25532553 }
25542554
25552555 /**
2556- * Calculate LVM LV size for CLVM_NG template allocation.
2557- * Templates need space for actual data plus overhead for QCOW2 metadata expansion during conversion.
2558- * Formula: physical_size + 50% of virtual_size
2556+ * Calculate LVM LV size for CLVM_NG volume allocation.
2557+ * Volumes use QCOW2-on-LVM with extended_l2=on and need:
2558+ * - Base size (virtual size)
2559+ * - QCOW2 metadata overhead (L1/L2 tables, refcount tables, headers)
2560+ *
2561+ * For QCOW2 with 64k clusters and extended L2 tables (extended_l2=on):
2562+ * - Each 64KB cluster contains data
2563+ * - Each L2 table entry is 16 bytes (extended L2, double the standard 8 bytes)
2564+ * - Each 64KB L2 cluster can hold 4096 entries (64KB / 16 bytes)
2565+ * Formula: Total overhead (MiB) = ((virtualSize_GiB × 1024 × 1024) / (64 × 4096)) × 2 + 2 MiB headers
25592566 *
2560- * @param physicalSize Physical size in bytes (actual allocated data in source template)
2561- * @param virtualSize Virtual size in bytes (full disk capacity)
2567+ * Quick reference (64k clusters, extended_l2=on):
2568+ * 10 GiB virtual → ~7 MiB overhead → 2 PEs (8 MiB)
2569+ * 100 GiB virtual → ~52 MiB overhead → 13 PEs (52 MiB)
2570+ * 1 TiB virtual → ~514 MiB overhead → 129 PEs (516 MiB)
2571+ * 2 TiB virtual → ~1026 MiB overhead → 257 PEs (1028 MiB)
2572+ *
2573+ * @param virtualSize Virtual disk size in bytes (for overhead calculation)
25622574 * @param vgName Volume group name to query PE size
2563- * @return Size in bytes to allocate for template LV
2575+ * @return Size in bytes to allocate for LV
25642576 */
2565- private long calculateClvmNgTemplateLvSize ( long physicalSize , long virtualSize , String vgName ) {
2577+ private long calculateClvmNgLvSize ( long virtualSize , String vgName ) {
25662578 long peSize = getVgPhysicalExtentSize (vgName );
25672579
2568- long minOverhead = 64 * 1024 * 1024L ;
2569- long virtualSizeOverhead = (long ) (virtualSize * 0.30 );
2570- long overhead = Math .max (minOverhead , virtualSizeOverhead );
2580+ long clusterSize = 64 * 1024L ;
2581+ // Each L2 entry is 16 bytes (extended_l2=on), and each 64KB cluster holds 4096 entries (64KB / 16 bytes)
2582+ long l2Multiplier = 4096L ;
2583+
2584+ long numDataClusters = (virtualSize + clusterSize - 1 ) / clusterSize ;
2585+ long numL2Clusters = (numDataClusters + l2Multiplier - 1 ) / l2Multiplier ;
2586+ long l2TableSize = numL2Clusters * clusterSize ;
2587+ long refcountTableSize = l2TableSize ;
25712588
2572- long targetSize = physicalSize + overhead ;
2589+ // Headers and other metadata (L1 table, QCOW2 header, etc.)
2590+ long headerOverhead = 2 * 1024 * 1024L ; // 2 MiB for headers
2591+ long metadataOverhead = l2TableSize + refcountTableSize + headerOverhead ;
2592+ long targetSize = virtualSize + metadataOverhead ;
25732593 long roundedSize = ((targetSize + peSize - 1 ) / peSize ) * peSize ;
2594+ long virtualSizeGiB = virtualSize / (1024 * 1024 * 1024L );
2595+ long overheadMiB = metadataOverhead / (1024 * 1024L );
25742596
2575- logger .info ("Calculated template LV size: {} bytes (physical: {}, virtual: {}, overhead: {} (50% of virtual), rounded to {} PEs, PE size = {} bytes)" ,
2576- roundedSize , physicalSize , virtualSize , overhead , roundedSize / peSize , peSize );
2597+ logger .info ("Calculated volume LV size: {} bytes (virtual: {} GiB, " +
2598+ "QCOW2 metadata overhead: {} MiB (64k clusters, extended_l2=on), rounded to {} PEs, PE size = {} bytes)" ,
2599+ roundedSize , virtualSizeGiB , overheadMiB , roundedSize / peSize , peSize );
25772600
25782601 return roundedSize ;
25792602 }
25802603
2581- /**
2582- * Calculate LVM LV size for CLVM_NG volume allocation.
2583- * Volumes with backing files need approximately the virtual size allocated on block devices.
2584- * Formula: virtual_size + 1 PE (for QCOW2 metadata and header)
2585- *
2586- * @param virtualSize Virtual size in bytes (full disk capacity)
2587- * @param vgName Volume group name to query PE size
2588- * @return Size in bytes to allocate for volume LV
2589- */
2590- private long calculateClvmNgVolumeLvSize (long virtualSize , String vgName ) {
2591- long peSize = getVgPhysicalExtentSize (vgName );
2592-
2593- long roundedSize = ((virtualSize + peSize - 1 ) / peSize ) * peSize ;
2594- long finalSize = roundedSize + peSize ;
2595-
2596- logger .info ("Calculated volume LV size: {} bytes (virtual: {}, rounded to {} PEs + 1 PE overhead, PE size = {} bytes)" ,
2597- finalSize , virtualSize , roundedSize / peSize , peSize );
2598-
2599- return finalSize ;
2600- }
26012604
26022605 private long getQcow2VirtualSize (String imagePath ) {
26032606 Script qemuImg = new Script ("qemu-img" , 300000 , logger );
@@ -2623,7 +2626,7 @@ private long getQcow2VirtualSize(String imagePath) {
26232626
26242627
26252628 private long getQcow2PhysicalSize (String imagePath ) {
2626- Script qemuImg = new Script ("qemu-img" , 300000 , logger );
2629+ Script qemuImg = new Script ("qemu-img" , Duration . millis ( 300000 ) , logger );
26272630 qemuImg .add ("info" );
26282631 qemuImg .add ("--output=json" );
26292632 qemuImg .add (imagePath );
@@ -2644,12 +2647,14 @@ private long getQcow2PhysicalSize(String imagePath) {
26442647 return info .get ("actual-size" ).getAsLong ();
26452648 }
26462649
2647- private KVMPhysicalDisk createClvmNgDiskWithBacking (String volumeUuid , int timeout , long virtualSize , String backingFile , KVMStoragePool pool ) {
2650+ private KVMPhysicalDisk createClvmNgDiskWithBacking (String volumeUuid , int timeout , long virtualSize , String backingFile ,
2651+ KVMStoragePool pool , Storage .ProvisioningType provisioningType ) {
26482652 String vgName = getVgName (pool .getLocalPath ());
2649- long lvSize = calculateClvmNgVolumeLvSize (virtualSize , vgName );
2653+ long lvSize = calculateClvmNgLvSize (virtualSize , vgName );
26502654 String volumePath = "/dev/" + vgName + "/" + volumeUuid ;
26512655
2652- logger .debug ("Creating CLVM_NG volume {} with LV size {} bytes (virtual size: {} bytes)" , volumeUuid , lvSize , virtualSize );
2656+ logger .debug ("Creating CLVM_NG volume {} with LV size {} bytes (virtual size: {} bytes, provisioning: {})" ,
2657+ volumeUuid , lvSize , virtualSize , provisioningType );
26532658
26542659 Script lvcreate = new Script ("lvcreate" , Duration .millis (timeout ), logger );
26552660 lvcreate .add ("-n" , volumeUuid );
@@ -2666,9 +2671,20 @@ private KVMPhysicalDisk createClvmNgDiskWithBacking(String volumeUuid, int timeo
26662671 qemuImg .add ("-f" , "qcow2" );
26672672
26682673 StringBuilder qcow2Options = new StringBuilder ();
2669- qcow2Options .append ("preallocation=metadata" );
2674+
2675+ // Set preallocation based on provisioning type
2676+ // THIN: preallocation=off (sparse file, allocate on write)
2677+ // SPARSE / FAT: preallocation=metadata (allocate metadata only)
2678+ String preallocation ;
2679+ if (provisioningType == Storage .ProvisioningType .THIN ) {
2680+ preallocation = "off" ;
2681+ } else {
2682+ preallocation = "metadata" ;
2683+ }
2684+
2685+ qcow2Options .append ("preallocation=" ).append (preallocation );
26702686 qcow2Options .append (",extended_l2=on" );
2671- qcow2Options .append (",cluster_size=128k " );
2687+ qcow2Options .append (",cluster_size=64k " );
26722688
26732689 if (backingFile != null && !backingFile .isEmpty ()) {
26742690 qcow2Options .append (",backing_file=" ).append (backingFile );
@@ -2691,8 +2707,8 @@ private KVMPhysicalDisk createClvmNgDiskWithBacking(String volumeUuid, int timeo
26912707 disk .setSize (lvSize );
26922708 disk .setVirtualSize (virtualSize );
26932709
2694- logger .info ("Successfully created CLVM_NG volume {} with backing file (LV size: {}, virtual size: {})" ,
2695- volumeUuid , lvSize , virtualSize );
2710+ logger .info ("Successfully created CLVM_NG volume {} with backing file (LV size: {}, virtual size: {}, provisioning: {}, preallocation: {} )" ,
2711+ volumeUuid , lvSize , virtualSize , provisioningType , preallocation );
26962712
26972713 return disk ;
26982714 }
@@ -2711,7 +2727,7 @@ public void createTemplateOnClvmNg(String templatePath, String templateUuid, int
27112727
27122728 long virtualSize = getQcow2VirtualSize (templatePath );
27132729 long physicalSize = getQcow2PhysicalSize (templatePath );
2714- long lvSize = calculateClvmNgTemplateLvSize ( physicalSize , virtualSize , vgName );
2730+ long lvSize = virtualSize ; // as extended_l2=off and preallocation=off
27152731
27162732 logger .info ("Template source - Physical: {} bytes, Virtual: {} bytes, LV will be: {} bytes" ,
27172733 physicalSize , virtualSize , lvSize );
@@ -2724,7 +2740,6 @@ public void createTemplateOnClvmNg(String templatePath, String templateUuid, int
27242740 if (result != null ) {
27252741 throw new CloudRuntimeException ("Failed to create LV for CLVM_NG template: " + result );
27262742 }
2727-
27282743 Script qemuImgConvert = new Script ("qemu-img" , Duration .millis (timeout ), logger );
27292744 qemuImgConvert .add ("convert" );
27302745 qemuImgConvert .add (templatePath );
@@ -2739,8 +2754,7 @@ public void createTemplateOnClvmNg(String templatePath, String templateUuid, int
27392754 }
27402755
27412756 long actualVirtualSize = getQcow2VirtualSize (lvPath );
2742- logger .info ("Created template LV {} with size {} bytes (source physical: {}, actual virtual: {}, overhead: {})" ,
2743- lvName , lvSize , physicalSize , actualVirtualSize , lvSize - physicalSize );
2757+ logger .info ("Created template LV {} with size {} bytes (source physical: {}, actual virtual: {})" , lvName , lvSize , physicalSize , actualVirtualSize );
27442758
27452759 try {
27462760 ensureTemplateLvInSharedMode (lvPath , true );
@@ -2754,7 +2768,6 @@ public void createTemplateOnClvmNg(String templatePath, String templateUuid, int
27542768 templateDisk .setFormat (PhysicalDiskFormat .QCOW2 );
27552769 templateDisk .setVirtualSize (actualVirtualSize );
27562770 templateDisk .setSize (lvSize );
2757- templateDisk .setSize (lvSize );
27582771
27592772 }
27602773
0 commit comments