mirror of https://github.com/apache/cloudstack.git
937 lines
47 KiB
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;
|
|
}
|
|
}
|