| // 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; |
| } |
| } |