cloudstack/server/src/test/java/com/cloud/storage/ClvmPoolManagerTest.java

937 lines
47 KiB
Java

// 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.<Command>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.<Command>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.<Command>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.<Command>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.<Command>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.<Command>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.<Command>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.<Command>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.<Command>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.<Command>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.<Command>argThat(cmd ->
cmd instanceof ClvmLockTransferCommand
&& ((ClvmLockTransferCommand) cmd).getOperation()
== ClvmLockTransferCommand.Operation.QUERY_LOCK_STATE)))
.thenReturn(inactiveAnswer);
when(agentMgr.send(eq(HOST_ID_1), Mockito.<Command>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;
}
}