This is an automated email from the ASF dual-hosted git repository.
dahn pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/main by this push:
new 48ffa5dc0b Support multiple ceph monitors (#6792)
48ffa5dc0b is described below
commit 48ffa5dc0b9e6dcbc4d208245862050e8814f69b
Author: Wei Zhou <[email protected]>
AuthorDate: Fri Oct 21 10:37:30 2022 +0200
Support multiple ceph monitors (#6792)
---
.../kvm/resource/LibvirtDomainXMLParser.java | 2 +-
.../kvm/resource/LibvirtStoragePoolDef.java | 12 +-
.../kvm/resource/LibvirtStoragePoolXMLParser.java | 13 +-
.../hypervisor/kvm/resource/LibvirtVMDef.java | 14 +-
.../hypervisor/kvm/storage/KVMPhysicalDisk.java | 28 +++-
.../kvm/resource/LibvirtStoragePoolDefTest.java | 32 +++++
.../resource/LibvirtStoragePoolXMLParserTest.java | 104 ++++++++++++++
.../hypervisor/kvm/resource/LibvirtVMDefTest.java | 66 +++++++++
.../kvm/storage/KVMPhysicalDiskTest.java | 20 +++
.../CloudStackPrimaryDataStoreLifeCycleImpl.java | 49 +++----
ui/public/locales/en.json | 1 +
ui/src/views/infra/AddPrimaryStorage.vue | 5 +-
utils/src/main/java/com/cloud/utils/UriUtils.java | 84 +++++++++++
.../test/java/com/cloud/utils/UriUtilsTest.java | 154 ++++++++++++++++++++-
14 files changed, 539 insertions(+), 45 deletions(-)
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 606115e6de..da9b2233c8 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 class LibvirtDomainXMLParser {
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 1bdf2db8c4..f0ec29f41f 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 class LibvirtStoragePoolDef {
}
if (_poolType == PoolType.RBD) {
storagePoolBuilder.append("<source>\n");
- if (_sourcePort > 0) {
- storagePoolBuilder.append("<host name='" + _sourceHost + "'
port='" + _sourcePort + "'/>\n");
- } else {
- storagePoolBuilder.append("<host name='" + _sourceHost +
"'/>\n");
+ for (String sourceHost : _sourceHost.split(",")) {
+ storagePoolBuilder.append("<host name='");
+ storagePoolBuilder.append(sourceHost);
+ if (_sourcePort != 0) {
+ storagePoolBuilder.append("' port='");
+ storagePoolBuilder.append(_sourcePort);
+ }
+ storagePoolBuilder.append("'/>\n");
}
storagePoolBuilder.append("<name>" + _sourceDir + "</name>\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 7f0bf8c0bc..d19c851d7d 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 @@ package com.cloud.hypervisor.kvm.resource;
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 class LibvirtStoragePoolXMLParser {
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 @@ public class LibvirtStoragePoolXMLParser {
Element node = (Element)tagNode.item(0);
return node.getAttribute(attr);
}
+
+ protected static String getStorageHosts(Element parentElement) {
+ List<String> 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 a6fec3b60c..2c1e362fc6 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 class LibvirtVMDef {
diskBuilder.append(" protocol='" + _diskProtocol + "'");
diskBuilder.append(" name='" + _sourcePath + "'");
diskBuilder.append(">\n");
- diskBuilder.append("<host name='");
- diskBuilder.append(_sourceHost);
- if (_sourcePort != 0) {
- diskBuilder.append("' port='");
- diskBuilder.append(_sourcePort);
+ for (String sourceHost : _sourceHost.split(",")) {
+ diskBuilder.append("<host name='");
+ diskBuilder.append(sourceHost.replace("[",
"").replace("]", ""));
+ if (_sourcePort != 0) {
+ diskBuilder.append("' port='");
+ diskBuilder.append(_sourcePort);
+ }
+ diskBuilder.append("'/>\n");
}
- diskBuilder.append("'/>\n");
diskBuilder.append("</source>\n");
if (_authUserName != null) {
diskBuilder.append("<auth username='" + _authUserName +
"'>\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 7de6230f33..c9abf39953 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 @@ package com.cloud.hypervisor.kvm.storage;
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 class KVMPhysicalDisk {
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 class KVMPhysicalDisk {
return rbdOpts;
}
+ private static String composeOptionForMonHosts(String monHost, int
monPort) {
+ List<String> 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/test/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolDefTest.java
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/LibvirtStoragePoolDefTest.java
index a1a43447e1..f5f3cc994e 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 @@ package com.cloud.hypervisor.kvm.resource;
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 class LibvirtStoragePoolDefTest extends TestCase {
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 = "<pool type='rbd'>\n" +
+ "<name>myRBDPool</name>\n" +
+ "<uuid>1583a25a-b192-436c-93e6-0ef60b198a32</uuid>\n" +
+ "<source>\n" +
+ "<host name='[fc00:1234::1]' port='3300'/>\n" +
+ "<host name='[fc00:1234::2]' port='3300'/>\n" +
+ "<host name='[fc00:1234::3]' port='3300'/>\n" +
+ "<name>rbd</name>\n" +
+ "<auth username='admin' type='ceph'>\n" +
+ "<secret uuid='28909c4f-314e-4db7-a6b3-5eccd9dcf973'/>\n" +
+ "</auth>\n" +
+ "</source>\n" +
+ "</pool>\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 0000000000..90dfae019a
--- /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 = "<pool type='netfs'>\n" +
+ " <name>feff06b5-84b2-3258-b5f9-1953217295de</name>\n" +
+ " <uuid>feff06b5-84b2-3258-b5f9-1953217295de</uuid>\n" +
+ " <capacity unit='bytes'>111111111</capacity>\n" +
+ " <allocation unit='bytes'>2222222</allocation>\n" +
+ " <available unit='bytes'>3333333</available>\n" +
+ " <source>\n" +
+ " <host name='10.11.12.13'/>\n" +
+ " <dir path='/mnt/primary1'/>\n" +
+ " <format type='auto'/>\n" +
+ " </source>\n" +
+ " <target>\n" +
+ " <path>/mnt/feff06b5-84b2-3258-b5f9-1953217295de</path>\n"
+
+ " <permissions>\n" +
+ " <mode>0755</mode>\n" +
+ " <owner>0</owner>\n" +
+ " <group>0</group>\n" +
+ " </permissions>\n" +
+ " </target>\n" +
+ "</pool>";
+
+ LibvirtStoragePoolXMLParser parser = new LibvirtStoragePoolXMLParser();
+ LibvirtStoragePoolDef pool = parser.parseStoragePoolXML(poolXML);
+
+ Assert.assertEquals("10.11.12.13", pool.getSourceHost());
+ }
+
+ public void testParseRbdStoragePoolXMLWithMultipleHosts() {
+ String poolXML = "<pool type='rbd'>\n" +
+ " <name>feff06b5-84b2-3258-b5f9-1953217295de</name>\n" +
+ " <uuid>feff06b5-84b2-3258-b5f9-1953217295de</uuid>\n" +
+ " <source>\n" +
+ " <name>rbdpool</name>\n" +
+ " <host name='10.11.12.13' port='6789'/>\n" +
+ " <host name='10.11.12.14' port='6789'/>\n" +
+ " <host name='10.11.12.15' port='6789'/>\n" +
+ " <format type='auto'/>\n" +
+ " <auth username='admin' type='ceph'>\n" +
+ " <secret
uuid='262f743a-3726-11ed-aaee-93e90b39d5c4'/>\n" +
+ " </auth>\n" +
+ " </source>\n" +
+ "</pool>";
+
+ 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 = "<pool type='rbd'>\n" +
+ " <name>feff06b5-84b2-3258-b5f9-1953217295de</name>\n" +
+ " <uuid>feff06b5-84b2-3258-b5f9-1953217295de</uuid>\n" +
+ " <source>\n" +
+ " <name>rbdpool</name>\n" +
+ " <host name='[fc00:aa:bb:cc::1]' port='6789'/>\n" +
+ " <host name='[fc00:aa:bb:cc::2]' port='6789'/>\n" +
+ " <host name='[fc00:aa:bb:cc::3]' port='6789'/>\n" +
+ " <format type='auto'/>\n" +
+ " <auth username='admin' type='ceph'>\n" +
+ " <secret
uuid='262f743a-3726-11ed-aaee-93e90b39d5c4'/>\n" +
+ " </auth>\n" +
+ " </source>\n" +
+ "</pool>";
+
+ 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 4eb464e4a6..9da1f5ea75 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 class LibvirtVMDefTest extends TestCase {
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 = "<disk device='disk' type='network'>\n" +
+ "<driver name='qemu' type='raw' cache='none' />\n" +
+ "<source protocol='rbd' name='/mnt/primary1'>\n" +
+ "<host name='10.11.12.13' port='3300'/>\n" +
+ "<host name='10.11.12.14' port='3300'/>\n" +
+ "<host name='10.11.12.15' port='3300'/>\n" +
+ "</source>\n" +
+ "<auth username='admin'>\n" +
+ "<secret type='ceph'
uuid='40b3f216-36b5-11ed-9357-9b4e21b0ed91'/>\n" +
+ "</auth>\n" +
+ "<target dev='vdc' bus='virtio'/>\n" +
+ "</disk>\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 = "<disk device='disk' type='network'>\n" +
+ "<driver name='qemu' type='raw' cache='none' />\n" +
+ "<source protocol='rbd' name='/mnt/primary1'>\n" +
+ "<host name='fc00:1234::1' port='3300'/>\n" +
+ "<host name='fc00:1234::2' port='3300'/>\n" +
+ "<host name='fc00:1234::3' port='3300'/>\n" +
+ "</source>\n" +
+ "<auth username='admin'>\n" +
+ "<secret type='ceph'
uuid='40b3f216-36b5-11ed-9357-9b4e21b0ed91'/>\n" +
+ "</auth>\n" +
+ "<target dev='vdc' bus='virtio'/>\n" +
+ "</disk>\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 cf39dceb1a..684a0e9daf 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 class KVMPhysicalDiskTest extends TestCase {
"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/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java
b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java
index f39b17076e..213e562055 100644
---
a/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java
+++
b/plugins/storage/volume/default/src/main/java/org/apache/cloudstack/storage/datastore/lifecycle/CloudStackPrimaryDataStoreLifeCycleImpl.java
@@ -138,38 +138,35 @@ public class CloudStackPrimaryDataStoreLifeCycleImpl
implements PrimaryDataStore
PrimaryDataStoreParameters parameters = new
PrimaryDataStoreParameters();
- URI uri = null;
+ UriUtils.UriInfo uriInfo = UriUtils.getUriInfo(url);
+
+ String scheme = uriInfo.getScheme();
+ String storageHost = uriInfo.getStorageHost();
+ String storagePath = uriInfo.getStoragePath();
try {
- uri = new URI(UriUtils.encodeURIComponent(url));
- if (uri.getScheme() == null) {
+ if (scheme == null) {
throw new InvalidParameterValueException("scheme is null " +
url + ", add nfs:// (or cifs://) as a prefix");
- } else if (uri.getScheme().equalsIgnoreCase("nfs")) {
- String uriHost = uri.getHost();
- String uriPath = uri.getPath();
- if (uriHost == null || uriPath == null ||
uriHost.trim().isEmpty() || uriPath.trim().isEmpty()) {
+ } else if (scheme.equalsIgnoreCase("nfs")) {
+ if (storageHost == null || storagePath == null ||
storageHost.trim().isEmpty() || storagePath.trim().isEmpty()) {
throw new InvalidParameterValueException("host or path is
null, should be nfs://hostname/path");
}
- } else if (uri.getScheme().equalsIgnoreCase("cifs")) {
+ } else if (scheme.equalsIgnoreCase("cifs")) {
// Don't validate against a URI encoded URI.
URI cifsUri = new URI(url);
String warnMsg =
UriUtils.getCifsUriParametersProblems(cifsUri);
if (warnMsg != null) {
throw new InvalidParameterValueException(warnMsg);
}
- } else if (uri.getScheme().equalsIgnoreCase("sharedMountPoint")) {
- String uriPath = uri.getPath();
- if (uriPath == null) {
+ } else if (scheme.equalsIgnoreCase("sharedMountPoint")) {
+ if (storagePath == null) {
throw new InvalidParameterValueException("host or path is
null, should be sharedmountpoint://localhost/path");
}
- } else if (uri.getScheme().equalsIgnoreCase("rbd")) {
- String uriPath = uri.getPath();
- if (uriPath == null) {
+ } else if (scheme.equalsIgnoreCase("rbd")) {
+ if (storagePath == null) {
throw new InvalidParameterValueException("host or path is
null, should be rbd://hostname/pool");
}
- } else if (uri.getScheme().equalsIgnoreCase("gluster")) {
- String uriHost = uri.getHost();
- String uriPath = uri.getPath();
- if (uriHost == null || uriPath == null ||
uriHost.trim().isEmpty() || uriPath.trim().isEmpty()) {
+ } else if (scheme.equalsIgnoreCase("gluster")) {
+ if (storageHost == null || storagePath == null ||
storageHost.trim().isEmpty() || storagePath.trim().isEmpty()) {
throw new InvalidParameterValueException("host or path is
null, should be gluster://hostname/volume");
}
}
@@ -183,24 +180,22 @@ public class CloudStackPrimaryDataStoreLifeCycleImpl
implements PrimaryDataStore
parameters.setTags(tags);
parameters.setDetails(details);
- String scheme = uri.getScheme();
- String storageHost = uri.getHost();
String hostPath = null;
try {
- hostPath = URLDecoder.decode(uri.getPath(), "UTF-8");
+ hostPath = URLDecoder.decode(storagePath, "UTF-8");
} catch (UnsupportedEncodingException e) {
s_logger.error("[ignored] we are on a platform not supporting
\"UTF-8\"!?!", e);
}
if (hostPath == null) { // if decoding fails, use getPath() anyway
- hostPath = uri.getPath();
+ hostPath = storagePath;
}
Object localStorage = dsInfos.get("localStorage");
if (localStorage != null) {
hostPath = hostPath.replaceFirst("/", "");
hostPath = hostPath.replace("+", " ");
}
- String userInfo = uri.getUserInfo();
- int port = uri.getPort();
+ String userInfo = uriInfo.getUserInfo();
+ int port = uriInfo.getPort();
if (s_logger.isDebugEnabled()) {
s_logger.debug("createPool Params @ scheme - " + scheme + "
storageHost - " + storageHost + " hostPath - " + hostPath + " port - " + port);
}
@@ -317,8 +312,8 @@ public class CloudStackPrimaryDataStoreLifeCycleImpl
implements PrimaryDataStore
parameters.setPort(0);
parameters.setPath(hostPath);
} else {
- s_logger.warn("Unable to figure out the scheme for URI: " +
uri);
- throw new IllegalArgumentException("Unable to figure out the
scheme for URI: " + uri);
+ s_logger.warn("Unable to figure out the scheme for URI: " +
uriInfo);
+ throw new IllegalArgumentException("Unable to figure out the
scheme for URI: " + uriInfo);
}
}
@@ -326,7 +321,7 @@ public class CloudStackPrimaryDataStoreLifeCycleImpl
implements PrimaryDataStore
List<StoragePoolVO> pools =
primaryDataStoreDao.listPoolByHostPath(storageHost, hostPath);
if (!pools.isEmpty() &&
!scheme.equalsIgnoreCase("sharedmountpoint")) {
Long oldPodId = pools.get(0).getPodId();
- throw new CloudRuntimeException("Storage pool " + uri + "
already in use by another pod (id=" + oldPodId + ")");
+ throw new CloudRuntimeException("Storage pool " + uriInfo + "
already in use by another pod (id=" + oldPodId + ")");
}
}
diff --git a/ui/public/locales/en.json b/ui/public/locales/en.json
index dce9040c33..babd453baa 100644
--- a/ui/public/locales/en.json
+++ b/ui/public/locales/en.json
@@ -1363,6 +1363,7 @@
"label.quota.value": "Quota value",
"label.quota_enforce": "Enforce Quota",
"label.rados.monitor": "RADOS monitor",
+"label.rados.monitor.description": "The RADOS monitor(s). If there are
multiple monitors, they are separated by comma. For example,
\"192.168.0.1,192.168.0.2,192.168.0.3\", \"mon1, mon2, mon3\". IPv6 addresses
must include square brackets, for example,
\"[fc00:1234::1],[fc00:1234::2],[fc00:1234::3]\".",
"label.rados.pool": "RADOS pool",
"label.rados.secret": "RADOS secret",
"label.rados.user": "RADOS user",
diff --git a/ui/src/views/infra/AddPrimaryStorage.vue
b/ui/src/views/infra/AddPrimaryStorage.vue
index 9e628dd287..b6f7922d79 100644
--- a/ui/src/views/infra/AddPrimaryStorage.vue
+++ b/ui/src/views/infra/AddPrimaryStorage.vue
@@ -284,7 +284,10 @@
</a-form-item>
</div>
<div v-if="form.protocol === 'RBD'">
- <a-form-item name="radosmonitor" ref="radosmonitor"
:label="$t('label.rados.monitor')">
+ <a-form-item name="radosmonitor" ref="radosmonitor">
+ <template #label>
+ <tooltip-label :title="$t('label.rados.monitor')"
:tooltip="$t('label.rados.monitor.description')"/>
+ </template>
<a-input v-model:value="form.radosmonitor"
:placeholder="$t('label.rados.monitor')" />
</a-form-item>
<a-form-item name="radospool" ref="radospool"
:label="$t('label.rados.pool')">
diff --git a/utils/src/main/java/com/cloud/utils/UriUtils.java
b/utils/src/main/java/com/cloud/utils/UriUtils.java
index fd7adf38d5..0dbf8d3f35 100644
--- a/utils/src/main/java/com/cloud/utils/UriUtils.java
+++ b/utils/src/main/java/com/cloud/utils/UriUtils.java
@@ -639,4 +639,88 @@ public class UriUtils {
expandedVlans.add(Integer.parseInt(parts[1]));
return expandedVlans;
}
+
+ public static class UriInfo {
+ String scheme;
+ String storageHost;
+ String storagePath;
+ String userInfo;
+ int port = -1;
+
+ public UriInfo() {
+ }
+
+ public UriInfo(String scheme, String storageHost, String storagePath,
String userInfo, int port) {
+ this.scheme = scheme;
+ this.storageHost = storageHost;
+ this.storagePath = storagePath;
+ this.userInfo = userInfo;
+ this.port = port;
+ }
+
+ public String getScheme() {
+ return scheme;
+ }
+
+ public String getStorageHost() {
+ return storageHost;
+ }
+
+ public String getStoragePath() {
+ return storagePath;
+ }
+
+ public String getUserInfo() {
+ return userInfo;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("%s://%s%s%s%s", scheme,
+ userInfo == null ? "" : userInfo + "@",
+ storageHost,
+ port == -1 ? "" : ":" + port,
+ storagePath == null ? "" : storagePath);
+ }
+ }
+
+ public static UriInfo getUriInfo(String url) {
+ try {
+ if (url == null) {
+ return new UriInfo();
+ }
+ if (url.startsWith("rbd://")) {
+ return getRbdUrlInfo(url);
+ }
+ URI uri = new URI(UriUtils.encodeURIComponent(url));
+ return new UriInfo(uri.getScheme(), uri.getHost(), uri.getPath(),
uri.getUserInfo(), uri.getPort());
+ } catch (URISyntaxException e) {
+ throw new CloudRuntimeException(url + " is not a valid uri");
+ }
+ }
+
+ private static UriInfo getRbdUrlInfo(String url) {
+ int secondSlash = StringUtils.ordinalIndexOf(url, "/", 2);
+ int thirdSlash = StringUtils.ordinalIndexOf(url, "/", 3);
+ int firstAt = StringUtils.indexOf(url, "@");
+ int lastColon = StringUtils.lastIndexOf(url,":");
+ int lastSquareBracket = StringUtils.lastIndexOf(url,"]");
+ int startOfHost = Math.max(secondSlash, firstAt) + 1;
+ int endOfHost = lastColon < startOfHost ? (thirdSlash > 0 ? thirdSlash
: url.length() + 1) :
+ (lastSquareBracket > lastColon ? lastSquareBracket + 1 :
lastColon);
+ String storageHosts = StringUtils.substring(url, startOfHost,
endOfHost);
+ String firstHost = storageHosts.split(",")[0];
+ String strBeforeHosts = StringUtils.substring(url, 0, startOfHost);
+ String strAfterHosts = StringUtils.substring(url, endOfHost);
+ try {
+ URI uri = new URI(UriUtils.encodeURIComponent(strBeforeHosts +
firstHost + strAfterHosts));
+ return new UriInfo(uri.getScheme(), storageHosts, uri.getPath(),
uri.getUserInfo(), uri.getPort());
+ } catch (URISyntaxException e) {
+ throw new CloudRuntimeException(url + " is not a valid uri for
RBD");
+ }
+ }
}
diff --git a/utils/src/test/java/com/cloud/utils/UriUtilsTest.java
b/utils/src/test/java/com/cloud/utils/UriUtilsTest.java
index b8d951db34..db02e607d6 100644
--- a/utils/src/test/java/com/cloud/utils/UriUtilsTest.java
+++ b/utils/src/test/java/com/cloud/utils/UriUtilsTest.java
@@ -19,7 +19,7 @@
package com.cloud.utils;
-import junit.framework.Assert;
+import org.junit.Assert;
import org.junit.Test;
import java.util.Arrays;
@@ -101,4 +101,156 @@ public class UriUtilsTest {
Assert.assertFalse(UriUtils.checkVlanUriOverlap("10,22,111", "12"));
Assert.assertFalse(UriUtils.checkVlanUriOverlap("100-200",
"30-40,50,201-250"));
}
+
+ private void testGetUriInfoInternal(String url, String host) {
+ UriUtils.UriInfo uriInfo = UriUtils.getUriInfo(url);
+
+ Assert.assertEquals(host, uriInfo.getStorageHost());
+ Assert.assertEquals(url, uriInfo.toString());
+ }
+
+ @Test
+ public void testGetRbdUriInfo() {
+ String host = "10.11.12.13";
+
+ String url0 =
String.format("rbd://user:password@%s:3300/pool/volume2", host);
+ String url1 = String.format("rbd://user:password@%s:3300/pool", host);
+ String url2 = String.format("rbd://user:password@%s/pool", host);
+ String url3 = String.format("rbd://%s:3300/pool", host);
+ String url4 = String.format("rbd://%s/pool", host);
+ String url5 = String.format("rbd://user:password@%s", host);
+ String url6 = String.format("rbd://%s:3300", host);
+ String url7 = String.format("rbd://%s", host);
+ String url8 = String.format("rbd://user@%s", host);
+
+ testGetUriInfoInternal(url0, host);
+ testGetUriInfoInternal(url1, host);
+ testGetUriInfoInternal(url2, host);
+ testGetUriInfoInternal(url3, host);
+ testGetUriInfoInternal(url4, host);
+ testGetUriInfoInternal(url5, host);
+ testGetUriInfoInternal(url6, host);
+ testGetUriInfoInternal(url7, host);
+ testGetUriInfoInternal(url8, host);
+ }
+
+ @Test
+ public void testGetRbdUriInfoSingleIpv6() {
+ String host = "[fc00:aa:bb:cc::1]";
+
+ String url0 =
String.format("rbd://user:password@%s:3300/pool/volume2", host);
+ String url1 = String.format("rbd://user:password@%s:3300/pool", host);
+ String url2 = String.format("rbd://user:password@%s/pool", host);
+ String url3 = String.format("rbd://%s:3300/pool", host);
+ String url4 = String.format("rbd://%s/pool", host);
+ String url5 = String.format("rbd://user:password@%s", host);
+ String url6 = String.format("rbd://%s:3300", host);
+ String url7 = String.format("rbd://%s", host);
+ String url8 = String.format("rbd://user@%s", host);
+
+ testGetUriInfoInternal(url0, host);
+ testGetUriInfoInternal(url1, host);
+ testGetUriInfoInternal(url2, host);
+ testGetUriInfoInternal(url3, host);
+ testGetUriInfoInternal(url4, host);
+ testGetUriInfoInternal(url5, host);
+ testGetUriInfoInternal(url6, host);
+ testGetUriInfoInternal(url7, host);
+ testGetUriInfoInternal(url8, host);
+ }
+
+ @Test
+ public void testGetRbdUriInfoMultipleIpv6() {
+ String host1 = "[fc00:aa:bb:cc::1]";
+ String host2 = "[fc00:aa:bb:cc::2]";
+ String host3 = "[fc00:aa:bb:cc::3]";
+
+ String url0 =
String.format("rbd://user:password@%s,%s,%s:3300/pool/volume2", host1, host2,
host3);
+ String url1 = String.format("rbd://user:password@%s,%s,%s:3300/pool",
host1, host2, host3);
+ String url2 = String.format("rbd://user:password@%s,%s,%s/pool",
host1, host2, host3);
+ String url3 = String.format("rbd://%s,%s,%s:3300/pool", host1, host2,
host3);
+ String url4 = String.format("rbd://%s,%s,%s/pool", host1, host2,
host3);
+ String url5 = String.format("rbd://user:password@%s,%s,%s", host1,
host2, host3);
+ String url6 = String.format("rbd://%s,%s,%s:3300", host1, host2,
host3);
+ String url7 = String.format("rbd://%s,%s,%s", host1, host2, host3);
+ String url8 = String.format("rbd://user@%s,%s,%s", host1, host2,
host3);
+
+ String host = String.format("%s,%s,%s", host1, host2, host3);
+
+ testGetUriInfoInternal(url0, host);
+ testGetUriInfoInternal(url1, host);
+ testGetUriInfoInternal(url2, host);
+ testGetUriInfoInternal(url3, host);
+ testGetUriInfoInternal(url4, host);
+ testGetUriInfoInternal(url5, host);
+ testGetUriInfoInternal(url6, host);
+ testGetUriInfoInternal(url7, host);
+ testGetUriInfoInternal(url8, host);
+ }
+
+ @Test
+ public void testGetUriInfo() {
+ String host = "10.11.12.13";
+
+ String url0 =
String.format("nfs://user:password@%s:3300/pool/volume2", host);
+ String url1 = String.format("cifs://user:password@%s:3300/pool", host);
+ String url2 = String.format("file://user:password@%s/pool", host);
+ String url3 = String.format("sharedMountPoint://%s:3300/pool", host);
+ String url4 = String.format("clvm://%s/pool", host);
+ String url5 = String.format("PreSetup://user@%s", host);
+ String url6 = String.format("DatastoreCluster://%s:3300", host);
+ String url7 = String.format("iscsi://%s", host);
+ String url8 = String.format("iso://user@%s:3300/pool/volume2", host);
+ String url9 = String.format("vmfs://user@%s:3300/pool", host);
+ String url10 = String.format("ocfs2://user@%s/pool", host);
+ String url11 = String.format("gluster://%s:3300/pool", host);
+ String url12 =
String.format("rbd://user:password@%s:3300/pool/volume2", host);
+
+ testGetUriInfoInternal(url0, host);
+ testGetUriInfoInternal(url1, host);
+ testGetUriInfoInternal(url2, host);
+ testGetUriInfoInternal(url3, host);
+ testGetUriInfoInternal(url4, host);
+ testGetUriInfoInternal(url5, host);
+ testGetUriInfoInternal(url6, host);
+ testGetUriInfoInternal(url7, host);
+ testGetUriInfoInternal(url8, host);
+ testGetUriInfoInternal(url9, host);
+ testGetUriInfoInternal(url10, host);
+ testGetUriInfoInternal(url11, host);
+ testGetUriInfoInternal(url12, host);
+ }
+
+ @Test
+ public void testGetUriInfoIpv6() {
+ String host = "[fc00:aa:bb:cc::1]";
+
+ String url0 =
String.format("nfs://user:password@%s:3300/pool/volume2", host);
+ String url1 = String.format("cifs://user:password@%s:3300/pool", host);
+ String url2 = String.format("file://user:password@%s/pool", host);
+ String url3 = String.format("sharedMountPoint://%s:3300/pool", host);
+ String url4 = String.format("clvm://%s/pool", host);
+ String url5 = String.format("PreSetup://user@%s", host);
+ String url6 = String.format("DatastoreCluster://%s:3300", host);
+ String url7 = String.format("iscsi://%s", host);
+ String url8 = String.format("iso://user@%s:3300/pool/volume2", host);
+ String url9 = String.format("vmfs://user@%s:3300/pool", host);
+ String url10 = String.format("ocfs2://user@%s/pool", host);
+ String url11 = String.format("gluster://%s:3300/pool", host);
+ String url12 =
String.format("rbd://user:password@%s:3300/pool/volume2", host);
+
+ testGetUriInfoInternal(url0, host);
+ testGetUriInfoInternal(url1, host);
+ testGetUriInfoInternal(url2, host);
+ testGetUriInfoInternal(url3, host);
+ testGetUriInfoInternal(url4, host);
+ testGetUriInfoInternal(url5, host);
+ testGetUriInfoInternal(url6, host);
+ testGetUriInfoInternal(url7, host);
+ testGetUriInfoInternal(url8, host);
+ testGetUriInfoInternal(url9, host);
+ testGetUriInfoInternal(url10, host);
+ testGetUriInfoInternal(url11, host);
+ testGetUriInfoInternal(url12, host);
+ }
}