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 606115e6de0..da9b2233c8d 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 1bdf2db8c4f..f0ec29f41f6 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("\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 7f0bf8c0bc8..d19c851d7dc 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 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 a6fec3b60c4..2c1e362fc63 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("\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 7de6230f334..c9abf399530 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 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 a1a43447e1c..f5f3cc994e9 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 = "\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 00000000000..90dfae019af --- /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 4eb464e4a68..9da1f5ea75b 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 = "\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 cf39dceb1a5..684a0e9daff 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 f39b17076e7..213e5620553 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 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 dce9040c33e..babd453baa1 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 9e628dd287c..b6f7922d797 100644 --- a/ui/src/views/infra/AddPrimaryStorage.vue +++ b/ui/src/views/infra/AddPrimaryStorage.vue @@ -284,7 +284,10 @@
- + + diff --git a/utils/src/main/java/com/cloud/utils/UriUtils.java b/utils/src/main/java/com/cloud/utils/UriUtils.java index fd7adf38d54..0dbf8d3f35c 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 b8d951db340..db02e607d63 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); + } }