Pearl1594 commented on code in PR #12617:
URL: https://github.com/apache/cloudstack/pull/12617#discussion_r3162729179


##########
plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java:
##########
@@ -6686,4 +6692,242 @@ public String getHypervisorPath() {
     public String getGuestCpuArch() {
         return guestCpuArch;
     }
+
+    /**
+     * CLVM volume state for migration operations on source host
+     */
+    public enum ClvmVolumeState {
+        /** Shared mode (-asy) - used before migration to allow both hosts to 
access volume */
+        SHARED("-asy", "shared", "Before migration: activating in shared 
mode"),
+
+        /** Deactivate (-an) - used after successful migration to release 
volume on source */
+        DEACTIVATE("-an", "deactivated", "After successful migration: 
deactivating volume"),
+
+        /** Exclusive mode (-aey) - used after failed migration to revert to 
original exclusive state */
+        EXCLUSIVE("-aey", "exclusive", "After failed migration: reverting to 
exclusive mode");
+
+        private final String lvchangeFlag;
+        private final String description;
+        private final String logMessage;
+
+        ClvmVolumeState(String lvchangeFlag, String description, String 
logMessage) {
+            this.lvchangeFlag = lvchangeFlag;
+            this.description = description;
+            this.logMessage = logMessage;
+        }
+
+        public String getLvchangeFlag() {
+            return lvchangeFlag;
+        }
+
+        public String getDescription() {
+            return description;
+        }
+
+        public String getLogMessage() {
+            return logMessage;
+        }
+    }
+
+    public static void modifyClvmVolumesStateForMigration(List<DiskDef> disks, 
LibvirtComputingResource resource,
+                                                             VirtualMachineTO 
vmSpec, ClvmVolumeState state) {
+        for (DiskDef disk : disks) {
+            if (isClvmVolume(disk, resource, vmSpec)) {
+                String volumePath = disk.getDiskPath();
+                try {
+                    modifyClvmVolumeState(volumePath, state.getLvchangeFlag(), 
state.getDescription(), state.getLogMessage());
+                } catch (Exception e) {
+                    LOGGER.error("[CLVM Migration] Exception while setting 
volume [{}] to {} state: {}",
+                            volumePath, state.getDescription(), 
e.getMessage(), e);
+                }
+            }
+        }
+    }
+
+    private static void modifyClvmVolumeState(String volumePath, String 
lvchangeFlag,
+                                       String stateDescription, String 
logMessage) {
+        try {
+            LOGGER.info("{} for volume [{}]", logMessage, volumePath);
+
+            Script cmd = new Script("lvchange", Duration.standardSeconds(300), 
LOGGER);
+            cmd.add(lvchangeFlag);
+            cmd.add(volumePath);
+
+            String result = cmd.execute();
+            if (result != null) {
+                String errorMsg = String.format(
+                        "Failed to set volume [%s] to %s state. Command 
result: %s",
+                        volumePath, stateDescription, result);
+                LOGGER.error(errorMsg);
+                throw new CloudRuntimeException(errorMsg);
+            } else {
+                LOGGER.info("Successfully set volume [{}] to {} state.",
+                        volumePath, stateDescription);
+            }
+        } catch (CloudRuntimeException e) {
+            throw e;
+        } catch (Exception e) {
+            String errorMsg = String.format(
+                    "Exception while setting volume [%s] to %s state: %s",
+                    volumePath, stateDescription, e.getMessage());
+            LOGGER.error(errorMsg, e);
+            throw new CloudRuntimeException(errorMsg, e);
+        }
+    }
+
+    public static void activateClvmVolumeExclusive(String volumePath) {
+        modifyClvmVolumeState(volumePath, 
ClvmVolumeState.EXCLUSIVE.getLvchangeFlag(),
+                ClvmVolumeState.EXCLUSIVE.getDescription(),
+                "Activating CLVM volume in exclusive mode");
+    }
+
+    public static void deactivateClvmVolume(String volumePath) {
+        try {
+            modifyClvmVolumeState(volumePath, 
ClvmVolumeState.DEACTIVATE.getLvchangeFlag(),
+                    ClvmVolumeState.DEACTIVATE.getDescription(),
+                    "Deactivating CLVM volume");
+        } catch (Exception e) {
+            LOGGER.warn("Failed to deactivate CLVM volume {}: {}", volumePath, 
e.getMessage());
+        }
+    }
+
+    public static void setClvmVolumeToSharedMode(String volumePath) {
+        try {
+            modifyClvmVolumeState(volumePath, 
ClvmVolumeState.SHARED.getLvchangeFlag(),
+                    ClvmVolumeState.SHARED.getDescription(),
+                    "Setting CLVM volume to shared mode");
+        } catch (Exception e) {
+            LOGGER.warn("Failed to set CLVM volume {} to shared mode: {}", 
volumePath, e.getMessage());
+        }
+    }
+
+    /**
+     * Determines if a disk is on a CLVM storage pool by checking the actual 
pool type from VirtualMachineTO.
+     * This is the most reliable method as it uses CloudStack's own storage 
pool information.
+     *
+     * @param disk The disk definition to check
+     * @param resource The LibvirtComputingResource instance (unused but kept 
for compatibility)
+     * @param vmSpec The VirtualMachineTO specification containing disk and 
pool information
+     * @return true if the disk is on a CLVM storage pool, false otherwise
+     */
+    private static boolean isClvmVolume(DiskDef disk, LibvirtComputingResource 
resource, VirtualMachineTO vmSpec) {
+        String diskPath = disk.getDiskPath();
+        if (diskPath == null || vmSpec == null) {
+            return false;
+        }
+
+        try {
+            if (vmSpec.getDisks() != null) {
+                for (DiskTO diskTO : vmSpec.getDisks()) {
+                    if (diskTO.getData() instanceof VolumeObjectTO) {
+                        VolumeObjectTO volumeTO = (VolumeObjectTO) 
diskTO.getData();
+                        if (diskPath.equals(volumeTO.getPath()) || 
diskPath.equals(diskTO.getPath())) {
+                            DataStoreTO dataStore = volumeTO.getDataStore();
+                            if (dataStore instanceof PrimaryDataStoreTO) {
+                                PrimaryDataStoreTO primaryStore = 
(PrimaryDataStoreTO) dataStore;
+                                boolean isClvm = StoragePoolType.CLVM == 
primaryStore.getPoolType() ||
+                                                StoragePoolType.CLVM_NG == 
primaryStore.getPoolType();
+                                LOGGER.debug("Disk {} identified as 
CLVM/CLVM_NG={} via VirtualMachineTO pool type: {}",
+                                            diskPath, isClvm, 
primaryStore.getPoolType());
+                                return isClvm;
+                            }
+                        }
+                    }
+                }
+            }
+
+            // Fallback: Check VG attributes using vgs command (reliable)
+            // CLVM VGs have the 'c' (clustered) or 's' (shared) flag in their 
attributes
+            // Example: 'wz--ns' = shared, 'wz--n-' = not clustered
+            if (diskPath.startsWith("/dev/") && 
!diskPath.contains("/dev/mapper/")) {
+                String vgName = extractVolumeGroupFromPath(diskPath);
+                if (vgName != null) {
+                    boolean isClustered = 
checkIfVolumeGroupIsClustered(vgName);
+                    LOGGER.debug("Disk {} VG {} identified as clustered={} via 
vgs attribute check",
+                                diskPath, vgName, isClustered);
+                    return isClustered;
+                }
+            }
+
+        } catch (Exception e) {
+            LOGGER.error("Error determining if volume {} is CLVM: {}", 
diskPath, e.getMessage(), e);
+        }
+
+        return false;
+    }
+
+    /**
+     * Extracts the volume group name from a device path.
+     *
+     * @param devicePath The device path (e.g., /dev/vgname/lvname)
+     * @return The volume group name, or null if cannot be determined
+     */
+     static String extractVolumeGroupFromPath(String devicePath) {
+        if (devicePath == null || !devicePath.startsWith("/dev/")) {
+            return null;
+        }
+
+        // Format: /dev/<vgname>/<lvname>
+        String[] parts = devicePath.split("/");
+        if (parts.length >= 3) {
+            return parts[2]; // ["", "dev", "vgname", ...]
+        }
+
+        return null;
+    }
+
+    /**
+     * Checks if a volume group is clustered (CLVM) by examining its 
attributes.
+     * Uses 'vgs' command to check for the clustered/shared flag in VG 
attributes.
+     *
+     * VG Attr format (6 characters): wz--nc or wz--ns
+     *   Position 6: Clustered flag - 'c' = CLVM (clustered), 's' = shared 
(lvmlockd), '-' = not clustered
+     *
+     * @param vgName The volume group name
+     * @return true if the VG is clustered or shared, false otherwise
+     */
+     static boolean checkIfVolumeGroupIsClustered(String vgName) {
+        if (vgName == null) {
+            return false;
+        }
+
+        try {
+            // Use vgs with --noheadings and -o attr to get VG attributes
+            OutputInterpreter.AllLinesParser parser = new 
OutputInterpreter.AllLinesParser();
+            Script vgsCmd = new Script("vgs", 5000, LOGGER);
+            vgsCmd.add("--noheadings");
+            vgsCmd.add("--unbuffered");
+            vgsCmd.add("-o");
+            vgsCmd.add("vg_attr");
+            vgsCmd.add(vgName);
+
+            String result = vgsCmd.execute(parser);
+
+            if (result == null && parser.getLines() != null) {
+                String output = parser.getLines();
+                if (output != null && !output.isEmpty()) {
+                    // Parse VG attributes (format: wz--nc or wz--ns or wz--n-)
+                    // Position 6 (0-indexed 5) indicates clustering/sharing:
+                    // 'c' = clustered (CLVM) or 's' = shared (lvmlockd) or 
'-' = not clustered/shared
+                    String vgAttr = output.trim();
+                    if (vgAttr.length() >= 6) {
+                        char clusterFlag = vgAttr.charAt(5);  // Position 6 
(0-indexed 5)
+                        boolean isClustered = (clusterFlag == 'c' || 
clusterFlag == 's');
+                        LOGGER.debug("VG {} has attributes '{}', 
cluster/shared flag '{}' = {}",
+                                    vgName, vgAttr, clusterFlag, isClustered);
+                        return isClustered;
+                    } else {
+                        LOGGER.warn("VG {} attributes '{}' have unexpected 
format (expected 6+ chars)", vgName, vgAttr);
+                    }
+                }
+            } else {
+                LOGGER.warn("Failed to get VG attributes for {}: {}", vgName, 
result);
+            }
+
+        } catch (Exception e) {
+            LOGGER.debug("Error checking if VG {} is clustered: {}", vgName, 
e.getMessage());
+        }
+
+        return false;
+    }

Review Comment:
   I will address this as a separate PR .



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to