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