// 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.storage; import com.cloud.agent.AgentManager; import com.cloud.agent.api.Answer; import com.cloud.agent.api.Command; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.OperationTimedoutException; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.Status; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor; import com.cloud.storage.clvm.ClvmPoolManager; import com.cloud.storage.dao.VolumeDetailsDao; import org.apache.cloudstack.storage.clvm.command.ClvmLockTransferAnswer; import org.apache.cloudstack.storage.clvm.command.ClvmLockTransferCommand; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import java.util.Arrays; import java.util.Collections; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class ClvmPoolManagerTest { @Mock private VolumeDetailsDao volsDetailsDao; @Mock private AgentManager agentMgr; @Mock private HostDao hostDao; @InjectMocks private ClvmPoolManager clvmPoolManager; private static final Long VOLUME_ID = 100L; private static final Long HOST_ID_1 = 1L; private static final Long HOST_ID_2 = 2L; private static final String VOLUME_UUID = "test-volume-uuid"; private static final String VOLUME_PATH = "test-volume-path"; private static final String VG_NAME = "acsvg"; @Before public void setUp() { Mockito.reset(volsDetailsDao, agentMgr, hostDao); } @Test public void testGetClvmLockHostId_Success() { VolumeDetailVO detail = new VolumeDetailVO(); detail.setValue("123"); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(detail); Long result = clvmPoolManager.getClvmLockHostId(VOLUME_ID, VOLUME_UUID); Assert.assertEquals(Long.valueOf(123), result); } @Test public void testGetClvmLockHostId_NoDetail() { when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); Long result = clvmPoolManager.getClvmLockHostId(VOLUME_ID, VOLUME_UUID); Assert.assertNull(result); } @Test public void testGetClvmLockHostId_InvalidNumber() { VolumeDetailVO detail = new VolumeDetailVO(); detail.setValue("invalid"); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(detail); Long result = clvmPoolManager.getClvmLockHostId(VOLUME_ID, VOLUME_UUID); Assert.assertNull(result); } @Test public void testSetClvmLockHostId_NewDetail() { when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); clvmPoolManager.setClvmLockHostId(VOLUME_ID, HOST_ID_1); verify(volsDetailsDao, times(1)).addDetail(eq(VOLUME_ID), eq(ClvmPoolManager.CLVM_LOCK_HOST_ID), eq(String.valueOf(HOST_ID_1)), eq(false)); verify(volsDetailsDao, never()).update(anyLong(), any()); } @Test public void testSetClvmLockHostId_UpdateExisting() { VolumeDetailVO existingDetail = Mockito.mock(VolumeDetailVO.class); when(existingDetail.getId()).thenReturn(50L); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(existingDetail); clvmPoolManager.setClvmLockHostId(VOLUME_ID, HOST_ID_2); verify(existingDetail, times(1)).setValue(String.valueOf(HOST_ID_2)); verify(volsDetailsDao, times(1)).update(eq(50L), eq(existingDetail)); verify(volsDetailsDao, never()).addDetail(anyLong(), any(), any(), Mockito.anyBoolean()); } @Test public void testClearClvmLockHostDetail_Success() { VolumeVO volume = Mockito.mock(VolumeVO.class); when(volume.getId()).thenReturn(VOLUME_ID); when(volume.getUuid()).thenReturn(VOLUME_UUID); VolumeDetailVO detail = Mockito.mock(VolumeDetailVO.class); when(detail.getId()).thenReturn(99L); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(detail); clvmPoolManager.clearClvmLockHostDetail(volume); verify(volsDetailsDao, times(1)).remove(99L); } @Test public void testClearClvmLockHostDetail_NoDetail() { VolumeVO volume = Mockito.mock(VolumeVO.class); when(volume.getId()).thenReturn(VOLUME_ID); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); clvmPoolManager.clearClvmLockHostDetail(volume); verify(volsDetailsDao, never()).remove(anyLong()); } @Test public void testTransferClvmVolumeLock_Success() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getPath()).thenReturn("/" + VG_NAME); HostVO sourceHost = Mockito.mock(HostVO.class); when(sourceHost.getStatus()).thenReturn(Status.Up); when(hostDao.findById(HOST_ID_1)).thenReturn(sourceHost); Answer deactivateAnswer = new Answer(null, true, null); Answer activateAnswer = new Answer(null, true, null); when(agentMgr.send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class))).thenReturn(deactivateAnswer); when(agentMgr.send(eq(HOST_ID_2), any(ClvmLockTransferCommand.class))).thenReturn(activateAnswer); boolean result = clvmPoolManager.transferClvmVolumeLock(VOLUME_UUID, VOLUME_ID, VOLUME_PATH, pool, HOST_ID_1, HOST_ID_2); Assert.assertTrue(result); verify(agentMgr, times(2)).send(anyLong(), any(ClvmLockTransferCommand.class)); } @Test public void testTransferClvmVolumeLock_NullPool() { boolean result = clvmPoolManager.transferClvmVolumeLock(VOLUME_UUID, VOLUME_ID, VOLUME_PATH, null, HOST_ID_1, HOST_ID_2); Assert.assertFalse(result); } @Test public void testTransferClvmVolumeLock_SameHost() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getPath()).thenReturn("/" + VG_NAME); Answer activateAnswer = new Answer(null, true, null); when(agentMgr.send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class))).thenReturn(activateAnswer); boolean result = clvmPoolManager.transferClvmVolumeLock(VOLUME_UUID, VOLUME_ID, VOLUME_PATH, pool, HOST_ID_1, HOST_ID_1); Assert.assertTrue(result); verify(agentMgr, times(1)).send(anyLong(), any(ClvmLockTransferCommand.class)); } @Test public void testTransferClvmVolumeLock_ActivationFails() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getPath()).thenReturn(VG_NAME); Answer activateAnswer = new Answer(null, false, "Activation failed"); when(agentMgr.send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class))).thenReturn(activateAnswer); boolean result = clvmPoolManager.transferClvmVolumeLock(VOLUME_UUID, VOLUME_ID, VOLUME_PATH, pool, HOST_ID_1, HOST_ID_1); Assert.assertFalse(result); } @Test public void testTransferClvmVolumeLock_AgentUnavailable() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getPath()).thenReturn(VG_NAME); when(agentMgr.send(anyLong(), any(ClvmLockTransferCommand.class))) .thenThrow(new AgentUnavailableException("Agent unavailable", HOST_ID_2)); boolean result = clvmPoolManager.transferClvmVolumeLock(VOLUME_UUID, VOLUME_ID, VOLUME_PATH, pool, HOST_ID_1, HOST_ID_2); Assert.assertFalse(result); } @Test public void testQueryCurrentLockHolder_NullPool() { Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, null, false); Assert.assertNull(result); verify(hostDao, never()).findByClusterId(anyLong(), any()); } @Test public void testQueryCurrentLockHolder_NoHostsInCluster() { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); when(pool.getDataCenterId()).thenReturn(1L); when(pool.getName()).thenReturn("test-pool"); when(pool.getPath()).thenReturn(VG_NAME); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Collections.emptyList()); lenient().when(hostDao.findByDataCenterId(1L)).thenReturn(Collections.emptyList()); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, false); Assert.assertNull(result); verify(hostDao, times(1)).findByClusterId(10L, Host.Type.Routing); verify(hostDao, times(0)).findByDataCenterId(1L); } @Test public void testQueryCurrentLockHolder_ZoneScopedPool() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(null); when(pool.getDataCenterId()).thenReturn(1L); Mockito.lenient().when(pool.getName()).thenReturn("zone-pool"); when(pool.getPath()).thenReturn(VG_NAME); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); HostVO host = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findByDataCenterId(1L)).thenReturn(Collections.singletonList(host)); ClvmLockTransferAnswer answer = new ClvmLockTransferAnswer(null, true, null, "host1", true, false, null); when(agentMgr.send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class))).thenReturn(answer); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, false); Assert.assertEquals(HOST_ID_1, result); verify(hostDao, never()).findByClusterId(anyLong(), any()); verify(hostDao, times(1)).findByDataCenterId(1L); } @Test public void testQueryCurrentLockHolder_SuccessfulQuery() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); HostVO host = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Collections.singletonList(host)); ClvmLockTransferAnswer answer = new ClvmLockTransferAnswer(null, true, null, "host1", true, false, null); when(agentMgr.send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class))).thenReturn(answer); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, false); Assert.assertEquals(HOST_ID_1, result); verify(agentMgr, times(1)).send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class)); verify(hostDao, never()).findByName(any()); } @Test public void testQueryCurrentLockHolder_VolumeNotLocked() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); HostVO host = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Collections.singletonList(host)); // QUERY reports inactive; ACTIVATE_EXCLUSIVE left unstubbed → null answer → recovery fails → returns null ClvmLockTransferAnswer inactiveAnswer = new ClvmLockTransferAnswer(null, true, null, null, false, false, null); when(agentMgr.send(eq(HOST_ID_1), Mockito.argThat(cmd -> cmd instanceof ClvmLockTransferCommand && ((ClvmLockTransferCommand) cmd).getOperation() == ClvmLockTransferCommand.Operation.QUERY_LOCK_STATE))) .thenReturn(inactiveAnswer); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, false); Assert.assertNull(result); } @Test public void testQueryCurrentLockHolder_EmptyHostname() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); HostVO host = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Collections.singletonList(host)); // QUERY reports inactive with empty hostname; ACTIVATE_EXCLUSIVE left unstubbed → null answer → recovery fails → returns null ClvmLockTransferAnswer answer = new ClvmLockTransferAnswer(null, true, null, "", false, false, null); when(agentMgr.send(eq(HOST_ID_1), Mockito.argThat(cmd -> cmd instanceof ClvmLockTransferCommand && ((ClvmLockTransferCommand) cmd).getOperation() == ClvmLockTransferCommand.Operation.QUERY_LOCK_STATE))) .thenReturn(answer); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, false); Assert.assertNull(result); } @Test public void testQueryCurrentLockHolder_HostnameNotResolved() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); HostVO host = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Collections.singletonList(host)); ClvmLockTransferAnswer answer = new ClvmLockTransferAnswer(null, true, null, "unknown-host", true, false, null); when(agentMgr.send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class))).thenReturn(answer); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, false); Assert.assertEquals(HOST_ID_1, result); verify(hostDao, never()).findByName(any()); } @Test public void testQueryCurrentLockHolder_QueryFails() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); HostVO host = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Collections.singletonList(host)); Answer failedAnswer = new Answer(null, false, "Query failed"); when(agentMgr.send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class))).thenReturn(failedAnswer); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, false); Assert.assertNull(result); } @Test public void testQueryCurrentLockHolder_NullAnswer() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); HostVO host = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Collections.singletonList(host)); when(agentMgr.send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class))).thenReturn(null); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, false); Assert.assertNull(result); } @Test public void testQueryCurrentLockHolder_AgentUnavailableException() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); HostVO host = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Collections.singletonList(host)); when(agentMgr.send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class))) .thenThrow(new AgentUnavailableException("Host unavailable", HOST_ID_1)); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, false); Assert.assertNull(result); } @Test public void testQueryCurrentLockHolder_OperationTimedoutException() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); HostVO host = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Collections.singletonList(host)); when(agentMgr.send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class))) .thenThrow(new OperationTimedoutException(null, HOST_ID_1, 0, 0, false)); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, false); Assert.assertNull(result); } @Test public void testQueryCurrentLockHolder_UpdateDatabase_MatchingValue() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); Mockito.lenient().when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); HostVO host = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); Mockito.lenient().when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Collections.singletonList(host)); // DB has correct value, fast path: query HOST_ID_1, returns active VolumeDetailVO detail = new VolumeDetailVO(); detail.setValue(String.valueOf(HOST_ID_1)); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(detail); when(hostDao.findById(HOST_ID_1)).thenReturn(host); ClvmLockTransferAnswer answer = new ClvmLockTransferAnswer(null, true, null, "host1", true, false, null); when(agentMgr.send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class))).thenReturn(answer); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, true); Assert.assertEquals(HOST_ID_1, result); // Fast path succeeded - no DB write needed, no fan-out verify(volsDetailsDao, never()).update(anyLong(), any()); verify(volsDetailsDao, never()).addDetail(anyLong(), any(), any(), Mockito.anyBoolean()); verify(hostDao, never()).findByClusterId(anyLong(), any()); } @Test public void testQueryCurrentLockHolder_UpdateDatabase_DifferentValue() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); // DB says HOST_ID_1, but actual lock is on HOST_ID_2 // Fast path: query HOST_ID_1, inactive: fall back to fan-out VolumeDetailVO detail = Mockito.mock(VolumeDetailVO.class); when(detail.getValue()).thenReturn(String.valueOf(HOST_ID_1)); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(detail); when(detail.getId()).thenReturn(99L); HostVO host1 = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); HostVO host2 = createMockHost(HOST_ID_2, "host2", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findById(HOST_ID_1)).thenReturn(host1); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Arrays.asList(host1, host2)); // HOST_ID_1 reports inactive (fast path miss), HOST_ID_2 reports active (fan-out) ClvmLockTransferAnswer inactiveAnswer = new ClvmLockTransferAnswer(null, true, null, "host1", false, false, null); ClvmLockTransferAnswer activeAnswer = new ClvmLockTransferAnswer(null, true, null, "host2", true, false, null); when(agentMgr.send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class))).thenReturn(inactiveAnswer); when(agentMgr.send(eq(HOST_ID_2), any(ClvmLockTransferCommand.class))).thenReturn(activeAnswer); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, true); Assert.assertEquals(HOST_ID_2, result); // DB should be corrected to HOST_ID_2 verify(detail, times(1)).setValue(String.valueOf(HOST_ID_2)); verify(volsDetailsDao, times(1)).update(eq(99L), eq(detail)); } @Test public void testQueryCurrentLockHolder_UpdateDatabase_NoExistingDetail() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); // No DB record, fast path skipped, fan-out finds HOST_ID_1 when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); HostVO host = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Collections.singletonList(host)); ClvmLockTransferAnswer answer = new ClvmLockTransferAnswer(null, true, null, "host1", true, false, null); when(agentMgr.send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class))).thenReturn(answer); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, true); Assert.assertEquals(HOST_ID_1, result); verify(volsDetailsDao, times(1)).addDetail(eq(VOLUME_ID), eq(ClvmPoolManager.CLVM_LOCK_HOST_ID), eq(String.valueOf(HOST_ID_1)), eq(false)); } @Test public void testQueryCurrentLockHolder_UpdateDatabase_RemoveDetailWhenUnlocked() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); // DB has HOST_ID_1, fast path query returns inactive: fan-out also finds nothing VolumeDetailVO detail = Mockito.mock(VolumeDetailVO.class); when(detail.getId()).thenReturn(99L); when(detail.getValue()).thenReturn(String.valueOf(HOST_ID_1)); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(detail); HostVO host = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findById(HOST_ID_1)).thenReturn(host); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Collections.singletonList(host)); // QUERY_LOCK_STATE: inactive (fast path miss; HOST_ID_1 skipped in fan-out as dbHostId) ClvmLockTransferAnswer inactiveAnswer = new ClvmLockTransferAnswer(null, true, null, null, false, false, null); when(agentMgr.send(eq(HOST_ID_1), Mockito.argThat(cmd -> cmd instanceof ClvmLockTransferCommand && ((ClvmLockTransferCommand) cmd).getOperation() == ClvmLockTransferCommand.Operation.QUERY_LOCK_STATE))) .thenReturn(inactiveAnswer); // ACTIVATE_EXCLUSIVE: recovery attempt fails — stale DB record must still be removed Answer failedActivation = new Answer(null, false, "Simulated activation failure for test"); when(agentMgr.send(eq(HOST_ID_1), Mockito.argThat(cmd -> cmd instanceof ClvmLockTransferCommand && ((ClvmLockTransferCommand) cmd).getOperation() == ClvmLockTransferCommand.Operation.ACTIVATE_EXCLUSIVE))) .thenReturn(failedActivation); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, true); Assert.assertNull(result); verify(volsDetailsDao, times(1)).remove(99L); } @Test public void testQueryCurrentLockHolder_SkipsNonKVMHosts() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); HostVO xenHost = createMockHost(10L, "xen-host", Status.Up, Hypervisor.HypervisorType.XenServer); HostVO kvmHost = createMockHost(HOST_ID_1, "kvm-host", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Arrays.asList(xenHost, kvmHost)); ClvmLockTransferAnswer answer = new ClvmLockTransferAnswer(null, true, null, "kvm-host", true, false, null); when(agentMgr.send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class))).thenReturn(answer); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, false); Assert.assertEquals(HOST_ID_1, result); verify(agentMgr, never()).send(eq(10L), any(ClvmLockTransferCommand.class)); verify(agentMgr, times(1)).send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class)); } @Test public void testQueryCurrentLockHolder_SkipsDownHosts() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); HostVO downHost = createMockHost(10L, "down-host", Status.Down, Hypervisor.HypervisorType.KVM); HostVO upHost = createMockHost(HOST_ID_1, "up-host", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Arrays.asList(downHost, upHost)); ClvmLockTransferAnswer answer = new ClvmLockTransferAnswer(null, true, null, "up-host", true, false, null); when(agentMgr.send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class))).thenReturn(answer); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, false); Assert.assertEquals(HOST_ID_1, result); verify(agentMgr, never()).send(eq(10L), any(ClvmLockTransferCommand.class)); verify(agentMgr, times(1)).send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class)); } @Test public void testQueryCurrentLockHolder_PathWithLeadingSlash() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn("/" + VG_NAME); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); HostVO host = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Collections.singletonList(host)); ClvmLockTransferAnswer answer = new ClvmLockTransferAnswer(null, true, null, "host1", true, false, null); when(agentMgr.send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class))).thenReturn(answer); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, false); Assert.assertEquals(HOST_ID_1, result); } /** * Fast path: DB has the correct host, single query confirms isActive=true. * No fan-out should occur. */ @Test public void testQueryCurrentLockHolder_FastPath_HitOnDbHost() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getPath()).thenReturn(VG_NAME); VolumeDetailVO detail = new VolumeDetailVO(); detail.setValue(String.valueOf(HOST_ID_1)); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(detail); HostVO host = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findById(HOST_ID_1)).thenReturn(host); ClvmLockTransferAnswer activeAnswer = new ClvmLockTransferAnswer(null, true, null, "host1", true, false, null); when(agentMgr.send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class))).thenReturn(activeAnswer); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, false); Assert.assertEquals(HOST_ID_1, result); // Only one agent call, no cluster host lookup, no fan-out verify(agentMgr, times(1)).send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class)); verify(hostDao, never()).findByClusterId(anyLong(), any()); verify(hostDao, never()).findByDataCenterId(anyLong()); } /** * Fast path miss: DB has HOST_ID_1 but it's inactive. Fan-out finds HOST_ID_2. * HOST_ID_1 should NOT be queried again during fan-out. */ @Test public void testQueryCurrentLockHolder_FastPath_MissDbHost_FanOutFindsOther() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); when(pool.getPath()).thenReturn(VG_NAME); VolumeDetailVO detail = new VolumeDetailVO(); detail.setValue(String.valueOf(HOST_ID_1)); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(detail); HostVO host1 = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); HostVO host2 = createMockHost(HOST_ID_2, "host2", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findById(HOST_ID_1)).thenReturn(host1); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Arrays.asList(host1, host2)); // Fast path: HOST_ID_1 inactive ClvmLockTransferAnswer inactiveAnswer = new ClvmLockTransferAnswer(null, true, null, "host1", false, false, null); // Fan-out: HOST_ID_2 active (HOST_ID_1 skipped) ClvmLockTransferAnswer activeAnswer = new ClvmLockTransferAnswer(null, true, null, "host2", true, false, null); when(agentMgr.send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class))).thenReturn(inactiveAnswer); when(agentMgr.send(eq(HOST_ID_2), any(ClvmLockTransferCommand.class))).thenReturn(activeAnswer); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, false); Assert.assertEquals(HOST_ID_2, result); // HOST_ID_1 queried once (fast path only), HOST_ID_2 queried once (fan-out) verify(agentMgr, times(1)).send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class)); verify(agentMgr, times(1)).send(eq(HOST_ID_2), any(ClvmLockTransferCommand.class)); } /** * Fast path skip: DB host is DOWN. Fan-out proceeds to all UP hosts. */ @Test public void testQueryCurrentLockHolder_FastPath_DbHostDown_FanOut() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); when(pool.getPath()).thenReturn(VG_NAME); VolumeDetailVO detail = new VolumeDetailVO(); detail.setValue(String.valueOf(HOST_ID_1)); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(detail); HostVO downHost = createMockHost(HOST_ID_1, "host1", Status.Down, Hypervisor.HypervisorType.KVM); HostVO upHost = createMockHost(HOST_ID_2, "host2", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findById(HOST_ID_1)).thenReturn(downHost); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Arrays.asList(downHost, upHost)); ClvmLockTransferAnswer activeAnswer = new ClvmLockTransferAnswer(null, true, null, "host2", true, false, null); when(agentMgr.send(eq(HOST_ID_2), any(ClvmLockTransferCommand.class))).thenReturn(activeAnswer); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, false); Assert.assertEquals(HOST_ID_2, result); // No query to the down host at all verify(agentMgr, never()).send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class)); verify(agentMgr, times(1)).send(eq(HOST_ID_2), any(ClvmLockTransferCommand.class)); } /** * Inactive everywhere, DB host is UP: recovery activates exclusively on the DB host. */ @Test public void testQueryCurrentLockHolder_InactiveEverywhere_ActivatesOnDbHost() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); VolumeDetailVO detail = Mockito.mock(VolumeDetailVO.class); when(detail.getId()).thenReturn(99L); when(detail.getValue()).thenReturn(String.valueOf(HOST_ID_1)); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(detail); HostVO host1 = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findById(HOST_ID_1)).thenReturn(host1); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Collections.singletonList(host1)); // Fast path QUERY → inactive ClvmLockTransferAnswer inactiveAnswer = new ClvmLockTransferAnswer(null, true, null, null, false, false, null); when(agentMgr.send(eq(HOST_ID_1), Mockito.argThat(cmd -> cmd instanceof ClvmLockTransferCommand && ((ClvmLockTransferCommand) cmd).getOperation() == ClvmLockTransferCommand.Operation.QUERY_LOCK_STATE))) .thenReturn(inactiveAnswer); // Recovery ACTIVATE_EXCLUSIVE → succeeds Answer activateAnswer = new Answer(null, true, null); when(agentMgr.send(eq(HOST_ID_1), Mockito.argThat(cmd -> cmd instanceof ClvmLockTransferCommand && ((ClvmLockTransferCommand) cmd).getOperation() == ClvmLockTransferCommand.Operation.ACTIVATE_EXCLUSIVE))) .thenReturn(activateAnswer); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, true); Assert.assertEquals(HOST_ID_1, result); verify(detail, times(1)).setValue(String.valueOf(HOST_ID_1)); verify(volsDetailsDao, times(1)).update(eq(99L), eq(detail)); verify(volsDetailsDao, never()).remove(anyLong()); } /** * Inactive everywhere, no DB record: recovery falls back to the first UP KVM host in cluster. */ @Test public void testQueryCurrentLockHolder_InactiveEverywhere_ActivatesOnClusterHostWhenNoDbRecord() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); HostVO host1 = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Collections.singletonList(host1)); // Fan-out QUERY → inactive ClvmLockTransferAnswer inactiveAnswer = new ClvmLockTransferAnswer(null, true, null, null, false, false, null); when(agentMgr.send(eq(HOST_ID_1), Mockito.argThat(cmd -> cmd instanceof ClvmLockTransferCommand && ((ClvmLockTransferCommand) cmd).getOperation() == ClvmLockTransferCommand.Operation.QUERY_LOCK_STATE))) .thenReturn(inactiveAnswer); // Recovery ACTIVATE_EXCLUSIVE → succeeds Answer activateAnswer = new Answer(null, true, null); when(agentMgr.send(eq(HOST_ID_1), Mockito.argThat(cmd -> cmd instanceof ClvmLockTransferCommand && ((ClvmLockTransferCommand) cmd).getOperation() == ClvmLockTransferCommand.Operation.ACTIVATE_EXCLUSIVE))) .thenReturn(activateAnswer); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, true); Assert.assertEquals(HOST_ID_1, result); verify(volsDetailsDao, times(1)).addDetail(eq(VOLUME_ID), eq(ClvmPoolManager.CLVM_LOCK_HOST_ID), eq(String.valueOf(HOST_ID_1)), eq(false)); } /** * Inactive everywhere, DB host is DOWN: selectActivationTargetHost skips it and picks * the first UP host from the cluster list. */ @Test public void testQueryCurrentLockHolder_InactiveEverywhere_SkipsDownDbHost_ActivatesOnClusterHost() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); VolumeDetailVO detail = new VolumeDetailVO(); detail.setValue(String.valueOf(HOST_ID_1)); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(detail); HostVO downHost = createMockHost(HOST_ID_1, "host1", Status.Down, Hypervisor.HypervisorType.KVM); HostVO upHost = createMockHost(HOST_ID_2, "host2", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findById(HOST_ID_1)).thenReturn(downHost); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Arrays.asList(downHost, upHost)); // Fan-out QUERY on HOST_ID_2 → inactive (HOST_ID_1 filtered by Status.Down) ClvmLockTransferAnswer inactiveAnswer = new ClvmLockTransferAnswer(null, true, null, null, false, false, null); when(agentMgr.send(eq(HOST_ID_2), Mockito.argThat(cmd -> cmd instanceof ClvmLockTransferCommand && ((ClvmLockTransferCommand) cmd).getOperation() == ClvmLockTransferCommand.Operation.QUERY_LOCK_STATE))) .thenReturn(inactiveAnswer); // Recovery ACTIVATE_EXCLUSIVE on HOST_ID_2 → succeeds Answer activateAnswer = new Answer(null, true, null); when(agentMgr.send(eq(HOST_ID_2), Mockito.argThat(cmd -> cmd instanceof ClvmLockTransferCommand && ((ClvmLockTransferCommand) cmd).getOperation() == ClvmLockTransferCommand.Operation.ACTIVATE_EXCLUSIVE))) .thenReturn(activateAnswer); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, true); Assert.assertEquals(HOST_ID_2, result); verify(agentMgr, never()).send(eq(HOST_ID_1), any(ClvmLockTransferCommand.class)); } /** * Inactive everywhere, recovery activation throws AgentUnavailableException: returns null, * no crash, no DB side-effects (updateDatabase=false). */ @Test public void testQueryCurrentLockHolder_InactiveEverywhere_ActivationThrows_ReturnsNull() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); HostVO host1 = createMockHost(HOST_ID_1, "host1", Status.Up, Hypervisor.HypervisorType.KVM); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Collections.singletonList(host1)); ClvmLockTransferAnswer inactiveAnswer = new ClvmLockTransferAnswer(null, true, null, null, false, false, null); when(agentMgr.send(eq(HOST_ID_1), Mockito.argThat(cmd -> cmd instanceof ClvmLockTransferCommand && ((ClvmLockTransferCommand) cmd).getOperation() == ClvmLockTransferCommand.Operation.QUERY_LOCK_STATE))) .thenReturn(inactiveAnswer); when(agentMgr.send(eq(HOST_ID_1), Mockito.argThat(cmd -> cmd instanceof ClvmLockTransferCommand && ((ClvmLockTransferCommand) cmd).getOperation() == ClvmLockTransferCommand.Operation.ACTIVATE_EXCLUSIVE))) .thenThrow(new AgentUnavailableException("Host unreachable during recovery", HOST_ID_1)); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, false); Assert.assertNull(result); verify(volsDetailsDao, never()).remove(anyLong()); verify(volsDetailsDao, never()).addDetail(anyLong(), any(), any(), Mockito.anyBoolean()); } /** * Inactive everywhere, no UP KVM host available: selectActivationTargetHost returns null, * no activation is attempted, returns null immediately. */ @Test public void testQueryCurrentLockHolder_InactiveEverywhere_NoEligibleHost_ReturnsNull() throws AgentUnavailableException, OperationTimedoutException { StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); when(pool.getClusterId()).thenReturn(10L); Mockito.lenient().when(pool.getName()).thenReturn("cluster-pool"); when(pool.getPath()).thenReturn(VG_NAME); when(volsDetailsDao.findDetail(VOLUME_ID, ClvmPoolManager.CLVM_LOCK_HOST_ID)).thenReturn(null); // Only a DOWN host exists — selectActivationTargetHost finds no eligible host HostVO downHost = createMockHost(HOST_ID_1, "host1", Status.Down, Hypervisor.HypervisorType.KVM); when(hostDao.findByClusterId(10L, Host.Type.Routing)).thenReturn(Collections.singletonList(downHost)); Long result = clvmPoolManager.queryCurrentLockHolder(VOLUME_ID, VOLUME_UUID, VOLUME_PATH, pool, false); Assert.assertNull(result); verify(agentMgr, never()).send(anyLong(), any(ClvmLockTransferCommand.class)); } // Helper method to create mock hosts private HostVO createMockHost(Long id, String name, Status status, Hypervisor.HypervisorType hypervisor) { HostVO host = Mockito.mock(HostVO.class); Mockito.lenient().when(host.getId()).thenReturn(id); Mockito.lenient().when(host.getName()).thenReturn(name); Mockito.lenient().when(host.getStatus()).thenReturn(status); Mockito.lenient().when(host.getType()).thenReturn(Host.Type.Routing); Mockito.lenient().when(host.getHypervisorType()).thenReturn(hypervisor); return host; } }