This is an automated email from the ASF dual-hosted git repository. Pearl1594 pushed a commit to branch fix-kvm-ha-checker-multi-pool in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit 3b16c3cb78681c805a120229d10f0394f8e8fcc3 Author: Pearl1594 <[email protected]> AuthorDate: Thu Oct 15 15:37:55 2026 -0400 kvm: fix HA checker deciding host state from the last pool only --- .../hypervisor/kvm/resource/KVMHAChecker.java | 25 ++++-- .../hypervisor/kvm/resource/KVMHACheckerTest.java | 93 ++++++++++++++++++++++ 2 files changed, 110 insertions(+), 8 deletions(-) diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAChecker.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAChecker.java index db6190fa8f2..d1cc8206b04 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAChecker.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAChecker.java @@ -40,18 +40,11 @@ public class KVMHAChecker extends KVMHABase implements Callable<Boolean> { */ @Override public Boolean checkingHeartBeat() { - boolean validResult = false; - String hostAndPools = String.format("host IP [%s] in pools [%s]", host.getPrivateNetwork().getIp(), storagePools.stream().map(pool -> pool.getPoolUUID()).collect(Collectors.joining(", "))); logger.debug(String.format("Checking heart beat with KVMHAChecker for %s", hostAndPools)); - for (HAStoragePool pool : storagePools) { - validResult = pool.getPool().checkingHeartBeat(pool, host); - if (reportFailureIfOneStorageIsDown && !validResult) { - break; - } - } + boolean validResult = checkPoolsHeartBeat(); if (!validResult) { logger.warn(String.format("All checks with KVMHAChecker for %s considered it as dead. It may cause a shutdown of the host.", hostAndPools)); @@ -60,6 +53,22 @@ public class KVMHAChecker extends KVMHABase implements Callable<Boolean> { return validResult; } + /* + * With reportFailureIfOneStorageIsDown the host is dead as soon as one pool has no heart beat, + * otherwise it is dead only when none of the pools has one. + */ + private boolean checkPoolsHeartBeat() { + boolean anyPoolHasHeartBeat = false; + for (HAStoragePool pool : storagePools) { + boolean poolHasHeartBeat = pool.getPool().checkingHeartBeat(pool, host); + if (reportFailureIfOneStorageIsDown && !poolHasHeartBeat) { + return false; + } + anyPoolHasHeartBeat |= poolHasHeartBeat; + } + return anyPoolHasHeartBeat; + } + @Override public Boolean call() throws Exception { // logger.addAppender(new org.apache.log4j.ConsoleAppender(new diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/KVMHACheckerTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/KVMHACheckerTest.java new file mode 100644 index 00000000000..af01f4e2fc6 --- /dev/null +++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/KVMHACheckerTest.java @@ -0,0 +1,93 @@ +/* + * 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. + */ + +package com.cloud.hypervisor.kvm.resource; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.agent.api.to.HostTO; +import com.cloud.agent.api.to.NetworkTO; +import com.cloud.hypervisor.kvm.resource.KVMHABase.HAStoragePool; +import com.cloud.hypervisor.kvm.resource.KVMHABase.PoolType; +import com.cloud.hypervisor.kvm.storage.KVMStoragePool; + +@RunWith(MockitoJUnitRunner.Silent.class) +public class KVMHACheckerTest { + + @Mock + private HostTO host; + @Mock + private NetworkTO privateNetwork; + + private List<HAStoragePool> poolsWithHeartBeats(boolean... heartBeats) { + when(host.getPrivateNetwork()).thenReturn(privateNetwork); + List<HAStoragePool> pools = new ArrayList<>(); + for (boolean heartBeat : heartBeats) { + KVMStoragePool kvmPool = org.mockito.Mockito.mock(KVMStoragePool.class); + when(kvmPool.checkingHeartBeat(any(HAStoragePool.class), any(HostTO.class))).thenReturn(heartBeat); + pools.add(new HAStoragePool(kvmPool, "host", "/path", PoolType.PrimaryStorage)); + } + return pools; + } + + private boolean hasHeartBeat(boolean reportIfFailedForOnePool, boolean... heartBeats) { + return new KVMHAChecker(poolsWithHeartBeats(heartBeats), host, reportIfFailedForOnePool).checkingHeartBeat(); + } + + @Test + public void testHostIsAliveWhenAnyPoolHasHeartBeatAndOnePoolFailureIsNotReported() { + assertTrue(hasHeartBeat(false, true, false)); + assertTrue(hasHeartBeat(false, false, true)); + } + + @Test + public void testHostIsDeadWhenNoPoolHasHeartBeat() { + assertFalse(hasHeartBeat(false, false, false)); + assertFalse(hasHeartBeat(true, false, false)); + } + + @Test + public void testHostIsDeadWhenOnePoolHasNoHeartBeatAndItIsReported() { + assertFalse(hasHeartBeat(true, true, false)); + assertFalse(hasHeartBeat(true, false, true)); + } + + @Test + public void testHostIsAliveWhenAllPoolsHaveHeartBeat() { + assertTrue(hasHeartBeat(true, true, true)); + assertTrue(hasHeartBeat(false, true, true)); + } + + @Test + public void testHostIsDeadWithoutPools() { + assertFalse(hasHeartBeat(false)); + assertFalse(hasHeartBeat(true)); + } +}
