diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java
index 606115e6de03..da9b2233c8d3 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtDomainXMLParser.java
@@ -83,7 +83,7 @@ public boolean parseDomainXML(String domXML) {
String protocol = getAttrValue("source", "protocol", disk);
String authUserName = getAttrValue("auth", "username", disk);
String poolUuid = getAttrValue("secret", "uuid", disk);
- String host = getAttrValue("host", "name", disk);
+ String host = LibvirtStoragePoolXMLParser.getStorageHosts(disk);
int port = 0;
String xmlPort = getAttrValue("host", "port", disk);
if (StringUtils.isNotBlank(xmlPort)) {
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolDef.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolDef.java
index 1bdf2db8c4f4..f0ec29f41f69 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolDef.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolDef.java
@@ -147,10 +147,14 @@ public String toString() {
}
if (_poolType == PoolType.RBD) {
storagePoolBuilder.append("\n");
- if (_sourcePort > 0) {
- storagePoolBuilder.append("\n");
- } else {
- storagePoolBuilder.append("\n");
+ for (String sourceHost : _sourceHost.split(",")) {
+ storagePoolBuilder.append("\n");
}
storagePoolBuilder.append("" + _sourceDir + "\n");
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolXMLParser.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolXMLParser.java
index 7f0bf8c0bc80..d19c851d7dcc 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolXMLParser.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolXMLParser.java
@@ -18,6 +18,8 @@
import java.io.IOException;
import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
@@ -52,7 +54,7 @@ public LibvirtStoragePoolDef parseStoragePoolXML(String poolXML) {
String poolName = getTagValue("name", rootElement);
Element source = (Element)rootElement.getElementsByTagName("source").item(0);
- String host = getAttrValue("host", "name", source);
+ String host = getStorageHosts(source);
String format = getAttrValue("format", "type", source);
if (type.equalsIgnoreCase("rbd") || type.equalsIgnoreCase("powerflex")) {
@@ -123,4 +125,13 @@ private static String getAttrValue(String tag, String attr, Element eElement) {
Element node = (Element)tagNode.item(0);
return node.getAttribute(attr);
}
+
+ protected static String getStorageHosts(Element parentElement) {
+ List storageHosts = new ArrayList<>();
+ NodeList hosts = parentElement.getElementsByTagName("host");
+ for (int j = 0; j < hosts.getLength(); j++) {
+ storageHosts.add(((Element) hosts.item(j)).getAttribute("name"));
+ }
+ return StringUtils.join(storageHosts, ",");
+ }
}
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java
index a6fec3b60c47..2c1e362fc63d 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java
@@ -1093,13 +1093,15 @@ public String toString() {
diskBuilder.append(" protocol='" + _diskProtocol + "'");
diskBuilder.append(" name='" + _sourcePath + "'");
diskBuilder.append(">\n");
- diskBuilder.append("\n");
}
- diskBuilder.append("'/>\n");
diskBuilder.append("\n");
if (_authUserName != null) {
diskBuilder.append("\n");
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDisk.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDisk.java
index 7de6230f334e..c9abf399530a 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDisk.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDisk.java
@@ -18,6 +18,10 @@
import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat;
import org.apache.cloudstack.utils.qemu.QemuObject;
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.ArrayList;
+import java.util.List;
public class KVMPhysicalDisk {
private String path;
@@ -29,10 +33,7 @@ public static String RBDStringBuilder(String monHost, int monPort, String authUs
String rbdOpts;
rbdOpts = "rbd:" + image;
- rbdOpts += ":mon_host=" + monHost;
- if (monPort > 0) {
- rbdOpts += "\\:" + monPort;
- }
+ rbdOpts += ":mon_host=" + composeOptionForMonHosts(monHost, monPort);
if (authUserName == null) {
rbdOpts += ":auth_supported=none";
@@ -48,6 +49,25 @@ public static String RBDStringBuilder(String monHost, int monPort, String authUs
return rbdOpts;
}
+ private static String composeOptionForMonHosts(String monHost, int monPort) {
+ List hosts = new ArrayList<>();
+ for (String host : monHost.split(",")) {
+ if (monPort > 0) {
+ hosts.add(replaceHostAddress(host) + "\\:" + monPort);
+ } else {
+ hosts.add(replaceHostAddress(host));
+ }
+ }
+ return StringUtils.join(hosts, "\\;");
+ }
+
+ private static String replaceHostAddress(String hostIp) {
+ if (hostIp != null && hostIp.startsWith("[") && hostIp.endsWith("]")) {
+ return hostIp.replaceAll("\\:", "\\\\:");
+ }
+ return hostIp;
+ }
+
private PhysicalDiskFormat format;
private long size;
private long virtualSize;
diff --git a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImageOptions.java b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImageOptions.java
index 4e2c1c4bc692..f9a2e4ba652e 100644
--- a/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImageOptions.java
+++ b/plugins/hypervisors/kvm/src/main/java/org/apache/cloudstack/utils/qemu/QemuImageOptions.java
@@ -18,7 +18,9 @@
import com.google.common.base.Joiner;
+import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@@ -27,6 +29,10 @@ public class QemuImageOptions {
private static final String FILENAME_PARAM_KEY = "file.filename";
private static final String LUKS_KEY_SECRET_PARAM_KEY = "key-secret";
private static final String QCOW2_KEY_SECRET_PARAM_KEY = "encrypt.key-secret";
+ private static final String DRIVER = "driver";
+
+ private QemuImg.PhysicalDiskFormat format;
+ private static final List DISK_FORMATS_THAT_SUPPORT_OPTION_IMAGE_OPTS = Arrays.asList(QemuImg.PhysicalDiskFormat.QCOW2, QemuImg.PhysicalDiskFormat.LUKS);
public QemuImageOptions(String filePath) {
params.put(FILENAME_PARAM_KEY, filePath);
@@ -55,14 +61,13 @@ public QemuImageOptions(QemuImg.PhysicalDiskFormat format, String filePath, Stri
params.put(LUKS_KEY_SECRET_PARAM_KEY, secretName);
}
}
- if (format != null) {
- params.put("driver", format.toString());
- }
+ setFormat(format);
}
public void setFormat(QemuImg.PhysicalDiskFormat format) {
if (format != null) {
- params.put("driver", format.toString());
+ params.put(DRIVER, format.toString());
+ this.format = format;
}
}
@@ -71,6 +76,9 @@ public void setFormat(QemuImg.PhysicalDiskFormat format) {
* @return array of strings representing command flag and value (--image-opts)
*/
public String[] toCommandFlag() {
+ if (format == null || !DISK_FORMATS_THAT_SUPPORT_OPTION_IMAGE_OPTS.contains(format)) {
+ return new String[] { params.get(FILENAME_PARAM_KEY) };
+ }
Map sorted = new TreeMap<>(params);
String paramString = Joiner.on(",").withKeyValueSeparator("=").join(sorted);
return new String[] {"--image-opts", paramString};
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolDefTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolDefTest.java
index a1a43447e1cc..f5f3cc994e95 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolDefTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolDefTest.java
@@ -22,6 +22,7 @@
import junit.framework.TestCase;
import com.cloud.hypervisor.kvm.resource.LibvirtStoragePoolDef.PoolType;
import com.cloud.hypervisor.kvm.resource.LibvirtStoragePoolDef.AuthenticationType;
+import org.junit.Test;
public class LibvirtStoragePoolDefTest extends TestCase {
@@ -102,4 +103,35 @@ public void testRbdStoragePoolWithoutPort() {
assertEquals(expectedXml, pool.toString());
}
+
+ @Test
+ public void testRbdStoragePoolWithMultipleHostsIpv6() {
+ PoolType type = PoolType.RBD;
+ String name = "myRBDPool";
+ String uuid = "1583a25a-b192-436c-93e6-0ef60b198a32";
+ String host = "[fc00:1234::1],[fc00:1234::2],[fc00:1234::3]";
+ int port = 3300;
+ String authUsername = "admin";
+ AuthenticationType auth = AuthenticationType.CEPH;
+ String dir = "rbd";
+ String secretUuid = "28909c4f-314e-4db7-a6b3-5eccd9dcf973";
+
+ LibvirtStoragePoolDef pool = new LibvirtStoragePoolDef(type, name, uuid, host, port, dir, authUsername, auth, secretUuid);
+
+ String expected = "\n" +
+ "myRBDPool\n" +
+ "1583a25a-b192-436c-93e6-0ef60b198a32\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "rbd\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n";
+
+ assertEquals(expected, pool.toString());
+ }
}
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolXMLParserTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolXMLParserTest.java
new file mode 100644
index 000000000000..90dfae019afb
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolXMLParserTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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.hypervisor.kvm.resource;
+
+import junit.framework.TestCase;
+import org.junit.Assert;
+
+public class LibvirtStoragePoolXMLParserTest extends TestCase {
+
+ public void testParseNfsStoragePoolXML() {
+ String poolXML = "\n" +
+ " feff06b5-84b2-3258-b5f9-1953217295de\n" +
+ " feff06b5-84b2-3258-b5f9-1953217295de\n" +
+ " 111111111\n" +
+ " 2222222\n" +
+ " 3333333\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " /mnt/feff06b5-84b2-3258-b5f9-1953217295de\n" +
+ " \n" +
+ " 0755\n" +
+ " 0\n" +
+ " 0\n" +
+ " \n" +
+ " \n" +
+ "";
+
+ LibvirtStoragePoolXMLParser parser = new LibvirtStoragePoolXMLParser();
+ LibvirtStoragePoolDef pool = parser.parseStoragePoolXML(poolXML);
+
+ Assert.assertEquals("10.11.12.13", pool.getSourceHost());
+ }
+
+ public void testParseRbdStoragePoolXMLWithMultipleHosts() {
+ String poolXML = "\n" +
+ " feff06b5-84b2-3258-b5f9-1953217295de\n" +
+ " feff06b5-84b2-3258-b5f9-1953217295de\n" +
+ " \n" +
+ " rbdpool\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ "";
+
+ LibvirtStoragePoolXMLParser parser = new LibvirtStoragePoolXMLParser();
+ LibvirtStoragePoolDef pool = parser.parseStoragePoolXML(poolXML);
+
+ Assert.assertEquals(LibvirtStoragePoolDef.PoolType.RBD, pool.getPoolType());
+ Assert.assertEquals(LibvirtStoragePoolDef.AuthenticationType.CEPH, pool.getAuthType());
+ Assert.assertEquals("10.11.12.13,10.11.12.14,10.11.12.15", pool.getSourceHost());
+ Assert.assertEquals(6789, pool.getSourcePort());
+ }
+
+ public void testParseRbdStoragePoolXMLWithMultipleHostsIpv6() {
+ String poolXML = "\n" +
+ " feff06b5-84b2-3258-b5f9-1953217295de\n" +
+ " feff06b5-84b2-3258-b5f9-1953217295de\n" +
+ " \n" +
+ " rbdpool\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ "";
+
+ LibvirtStoragePoolXMLParser parser = new LibvirtStoragePoolXMLParser();
+ LibvirtStoragePoolDef pool = parser.parseStoragePoolXML(poolXML);
+
+ Assert.assertEquals(LibvirtStoragePoolDef.PoolType.RBD, pool.getPoolType());
+ Assert.assertEquals(LibvirtStoragePoolDef.AuthenticationType.CEPH, pool.getAuthType());
+ Assert.assertEquals("[fc00:aa:bb:cc::1],[fc00:aa:bb:cc::2],[fc00:aa:bb:cc::3]", pool.getSourceHost());
+ Assert.assertEquals(6789, pool.getSourcePort());
+ }
+}
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java
index 4eb464e4a68d..9da1f5ea75be 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDefTest.java
@@ -238,6 +238,72 @@ public void testDiskDefWithEncryption() {
assertEquals(disk.toString(), expectedXML);
}
+ @Test
+ public void testDiskDefWithMultipleHosts() {
+ String path = "/mnt/primary1";
+ String host = "10.11.12.13,10.11.12.14,10.11.12.15";
+ int port = 3300;
+ String authUsername = "admin";
+ String uuid = "40b3f216-36b5-11ed-9357-9b4e21b0ed91";
+ int devId = 2;
+
+ DiskDef diskdef = new DiskDef();
+ diskdef.defNetworkBasedDisk(path, host, port, authUsername,
+ uuid, devId, DiskDef.DiskBus.VIRTIO, DiskDef.DiskProtocol.RBD, DiskDef.DiskFmtType.RAW);
+
+ assertEquals(path, diskdef.getDiskPath());
+ assertEquals(DiskDef.DiskType.NETWORK, diskdef.getDiskType());
+ assertEquals(DiskDef.DiskFmtType.RAW, diskdef.getDiskFormatType());
+
+ String expected = "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n";
+
+ assertEquals(expected, diskdef.toString());
+ }
+
+ @Test
+ public void testDiskDefWithMultipleHostsIpv6() {
+ String path = "/mnt/primary1";
+ String host = "[fc00:1234::1],[fc00:1234::2],[fc00:1234::3]";
+ int port = 3300;
+ String authUsername = "admin";
+ String uuid = "40b3f216-36b5-11ed-9357-9b4e21b0ed91";
+ int devId = 2;
+
+ DiskDef diskdef = new DiskDef();
+ diskdef.defNetworkBasedDisk(path, host, port, authUsername,
+ uuid, devId, DiskDef.DiskBus.VIRTIO, DiskDef.DiskProtocol.RBD, DiskDef.DiskFmtType.RAW);
+
+ assertEquals(path, diskdef.getDiskPath());
+ assertEquals(DiskDef.DiskType.NETWORK, diskdef.getDiskType());
+ assertEquals(DiskDef.DiskFmtType.RAW, diskdef.getDiskFormatType());
+
+ String expected = "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n";
+
+ assertEquals(expected, diskdef.toString());
+ }
+
@Test
public void testDiskDefWithBurst() {
String filePath = "/var/lib/libvirt/images/disk.qcow2";
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDiskTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDiskTest.java
index cf39dceb1a5c..684a0e9daff9 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDiskTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/KVMPhysicalDiskTest.java
@@ -28,6 +28,26 @@ public void testRBDStringBuilder() {
"rbd:volume1:mon_host=ceph-monitor\\:8000:auth_supported=cephx:id=admin:key=supersecret:rbd_default_format=2:client_mount_timeout=30");
}
+ public void testRBDStringBuilder2() {
+ String monHosts = "ceph-monitor1,ceph-monitor2,ceph-monitor3";
+ int monPort = 3300;
+ String expected = "rbd:volume1:" +
+ "mon_host=ceph-monitor1\\:3300\\;ceph-monitor2\\:3300\\;ceph-monitor3\\:3300:" +
+ "auth_supported=cephx:id=admin:key=supersecret:rbd_default_format=2:client_mount_timeout=30";
+ String actualResult = KVMPhysicalDisk.RBDStringBuilder(monHosts, monPort, "admin", "supersecret", "volume1");
+ assertEquals(expected, actualResult);
+ }
+
+ public void testRBDStringBuilder3() {
+ String monHosts = "[fc00:1234::1],[fc00:1234::2],[fc00:1234::3]";
+ int monPort = 3300;
+ String expected = "rbd:volume1:" +
+ "mon_host=[fc00\\:1234\\:\\:1]\\:3300\\;[fc00\\:1234\\:\\:2]\\:3300\\;[fc00\\:1234\\:\\:3]\\:3300:" +
+ "auth_supported=cephx:id=admin:key=supersecret:rbd_default_format=2:client_mount_timeout=30";
+ String actualResult = KVMPhysicalDisk.RBDStringBuilder(monHosts, monPort, "admin", "supersecret", "volume1");
+ assertEquals(expected, actualResult);
+ }
+
public void testAttributes() {
String name = "3bc186e0-6c29-45bf-b2b0-ddef6f91f5ef";
String path = "/" + name;
diff --git a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImageOptionsTest.java b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImageOptionsTest.java
index 2b56b69d1c5d..644c49543aa2 100644
--- a/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImageOptionsTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/org/apache/cloudstack/utils/qemu/QemuImageOptionsTest.java
@@ -33,9 +33,9 @@ public static Collection