Skip to content

Commit ebc71b7

Browse files
committed
kvm: address review comments on Host HA for Ceph RBD
- pass the pool's monitor port to the RBD heartbeat/activity scripts, for every monitor, instead of relying on the default port - kvmheartbeat_rbd.sh: keep the heartbeat age out of the function return status, which wraps above 255 and made a long-stale heartbeat look ALIVE - remove pools from the HA monitor on deletion for all storage pool types with HA support, without trying to umount an empty mount path - make the missing-script error message generic - add unit tests for the RBD monitor list (IPv4, IPv6, mixed, with/without port)
1 parent 806fc22 commit ebc71b7

5 files changed

Lines changed: 75 additions & 13 deletions

File tree

‎plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/KVMHAMonitor.java‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.cloud.agent.properties.AgentPropertiesFileHandler;
2121
import com.cloud.ha.HighAvailabilityManager;
2222
import com.cloud.utils.script.Script;
23+
import org.apache.commons.lang3.StringUtils;
2324
import org.libvirt.Connect;
2425
import org.libvirt.LibvirtException;
2526
import org.libvirt.StoragePool;
@@ -54,7 +55,9 @@ public void removeStoragePool(String uuid) {
5455
synchronized (haStoragePools) {
5556
HAStoragePool pool = haStoragePools.get(uuid);
5657
if (pool != null) {
57-
Script.runSimpleBashScript("umount " + pool.getMountDestPath());
58+
if (StringUtils.isNotEmpty(pool.getMountDestPath())) {
59+
Script.runSimpleBashScript("umount " + pool.getMountDestPath());
60+
}
5861
haStoragePools.remove(uuid);
5962
}
6063
}

‎plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737

3838
import com.cloud.agent.api.to.DiskTO;
3939
import com.cloud.agent.api.to.VirtualMachineTO;
40+
import com.cloud.ha.HighAvailabilityManager;
4041
import com.cloud.hypervisor.kvm.resource.KVMHABase;
4142
import com.cloud.hypervisor.kvm.resource.KVMHABase.PoolType;
4243
import com.cloud.hypervisor.kvm.resource.KVMHAMonitor;
@@ -445,7 +446,7 @@ public boolean disconnectPhysicalDisk(StoragePoolType type, String poolUuid, Str
445446

446447
public synchronized boolean deleteStoragePool(StoragePoolType type, String uuid) {
447448
StorageAdaptor adaptor = getStorageAdaptor(type);
448-
if (type == StoragePoolType.NetworkFilesystem) {
449+
if (HighAvailabilityManager.LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT.contains(type)) {
449450
_haMonitor.removeStoragePool(uuid);
450451
}
451452
boolean deleteStatus = adaptor.deleteStoragePool(uuid);;
@@ -457,7 +458,7 @@ public synchronized boolean deleteStoragePool(StoragePoolType type, String uuid)
457458

458459
public boolean deleteStoragePool(StoragePoolType type, String uuid, Map<String, String> details) {
459460
StorageAdaptor adaptor = getStorageAdaptor(type);
460-
if (type == StoragePoolType.NetworkFilesystem) {
461+
if (HighAvailabilityManager.LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT.contains(type)) {
461462
_haMonitor.removeStoragePool(uuid);
462463
}
463464
boolean deleteStatus = adaptor.deleteStoragePool(uuid, details);

‎plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePool.java‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.cloud.hypervisor.kvm.storage;
1818

1919
import java.io.File;
20+
import java.util.ArrayList;
2021
import java.util.List;
2122
import java.util.Map;
2223

@@ -343,18 +344,37 @@ private String findKvmHaScript(String scriptName) {
343344
String kvmScriptsDir = AgentPropertiesFileHandler.getPropertyValue(AgentProperties.KVM_SCRIPTS_DIR);
344345
String scriptPath = Script.findScript(kvmScriptsDir, scriptName);
345346
if (scriptPath == null) {
346-
throw new CloudRuntimeException(String.format("Unable to find heartbeat script '%s' in directory: %s", scriptName, kvmScriptsDir));
347+
throw new CloudRuntimeException(String.format("Unable to find script '%s' in directory: %s", scriptName, kvmScriptsDir));
347348
}
348349
return scriptPath;
349350
}
350351

352+
/**
353+
* Returns the Ceph monitors as expected by "--mon-host": the comma-separated monitors of the pool,
354+
* each with the pool's monitor port if one is set (IPv6 addresses are enclosed in square brackets).
355+
*/
356+
protected String getRbdMonitors() {
357+
if (sourcePort <= 0) {
358+
return sourceHost;
359+
}
360+
List<String> monitors = new ArrayList<>();
361+
for (String monitor : sourceHost.split(",")) {
362+
monitor = monitor.trim();
363+
if (monitor.contains(":") && !monitor.startsWith("[")) {
364+
monitor = "[" + monitor + "]";
365+
}
366+
monitors.add(monitor + ":" + sourcePort);
367+
}
368+
return String.join(",", monitors);
369+
}
370+
351371
/**
352372
* Adds the Ceph cluster connection details (monitors, pool and, if cephx is enabled, credentials)
353373
* to a heartbeat/VM-activity check {@link Script} for a RBD storage pool. Mirrors the "mon_host"/"id"/"key"
354374
* options that qemu itself uses to talk to RBD (see {@link KVMPhysicalDisk#RBDStringBuilder}).
355375
*/
356376
private void addRbdConnectionArgs(Script cmd) {
357-
cmd.add("-s", sourceHost);
377+
cmd.add("-s", getRbdMonitors());
358378
cmd.add("-o", sourceDir);
359379
if (authUsername != null) {
360380
cmd.add("-n", authUsername);

‎plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/LibvirtStoragePoolTest.java‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,39 @@ public void testIsPoolSupportHA() {
117117
assertFalse(new LibvirtStoragePool(uuid, name, StoragePoolType.CLVM, adapter, storage).isPoolSupportHA());
118118
assertFalse(new LibvirtStoragePool(uuid, name, StoragePoolType.Filesystem, adapter, storage).isPoolSupportHA());
119119
}
120+
121+
private String getRbdMonitors(String sourceHost, int sourcePort) {
122+
LibvirtStoragePool pool = new LibvirtStoragePool("0f7a58bd-1a85-4b1f-9f91-12f3d1ecf5a5", "myfirstpool", StoragePoolType.RBD,
123+
Mockito.mock(LibvirtStorageAdaptor.class), Mockito.mock(StoragePool.class));
124+
pool.setSourceHost(sourceHost);
125+
pool.setSourcePort(sourcePort);
126+
return pool.getRbdMonitors();
127+
}
128+
129+
@Test
130+
public void testRbdMonitorsWithoutPort() {
131+
assertEquals("10.0.0.1", getRbdMonitors("10.0.0.1", 0));
132+
assertEquals("10.0.0.1,10.0.0.2,10.0.0.3", getRbdMonitors("10.0.0.1,10.0.0.2,10.0.0.3", 0));
133+
assertEquals("fd00::1,fd00::2", getRbdMonitors("fd00::1,fd00::2", 0));
134+
}
135+
136+
@Test
137+
public void testRbdMonitorsIpv4WithPort() {
138+
assertEquals("10.0.0.1:6789", getRbdMonitors("10.0.0.1", 6789));
139+
assertEquals("10.0.0.1:3300,10.0.0.2:3300,10.0.0.3:3300", getRbdMonitors("10.0.0.1,10.0.0.2,10.0.0.3", 3300));
140+
}
141+
142+
@Test
143+
public void testRbdMonitorsIpv6WithPort() {
144+
assertEquals("[fd00::1]:3300", getRbdMonitors("fd00::1", 3300));
145+
assertEquals("[fd00::1]:3300,[fd00::2]:3300", getRbdMonitors("fd00::1,fd00::2", 3300));
146+
// already enclosed in square brackets
147+
assertEquals("[fd00::1]:3300,[fd00::2]:3300", getRbdMonitors("[fd00::1],[fd00::2]", 3300));
148+
}
149+
150+
@Test
151+
public void testRbdMonitorsMixedIpv4AndIpv6WithPort() {
152+
assertEquals("10.0.0.1:3300,[fd00::1]:3300,[fd00::2]:3300,mon4.example.com:3300",
153+
getRbdMonitors("10.0.0.1, fd00::1,[fd00::2] ,mon4.example.com", 3300));
154+
}
120155
}

‎scripts/vm/hypervisor/kvm/kvmheartbeat_rbd.sh‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,25 +109,28 @@ check_hbLog() {
109109
# Either the RADOS object doesn't exist yet (host never wrote a heartbeat)
110110
# or the Ceph cluster can't be reached right now. Either way we can't
111111
# confirm the host is alive, so fail safe and report it as DEAD.
112-
return 255
112+
hbAge=
113+
return 1
113114
fi
114-
diff=$(expr $now - $hb)
115-
if [ $diff -gt $interval ]
115+
# the age is kept in a variable, not in the return status, as a status above 255 wraps around
116+
hbAge=$(expr $now - $hb)
117+
if [ $hbAge -gt $interval ]
116118
then
117-
return $diff
119+
return 1
118120
fi
119121
return 0
120122
}
121123

122124
if [ "$rflag" == "1" ]
123125
then
124-
check_hbLog
125-
diff=$?
126-
if [ $diff == 0 ]
126+
if check_hbLog
127127
then
128128
echo "=====> ALIVE <====="
129+
elif [ -z "$hbAge" ]
130+
then
131+
echo "=====> Considering host as DEAD because RADOS object [$hbObject] in pool [$PoolName] could not be read <======"
129132
else
130-
echo "=====> Considering host as DEAD because last write to RADOS object [$hbObject] in pool [$PoolName] was [$diff] seconds ago, but the max interval is [$interval] <======"
133+
echo "=====> Considering host as DEAD because last write to RADOS object [$hbObject] in pool [$PoolName] was [$hbAge] seconds ago, but the max interval is [$interval] <======"
131134
fi
132135
exit 0
133136
elif [ "$cflag" == "1" ]

0 commit comments

Comments
 (0)