Skip to content

Commit 97f176f

Browse files
committed
CLVM: send the sanlock host id with the fence command and report hosts without one
- KVMFencer sets the sanlock host id of the fenced host in the FenceCommand, so the agents can check its CLVM leases instead of assuming it is alive - ClvmPoolManager.addSanlockHostId is shared by KVMFencer and KVMHostActivityChecker - raise an alert when a host connects to a CLVM pool without a sanlock host id, and log the cause when fencing fails for it
1 parent f53764c commit 97f176f

7 files changed

Lines changed: 219 additions & 29 deletions

File tree

‎engine/storage/volume/src/main/java/org/apache/cloudstack/storage/datastore/provider/DefaultHostListener.java‎

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -197,22 +197,28 @@ public boolean hostConnect(long hostId, long poolId) throws StorageConflictExcep
197197
storageService.updateStorageCapabilities(poolId, false);
198198

199199
if (ClvmPoolManager.isClvmPoolType(pool.getPoolType())) {
200-
alertOnSharedSanlockId(pool, host);
200+
alertOnSanlockHostId(pool, host);
201201
}
202202

203203
logger.info("Connection established between storage pool {} and host {}", pool, host);
204204

205205
return createPersistentNetworkResourcesOnHost(hostId);
206206
}
207207

208-
/** CLVM lock holder lookups map a sanlock host id to exactly one host of the pool. */
209-
private void alertOnSharedSanlockId(StoragePool pool, HostVO host) {
210-
List<Long> otherHostIds = clvmPoolManager.findHostsSharingSanlockId(pool, host.getId());
211-
if (otherHostIds.isEmpty()) {
212-
return;
208+
/** The sanlock host id is needed to check the leases of a host, and must identify exactly one host of the pool. */
209+
private void alertOnSanlockHostId(StoragePool pool, HostVO host) {
210+
String msg;
211+
if (!clvmPoolManager.hasSanlockHostId(host.getId())) {
212+
msg = String.format("Host %s reports no %s, so the CLVM leases of storage pool %s cannot be checked and the host cannot be fenced. "
213+
+ "Restart its agent and check that lvmlockd is running on it.", host, Host.HOST_SANLOCK_ID, pool);
214+
} else {
215+
List<Long> otherHostIds = clvmPoolManager.findHostsSharingSanlockId(pool, host.getId());
216+
if (otherHostIds.isEmpty()) {
217+
return;
218+
}
219+
msg = String.format("Host %s reports the same %s as hosts %s of CLVM storage pool %s. Set a unique host_id in lvmlocal.conf on each host.",
220+
host, Host.HOST_SANLOCK_ID, otherHostIds, pool);
213221
}
214-
String msg = String.format("Host %s reports the same %s as hosts %s of CLVM storage pool %s. Set a unique host_id in lvmlocal.conf on each host.",
215-
host, Host.HOST_SANLOCK_ID, otherHostIds, pool);
216222
logger.error(msg);
217223
alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, pool.getDataCenterId(), pool.getPodId(), msg, msg);
218224
}

‎plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/kvm/ha/KVMHostActivityChecker.java‎

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,12 @@
2222
import com.cloud.agent.api.CheckOnHostAnswer;
2323
import com.cloud.agent.api.CheckOnHostCommand;
2424
import com.cloud.agent.api.CheckVMActivityOnStoragePoolCommand;
25-
import com.cloud.agent.api.to.HostTO;
2625
import com.cloud.dc.dao.ClusterDao;
2726
import com.cloud.exception.StorageUnavailableException;
2827
import com.cloud.ha.HighAvailabilityManager;
2928
import com.cloud.host.Host;
3029
import com.cloud.host.HostVO;
3130
import com.cloud.host.Status;
32-
import com.cloud.host.DetailVO;
33-
import com.cloud.host.dao.HostDetailsDao;
3431
import com.cloud.hypervisor.Hypervisor;
3532
import com.cloud.resource.ResourceManager;
3633
import com.cloud.storage.clvm.ClvmPoolManager;
@@ -48,7 +45,6 @@
4845
import org.apache.cloudstack.ha.provider.HealthCheckerInterface;
4946
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
5047
import org.apache.commons.lang.ArrayUtils;
51-
import org.apache.commons.lang3.math.NumberUtils;
5248

5349
import javax.inject.Inject;
5450
import java.util.ArrayList;
@@ -73,7 +69,7 @@ public class KVMHostActivityChecker extends AdapterBase implements ActivityCheck
7369
@Inject
7470
private ResourceManager resourceManager;
7571
@Inject
76-
private HostDetailsDao hostDetailsDao;
72+
private ClvmPoolManager clvmPoolManager;
7773

7874
@Override
7975
public boolean isActive(Host r, DateTime suspectTime) throws HACheckerException {
@@ -133,7 +129,7 @@ private Status checkHostStatusWithSameHost(Host host) {
133129
Status hostStatus;
134130
boolean reportFailureIfOneStorageIsDown = HighAvailabilityManager.KvmHAFenceHostIfHeartbeatFailsOnStorage.value();
135131
final CheckOnHostCommand cmd = new CheckOnHostCommand(host, reportFailureIfOneStorageIsDown);
136-
addSanlockHostId(cmd.getHost(), host);
132+
clvmPoolManager.addSanlockHostId(cmd.getHost(), host);
137133
try {
138134
logger.debug("Checking {} status...", host.toString());
139135
Answer answer = agentMgr.easySend(host.getId(), cmd);
@@ -161,7 +157,7 @@ private Status checkHostStatusWithNeighbourHosts(Host host) {
161157
Status hostStatusFromNeighbour = Status.Unknown;
162158
boolean reportFailureIfOneStorageIsDown = HighAvailabilityManager.KvmHAFenceHostIfHeartbeatFailsOnStorage.value();
163159
final CheckOnHostCommand cmd = new CheckOnHostCommand(host, reportFailureIfOneStorageIsDown);
164-
addSanlockHostId(cmd.getHost(), host);
160+
clvmPoolManager.addSanlockHostId(cmd.getHost(), host);
165161
List<HostVO> neighbors = resourceManager.listHostsInClusterByStatus(host.getClusterId(), Status.Up);
166162
for (HostVO neighbor : neighbors) {
167163
if (neighbor.getId() == host.getId()
@@ -214,7 +210,7 @@ protected boolean verifyActivityOfStorageOnHost(HashMap<StoragePool, List<Volume
214210
List<Volume> volume_list = poolVolMap.get(pool);
215211
final CheckVMActivityOnStoragePoolCommand cmd = new CheckVMActivityOnStoragePoolCommand(host, pool, volume_list, suspectTime);
216212
if (ClvmPoolManager.isClvmPoolType(pool.getPoolType())) {
217-
addSanlockHostId(cmd.getHost(), host);
213+
clvmPoolManager.addSanlockHostId(cmd.getHost(), host);
218214
}
219215

220216
logger.debug("Checking VM activity for {} on storage pool [{}].", host.toString(), pool);
@@ -236,13 +232,6 @@ protected boolean verifyActivityOfStorageOnHost(HashMap<StoragePool, List<Volume
236232
return activityStatus;
237233
}
238234

239-
private void addSanlockHostId(HostTO hostTO, Host host) {
240-
DetailVO detail = hostDetailsDao.findDetail(host.getId(), Host.HOST_SANLOCK_ID);
241-
if (detail != null && NumberUtils.isDigits(detail.getValue())) {
242-
hostTO.setSanlockHostId(Long.valueOf(detail.getValue()));
243-
}
244-
}
245-
246235
private HashMap<StoragePool, List<Volume>> getStoragePoolAndVolumeInfoOnHost(Host host) {
247236
List<VMInstanceVO> vmListOnHost = vmInstanceDao.listByHostId(host.getId());
248237
List<VolumeVO> volumeListOnHost = new ArrayList<>();

‎plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/kvm/ha/KVMHostActivityCheckerTest.java‎

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import static org.junit.Assert.assertNull;
2424
import static org.mockito.ArgumentMatchers.any;
2525
import static org.mockito.ArgumentMatchers.anyLong;
26+
import static org.mockito.ArgumentMatchers.eq;
27+
import static org.mockito.Mockito.doAnswer;
2628
import static org.mockito.Mockito.never;
2729
import static org.mockito.Mockito.verify;
2830
import static org.mockito.Mockito.when;
@@ -42,22 +44,22 @@
4244
import com.cloud.agent.api.Answer;
4345
import com.cloud.agent.api.CheckOnHostCommand;
4446
import com.cloud.agent.api.CheckVMActivityOnStoragePoolCommand;
45-
import com.cloud.host.DetailVO;
47+
import com.cloud.agent.api.to.HostTO;
4648
import com.cloud.host.Host;
4749
import com.cloud.hypervisor.Hypervisor.HypervisorType;
48-
import com.cloud.host.dao.HostDetailsDao;
4950
import com.cloud.resource.ResourceManager;
5051
import com.cloud.dc.dao.ClusterDao;
5152
import com.cloud.storage.Storage.StoragePoolType;
5253
import com.cloud.storage.StorageManager;
5354
import com.cloud.storage.StoragePool;
55+
import com.cloud.storage.clvm.ClvmPoolManager;
5456
import com.cloud.storage.Volume;
5557

5658
@RunWith(MockitoJUnitRunner.Silent.class)
5759
public class KVMHostActivityCheckerTest {
5860

5961
@Mock
60-
private HostDetailsDao hostDetailsDao;
62+
private ClvmPoolManager clvmPoolManager;
6163
@Mock
6264
private StorageManager storageManager;
6365
@Mock
@@ -75,11 +77,18 @@ public class KVMHostActivityCheckerTest {
7577
@InjectMocks
7678
private KVMHostActivityChecker checker;
7779

80+
private void reportSanlockHostId(Long sanlockHostId) {
81+
doAnswer(invocation -> {
82+
invocation.<HostTO>getArgument(0).setSanlockHostId(sanlockHostId);
83+
return null;
84+
}).when(clvmPoolManager).addSanlockHostId(any(HostTO.class), eq(host));
85+
}
86+
7887
private void runActivityCheck(StoragePoolType poolType) throws Exception {
7988
when(pool.getPoolType()).thenReturn(poolType);
8089
when(host.getId()).thenReturn(8L);
8190
when(volume.getPath()).thenReturn("vol");
82-
when(hostDetailsDao.findDetail(8L, Host.HOST_SANLOCK_ID)).thenReturn(new DetailVO(8L, Host.HOST_SANLOCK_ID, "2"));
91+
reportSanlockHostId(2L);
8392
when(storageManager.sendToPool(any(StoragePool.class), any(long[].class), any(CheckVMActivityOnStoragePoolCommand.class))).thenReturn(new Answer(null));
8493
HashMap<StoragePool, List<Volume>> poolVolumes = new HashMap<>();
8594
poolVolumes.put(pool, List.of(volume));
@@ -89,7 +98,7 @@ private void runActivityCheck(StoragePoolType poolType) throws Exception {
8998
@Test
9099
public void testSanlockHostIdIsNotLookedUpForNonClvmPool() throws Exception {
91100
runActivityCheck(StoragePoolType.NetworkFilesystem);
92-
verify(hostDetailsDao, never()).findDetail(anyLong(), any());
101+
verify(clvmPoolManager, never()).addSanlockHostId(any(), any());
93102
}
94103

95104
@Test
@@ -104,7 +113,7 @@ public void testSanlockHostIdIsSentForClvmPool() throws Exception {
104113
public void testHostCheckSendsSanlockHostIdWhenHostReportsOne() throws Exception {
105114
when(host.getId()).thenReturn(8L);
106115
when(host.getHypervisorType()).thenReturn(HypervisorType.KVM);
107-
when(hostDetailsDao.findDetail(8L, Host.HOST_SANLOCK_ID)).thenReturn(new DetailVO(8L, Host.HOST_SANLOCK_ID, "2"));
116+
reportSanlockHostId(2L);
108117
when(agentMgr.easySend(anyLong(), any(CheckOnHostCommand.class))).thenReturn(new Answer(null, false, "down"));
109118
checker.getHostAgentStatus(host);
110119
ArgumentCaptor<CheckOnHostCommand> captor = ArgumentCaptor.forClass(CheckOnHostCommand.class);

‎server/src/main/java/com/cloud/ha/KVMFencer.java‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import com.cloud.host.dao.HostDao;
3636
import com.cloud.hypervisor.Hypervisor.HypervisorType;
3737
import com.cloud.resource.ResourceManager;
38+
import com.cloud.storage.clvm.ClvmPoolManager;
3839
import com.cloud.utils.component.AdapterBase;
3940
import com.cloud.vm.VirtualMachine;
4041

@@ -48,6 +49,8 @@ public class KVMFencer extends AdapterBase implements FenceBuilder {
4849
AlertManager _alertMgr;
4950
@Inject
5051
ResourceManager _resourceMgr;
52+
@Inject
53+
ClvmPoolManager _clvmPoolManager;
5154

5255
@Override
5356
public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
@@ -80,6 +83,7 @@ public Boolean fenceOff(VirtualMachine vm, Host host) {
8083

8184
List<HostVO> hosts = _resourceMgr.listAllHostsInCluster(host.getClusterId());
8285
FenceCommand fence = new FenceCommand(vm, host);
86+
_clvmPoolManager.addSanlockHostId(fence.getHost(), host);
8387
fence.setReportCheckFailureIfOneStorageIsDown(HighAvailabilityManager.KvmHAFenceHostIfHeartbeatFailsOnStorage.value());
8488

8589
int i = 0;
@@ -107,6 +111,11 @@ public Boolean fenceOff(VirtualMachine vm, Host host) {
107111
}
108112
}
109113

114+
if (fence.getHost().getSanlockHostId() == null && _clvmPoolManager.hasClvmPool(host)) {
115+
logger.error("Host {} reports no {}, so the CLVM leases of its pools cannot be checked and it is assumed alive. "
116+
+ "Restart its agent and check that lvmlockd is running on it.", host, Host.HOST_SANLOCK_ID);
117+
}
118+
110119
_alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, host.getDataCenterId(), host.getPodId(),
111120
"Unable to fence off host: " + host.getId(),
112121
"Fencing off host " + host.getId() + " did not succeed after asking " + i + " hosts. " +

‎server/src/main/java/com/cloud/storage/clvm/ClvmPoolManager.java‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141

4242
import com.cloud.agent.AgentManager;
4343
import com.cloud.agent.api.Answer;
44+
import com.cloud.agent.api.to.HostTO;
4445
import com.cloud.exception.AgentUnavailableException;
4546
import com.cloud.exception.OperationTimedoutException;
4647
import com.cloud.host.Host;
@@ -63,6 +64,7 @@
6364
import org.apache.cloudstack.storage.clvm.command.ClvmLockTransferAnswer;
6465
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
6566
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
67+
import org.apache.commons.lang3.math.NumberUtils;
6668
import org.apache.logging.log4j.LogManager;
6769
import org.apache.logging.log4j.Logger;
6870
import org.springframework.stereotype.Component;
@@ -546,6 +548,27 @@ private String getSanlockId(long hostId) {
546548
return detail != null ? detail.getValue() : null;
547549
}
548550

551+
/** Sets the sanlock host id of the host in the command's host, so the agents can check its CLVM leases. */
552+
public void addSanlockHostId(HostTO hostTO, Host host) {
553+
String sanlockId = getSanlockId(host.getId());
554+
if (NumberUtils.isDigits(sanlockId)) {
555+
hostTO.setSanlockHostId(Long.valueOf(sanlockId));
556+
}
557+
}
558+
559+
public boolean hasSanlockHostId(long hostId) {
560+
return NumberUtils.isDigits(getSanlockId(hostId));
561+
}
562+
563+
/** Whether the host can use a CLVM or CLVM_NG pool: one of its cluster or a zone-wide one. */
564+
public boolean hasClvmPool(Host host) {
565+
List<StoragePoolVO> pools = new ArrayList<>(_storagePoolDao.findZoneWideStoragePoolsByHypervisor(host.getDataCenterId(), host.getHypervisorType()));
566+
if (host.getClusterId() != null) {
567+
pools.addAll(_storagePoolDao.findPoolsInClusters(Collections.singletonList(host.getClusterId()), null));
568+
}
569+
return pools.stream().anyMatch(pool -> isClvmPoolType(pool.getPoolType()));
570+
}
571+
549572
/**
550573
* Checks that the host's sanlock host id identifies it among the hosts of the pool, which the lease based
551574
* lock holder lookup relies on.

‎server/src/test/java/com/cloud/ha/KVMFencerTest.java‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.cloud.agent.AgentManager;
2222
import com.cloud.agent.api.FenceAnswer;
2323
import com.cloud.agent.api.FenceCommand;
24+
import com.cloud.agent.api.to.HostTO;
2425
import com.cloud.alert.AlertManager;
2526
import com.cloud.exception.AgentUnavailableException;
2627
import com.cloud.exception.OperationTimedoutException;
@@ -29,11 +30,13 @@
2930
import com.cloud.host.dao.HostDao;
3031
import com.cloud.hypervisor.Hypervisor.HypervisorType;
3132
import com.cloud.resource.ResourceManager;
33+
import com.cloud.storage.clvm.ClvmPoolManager;
3234
import com.cloud.vm.VirtualMachine;
3335
import org.junit.Assert;
3436
import org.junit.Before;
3537
import org.junit.Test;
3638
import org.junit.runner.RunWith;
39+
import org.mockito.ArgumentCaptor;
3740
import org.mockito.ArgumentMatchers;
3841
import org.mockito.Mock;
3942
import org.mockito.Mockito;
@@ -53,6 +56,8 @@ public class KVMFencerTest {
5356
AlertManager alertMgr;
5457
@Mock
5558
ResourceManager resourceManager;
59+
@Mock
60+
ClvmPoolManager clvmPoolManager;
5661

5762
KVMFencer fencer;
5863

@@ -63,6 +68,7 @@ public void setup() {
6368
fencer._alertMgr = alertMgr;
6469
fencer._hostDao = hostDao;
6570
fencer._resourceMgr = resourceManager;
71+
fencer._clvmPoolManager = clvmPoolManager;
6672
}
6773

6874
@Test
@@ -192,4 +198,63 @@ public void testWithSingleNotKVM() {
192198
Assert.assertNull(fencer.fenceOff(virtualMachine, host));
193199
}
194200

201+
@Test
202+
public void testFenceCommandCarriesSanlockHostIdOfFencedHost() throws AgentUnavailableException, OperationTimedoutException {
203+
HostVO host = Mockito.mock(HostVO.class);
204+
Mockito.when(host.getClusterId()).thenReturn(1l);
205+
Mockito.when(host.getHypervisorType()).thenReturn(HypervisorType.KVM);
206+
Mockito.lenient().when(host.getStatus()).thenReturn(Status.Down);
207+
Mockito.when(host.getId()).thenReturn(2l);
208+
HostVO otherHost = Mockito.mock(HostVO.class);
209+
Mockito.when(otherHost.getHypervisorType()).thenReturn(HypervisorType.KVM);
210+
Mockito.when(otherHost.getStatus()).thenReturn(Status.Up);
211+
Mockito.when(otherHost.getId()).thenReturn(3l);
212+
Mockito.doAnswer(invocation -> {
213+
invocation.<HostTO>getArgument(0).setSanlockHostId(7L);
214+
return null;
215+
}).when(clvmPoolManager).addSanlockHostId(ArgumentMatchers.any(HostTO.class), ArgumentMatchers.eq(host));
216+
Mockito.when(resourceManager.listAllHostsInCluster(1l)).thenReturn(Arrays.asList(host, otherHost));
217+
Mockito.when(agentManager.send(ArgumentMatchers.anyLong(), ArgumentMatchers.any(FenceCommand.class))).thenReturn(new FenceAnswer(null, true, "ok"));
218+
219+
Assert.assertTrue(fencer.fenceOff(Mockito.mock(VirtualMachine.class), host));
220+
221+
ArgumentCaptor<FenceCommand> captor = ArgumentCaptor.forClass(FenceCommand.class);
222+
Mockito.verify(agentManager).send(ArgumentMatchers.eq(3l), captor.capture());
223+
Assert.assertEquals(Long.valueOf(7), captor.getValue().getHost().getSanlockHostId());
224+
}
225+
226+
private HostVO mockDownHostInCluster(long id) {
227+
HostVO host = Mockito.mock(HostVO.class);
228+
Mockito.when(host.getClusterId()).thenReturn(1l);
229+
Mockito.when(host.getHypervisorType()).thenReturn(HypervisorType.KVM);
230+
Mockito.lenient().when(host.getDataCenterId()).thenReturn(1l);
231+
Mockito.lenient().when(host.getPodId()).thenReturn(1l);
232+
Mockito.lenient().when(host.getStatus()).thenReturn(Status.Down);
233+
Mockito.when(host.getId()).thenReturn(id);
234+
return host;
235+
}
236+
237+
@Test
238+
public void testFailedFenceChecksForClvmPoolWhenHostReportsNoSanlockHostId() {
239+
HostVO host = mockDownHostInCluster(2l);
240+
Mockito.when(resourceManager.listAllHostsInCluster(1l)).thenReturn(Collections.singletonList(host));
241+
242+
Assert.assertFalse(fencer.fenceOff(Mockito.mock(VirtualMachine.class), host));
243+
244+
Mockito.verify(clvmPoolManager).hasClvmPool(host);
245+
}
246+
247+
@Test
248+
public void testFailedFenceDoesNotCheckForClvmPoolWhenHostReportsSanlockHostId() {
249+
HostVO host = mockDownHostInCluster(2l);
250+
Mockito.when(resourceManager.listAllHostsInCluster(1l)).thenReturn(Collections.singletonList(host));
251+
Mockito.doAnswer(invocation -> {
252+
invocation.<HostTO>getArgument(0).setSanlockHostId(7L);
253+
return null;
254+
}).when(clvmPoolManager).addSanlockHostId(ArgumentMatchers.any(HostTO.class), ArgumentMatchers.eq(host));
255+
256+
Assert.assertFalse(fencer.fenceOff(Mockito.mock(VirtualMachine.class), host));
257+
258+
Mockito.verify(clvmPoolManager, Mockito.never()).hasClvmPool(ArgumentMatchers.any());
259+
}
195260
}

0 commit comments

Comments
 (0)