5555
5656import com .cloud .agent .api .Command ;
5757import com .cloud .hypervisor .kvm .resource .LibvirtXMLParser ;
58+ import com .fasterxml .jackson .core .JsonProcessingException ;
59+ import com .fasterxml .jackson .databind .JsonNode ;
60+ import com .fasterxml .jackson .databind .ObjectMapper ;
5861import org .apache .cloudstack .agent .directdownload .DirectDownloadAnswer ;
5962import org .apache .cloudstack .agent .directdownload .DirectDownloadCommand ;
6063import org .apache .cloudstack .direct .download .DirectDownloadHelper ;
@@ -368,7 +371,16 @@ public Answer copyTemplateToPrimaryStorage(final CopyCommand cmd) {
368371 final TemplateObjectTO newTemplate = new TemplateObjectTO ();
369372 newTemplate .setPath (primaryVol .getName ());
370373 newTemplate .setSize (primaryVol .getSize ());
371- newTemplate .setFormat (getFormat (primaryPool .getType ()));
374+
375+ if (List .of (
376+ StoragePoolType .RBD ,
377+ StoragePoolType .PowerFlex ,
378+ StoragePoolType .Linstor ,
379+ StoragePoolType .FiberChannel ).contains (primaryPool .getType ())) {
380+ newTemplate .setFormat (ImageFormat .RAW );
381+ } else {
382+ newTemplate .setFormat (ImageFormat .QCOW2 );
383+ }
372384 data = newTemplate ;
373385 } else if (destData .getObjectType () == DataObjectType .VOLUME ) {
374386 final VolumeObjectTO volumeObjectTO = new VolumeObjectTO ();
@@ -757,7 +769,7 @@ public Answer createTemplateFromVolume(final CopyCommand cmd) {
757769 templateContent += "snapshot.name=" + dateFormat .format (date ) + System .getProperty ("line.separator" );
758770
759771
760- try (FileOutputStream templFo = new FileOutputStream (templateProp );){
772+ try (FileOutputStream templFo = new FileOutputStream (templateProp );) {
761773 templFo .write (templateContent .getBytes ());
762774 templFo .flush ();
763775 } catch (final IOException e ) {
@@ -822,11 +834,9 @@ private Answer createTemplateFromVolumeOrSnapshot(CopyCommand cmd) {
822834
823835 if (srcData instanceof VolumeObjectTO ) {
824836 isVolume = true ;
825- }
826- else if (srcData instanceof SnapshotObjectTO ) {
837+ } else if (srcData instanceof SnapshotObjectTO ) {
827838 isVolume = false ;
828- }
829- else {
839+ } else {
830840 return new CopyCmdAnswer ("unsupported object type" );
831841 }
832842
@@ -892,8 +902,7 @@ else if (srcData instanceof SnapshotObjectTO) {
892902
893903 if (isVolume ) {
894904 templateContent += "volume.name=" + dateFormat .format (date ) + System .getProperty ("line.separator" );
895- }
896- else {
905+ } else {
897906 templateContent += "snapshot.name=" + dateFormat .format (date ) + System .getProperty ("line.separator" );
898907 }
899908
@@ -931,8 +940,7 @@ else if (srcData instanceof SnapshotObjectTO) {
931940 } catch (Exception ex ) {
932941 if (isVolume ) {
933942 logger .debug ("Failed to create template from volume: " , ex );
934- }
935- else {
943+ } else {
936944 logger .debug ("Failed to create template from snapshot: " , ex );
937945 }
938946
@@ -1093,7 +1101,7 @@ public Answer backupSnapshot(final CopyCommand cmd) {
10931101 q .convert (srcFile , destFile );
10941102
10951103 final File snapFile = new File (snapshotFile );
1096- if (snapFile .exists ()) {
1104+ if (snapFile .exists ()) {
10971105 size = snapFile .length ();
10981106 }
10991107
@@ -1127,7 +1135,7 @@ public Answer backupSnapshot(final CopyCommand cmd) {
11271135 return new CopyCmdAnswer (result );
11281136 }
11291137 final File snapFile = new File (snapshotDestPath + "/" + descName );
1130- if (snapFile .exists ()){
1138+ if (snapFile .exists ()) {
11311139 size = snapFile .length ();
11321140 }
11331141 }
@@ -1473,7 +1481,7 @@ protected synchronized void attachOrDetachDisk(final Connect conn, final boolean
14731481 if (resource .getHypervisorType () == Hypervisor .HypervisorType .LXC ) {
14741482 final String device = resource .mapRbdDevice (attachingDisk , kvdoEnable );
14751483 if (device != null ) {
1476- logger .debug ("RBD device on host is: " + device );
1484+ logger .debug ("RBD device on host is: " + device );
14771485 attachingDisk .setPath (device );
14781486 } else {
14791487 throw new InternalErrorException ("Error while mapping disk " +attachingDisk .getPath ()+" on host" );
@@ -1516,11 +1524,11 @@ protected synchronized void attachOrDetachDisk(final Connect conn, final boolean
15161524 }
15171525 diskdef .setSerial (serial );
15181526 if (attachingPool .getType () == StoragePoolType .RBD ) {
1519- if (resource .getHypervisorType () == Hypervisor .HypervisorType .LXC ){
1527+ if (resource .getHypervisorType () == Hypervisor .HypervisorType .LXC ) {
15201528 // For LXC, map image to host and then attach to Vm
15211529 final String device = resource .mapRbdDevice (attachingDisk , false );
15221530 if (device != null ) {
1523- logger .debug ("RBD device on host is: " + device );
1531+ logger .debug ("RBD device on host is: " + device );
15241532 diskdef .defBlockBasedDisk (device , devId , busT );
15251533 } else {
15261534 throw new InternalErrorException ("Error while mapping disk " +attachingDisk .getPath ()+" on host" );
@@ -1605,7 +1613,7 @@ protected synchronized void attachOrDetachDisk(final Connect conn, final boolean
16051613 if ((iopsWriteRateMaxLength != null ) && (iopsWriteRateMaxLength > 0 )) {
16061614 diskdef .setIopsWriteRateMaxLength (iopsWriteRateMaxLength );
16071615 }
1608- if (cacheMode != null ) {
1616+ if (cacheMode != null ) {
16091617 diskdef .setCacheMode (DiskDef .DiskCacheMode .valueOf (cacheMode .toUpperCase ()));
16101618 }
16111619
@@ -1822,7 +1830,7 @@ public Answer createVolume(final CreateObjectCommand cmd) {
18221830 }
18231831
18241832 final VolumeObjectTO newVol = new VolumeObjectTO ();
1825- if (vol != null ) {
1833+ if (vol != null ) {
18261834 newVol .setPath (vol .getName ());
18271835 if (vol .getQemuEncryptFormat () != null ) {
18281836 newVol .setEncryptFormat (vol .getQemuEncryptFormat ().toString ());
@@ -1918,17 +1926,64 @@ public Answer createSnapshot(final CreateObjectCommand cmd) {
19181926
19191927 String diskPath = disk .getPath ();
19201928 String snapshotPath = diskPath + File .separator + snapshotName ;
1921- SnapshotObjectTO newSnapshot = new SnapshotObjectTO ();
1922- if (DomainInfo .DomainState .VIR_DOMAIN_RUNNING .equals (state ) && !primaryPool .isExternalSnapshot ()) {
1923- if (snapshotTO .isKvmIncrementalSnapshot ()) {
1924- newSnapshot = takeIncrementalVolumeSnapshotOfRunningVm (snapshotTO , primaryPool , secondaryPool , imageStoreTo != null ? imageStoreTo .getUrl () : null , snapshotName , volume , vm , conn , cmd .getWait ());
1925- } else {
1926- newSnapshot = takeFullVolumeSnapshotOfRunningVm (cmd , primaryPool , secondaryPool , disk , snapshotName , conn , vmName , diskPath , vm , volume , snapshotPath );
1929+ Long snapshotSize = null ;
1930+ if (state == DomainInfo .DomainState .VIR_DOMAIN_RUNNING && !primaryPool .isExternalSnapshot ()) {
1931+
1932+ validateAvailableSizeOnPoolToTakeVolumeSnapshot (primaryPool , disk );
1933+
1934+ try {
1935+ snapshotPath = getSnapshotPathInPrimaryStorage (primaryPool .getLocalPath (), snapshotName );
1936+
1937+ String diskLabel = takeVolumeSnapshot (resource .getDisks (conn , vmName ), snapshotName , diskPath , vm );
1938+ String convertResult = convertBaseFileToSnapshotFileInPrimaryStorageDir (primaryPool , disk , snapshotPath , volume , cmd .getWait ());
1939+
1940+ mergeSnapshotIntoBaseFile (vm , diskLabel , diskPath , snapshotName , volume , conn );
1941+
1942+ validateConvertResult (convertResult , snapshotPath );
1943+ } catch (LibvirtException e ) {
1944+ if (!e .getMessage ().contains (LIBVIRT_OPERATION_NOT_SUPPORTED_MESSAGE )) {
1945+ throw e ;
1946+ }
1947+
1948+ logger .info (String .format ("It was not possible to take live disk snapshot for volume [%s], in VM [%s], due to [%s]. We will take full snapshot of the VM"
1949+ + " and extract the disk instead. Consider upgrading your QEMU binary." , volume , vmName , e .getMessage ()));
1950+
1951+ takeFullVmSnapshotForBinariesThatDoesNotSupportLiveDiskSnapshot (vm , snapshotName , vmName );
1952+ primaryPool .createFolder (TemplateConstants .DEFAULT_SNAPSHOT_ROOT_DIR );
1953+ extractDiskFromFullVmSnapshot (disk , volume , snapshotPath , snapshotName , vmName , vm );
1954+ }
1955+
1956+ /*
1957+ * libvirt on RHEL6 doesn't handle resume event emitted from
1958+ * qemu
1959+ */
1960+ vm = resource .getDomain (conn , vmName );
1961+ state = vm .getInfo ().state ;
1962+ if (state == DomainInfo .DomainState .VIR_DOMAIN_PAUSED ) {
1963+ vm .resume ();
19271964 }
19281965 } else {
19291966 if (primaryPool .getType () == StoragePoolType .RBD ) {
1930- takeRbdVolumeSnapshotOfStoppedVm (primaryPool , disk , snapshotName );
1931- newSnapshot .setPath (snapshotPath );
1967+ try {
1968+ Rados r = radosConnect (primaryPool );
1969+
1970+ final IoCTX io = r .ioCtxCreate (primaryPool .getSourceDir ());
1971+ final Rbd rbd = new Rbd (io );
1972+ final RbdImage image = rbd .open (disk .getName ());
1973+
1974+ logger .debug ("Attempting to create RBD snapshot " + disk .getName () + "@" + snapshotName );
1975+ image .snapCreate (snapshotName );
1976+
1977+ long rbdSnapshotSize = getRbdSnapshotSize (primaryPool .getSourceDir (), disk .getName (), snapshotName , primaryPool .getSourceHost (), primaryPool .getAuthUserName (), primaryPool .getAuthSecret ());
1978+ if (rbdSnapshotSize > 0 ) {
1979+ snapshotSize = rbdSnapshotSize ;
1980+ }
1981+
1982+ rbd .close (image );
1983+ r .ioCtxDestroy (io );
1984+ } catch (final Exception e ) {
1985+ logger .error ("A RBD snapshot operation on " + disk .getName () + " failed. The error was: " + e .getMessage ());
1986+ }
19321987 } else if (primaryPool .getType () == StoragePoolType .CLVM ) {
19331988 CreateObjectAnswer result = takeClvmVolumeSnapshotOfStoppedVm (disk , snapshotName );
19341989 if (result != null ) return result ;
@@ -1942,8 +1997,10 @@ public Answer createSnapshot(final CreateObjectCommand cmd) {
19421997 }
19431998 }
19441999
1945- if (secondaryPool != null ) {
1946- storagePoolMgr .deleteStoragePool (secondaryPool .getType (), secondaryPool .getUuid ());
2000+ final SnapshotObjectTO newSnapshot = new SnapshotObjectTO ();
2001+ newSnapshot .setPath (snapshotPath );
2002+ if (snapshotSize != null ) {
2003+ newSnapshot .setPhysicalSize (snapshotSize );
19472004 }
19482005
19492006 return new CreateObjectAnswer (newSnapshot );
@@ -2454,6 +2511,31 @@ private Pair<String, String> getFullSnapshotOrCheckpointPathAndDirPathOnCorrectS
24542511 return new Pair <>(fullSnapshotPath , dirPath );
24552512 }
24562513
2514+ private long getRbdSnapshotSize (String poolPath , String diskName , String snapshotName , String rbdMonitor , String authUser , String authSecret ) {
2515+ logger .debug ("Get RBD snapshot size for {}/{}@{}" , poolPath , diskName , snapshotName );
2516+ //cmd: rbd du <pool>/<disk-name>@<snapshot-name> --format json --mon-host <monitor-host> --id <user> --key <key> 2>/dev/null
2517+ String snapshotDetailsInJson = Script .runSimpleBashScript (String .format ("rbd du %s/%s@%s --format json --mon-host %s --id %s --key %s 2>/dev/null" , poolPath , diskName , snapshotName , rbdMonitor , authUser , authSecret ));
2518+ if (StringUtils .isNotBlank (snapshotDetailsInJson )) {
2519+ ObjectMapper mapper = new ObjectMapper ();
2520+ try {
2521+ JsonNode root = mapper .readTree (snapshotDetailsInJson );
2522+ for (JsonNode image : root .path ("images" )) {
2523+ if (snapshotName .equals (image .path ("snapshot" ).asText ())) {
2524+ long usedSizeInBytes = image .path ("used_size" ).asLong ();
2525+ logger .debug ("RBD snapshot {}/{}@{} used size in bytes: {}" , poolPath , diskName , snapshotName , usedSizeInBytes );
2526+ return usedSizeInBytes ;
2527+ }
2528+ }
2529+ } catch (JsonProcessingException e ) {
2530+ logger .error ("Unable to get the RBD snapshot size, RBD snapshot cmd output: {}" , snapshotDetailsInJson , e );
2531+ }
2532+ } else {
2533+ logger .warn ("Failed to get RBD snapshot size for {}/{}@{} - no output for RBD snapshot cmd" , poolPath , diskName , snapshotName );
2534+ }
2535+
2536+ return 0 ;
2537+ }
2538+
24572539 protected void deleteFullVmSnapshotAfterConvertingItToExternalDiskSnapshot (Domain vm , String snapshotName , VolumeObjectTO volume , String vmName ) throws LibvirtException {
24582540 logger .debug (String .format ("Deleting full Instance Snapshot [%s] of Instance [%s] as we already converted it to an external disk Snapshot of the volume [%s]." , snapshotName , vmName ,
24592541 volume ));
0 commit comments