@@ -6482,4 +6482,198 @@ public String getHypervisorPath() {
64826482 public String getGuestCpuArch () {
64836483 return guestCpuArch ;
64846484 }
6485+
6486+ /**
6487+ * CLVM volume state for migration operations on source host
6488+ */
6489+ public enum ClvmVolumeState {
6490+ /** Shared mode (-asy) - used before migration to allow both hosts to access volume */
6491+ SHARED ("-asy" , "shared" , "Before migration: activating in shared mode" ),
6492+
6493+ /** Deactivate (-an) - used after successful migration to release volume on source */
6494+ DEACTIVATE ("-an" , "deactivated" , "After successful migration: deactivating volume" ),
6495+
6496+ /** Exclusive mode (-aey) - used after failed migration to revert to original exclusive state */
6497+ EXCLUSIVE ("-aey" , "exclusive" , "After failed migration: reverting to exclusive mode" );
6498+
6499+ private final String lvchangeFlag ;
6500+ private final String description ;
6501+ private final String logMessage ;
6502+
6503+ ClvmVolumeState (String lvchangeFlag , String description , String logMessage ) {
6504+ this .lvchangeFlag = lvchangeFlag ;
6505+ this .description = description ;
6506+ this .logMessage = logMessage ;
6507+ }
6508+
6509+ public String getLvchangeFlag () {
6510+ return lvchangeFlag ;
6511+ }
6512+
6513+ public String getDescription () {
6514+ return description ;
6515+ }
6516+
6517+ public String getLogMessage () {
6518+ return logMessage ;
6519+ }
6520+ }
6521+
6522+ public static void modifyClvmVolumesStateForMigration (List <DiskDef > disks , LibvirtComputingResource resource ,
6523+ VirtualMachineTO vmSpec , ClvmVolumeState state ) {
6524+ for (DiskDef disk : disks ) {
6525+ if (isClvmVolume (disk , resource , vmSpec )) {
6526+ String volumePath = disk .getDiskPath ();
6527+ try {
6528+ LOGGER .info ("[CLVM Migration] {} for volume [{}]" ,
6529+ state .getLogMessage (), volumePath );
6530+
6531+ Script cmd = new Script ("lvchange" , Duration .standardSeconds (300 ), LOGGER );
6532+ cmd .add (state .getLvchangeFlag ());
6533+ cmd .add (volumePath );
6534+
6535+ String result = cmd .execute ();
6536+ if (result != null ) {
6537+ LOGGER .error ("[CLVM Migration] Failed to set volume [{}] to {} state. Command result: {}" ,
6538+ volumePath , state .getDescription (), result );
6539+ } else {
6540+ LOGGER .info ("[CLVM Migration] Successfully set volume [{}] to {} state." ,
6541+ volumePath , state .getDescription ());
6542+ }
6543+ } catch (Exception e ) {
6544+ LOGGER .error ("[CLVM Migration] Exception while setting volume [{}] to {} state: {}" ,
6545+ volumePath , state .getDescription (), e .getMessage (), e );
6546+ }
6547+ }
6548+ }
6549+ }
6550+
6551+ /**
6552+ * Determines if a disk is on a CLVM storage pool by checking the actual pool type from VirtualMachineTO.
6553+ * This is the most reliable method as it uses CloudStack's own storage pool information.
6554+ *
6555+ * @param disk The disk definition to check
6556+ * @param resource The LibvirtComputingResource instance (unused but kept for compatibility)
6557+ * @param vmSpec The VirtualMachineTO specification containing disk and pool information
6558+ * @return true if the disk is on a CLVM storage pool, false otherwise
6559+ */
6560+ private static boolean isClvmVolume (DiskDef disk , LibvirtComputingResource resource , VirtualMachineTO vmSpec ) {
6561+ String diskPath = disk .getDiskPath ();
6562+ if (diskPath == null || vmSpec == null ) {
6563+ return false ;
6564+ }
6565+
6566+ try {
6567+ if (vmSpec .getDisks () != null ) {
6568+ for (DiskTO diskTO : vmSpec .getDisks ()) {
6569+ if (diskTO .getData () instanceof VolumeObjectTO ) {
6570+ VolumeObjectTO volumeTO = (VolumeObjectTO ) diskTO .getData ();
6571+ if (diskPath .equals (volumeTO .getPath ()) || diskPath .equals (diskTO .getPath ())) {
6572+ DataStoreTO dataStore = volumeTO .getDataStore ();
6573+ if (dataStore instanceof PrimaryDataStoreTO ) {
6574+ PrimaryDataStoreTO primaryStore = (PrimaryDataStoreTO ) dataStore ;
6575+ boolean isClvm = StoragePoolType .CLVM == primaryStore .getPoolType ();
6576+ LOGGER .debug ("Disk {} identified as CLVM={} via VirtualMachineTO pool type: {}" ,
6577+ diskPath , isClvm , primaryStore .getPoolType ());
6578+ return isClvm ;
6579+ }
6580+ }
6581+ }
6582+ }
6583+ }
6584+
6585+ // Fallback: Check VG attributes using vgs command (reliable)
6586+ // CLVM VGs have the 'c' (clustered) or 's' (shared) flag in their attributes
6587+ // Example: 'wz--ns' = shared, 'wz--n-' = not clustered
6588+ if (diskPath .startsWith ("/dev/" ) && !diskPath .contains ("/dev/mapper/" )) {
6589+ String vgName = extractVolumeGroupFromPath (diskPath );
6590+ if (vgName != null ) {
6591+ boolean isClustered = checkIfVolumeGroupIsClustered (vgName );
6592+ LOGGER .debug ("Disk {} VG {} identified as clustered={} via vgs attribute check" ,
6593+ diskPath , vgName , isClustered );
6594+ return isClustered ;
6595+ }
6596+ }
6597+
6598+ } catch (Exception e ) {
6599+ LOGGER .error ("Error determining if volume {} is CLVM: {}" , diskPath , e .getMessage (), e );
6600+ }
6601+
6602+ return false ;
6603+ }
6604+
6605+ /**
6606+ * Extracts the volume group name from a device path.
6607+ *
6608+ * @param devicePath The device path (e.g., /dev/vgname/lvname)
6609+ * @return The volume group name, or null if cannot be determined
6610+ */
6611+ static String extractVolumeGroupFromPath (String devicePath ) {
6612+ if (devicePath == null || !devicePath .startsWith ("/dev/" )) {
6613+ return null ;
6614+ }
6615+
6616+ // Format: /dev/<vgname>/<lvname>
6617+ String [] parts = devicePath .split ("/" );
6618+ if (parts .length >= 3 ) {
6619+ return parts [2 ]; // ["", "dev", "vgname", ...]
6620+ }
6621+
6622+ return null ;
6623+ }
6624+
6625+ /**
6626+ * Checks if a volume group is clustered (CLVM) by examining its attributes.
6627+ * Uses 'vgs' command to check for the clustered/shared flag in VG attributes.
6628+ *
6629+ * VG Attr format (6 characters): wz--nc or wz--ns
6630+ * Position 6: Clustered flag - 'c' = CLVM (clustered), 's' = shared (lvmlockd), '-' = not clustered
6631+ *
6632+ * @param vgName The volume group name
6633+ * @return true if the VG is clustered or shared, false otherwise
6634+ */
6635+ static boolean checkIfVolumeGroupIsClustered (String vgName ) {
6636+ if (vgName == null ) {
6637+ return false ;
6638+ }
6639+
6640+ try {
6641+ // Use vgs with --noheadings and -o attr to get VG attributes
6642+ OutputInterpreter .AllLinesParser parser = new OutputInterpreter .AllLinesParser ();
6643+ Script vgsCmd = new Script ("vgs" , 5000 , LOGGER );
6644+ vgsCmd .add ("--noheadings" );
6645+ vgsCmd .add ("--unbuffered" );
6646+ vgsCmd .add ("-o" );
6647+ vgsCmd .add ("vg_attr" );
6648+ vgsCmd .add (vgName );
6649+
6650+ String result = vgsCmd .execute (parser );
6651+
6652+ if (result == null && parser .getLines () != null ) {
6653+ String output = parser .getLines ();
6654+ if (output != null && !output .isEmpty ()) {
6655+ // Parse VG attributes (format: wz--nc or wz--ns or wz--n-)
6656+ // Position 6 (0-indexed 5) indicates clustering/sharing:
6657+ // 'c' = clustered (CLVM) or 's' = shared (lvmlockd) or '-' = not clustered/shared
6658+ String vgAttr = output .trim ();
6659+ if (vgAttr .length () >= 6 ) {
6660+ char clusterFlag = vgAttr .charAt (5 ); // Position 6 (0-indexed 5)
6661+ boolean isClustered = (clusterFlag == 'c' || clusterFlag == 's' );
6662+ LOGGER .debug ("VG {} has attributes '{}', cluster/shared flag '{}' = {}" ,
6663+ vgName , vgAttr , clusterFlag , isClustered );
6664+ return isClustered ;
6665+ } else {
6666+ LOGGER .warn ("VG {} attributes '{}' have unexpected format (expected 6+ chars)" , vgName , vgAttr );
6667+ }
6668+ }
6669+ } else {
6670+ LOGGER .warn ("Failed to get VG attributes for {}: {}" , vgName , result );
6671+ }
6672+
6673+ } catch (Exception e ) {
6674+ LOGGER .debug ("Error checking if VG {} is clustered: {}" , vgName , e .getMessage ());
6675+ }
6676+
6677+ return false ;
6678+ }
64856679}
0 commit comments