This is an automated email from the ASF dual-hosted git repository.

sureshanaparti pushed a commit to branch 4.22
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/4.22 by this push:
     new e297644ce1e KVM: Enable HA heartbeat on ShareMountPoint (#12773)
e297644ce1e is described below

commit e297644ce1e8c0d37cb5fa86c71dc247df9ccd4a
Author: Wei Zhou <[email protected]>
AuthorDate: Fri Apr 10 10:42:40 2026 +0200

    KVM: Enable HA heartbeat on ShareMountPoint (#12773)
---
 .../java/com/cloud/ha/HighAvailabilityManager.java |   3 +
 .../cloud/hypervisor/kvm/resource/KVMHABase.java   |   1 -
 .../hypervisor/kvm/resource/KVMHAMonitor.java      |  15 +-
 .../kvm/resource/LibvirtComputingResource.java     |   7 +-
 ...CheckVMActivityOnStoragePoolCommandWrapper.java |   2 +-
 .../kvm/storage/KVMStoragePoolManager.java         |   4 +
 .../hypervisor/kvm/storage/LibvirtStoragePool.java |  22 ++-
 .../CloudStackPrimaryDataStoreDriverImpl.java      |   3 +-
 scripts/vm/hypervisor/kvm/kvmsmpheartbeat.sh       | 218 +++++++++++++++++++++
 9 files changed, 253 insertions(+), 22 deletions(-)

diff --git 
a/engine/components-api/src/main/java/com/cloud/ha/HighAvailabilityManager.java 
b/engine/components-api/src/main/java/com/cloud/ha/HighAvailabilityManager.java
index ddc8153d739..3ae94479cea 100644
--- 
a/engine/components-api/src/main/java/com/cloud/ha/HighAvailabilityManager.java
+++ 
b/engine/components-api/src/main/java/com/cloud/ha/HighAvailabilityManager.java
@@ -21,6 +21,7 @@ import static 
org.apache.cloudstack.framework.config.ConfigKey.Scope.Cluster;
 import com.cloud.deploy.DeploymentPlanner;
 import com.cloud.host.HostVO;
 import com.cloud.host.Status;
+import com.cloud.storage.Storage.StoragePoolType;
 import com.cloud.utils.component.Manager;
 import com.cloud.vm.VMInstanceVO;
 import org.apache.cloudstack.framework.config.ConfigKey;
@@ -32,6 +33,8 @@ import java.util.List;
  */
 public interface HighAvailabilityManager extends Manager {
 
+    List<StoragePoolType> LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT = 
List.of(StoragePoolType.NetworkFilesystem, StoragePoolType.SharedMountPoint);
+
     ConfigKey<Boolean> ForceHA = new ConfigKey<>("Advanced", Boolean.class, 
"force.ha", "false",
         "Force High-Availability to happen even if the VM says no.", true, 
Cluster);
 
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHABase.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHABase.java
index 896426addca..e9a7ac8951c 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHABase.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHABase.java
@@ -35,7 +35,6 @@ import com.cloud.agent.properties.AgentPropertiesFileHandler;
 public class KVMHABase {
     protected Logger logger = LogManager.getLogger(getClass());
     private long _timeout = 60000; /* 1 minutes */
-    protected static String s_heartBeatPath;
     protected long _heartBeatUpdateTimeout = 
AgentPropertiesFileHandler.getPropertyValue(AgentProperties.HEARTBEAT_UPDATE_TIMEOUT);
     protected long _heartBeatUpdateFreq = 
AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_FREQUENCY);
     protected long _heartBeatUpdateMaxTries = 
AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_HEARTBEAT_UPDATE_MAX_TRIES);
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAMonitor.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAMonitor.java
index cf407bfc08a..aa868ff1d3f 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAMonitor.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAMonitor.java
@@ -18,7 +18,7 @@ package com.cloud.hypervisor.kvm.resource;
 
 import com.cloud.agent.properties.AgentProperties;
 import com.cloud.agent.properties.AgentPropertiesFileHandler;
-import com.cloud.storage.Storage.StoragePoolType;
+import com.cloud.ha.HighAvailabilityManager;
 import com.cloud.utils.script.Script;
 import org.libvirt.Connect;
 import org.libvirt.LibvirtException;
@@ -39,20 +39,15 @@ public class KVMHAMonitor extends KVMHABase implements 
Runnable {
 
     private final String hostPrivateIp;
 
-    public KVMHAMonitor(HAStoragePool pool, String host, String scriptPath) {
+    public KVMHAMonitor(HAStoragePool pool, String host) {
         if (pool != null) {
             storagePool.put(pool.getPoolUUID(), pool);
         }
         hostPrivateIp = host;
-        configureHeartBeatPath(scriptPath);
 
         rebootHostAndAlertManagementOnHeartbeatTimeout = 
AgentPropertiesFileHandler.getPropertyValue(AgentProperties.REBOOT_HOST_AND_ALERT_MANAGEMENT_ON_HEARTBEAT_TIMEOUT);
     }
 
-    private static synchronized void configureHeartBeatPath(String scriptPath) 
{
-        KVMHABase.s_heartBeatPath = scriptPath;
-    }
-
     public void addStoragePool(HAStoragePool pool) {
         synchronized (storagePool) {
             storagePool.put(pool.getPoolUUID(), pool);
@@ -86,8 +81,8 @@ public class KVMHAMonitor extends KVMHABase implements 
Runnable {
             Set<String> removedPools = new HashSet<>();
             for (String uuid : storagePool.keySet()) {
                 HAStoragePool primaryStoragePool = storagePool.get(uuid);
-                if (primaryStoragePool.getPool().getType() == 
StoragePoolType.NetworkFilesystem) {
-                    checkForNotExistingPools(removedPools, uuid);
+                if 
(HighAvailabilityManager.LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT.contains(primaryStoragePool.getPool().getType()))
 {
+                    checkForNotExistingLibvirtStoragePools(removedPools, uuid);
                     if (removedPools.contains(uuid)) {
                         continue;
                     }
@@ -127,7 +122,7 @@ public class KVMHAMonitor extends KVMHABase implements 
Runnable {
         return result;
     }
 
-    private void checkForNotExistingPools(Set<String> removedPools, String 
uuid) {
+    private void checkForNotExistingLibvirtStoragePools(Set<String> 
removedPools, String uuid) {
         try {
             Connect conn = LibvirtConnection.getConnection();
             StoragePool storage = conn.storagePoolLookupByUUIDString(uuid);
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
index b561cedd018..64df98f413a 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
@@ -1063,11 +1063,6 @@ public class LibvirtComputingResource extends 
ServerResourceBase implements Serv
             throw new ConfigurationException("Unable to find patch.sh");
         }
 
-        heartBeatPath = Script.findScript(kvmScriptsDir, "kvmheartbeat.sh");
-        if (heartBeatPath == null) {
-            throw new ConfigurationException("Unable to find kvmheartbeat.sh");
-        }
-
         createVmPath = Script.findScript(storageScriptsDir, "createvm.sh");
         if (createVmPath == null) {
             throw new ConfigurationException("Unable to find the createvm.sh");
@@ -1330,7 +1325,7 @@ public class LibvirtComputingResource extends 
ServerResourceBase implements Serv
 
         final String[] info = NetUtils.getNetworkParams(privateNic);
 
-        kvmhaMonitor = new KVMHAMonitor(null, info[0], heartBeatPath);
+        kvmhaMonitor = new KVMHAMonitor(null, info[0]);
         final Thread ha = new Thread(kvmhaMonitor);
         ha.start();
 
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVMActivityOnStoragePoolCommandWrapper.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVMActivityOnStoragePoolCommandWrapper.java
index a708d441be5..d3f537dc917 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVMActivityOnStoragePoolCommandWrapper.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCheckVMActivityOnStoragePoolCommandWrapper.java
@@ -48,7 +48,7 @@ public final class 
LibvirtCheckVMActivityOnStoragePoolCommandWrapper extends Com
 
         KVMStoragePool primaryPool = 
storagePoolMgr.getStoragePool(pool.getType(), pool.getUuid());
 
-        if (primaryPool.isPoolSupportHA()){
+        if (primaryPool.isPoolSupportHA()) {
             final HAStoragePool nfspool = 
monitor.getStoragePool(pool.getUuid());
             final KVMHAVMActivityChecker ha = new 
KVMHAVMActivityChecker(nfspool, command.getHost(), command.getVolumeList(), 
libvirtComputingResource.getVmActivityCheckPath(), 
command.getSuspectTimeInSeconds());
             final Future<Boolean> future = executors.submit(ha);
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java
index 6665cf625e2..35cc864268c 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java
@@ -289,6 +289,7 @@ public class KVMStoragePoolManager {
 
         if (pool instanceof LibvirtStoragePool) {
             addPoolDetails(uuid, (LibvirtStoragePool) pool);
+            ((LibvirtStoragePool) pool).setType(type);
         }
 
         return pool;
@@ -390,6 +391,9 @@ public class KVMStoragePoolManager {
     private synchronized KVMStoragePool createStoragePool(String name, String 
host, int port, String path, String userInfo, StoragePoolType type, Map<String, 
String> details, boolean primaryStorage) {
         StorageAdaptor adaptor = getStorageAdaptor(type);
         KVMStoragePool pool = adaptor.createStoragePool(name, host, port, 
path, userInfo, type, details, primaryStorage);
+        if (pool instanceof LibvirtStoragePool) {
+            ((LibvirtStoragePool) pool).setType(type);
+        }
 
         // LibvirtStorageAdaptor-specific statement
         if (pool.isPoolSupportHA() && primaryStorage) {
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java
index ab39f7bc6ff..45c22d3ac75 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java
@@ -31,6 +31,7 @@ import 
org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
 import com.cloud.agent.api.to.HostTO;
 import com.cloud.agent.properties.AgentProperties;
 import com.cloud.agent.properties.AgentPropertiesFileHandler;
+import com.cloud.ha.HighAvailabilityManager;
 import com.cloud.hypervisor.kvm.resource.KVMHABase.HAStoragePool;
 import com.cloud.storage.Storage;
 import com.cloud.storage.Storage.StoragePoolType;
@@ -320,13 +321,24 @@ public class LibvirtStoragePool implements KVMStoragePool 
{
 
     @Override
     public boolean isPoolSupportHA() {
-        return type == StoragePoolType.NetworkFilesystem;
+        return 
HighAvailabilityManager.LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT.contains(type);
     }
 
     public String getHearthBeatPath() {
-        if (type == StoragePoolType.NetworkFilesystem) {
+        if (StoragePoolType.NetworkFilesystem.equals(type)) {
             String kvmScriptsDir = 
AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_SCRIPTS_DIR);
-            return Script.findScript(kvmScriptsDir, "kvmheartbeat.sh");
+            String scriptPath = Script.findScript(kvmScriptsDir, 
"kvmheartbeat.sh");
+            if (scriptPath == null) {
+                throw new CloudRuntimeException("Unable to find heartbeat 
script 'kvmheartbeat.sh' in directory: " + kvmScriptsDir);
+            }
+            return scriptPath;
+        } else if (StoragePoolType.SharedMountPoint.equals(type)) {
+            String kvmScriptsDir = 
AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_SCRIPTS_DIR);
+            String scriptPath = Script.findScript(kvmScriptsDir, 
"kvmsmpheartbeat.sh");
+            if (scriptPath == null) {
+                throw new CloudRuntimeException("Unable to find heartbeat 
script 'kvmsmpheartbeat.sh' in directory: " + kvmScriptsDir);
+            }
+            return scriptPath;
         }
         return null;
     }
@@ -410,4 +422,8 @@ public class LibvirtStoragePool implements KVMStoragePool {
             return true;
         }
     }
+
+    public void setType(StoragePoolType type) {
+        this.type = type;
+    }
 }
diff --git 
a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java
 
b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java
index 5faa377ce3d..6a7f1d58043 100644
--- 
a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java
+++ 
b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/driver/CloudStackPrimaryDataStoreDriverImpl.java
@@ -27,6 +27,7 @@ import java.util.UUID;
 import javax.inject.Inject;
 
 import com.cloud.agent.api.to.DiskTO;
+import com.cloud.ha.HighAvailabilityManager;
 import com.cloud.storage.VolumeVO;
 import 
org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
 import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo;
@@ -587,7 +588,7 @@ public class CloudStackPrimaryDataStoreDriverImpl 
implements PrimaryDataStoreDri
 
     @Override
     public boolean isStorageSupportHA(StoragePoolType type) {
-        return StoragePoolType.NetworkFilesystem == type;
+        return type != null && 
HighAvailabilityManager.LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT.contains(type);
     }
 
     @Override
diff --git a/scripts/vm/hypervisor/kvm/kvmsmpheartbeat.sh 
b/scripts/vm/hypervisor/kvm/kvmsmpheartbeat.sh
new file mode 100755
index 00000000000..b102a1a866b
--- /dev/null
+++ b/scripts/vm/hypervisor/kvm/kvmsmpheartbeat.sh
@@ -0,0 +1,218 @@
+#!/bin/bash
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+help() {
+  printf "Usage: $0
+                -i identifier (required for CLI compatibility; value ignored 
by local-only heartbeat)
+                -p path (required for CLI compatibility; value ignored by 
local-only heartbeat)
+                -m mount point (local path where heartbeat will be written)
+                -h host (host IP/name to include in heartbeat filename)
+                -r write/read hb log (read-check mode)
+                -c cleanup (trigger emergency reboot)
+                -t interval between read hb log\n"
+  exit 1
+}
+
+#set -x
+NfsSvrIP=
+NfsSvrPath=
+MountPoint=
+HostIP=
+interval=
+rflag=0
+cflag=0
+
+while getopts 'i:p:m:h:t:rc' OPTION
+do
+  case $OPTION in
+  i)
+     NfsSvrIP="$OPTARG"
+     ;; # retained for CLI compatibility but unused for this script
+  p)
+     NfsSvrPath="$OPTARG"
+     ;; # retained for CLI compatibility but unused for this script
+  m)
+     MountPoint="$OPTARG"
+     ;;
+  h)
+     HostIP="$OPTARG"
+     ;;
+  r)
+     rflag=1
+     ;;
+  t)
+     interval="$OPTARG"
+     ;;
+  c)
+    cflag=1
+     ;;
+  *)
+     help
+     ;;
+  esac
+done
+
+# For heartbeat we require a mountpoint
+if [ -z "$MountPoint" ]
+then
+   echo "Mount point (-m) is required"
+   help
+fi
+
+# Validate mount point exists, is (if possible) a mounted filesystem, and is 
writable
+if [ ! -d "$MountPoint" ]; then
+  echo "Mount point directory does not exist: $MountPoint" >&2
+  exit 1
+fi
+
+# If the 'mountpoint' utility is available, ensure this is an actual mount
+if command -v mountpoint >/dev/null 2>&1; then
+  if ! mountpoint -q "$MountPoint"; then
+    echo "Mount point is not a mounted filesystem: $MountPoint" >&2
+    exit 1
+  fi
+fi
+
+# Ensure the mount point is writable
+if [ ! -w "$MountPoint" ]; then
+  echo "Mount point is not writable: $MountPoint" >&2
+  exit 1
+fi
+#delete VMs on this mountpoint (best-effort)
+deleteVMs() {
+  local mountPoint=$1
+  # ensure it ends with a single trailing slash
+  mountPoint="${mountPoint%/}/"
+
+  vmPids=$(ps aux | grep qemu | grep "$mountPoint" | awk '{print $2}' 2> 
/dev/null)
+
+  if [ -z "$vmPids" ]
+  then
+     return
+  fi
+
+  for pid in $vmPids
+  do
+     kill -9 $pid &> /dev/null
+  done
+}
+
+#checking is there the mount point present under $MountPoint?
+if grep -q "^[^ ]\+ $MountPoint " /proc/mounts
+then
+   # mount exists; nothing to do here; keep for compatibility with original 
flow
+   :
+else
+   # mount point not present
+   # if not in read-check mode, consider deleting VMs similar to original 
behavior
+   if [ "$rflag" == "0" ]
+   then
+     deleteVMs $MountPoint
+   fi
+fi
+
+hbFolder="$MountPoint/KVMHA"
+hbFile="$hbFolder/hb-$HostIP"
+
+write_hbLog() {
+#write the heart beat log
+  stat "$hbFile" &> /dev/null
+  if [ $? -gt 0 ]
+  then
+     # create a new one
+     mkdir -p "$hbFolder" &> /dev/null
+     # touch will be done by atomic write below; ensure folder is writable
+     if [ ! -w "$hbFolder" ]; then
+       printf "Folder not writable: $hbFolder" >&2
+       return 2
+     fi
+  fi
+
+  timestamp=$(date +%s)
+  # Write atomically to avoid partial writes (write to tmp then mv)
+  tmpfile="${hbFile}.$$"
+  printf "%s\n" "$timestamp" > "$tmpfile" 2>/dev/null
+  if [ $? -ne 0 ]; then
+    printf "Failed to write heartbeat to $tmpfile" >&2
+    return 2
+  fi
+  mv -f "$tmpfile" "$hbFile" 2>/dev/null
+  return $?
+}
+
+check_hbLog() {
+  hb_diff=0
+  if [ ! -f "$hbFile" ]; then
+    # signal large difference if file missing
+    hb_diff=999999
+    return 1
+  fi
+  now=$(date +%s)
+  hb=$(cat "$hbFile" 2>/dev/null)
+  if [ -z "$hb" ]; then
+    hb_diff=999998
+    return 1
+  fi
+  diff=`expr $now - $hb 2>/dev/null`
+  if [ $? -ne 0 ]
+  then
+    hb_diff=999997
+    return 1
+  fi
+  if [ -z "$interval" ]; then
+    # if no interval provided, consider 0 as success
+    if [ $diff -gt 0 ]; then
+      hb_diff=$diff
+      return 1
+    else
+      hb_diff=0
+      return 0
+    fi
+  fi
+  if [ $diff -gt $interval ]
+  then
+    hb_diff=$diff
+    return 1
+  fi
+  hb_diff=0
+  return 0
+}
+
+if [ "$rflag" == "1" ]
+then
+  check_hbLog
+  status=$?
+  diff="${hb_diff:-0}"
+  if [ $status -eq 0 ]
+  then
+    echo "=====> ALIVE <====="
+  else
+    echo "=====> Considering host as DEAD because last write on [$hbFile] was 
[$diff] seconds ago, but the max interval is [$interval] <======"
+  fi
+  exit 0
+elif [ "$cflag" == "1" ]
+then
+  /usr/bin/logger -t heartbeat "kvmsmpheartbeat.sh will reboot system because 
it was unable to write the heartbeat to the storage."
+  sync &
+  sleep 5
+  echo b > /proc/sysrq-trigger
+  exit $?
+else
+  write_hbLog
+  exit $?
+fi

Reply via email to