diff --git a/agent/src/com/cloud/agent/resource/computing/LibvirtComputingResource.java b/agent/src/com/cloud/agent/resource/computing/LibvirtComputingResource.java index 112ab61e50b..347ff87ac9d 100755 --- a/agent/src/com/cloud/agent/resource/computing/LibvirtComputingResource.java +++ b/agent/src/com/cloud/agent/resource/computing/LibvirtComputingResource.java @@ -30,12 +30,15 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; +import java.text.DateFormat; import java.text.MessageFormat; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -154,6 +157,7 @@ import com.cloud.agent.resource.computing.KVMHABase.NfsStoragePool; import com.cloud.agent.resource.computing.LibvirtVMDef.ConsoleDef; import com.cloud.agent.resource.computing.LibvirtVMDef.DevicesDef; import com.cloud.agent.resource.computing.LibvirtVMDef.DiskDef; +import com.cloud.agent.resource.computing.LibvirtVMDef.DiskDef.diskProtocol; import com.cloud.agent.resource.computing.LibvirtVMDef.FeaturesDef; import com.cloud.agent.resource.computing.LibvirtVMDef.GraphicDef; import com.cloud.agent.resource.computing.LibvirtVMDef.GuestDef; @@ -1298,6 +1302,13 @@ public class LibvirtComputingResource extends ServerResourceBase implements KVMStoragePool primaryPool = _storagePoolMgr.getStoragePool(cmd .getPool().getUuid()); + + if (primaryPool.getType() == StoragePoolType.RBD) { + s_logger.debug("Snapshots are not supported on RBD volumes"); + return new ManageSnapshotAnswer(cmd, false, + "Snapshots are not supported on RBD volumes"); + } + KVMPhysicalDisk disk = primaryPool.getPhysicalDisk(cmd .getVolumePath()); if (state == DomainInfo.DomainState.VIR_DOMAIN_RUNNING @@ -1644,16 +1655,43 @@ public class LibvirtComputingResource extends ServerResourceBase implements + templateInstallFolder; _storage.mkdirs(tmpltPath); - Script command = new Script(_createTmplPath, _cmdsTimeout, s_logger); - command.add("-f", disk.getPath()); - command.add("-t", tmpltPath); - command.add("-n", cmd.getUniqueName() + ".qcow2"); + if (primary.getType() != StoragePoolType.RBD) { + Script command = new Script(_createTmplPath, _cmdsTimeout, s_logger); + command.add("-f", disk.getPath()); + command.add("-t", tmpltPath); + command.add("-n", cmd.getUniqueName() + ".qcow2"); - String result = command.execute(); + String result = command.execute(); - if (result != null) { - s_logger.debug("failed to create template: " + result); - return new CreatePrivateTemplateAnswer(cmd, false, result); + if (result != null) { + s_logger.debug("failed to create template: " + result); + return new CreatePrivateTemplateAnswer(cmd, false, result); + } + } else { + s_logger.debug("Converting RBD disk " + disk.getPath() + " into template " + cmd.getUniqueName()); + Script.runSimpleBashScript("qemu-img convert" + + " -f raw -O qcow2 " + + KVMPhysicalDisk.RBDStringBuilder(primary.getSourceHost(), + primary.getSourcePort(), + primary.getAuthUserName(), + primary.getAuthSecret(), + disk.getPath()) + + " " + tmpltPath + "/" + cmd.getUniqueName() + ".qcow2"); + File templateProp = new File(tmpltPath + "/template.properties"); + if (!templateProp.exists()) { + templateProp.createNewFile(); + } + + String templateContent = "filename=" + cmd.getUniqueName() + ".qcow2" + System.getProperty("line.separator"); + + DateFormat dateFormat = new SimpleDateFormat("MM_dd_yyyy"); + Date date = new Date(); + templateContent += "snapshot.name=" + dateFormat.format(date) + System.getProperty("line.separator"); + + FileOutputStream templFo = new FileOutputStream(templateProp); + templFo.write(templateContent.getBytes()); + templFo.flush(); + templFo.close(); } Map params = new HashMap(); @@ -1756,8 +1794,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements protected Answer execute(ModifyStoragePoolCommand cmd) { KVMStoragePool storagepool = _storagePoolMgr.createStoragePool(cmd - .getPool().getUuid(), cmd.getPool().getHost(), cmd.getPool() - .getPath(), cmd.getPool().getType()); + .getPool().getUuid(), cmd.getPool().getHost(), cmd.getPool().getPort(), + cmd.getPool().getPath(), cmd.getPool().getUserInfo(), cmd.getPool().getType()); if (storagepool == null) { return new Answer(cmd, false, " Failed to create storage pool"); } @@ -2624,10 +2662,19 @@ public class LibvirtComputingResource extends ServerResourceBase implements } else { int devId = (int) volume.getDeviceId(); - if (volume.getType() == Volume.Type.DATADISK) { - disk.defFileBasedDisk(physicalDisk.getPath(), devId, - DiskDef.diskBus.VIRTIO, - DiskDef.diskFmtType.QCOW2); + if (pool.getType() == StoragePoolType.RBD) { + /* + For RBD pools we use the secret mechanism in libvirt. + We store the secret under the UUID of the pool, that's why + we pass the pool's UUID as the authSecret + */ + disk.defNetworkBasedDisk(physicalDisk.getPath().replace("rbd:", ""), pool.getSourceHost(), pool.getSourcePort(), + pool.getAuthUserName(), pool.getUuid(), + devId, diskBusType, diskProtocol.RBD); + } else if (volume.getType() == Volume.Type.DATADISK) { + disk.defFileBasedDisk(physicalDisk.getPath(), devId, + DiskDef.diskBus.VIRTIO, + DiskDef.diskFmtType.QCOW2); } else { disk.defFileBasedDisk(physicalDisk.getPath(), devId, diskBusType, DiskDef.diskFmtType.QCOW2); @@ -2982,8 +3029,8 @@ public class LibvirtComputingResource extends ServerResourceBase implements try { KVMStoragePool localStoragePool = _storagePoolMgr - .createStoragePool(_localStorageUUID, "localhost", - _localStoragePath, StoragePoolType.Filesystem); + .createStoragePool(_localStorageUUID, "localhost", -1, + _localStoragePath, "", StoragePoolType.Filesystem); com.cloud.agent.api.StoragePoolInfo pi = new com.cloud.agent.api.StoragePoolInfo( localStoragePool.getUuid(), cmd.getPrivateIpAddress(), _localStoragePath, _localStoragePath, @@ -4083,5 +4130,4 @@ public class LibvirtComputingResource extends ServerResourceBase implements return new Answer(cmd, success, ""); } - } diff --git a/agent/src/com/cloud/agent/resource/computing/LibvirtSecretDef.java b/agent/src/com/cloud/agent/resource/computing/LibvirtSecretDef.java new file mode 100644 index 00000000000..f7e10c38ddf --- /dev/null +++ b/agent/src/com/cloud/agent/resource/computing/LibvirtSecretDef.java @@ -0,0 +1,106 @@ +// 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.agent.resource.computing; + +public class LibvirtSecretDef { + + public enum usage { + VOLUME("volume"), CEPH("ceph"); + String _usage; + + usage(String usage) { + _usage = usage; + } + + @Override + public String toString() { + return _usage; + } + } + + private usage _usage; + private boolean _ephemeral; + private boolean _private; + private String _uuid; + private String _description; + private String _cephName; + private String _volumeVolume; + + public LibvirtSecretDef (usage usage, String uuid) { + _usage = usage; + _uuid = uuid; + } + + public LibvirtSecretDef (usage usage, String uuid, String description) { + _usage = usage; + _uuid = uuid; + _description = description; + } + + public boolean getEphemeral() { + return _ephemeral; + } + + public boolean getPrivate() { + return _private; + } + + public String getUuid() { + return _uuid; + } + + public String getDescription() { + return _description; + } + + public String getVolumeVolume() { + return _volumeVolume; + } + + public String getCephName() { + return _cephName; + } + + public void setVolumeVolume(String volume) { + _volumeVolume = volume; + } + + public void setCephName(String name) { + _cephName = name; + } + + @Override + public String toString() { + StringBuilder secretBuilder = new StringBuilder(); + secretBuilder.append("\n"); + secretBuilder.append("" + _uuid + "\n"); + if (_description != null) { + secretBuilder.append("" + _description + "\n"); + } + secretBuilder.append("\n"); + if (_usage == _usage.VOLUME) { + secretBuilder.append("" + _volumeVolume + "\n"); + } + if (_usage == _usage.CEPH) { + secretBuilder.append("" + _cephName + "\n"); + } + secretBuilder.append("\n"); + secretBuilder.append("\n"); + return secretBuilder.toString(); + } + +} diff --git a/agent/src/com/cloud/agent/resource/computing/LibvirtStoragePoolDef.java b/agent/src/com/cloud/agent/resource/computing/LibvirtStoragePoolDef.java index 582cd2e2e6d..9c285284563 100644 --- a/agent/src/com/cloud/agent/resource/computing/LibvirtStoragePoolDef.java +++ b/agent/src/com/cloud/agent/resource/computing/LibvirtStoragePoolDef.java @@ -18,7 +18,7 @@ package com.cloud.agent.resource.computing; public class LibvirtStoragePoolDef { public enum poolType { - ISCSI("iscsi"), NETFS("netfs"), LOGICAL("logical"), DIR("dir"); + ISCSI("iscsi"), NETFS("netfs"), LOGICAL("logical"), DIR("dir"), RBD("rbd"); String _poolType; poolType(String poolType) { @@ -31,12 +31,41 @@ public class LibvirtStoragePoolDef { } } + public enum authType { + CHAP("chap"), CEPH("ceph"); + String _authType; + + authType(String authType) { + _authType = authType; + } + + @Override + public String toString() { + return _authType; + } + } + private poolType _poolType; private String _poolName; private String _uuid; private String _sourceHost; + private int _sourcePort; private String _sourceDir; private String _targetPath; + private String _authUsername; + private authType _authType; + private String _secretUuid; + + public LibvirtStoragePoolDef(poolType type, String poolName, String uuid, + String host, int port, String dir, String targetPath) { + _poolType = type; + _poolName = poolName; + _uuid = uuid; + _sourceHost = host; + _sourcePort = port; + _sourceDir = dir; + _targetPath = targetPath; + } public LibvirtStoragePoolDef(poolType type, String poolName, String uuid, String host, String dir, String targetPath) { @@ -48,6 +77,20 @@ public class LibvirtStoragePoolDef { _targetPath = targetPath; } + public LibvirtStoragePoolDef(poolType type, String poolName, String uuid, + String sourceHost, int sourcePort, String dir, String authUsername, + authType authType, String secretUuid) { + _poolType = type; + _poolName = poolName; + _uuid = uuid; + _sourceHost = sourceHost; + _sourcePort = sourcePort; + _sourceDir = dir; + _authUsername = authUsername; + _authType = authType; + _secretUuid = secretUuid; + } + public String getPoolName() { return _poolName; } @@ -60,6 +103,10 @@ public class LibvirtStoragePoolDef { return _sourceHost; } + public int getSourcePort() { + return _sourcePort; + } + public String getSourceDir() { return _sourceDir; } @@ -68,6 +115,18 @@ public class LibvirtStoragePoolDef { return _targetPath; } + public String getAuthUserName() { + return _authUsername; + } + + public String getSecretUUID() { + return _secretUuid; + } + + public authType getAuthType() { + return _authType; + } + @Override public String toString() { StringBuilder storagePoolBuilder = new StringBuilder(); @@ -81,9 +140,22 @@ public class LibvirtStoragePoolDef { storagePoolBuilder.append("\n"); storagePoolBuilder.append("\n"); } - storagePoolBuilder.append("\n"); - storagePoolBuilder.append("" + _targetPath + "\n"); - storagePoolBuilder.append("\n"); + if (_poolType == poolType.RBD) { + storagePoolBuilder.append("\n"); + storagePoolBuilder.append("\n"); + storagePoolBuilder.append("" + _sourceDir + "\n"); + if (_authUsername != null) { + storagePoolBuilder.append("\n"); + storagePoolBuilder.append("\n"); + storagePoolBuilder.append("\n"); + } + storagePoolBuilder.append("\n"); + } + if (_poolType != poolType.RBD) { + storagePoolBuilder.append("\n"); + storagePoolBuilder.append("" + _targetPath + "\n"); + storagePoolBuilder.append("\n"); + } storagePoolBuilder.append("\n"); return storagePoolBuilder.toString(); } diff --git a/agent/src/com/cloud/agent/resource/computing/LibvirtStoragePoolXMLParser.java b/agent/src/com/cloud/agent/resource/computing/LibvirtStoragePoolXMLParser.java index 5c45d76e82d..cff4c2b74aa 100644 --- a/agent/src/com/cloud/agent/resource/computing/LibvirtStoragePoolXMLParser.java +++ b/agent/src/com/cloud/agent/resource/computing/LibvirtStoragePoolXMLParser.java @@ -51,15 +51,34 @@ public class LibvirtStoragePoolXMLParser { Element source = (Element) rootElement.getElementsByTagName( "source").item(0); String host = getAttrValue("host", "name", source); - String path = getAttrValue("dir", "path", source); - Element target = (Element) rootElement.getElementsByTagName( - "target").item(0); - String targetPath = getTagValue("path", target); + if (type.equalsIgnoreCase("rbd")) { + int port = Integer.parseInt(getAttrValue("host", "port", source)); + String pool = getTagValue("name", source); - return new LibvirtStoragePoolDef( - LibvirtStoragePoolDef.poolType.valueOf(type.toUpperCase()), - poolName, uuid, host, path, targetPath); + Element auth = (Element) source.getElementsByTagName( + "auth").item(0); + + if (auth != null) { + String authUsername = auth.getAttribute("username"); + String authType = auth.getAttribute("type"); + return new LibvirtStoragePoolDef(LibvirtStoragePoolDef.poolType.valueOf(type.toUpperCase()), + poolName, uuid, host, port, pool, authUsername, LibvirtStoragePoolDef.authType.valueOf(authType.toUpperCase()), uuid); + } else { + return new LibvirtStoragePoolDef(LibvirtStoragePoolDef.poolType.valueOf(type.toUpperCase()), + poolName, uuid, host, port, pool, ""); + } + } else { + String path = getAttrValue("dir", "path", source); + + Element target = (Element) rootElement.getElementsByTagName( + "target").item(0); + String targetPath = getTagValue("path", target); + + return new LibvirtStoragePoolDef( + LibvirtStoragePoolDef.poolType.valueOf(type.toUpperCase()), + poolName, uuid, host, path, targetPath); + } } catch (ParserConfigurationException e) { s_logger.debug(e.toString()); } catch (SAXException e) { diff --git a/agent/src/com/cloud/agent/resource/computing/LibvirtVMDef.java b/agent/src/com/cloud/agent/resource/computing/LibvirtVMDef.java index 8fd7815bf69..3b07bcd16b7 100644 --- a/agent/src/com/cloud/agent/resource/computing/LibvirtVMDef.java +++ b/agent/src/com/cloud/agent/resource/computing/LibvirtVMDef.java @@ -338,7 +338,7 @@ public class LibvirtVMDef { } enum diskType { - FILE("file"), BLOCK("block"), DIRECTROY("dir"); + FILE("file"), BLOCK("block"), DIRECTROY("dir"), NETWORK("network"); String _diskType; diskType(String type) { @@ -351,6 +351,20 @@ public class LibvirtVMDef { } } + enum diskProtocol { + RBD("rbd"), SHEEPDOG("sheepdog"); + String _diskProtocol; + + diskProtocol(String protocol) { + _diskProtocol = protocol; + } + + @Override + public String toString() { + return _diskProtocol; + } + } + enum diskBus { IDE("ide"), SCSI("scsi"), VIRTIO("virtio"), XEN("xen"), USB("usb"), UML( "uml"), FDC("fdc"); @@ -382,7 +396,12 @@ public class LibvirtVMDef { private deviceType _deviceType; /* floppy, disk, cdrom */ private diskType _diskType; + private diskProtocol _diskProtocol; private String _sourcePath; + private String _sourceHost; + private int _sourcePort; + private String _authUserName; + private String _authSecretUUID; private String _diskLabel; private diskBus _bus; private diskFmtType _diskFmtType; /* qcow2, raw etc. */ @@ -461,6 +480,38 @@ public class LibvirtVMDef { _bus = bus; } + public void defNetworkBasedDisk(String diskName, String sourceHost, int sourcePort, + String authUserName, String authSecretUUID, + int devId, diskBus bus, diskProtocol protocol) { + _diskType = diskType.NETWORK; + _deviceType = deviceType.DISK; + _diskFmtType = diskFmtType.RAW; + _sourcePath = diskName; + _sourceHost = sourceHost; + _sourcePort = sourcePort; + _authUserName = authUserName; + _authSecretUUID = authSecretUUID; + _diskLabel = getDevLabel(devId, bus); + _bus = bus; + _diskProtocol = protocol; + } + + public void defNetworkBasedDisk(String diskName, String sourceHost, int sourcePort, + String authUserName, String authSecretUUID, + String diskLabel, diskBus bus, diskProtocol protocol) { + _diskType = diskType.NETWORK; + _deviceType = deviceType.DISK; + _diskFmtType = diskFmtType.RAW; + _sourcePath = diskName; + _sourceHost = sourceHost; + _sourcePort = sourcePort; + _authUserName = authUserName; + _authSecretUUID = authSecretUUID; + _diskLabel = diskLabel; + _bus = bus; + _diskProtocol = protocol; + } + public void setReadonly() { _readonly = true; } @@ -527,6 +578,18 @@ public class LibvirtVMDef { diskBuilder.append(" dev='" + _sourcePath + "'"); } diskBuilder.append("/>\n"); + } else if (_diskType == diskType.NETWORK) { + diskBuilder.append("\n"); + diskBuilder.append("\n"); + diskBuilder.append("\n"); + if (_authUserName != null) { + diskBuilder.append("\n"); + diskBuilder.append("\n"); + diskBuilder.append("\n"); + } } diskBuilder.append("', 'label.SharedMountPoint': '', 'label.clvm': '', +'label.rbd': '', 'label.volgroup': '', 'label.VMFS.datastore': '', 'label.network.device': '', diff --git a/ui/scripts/sharedFunctions.js b/ui/scripts/sharedFunctions.js index 12d3498350c..c4cf58c86e5 100644 --- a/ui/scripts/sharedFunctions.js +++ b/ui/scripts/sharedFunctions.js @@ -475,6 +475,31 @@ function SharedMountPointURL(server, path) { return url; } +function rbdURL(monitor, pool, id, secret) { + var url; + + /* + Replace the + and / symbols by - and _ to have URL-safe base64 going to the API + It's hacky, but otherwise we'll confuse java.net.URI which splits the incoming URI + */ + secret = str.replace("+", "-"); + secret = str.replace("/", "_"); + + if (id != null && secret != null) { + monitor = id + ":" + secret + "@" + monitor; + } + + if(pool.substring(0,1) != "/") + pool = "/" + pool; + + if(monitor.indexOf("://")==-1) + url = "rbd://" + monitor + pool; + else + url = monitor + pool; + + return url; +} + function clvmURL(vgname) { var url; if(vgname.indexOf("://")==-1) diff --git a/ui/scripts/system.js b/ui/scripts/system.js index 712c122f68a..59d2d8703d9 100644 --- a/ui/scripts/system.js +++ b/ui/scripts/system.js @@ -7494,6 +7494,7 @@ var items = []; items.push({id: "nfs", description: "nfs"}); items.push({id: "SharedMountPoint", description: "SharedMountPoint"}); + items.push({id: "rbd", description: "RBD"}); args.response.success({data: items}); } else if(selectedClusterObj.hypervisortype == "XenServer") { @@ -7677,6 +7678,27 @@ $form.find('.form-item[rel=vCenterDataCenter]').hide(); $form.find('.form-item[rel=vCenterDataStore]').hide(); } + else if(protocol == "rbd") { + $form.find('.form-item[rel=rbdmonitor]').css('display', 'inline-block'); + $form.find('.form-item[rel=rbdmonitor]').find(".name").find("label").text("RADOS Monitor:"); + + $form.find('.form-item[rel=rbdpool]').css('display', 'inline-block'); + $form.find('.form-item[rel=rbdpool]').find(".name").find("label").text("RADOS Pool:"); + + $form.find('.form-item[rel=rbdid]').css('display', 'inline-block'); + $form.find('.form-item[rel=rbdid]').find(".name").find("label").text("RADOS User:"); + + $form.find('.form-item[rel=rbdsecret]').css('display', 'inline-block'); + $form.find('.form-item[rel=rbdsecret]').find(".name").find("label").text("RADOS Secret:"); + + $form.find('.form-item[rel=server]').hide(); + $form.find('.form-item[rel=iqn]').hide(); + $form.find('.form-item[rel=lun]').hide(); + $form.find('.form-item[rel=volumegroup]').hide(); + $form.find('.form-item[rel=path]').hide(); + $form.find('.form-item[rel=vCenterDataCenter]').hide(); + $form.find('.form-item[rel=vCenterDataStore]').hide(); + } else { //$dialogAddPool.find("#add_pool_server_container").show(); $form.find('.form-item[rel=server]').css('display', 'inline-block'); @@ -7744,6 +7766,28 @@ validation: { required: true }, isHidden: true }, + + // RBD + rbdmonitor: { + label: 'label.rbd.monitor', + validation: { required: true }, + isHidden: true + }, + rbdpool: { + label: 'label.rbd.pool', + validation: { required: true }, + isHidden: true + }, + rbdid: { + label: 'label.rbd.id', + validation: { required: false }, + isHidden: true + }, + rbdsecret: { + label: 'label.rbd.secret', + validation: { required: false }, + isHidden: true + }, //always appear (begin) storageTags: { @@ -7803,6 +7847,14 @@ vg = "/" + vg; url = clvmURL(vg); } + else if (args.data.protocol == "rbd") { + var rbdmonitor = args.data.rbdmonitor; + var rbdpool = args.data.rbdpool; + var rbdid = args.data.rbdid; + var rbdsecret = args.data.rbdsecret; + + url = rbdURL(rbdmonitor, rbdpool, rbdid, rbdsecret); + } else if (args.data.protocol == "vmfs") { //var path = trim($thisDialog.find("#add_pool_vmfs_dc").val()); var path = args.data.vCenterDataCenter;