Return-Path: X-Original-To: apmail-cloudstack-commits-archive@www.apache.org Delivered-To: apmail-cloudstack-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id E68AF18B71 for ; Wed, 13 May 2015 09:35:51 +0000 (UTC) Received: (qmail 46914 invoked by uid 500); 13 May 2015 09:35:44 -0000 Delivered-To: apmail-cloudstack-commits-archive@cloudstack.apache.org Received: (qmail 46828 invoked by uid 500); 13 May 2015 09:35:44 -0000 Mailing-List: contact commits-help@cloudstack.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cloudstack.apache.org Delivered-To: mailing list commits@cloudstack.apache.org Received: (qmail 45727 invoked by uid 99); 13 May 2015 09:35:44 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 13 May 2015 09:35:44 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 31450E1801; Wed, 13 May 2015 09:35:44 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: ekho@apache.org To: commits@cloudstack.apache.org Date: Wed, 13 May 2015 09:36:10 -0000 Message-Id: In-Reply-To: <8e3b583d2d214bcc87374b4b443a1bd0@git.apache.org> References: <8e3b583d2d214bcc87374b4b443a1bd0@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [28/41] git commit: updated refs/heads/master to 45c0fa2 Refactoring the LibvirtComputingResource - Adding LibvirtBackupSnapshotCommandWrapper, LibvirtCreatePrivateTemplateFromVolumeCommandWrapper and LibvirtManageSnapshotCommandWrapper - 3 unit tests added - KVM hypervisor plugin with 18.3% coverage Less tests added to those classes because the code is quite complex and way too long. The tests added are just covering the new flow, to make sure it works fine. I will come back to those classes later. Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/bcf78d3b Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/bcf78d3b Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/bcf78d3b Branch: refs/heads/master Commit: bcf78d3b4304800d9a60db72742fb169c470093b Parents: 52d9f0c Author: wilderrodrigues Authored: Tue May 5 11:48:55 2015 +0200 Committer: wilderrodrigues Committed: Wed May 6 19:24:13 2015 +0200 ---------------------------------------------------------------------- .../kvm/resource/LibvirtComputingResource.java | 402 +------------------ .../LibvirtBackupSnapshotCommandWrapper.java | 204 ++++++++++ ...PrivateTemplateFromVolumeCommandWrapper.java | 176 ++++++++ .../LibvirtManageSnapshotCommandWrapper.java | 163 ++++++++ .../resource/wrapper/LibvirtRequestWrapper.java | 6 + .../resource/LibvirtComputingResourceTest.java | 137 ++++++- 6 files changed, 699 insertions(+), 389 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/bcf78d3b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index 3c40c90..4e8b4cf 100644 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -16,26 +16,20 @@ // under the License. package com.cloud.hypervisor.kvm.resource; -import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; -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.HashSet; import java.util.List; @@ -54,10 +48,7 @@ import javax.naming.ConfigurationException; import org.apache.cloudstack.storage.command.StorageSubSystemCommand; import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; import org.apache.cloudstack.storage.to.VolumeObjectTO; -import org.apache.cloudstack.utils.qemu.QemuImg; import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat; -import org.apache.cloudstack.utils.qemu.QemuImgException; -import org.apache.cloudstack.utils.qemu.QemuImgFile; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.log4j.Logger; @@ -67,26 +58,14 @@ import org.libvirt.DomainBlockStats; import org.libvirt.DomainInfo; import org.libvirt.DomainInfo.DomainState; import org.libvirt.DomainInterfaceStats; -import org.libvirt.DomainSnapshot; import org.libvirt.LibvirtException; import org.libvirt.NodeInfo; import org.libvirt.StorageVol; -import com.ceph.rados.IoCTX; -import com.ceph.rados.Rados; -import com.ceph.rados.RadosException; -import com.ceph.rbd.Rbd; -import com.ceph.rbd.RbdException; -import com.ceph.rbd.RbdImage; import com.cloud.agent.api.Answer; -import com.cloud.agent.api.BackupSnapshotAnswer; -import com.cloud.agent.api.BackupSnapshotCommand; import com.cloud.agent.api.Command; import com.cloud.agent.api.CreatePrivateTemplateFromSnapshotCommand; -import com.cloud.agent.api.CreatePrivateTemplateFromVolumeCommand; import com.cloud.agent.api.HostVmStateReportEntry; -import com.cloud.agent.api.ManageSnapshotAnswer; -import com.cloud.agent.api.ManageSnapshotCommand; import com.cloud.agent.api.PingCommand; import com.cloud.agent.api.PingRoutingCommand; import com.cloud.agent.api.PingRoutingWithNwGroupsCommand; @@ -156,7 +135,6 @@ import com.cloud.resource.ServerResource; import com.cloud.resource.ServerResourceBase; import com.cloud.storage.JavaStorageLayer; import com.cloud.storage.Storage; -import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.StorageLayer; import com.cloud.storage.Volume; @@ -234,7 +212,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv public static final String SSHPUBKEYPATH = SSHKEYSPATH + File.separator + "id_rsa.pub.cloud"; private String _mountPoint = "/mnt"; - StorageLayer _storage; + private StorageLayer _storage; private KVMStoragePoolManager _storagePoolMgr; private VifDriver _defaultVifDriver; @@ -276,9 +254,6 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv private final Map _pifs = new HashMap(); private final Map _vmStats = new ConcurrentHashMap(); - protected static final MessageFormat SnapshotXML = new MessageFormat(" " + " {0}" + " " - + " {1}" + " " + " "); - protected static final HashMap s_powerStatesTable; static { s_powerStatesTable = new HashMap(); @@ -414,6 +389,22 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv return _monitor; } + public StorageLayer getStorage() { + return _storage; + } + + public String createTmplPath() { + return _createTmplPath; + } + + public int getCmdsTimeout() { + return _cmdsTimeout; + } + + public String manageSnapshotPath() { + return _manageSnapshotPath; + } + private static final class KeyValueInterpreter extends OutputInterpreter { private final Map map = new HashMap(); @@ -1261,13 +1252,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv } try { - if (cmd instanceof CreatePrivateTemplateFromVolumeCommand) { - return execute((CreatePrivateTemplateFromVolumeCommand)cmd); - } else if (cmd instanceof ManageSnapshotCommand) { - return execute((ManageSnapshotCommand)cmd); - } else if (cmd instanceof BackupSnapshotCommand) { - return execute((BackupSnapshotCommand)cmd); - } else if (cmd instanceof CreatePrivateTemplateFromSnapshotCommand) { + if (cmd instanceof CreatePrivateTemplateFromSnapshotCommand) { return execute((CreatePrivateTemplateFromSnapshotCommand)cmd); } else if (cmd instanceof StartCommand) { return execute((StartCommand)cmd); @@ -1899,248 +1884,6 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv return new ExecutionResult(true, null); } - protected ManageSnapshotAnswer execute(final ManageSnapshotCommand cmd) { - final String snapshotName = cmd.getSnapshotName(); - final String snapshotPath = cmd.getSnapshotPath(); - final String vmName = cmd.getVmName(); - try { - final Connect conn = LibvirtConnection.getConnectionByVmName(vmName); - DomainState state = null; - Domain vm = null; - if (vmName != null) { - try { - vm = getDomain(conn, cmd.getVmName()); - state = vm.getInfo().state; - } catch (final LibvirtException e) { - s_logger.trace("Ignoring libvirt error.", e); - } - } - - final KVMStoragePool primaryPool = _storagePoolMgr.getStoragePool(cmd.getPool().getType(), cmd.getPool().getUuid()); - - final KVMPhysicalDisk disk = primaryPool.getPhysicalDisk(cmd.getVolumePath()); - if (state == DomainState.VIR_DOMAIN_RUNNING && !primaryPool.isExternalSnapshot()) { - final String vmUuid = vm.getUUIDString(); - final Object[] args = new Object[] {snapshotName, vmUuid}; - final String snapshot = SnapshotXML.format(args); - s_logger.debug(snapshot); - if (cmd.getCommandSwitch().equalsIgnoreCase(ManageSnapshotCommand.CREATE_SNAPSHOT)) { - vm.snapshotCreateXML(snapshot); - } else { - final DomainSnapshot snap = vm.snapshotLookupByName(snapshotName); - snap.delete(0); - } - - /* - * libvirt on RHEL6 doesn't handle resume event emitted from - * qemu - */ - vm = getDomain(conn, cmd.getVmName()); - state = vm.getInfo().state; - if (state == DomainState.VIR_DOMAIN_PAUSED) { - vm.resume(); - } - } else { - /** - * For RBD we can't use libvirt to do our snapshotting or any Bash scripts. - * libvirt also wants to store the memory contents of the Virtual Machine, - * but that's not possible with RBD since there is no way to store the memory - * contents in RBD. - * - * So we rely on the Java bindings for RBD to create our snapshot - * - * This snapshot might not be 100% consistent due to writes still being in the - * memory of the Virtual Machine, but if the VM runs a kernel which supports - * barriers properly (>2.6.32) this won't be any different then pulling the power - * cord out of a running machine. - */ - if (primaryPool.getType() == StoragePoolType.RBD) { - try { - final Rados r = new Rados(primaryPool.getAuthUserName()); - r.confSet("mon_host", primaryPool.getSourceHost() + ":" + primaryPool.getSourcePort()); - r.confSet("key", primaryPool.getAuthSecret()); - r.confSet("client_mount_timeout", "30"); - r.connect(); - s_logger.debug("Succesfully connected to Ceph cluster at " + r.confGet("mon_host")); - - final IoCTX io = r.ioCtxCreate(primaryPool.getSourceDir()); - final Rbd rbd = new Rbd(io); - final RbdImage image = rbd.open(disk.getName()); - - if (cmd.getCommandSwitch().equalsIgnoreCase(ManageSnapshotCommand.CREATE_SNAPSHOT)) { - s_logger.debug("Attempting to create RBD snapshot " + disk.getName() + "@" + snapshotName); - image.snapCreate(snapshotName); - } else { - s_logger.debug("Attempting to remove RBD snapshot " + disk.getName() + "@" + snapshotName); - image.snapRemove(snapshotName); - } - - rbd.close(image); - r.ioCtxDestroy(io); - } catch (final Exception e) { - s_logger.error("A RBD snapshot operation on " + disk.getName() + " failed. The error was: " + e.getMessage()); - } - } else { - /* VM is not running, create a snapshot by ourself */ - final Script command = new Script(_manageSnapshotPath, _cmdsTimeout, s_logger); - if (cmd.getCommandSwitch().equalsIgnoreCase(ManageSnapshotCommand.CREATE_SNAPSHOT)) { - command.add("-c", disk.getPath()); - } else { - command.add("-d", snapshotPath); - } - - command.add("-n", snapshotName); - final String result = command.execute(); - if (result != null) { - s_logger.debug("Failed to manage snapshot: " + result); - return new ManageSnapshotAnswer(cmd, false, "Failed to manage snapshot: " + result); - } - } - } - return new ManageSnapshotAnswer(cmd, cmd.getSnapshotId(), disk.getPath() + File.separator + snapshotName, true, null); - } catch (final LibvirtException e) { - s_logger.debug("Failed to manage snapshot: " + e.toString()); - return new ManageSnapshotAnswer(cmd, false, "Failed to manage snapshot: " + e.toString()); - } - - } - - protected BackupSnapshotAnswer execute(final BackupSnapshotCommand cmd) { - final Long dcId = cmd.getDataCenterId(); - final Long accountId = cmd.getAccountId(); - final Long volumeId = cmd.getVolumeId(); - final String secondaryStoragePoolUrl = cmd.getSecondaryStorageUrl(); - final String snapshotName = cmd.getSnapshotName(); - String snapshotDestPath = null; - String snapshotRelPath = null; - final String vmName = cmd.getVmName(); - KVMStoragePool secondaryStoragePool = null; - try { - final Connect conn = LibvirtConnection.getConnectionByVmName(vmName); - - secondaryStoragePool = _storagePoolMgr.getStoragePoolByURI(secondaryStoragePoolUrl); - - final String ssPmountPath = secondaryStoragePool.getLocalPath(); - snapshotRelPath = File.separator + "snapshots" + File.separator + dcId + File.separator + accountId + File.separator + volumeId; - - snapshotDestPath = ssPmountPath + File.separator + "snapshots" + File.separator + dcId + File.separator + accountId + File.separator + volumeId; - final KVMStoragePool primaryPool = _storagePoolMgr.getStoragePool(cmd.getPool().getType(), cmd.getPrimaryStoragePoolNameLabel()); - final KVMPhysicalDisk snapshotDisk = primaryPool.getPhysicalDisk(cmd.getVolumePath()); - - /** - * RBD snapshots can't be copied using qemu-img, so we have to use - * the Java bindings for librbd here. - * - * These bindings will read the snapshot and write the contents to - * the secondary storage directly - * - * It will stop doing so if the amount of time spend is longer then - * cmds.timeout - */ - if (primaryPool.getType() == StoragePoolType.RBD) { - try { - final Rados r = new Rados(primaryPool.getAuthUserName()); - r.confSet("mon_host", primaryPool.getSourceHost() + ":" + primaryPool.getSourcePort()); - r.confSet("key", primaryPool.getAuthSecret()); - r.confSet("client_mount_timeout", "30"); - r.connect(); - s_logger.debug("Succesfully connected to Ceph cluster at " + r.confGet("mon_host")); - - final IoCTX io = r.ioCtxCreate(primaryPool.getSourceDir()); - final Rbd rbd = new Rbd(io); - final RbdImage image = rbd.open(snapshotDisk.getName(), snapshotName); - final File fh = new File(snapshotDestPath); - try(BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fh));) { - final int chunkSize = 4194304; - long offset = 0; - s_logger.debug("Backuping up RBD snapshot " + snapshotName + " to " + snapshotDestPath); - while (true) { - final byte[] buf = new byte[chunkSize]; - final int bytes = image.read(offset, buf, chunkSize); - if (bytes <= 0) { - break; - } - bos.write(buf, 0, bytes); - offset += bytes; - } - s_logger.debug("Completed backing up RBD snapshot " + snapshotName + " to " + snapshotDestPath + ". Bytes written: " + offset); - }catch(final IOException ex) - { - s_logger.error("BackupSnapshotAnswer:Exception:"+ ex.getMessage()); - } - r.ioCtxDestroy(io); - } catch (final RadosException e) { - s_logger.error("A RADOS operation failed. The error was: " + e.getMessage()); - return new BackupSnapshotAnswer(cmd, false, e.toString(), null, true); - } catch (final RbdException e) { - s_logger.error("A RBD operation on " + snapshotDisk.getName() + " failed. The error was: " + e.getMessage()); - return new BackupSnapshotAnswer(cmd, false, e.toString(), null, true); - } - } else { - final Script command = new Script(_manageSnapshotPath, _cmdsTimeout, s_logger); - command.add("-b", snapshotDisk.getPath()); - command.add("-n", snapshotName); - command.add("-p", snapshotDestPath); - command.add("-t", snapshotName); - final String result = command.execute(); - if (result != null) { - s_logger.debug("Failed to backup snaptshot: " + result); - return new BackupSnapshotAnswer(cmd, false, result, null, true); - } - } - /* Delete the snapshot on primary */ - - DomainState state = null; - Domain vm = null; - if (vmName != null) { - try { - vm = getDomain(conn, cmd.getVmName()); - state = vm.getInfo().state; - } catch (final LibvirtException e) { - s_logger.trace("Ignoring libvirt error.", e); - } - } - - final KVMStoragePool primaryStorage = _storagePoolMgr.getStoragePool(cmd.getPool().getType(), cmd.getPool().getUuid()); - if (state == DomainState.VIR_DOMAIN_RUNNING && !primaryStorage.isExternalSnapshot()) { - final String vmUuid = vm.getUUIDString(); - final Object[] args = new Object[] {snapshotName, vmUuid}; - final String snapshot = SnapshotXML.format(args); - s_logger.debug(snapshot); - final DomainSnapshot snap = vm.snapshotLookupByName(snapshotName); - snap.delete(0); - - /* - * libvirt on RHEL6 doesn't handle resume event emitted from - * qemu - */ - vm = getDomain(conn, cmd.getVmName()); - state = vm.getInfo().state; - if (state == DomainState.VIR_DOMAIN_PAUSED) { - vm.resume(); - } - } else { - final Script command = new Script(_manageSnapshotPath, _cmdsTimeout, s_logger); - command.add("-d", snapshotDisk.getPath()); - command.add("-n", snapshotName); - final String result = command.execute(); - if (result != null) { - s_logger.debug("Failed to backup snapshot: " + result); - return new BackupSnapshotAnswer(cmd, false, "Failed to backup snapshot: " + result, null, true); - } - } - } catch (final LibvirtException e) { - return new BackupSnapshotAnswer(cmd, false, e.toString(), null, true); - } catch (final CloudRuntimeException e) { - return new BackupSnapshotAnswer(cmd, false, e.toString(), null, true); - } finally { - if (secondaryStoragePool != null) { - _storagePoolMgr.deleteStoragePool(secondaryStoragePool.getType(), secondaryStoragePool.getUuid()); - } - } - return new BackupSnapshotAnswer(cmd, true, null, snapshotRelPath + File.separator + snapshotName, true); - } - protected CreatePrivateTemplateAnswer execute(final CreatePrivateTemplateFromSnapshotCommand cmd) { final String templateFolder = cmd.getAccountId() + File.separator + cmd.getNewTemplateId(); final String templateInstallFolder = "template/tmpl/" + templateFolder; @@ -2198,115 +1941,6 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv } } - protected CreatePrivateTemplateAnswer execute(final CreatePrivateTemplateFromVolumeCommand cmd) { - final String secondaryStorageURL = cmd.getSecondaryStorageUrl(); - - KVMStoragePool secondaryStorage = null; - KVMStoragePool primary = null; - try { - final String templateFolder = cmd.getAccountId() + File.separator + cmd.getTemplateId() + File.separator; - final String templateInstallFolder = "/template/tmpl/" + templateFolder; - - secondaryStorage = _storagePoolMgr.getStoragePoolByURI(secondaryStorageURL); - - try { - primary = _storagePoolMgr.getStoragePool(cmd.getPool().getType(), cmd.getPrimaryStoragePoolNameLabel()); - } catch (final CloudRuntimeException e) { - if (e.getMessage().contains("not found")) { - primary = - _storagePoolMgr.createStoragePool(cmd.getPool().getUuid(), cmd.getPool().getHost(), cmd.getPool().getPort(), cmd.getPool().getPath(), - cmd.getPool().getUserInfo(), cmd.getPool().getType()); - } else { - return new CreatePrivateTemplateAnswer(cmd, false, e.getMessage()); - } - } - - final KVMPhysicalDisk disk = primary.getPhysicalDisk(cmd.getVolumePath()); - final String tmpltPath = secondaryStorage.getLocalPath() + File.separator + templateInstallFolder; - _storage.mkdirs(tmpltPath); - - if (primary.getType() != StoragePoolType.RBD) { - final Script command = new Script(_createTmplPath, _cmdsTimeout, s_logger); - command.add("-f", disk.getPath()); - command.add("-t", tmpltPath); - command.add("-n", cmd.getUniqueName() + ".qcow2"); - - final String result = command.execute(); - - 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()); - - final QemuImgFile srcFile = - new QemuImgFile(KVMPhysicalDisk.RBDStringBuilder(primary.getSourceHost(), primary.getSourcePort(), primary.getAuthUserName(), - primary.getAuthSecret(), disk.getPath())); - srcFile.setFormat(PhysicalDiskFormat.RAW); - - final QemuImgFile destFile = new QemuImgFile(tmpltPath + "/" + cmd.getUniqueName() + ".qcow2"); - destFile.setFormat(PhysicalDiskFormat.QCOW2); - - final QemuImg q = new QemuImg(0); - try { - q.convert(srcFile, destFile); - } catch (final QemuImgException e) { - s_logger.error("Failed to create new template while converting " + srcFile.getFileName() + " to " + destFile.getFileName() + " the error was: " + - e.getMessage()); - } - - final File templateProp = new File(tmpltPath + "/template.properties"); - if (!templateProp.exists()) { - templateProp.createNewFile(); - } - - String templateContent = "filename=" + cmd.getUniqueName() + ".qcow2" + System.getProperty("line.separator"); - - final DateFormat dateFormat = new SimpleDateFormat("MM_dd_yyyy"); - final Date date = new Date(); - templateContent += "snapshot.name=" + dateFormat.format(date) + System.getProperty("line.separator"); - - try(FileOutputStream templFo = new FileOutputStream(templateProp);) { - templFo.write(templateContent.getBytes()); - templFo.flush(); - }catch(final IOException ex) - { - s_logger.error("CreatePrivateTemplateAnswer:Exception:"+ex.getMessage()); - } - - } - - final Map params = new HashMap(); - params.put(StorageLayer.InstanceConfigKey, _storage); - final Processor qcow2Processor = new QCOW2Processor(); - - qcow2Processor.configure("QCOW2 Processor", params); - - final FormatInfo info = qcow2Processor.process(tmpltPath, null, cmd.getUniqueName()); - - final TemplateLocation loc = new TemplateLocation(_storage, tmpltPath); - loc.create(1, true, cmd.getUniqueName()); - loc.addFormat(info); - loc.save(); - - return new CreatePrivateTemplateAnswer(cmd, true, null, templateInstallFolder + cmd.getUniqueName() + ".qcow2", info.virtualSize, info.size, - cmd.getUniqueName(), ImageFormat.QCOW2); - } catch (final InternalErrorException e) { - return new CreatePrivateTemplateAnswer(cmd, false, e.toString()); - } catch (final IOException e) { - return new CreatePrivateTemplateAnswer(cmd, false, e.toString()); - } catch (final ConfigurationException e) { - return new CreatePrivateTemplateAnswer(cmd, false, e.toString()); - } catch (final CloudRuntimeException e) { - return new CreatePrivateTemplateAnswer(cmd, false, e.toString()); - } finally { - if (secondaryStorage != null) { - _storagePoolMgr.deleteStoragePool(secondaryStorage.getType(), secondaryStorage.getUuid()); - } - } - } - protected PowerState convertToPowerState(final DomainState ps) { final PowerState state = s_powerStatesTable.get(ps); return state == null ? PowerState.PowerUnknown : state; http://git-wip-us.apache.org/repos/asf/cloudstack/blob/bcf78d3b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtBackupSnapshotCommandWrapper.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtBackupSnapshotCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtBackupSnapshotCommandWrapper.java new file mode 100644 index 0000000..afbbf73 --- /dev/null +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtBackupSnapshotCommandWrapper.java @@ -0,0 +1,204 @@ +// +// 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.wrapper; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.MessageFormat; + +import org.apache.log4j.Logger; +import org.libvirt.Connect; +import org.libvirt.Domain; +import org.libvirt.DomainInfo.DomainState; +import org.libvirt.DomainSnapshot; +import org.libvirt.LibvirtException; + +import com.ceph.rados.IoCTX; +import com.ceph.rados.Rados; +import com.ceph.rados.RadosException; +import com.ceph.rbd.Rbd; +import com.ceph.rbd.RbdException; +import com.ceph.rbd.RbdImage; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.BackupSnapshotAnswer; +import com.cloud.agent.api.BackupSnapshotCommand; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk; +import com.cloud.hypervisor.kvm.storage.KVMStoragePool; +import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager; +import com.cloud.resource.CommandWrapper; +import com.cloud.storage.Storage.StoragePoolType; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.script.Script; + +public final class LibvirtBackupSnapshotCommandWrapper extends CommandWrapper { + + private static final Logger s_logger = Logger.getLogger(LibvirtBackupSnapshotCommandWrapper.class); + + @Override + public Answer execute(final BackupSnapshotCommand command, final LibvirtComputingResource libvirtComputingResource) { + final Long dcId = command.getDataCenterId(); + final Long accountId = command.getAccountId(); + final Long volumeId = command.getVolumeId(); + final String secondaryStoragePoolUrl = command.getSecondaryStorageUrl(); + final String snapshotName = command.getSnapshotName(); + String snapshotDestPath = null; + String snapshotRelPath = null; + final String vmName = command.getVmName(); + KVMStoragePool secondaryStoragePool = null; + final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr(); + + try { + final LibvirtConnectionWrapper libvirtConnectionWrapper = libvirtComputingResource.getLibvirtConnectionWrapper(); + final Connect conn = libvirtConnectionWrapper.getConnectionByVmName(vmName); + + secondaryStoragePool = storagePoolMgr.getStoragePoolByURI(secondaryStoragePoolUrl); + + final String ssPmountPath = secondaryStoragePool.getLocalPath(); + snapshotRelPath = File.separator + "snapshots" + File.separator + dcId + File.separator + accountId + File.separator + volumeId; + + snapshotDestPath = ssPmountPath + File.separator + "snapshots" + File.separator + dcId + File.separator + accountId + File.separator + volumeId; + final KVMStoragePool primaryPool = storagePoolMgr.getStoragePool(command.getPool().getType(), command.getPrimaryStoragePoolNameLabel()); + final KVMPhysicalDisk snapshotDisk = primaryPool.getPhysicalDisk(command.getVolumePath()); + + final String manageSnapshotPath = libvirtComputingResource.manageSnapshotPath(); + final int cmdsTimeout = libvirtComputingResource.getCmdsTimeout(); + + /** + * RBD snapshots can't be copied using qemu-img, so we have to use + * the Java bindings for librbd here. + * + * These bindings will read the snapshot and write the contents to + * the secondary storage directly + * + * It will stop doing so if the amount of time spend is longer then + * cmds.timeout + */ + if (primaryPool.getType() == StoragePoolType.RBD) { + try { + final Rados r = new Rados(primaryPool.getAuthUserName()); + r.confSet("mon_host", primaryPool.getSourceHost() + ":" + primaryPool.getSourcePort()); + r.confSet("key", primaryPool.getAuthSecret()); + r.confSet("client_mount_timeout", "30"); + r.connect(); + s_logger.debug("Succesfully connected to Ceph cluster at " + r.confGet("mon_host")); + + final IoCTX io = r.ioCtxCreate(primaryPool.getSourceDir()); + final Rbd rbd = new Rbd(io); + final RbdImage image = rbd.open(snapshotDisk.getName(), snapshotName); + final File fh = new File(snapshotDestPath); + try(BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fh));) { + final int chunkSize = 4194304; + long offset = 0; + s_logger.debug("Backuping up RBD snapshot " + snapshotName + " to " + snapshotDestPath); + while (true) { + final byte[] buf = new byte[chunkSize]; + final int bytes = image.read(offset, buf, chunkSize); + if (bytes <= 0) { + break; + } + bos.write(buf, 0, bytes); + offset += bytes; + } + s_logger.debug("Completed backing up RBD snapshot " + snapshotName + " to " + snapshotDestPath + ". Bytes written: " + offset); + }catch(final IOException ex) + { + s_logger.error("BackupSnapshotAnswer:Exception:"+ ex.getMessage()); + } + r.ioCtxDestroy(io); + } catch (final RadosException e) { + s_logger.error("A RADOS operation failed. The error was: " + e.getMessage()); + return new BackupSnapshotAnswer(command, false, e.toString(), null, true); + } catch (final RbdException e) { + s_logger.error("A RBD operation on " + snapshotDisk.getName() + " failed. The error was: " + e.getMessage()); + return new BackupSnapshotAnswer(command, false, e.toString(), null, true); + } + } else { + final Script scriptCommand = new Script(manageSnapshotPath, cmdsTimeout, s_logger); + scriptCommand.add("-b", snapshotDisk.getPath()); + scriptCommand.add("-n", snapshotName); + scriptCommand.add("-p", snapshotDestPath); + scriptCommand.add("-t", snapshotName); + final String result = scriptCommand.execute(); + + if (result != null) { + s_logger.debug("Failed to backup snaptshot: " + result); + return new BackupSnapshotAnswer(command, false, result, null, true); + } + } + /* Delete the snapshot on primary */ + + DomainState state = null; + Domain vm = null; + if (vmName != null) { + try { + vm = libvirtComputingResource.getDomain(conn, command.getVmName()); + state = vm.getInfo().state; + } catch (final LibvirtException e) { + s_logger.trace("Ignoring libvirt error.", e); + } + } + + final KVMStoragePool primaryStorage = storagePoolMgr.getStoragePool(command.getPool().getType(), command.getPool().getUuid()); + + if (state == DomainState.VIR_DOMAIN_RUNNING && !primaryStorage.isExternalSnapshot()) { + final MessageFormat snapshotXML = new MessageFormat(" " + " {0}" + " " + + " {1}" + " " + " "); + + final String vmUuid = vm.getUUIDString(); + final Object[] args = new Object[] {snapshotName, vmUuid}; + final String snapshot = snapshotXML.format(args); + s_logger.debug(snapshot); + final DomainSnapshot snap = vm.snapshotLookupByName(snapshotName); + snap.delete(0); + + /* + * libvirt on RHEL6 doesn't handle resume event emitted from + * qemu + */ + vm = libvirtComputingResource.getDomain(conn, command.getVmName()); + state = vm.getInfo().state; + if (state == DomainState.VIR_DOMAIN_PAUSED) { + vm.resume(); + } + } else { + final Script scriptCommand = new Script(manageSnapshotPath, cmdsTimeout, s_logger); + scriptCommand.add("-d", snapshotDisk.getPath()); + scriptCommand.add("-n", snapshotName); + final String result = scriptCommand.execute(); + if (result != null) { + s_logger.debug("Failed to backup snapshot: " + result); + return new BackupSnapshotAnswer(command, false, "Failed to backup snapshot: " + result, null, true); + } + } + } catch (final LibvirtException e) { + return new BackupSnapshotAnswer(command, false, e.toString(), null, true); + } catch (final CloudRuntimeException e) { + return new BackupSnapshotAnswer(command, false, e.toString(), null, true); + } finally { + if (secondaryStoragePool != null) { + storagePoolMgr.deleteStoragePool(secondaryStoragePool.getType(), secondaryStoragePool.getUuid()); + } + } + return new BackupSnapshotAnswer(command, true, null, snapshotRelPath + File.separator + snapshotName, true); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cloudstack/blob/bcf78d3b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreatePrivateTemplateFromVolumeCommandWrapper.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreatePrivateTemplateFromVolumeCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreatePrivateTemplateFromVolumeCommandWrapper.java new file mode 100644 index 0000000..f3bfe1f --- /dev/null +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtCreatePrivateTemplateFromVolumeCommandWrapper.java @@ -0,0 +1,176 @@ +// +// 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.wrapper; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import javax.naming.ConfigurationException; + +import org.apache.cloudstack.utils.qemu.QemuImg; +import org.apache.cloudstack.utils.qemu.QemuImg.PhysicalDiskFormat; +import org.apache.cloudstack.utils.qemu.QemuImgException; +import org.apache.cloudstack.utils.qemu.QemuImgFile; +import org.apache.log4j.Logger; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CreatePrivateTemplateFromVolumeCommand; +import com.cloud.agent.api.storage.CreatePrivateTemplateAnswer; +import com.cloud.exception.InternalErrorException; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk; +import com.cloud.hypervisor.kvm.storage.KVMStoragePool; +import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager; +import com.cloud.resource.CommandWrapper; +import com.cloud.storage.Storage.ImageFormat; +import com.cloud.storage.Storage.StoragePoolType; +import com.cloud.storage.StorageLayer; +import com.cloud.storage.template.Processor; +import com.cloud.storage.template.Processor.FormatInfo; +import com.cloud.storage.template.QCOW2Processor; +import com.cloud.storage.template.TemplateLocation; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.script.Script; + +public final class LibvirtCreatePrivateTemplateFromVolumeCommandWrapper extends CommandWrapper { + + private static final Logger s_logger = Logger.getLogger(LibvirtCreatePrivateTemplateFromVolumeCommandWrapper.class); + + @Override + public Answer execute(final CreatePrivateTemplateFromVolumeCommand command, final LibvirtComputingResource libvirtComputingResource) { + final String secondaryStorageURL = command.getSecondaryStorageUrl(); + + KVMStoragePool secondaryStorage = null; + KVMStoragePool primary = null; + final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr(); + try { + final String templateFolder = command.getAccountId() + File.separator + command.getTemplateId() + File.separator; + final String templateInstallFolder = "/template/tmpl/" + templateFolder; + + secondaryStorage = storagePoolMgr.getStoragePoolByURI(secondaryStorageURL); + + try { + primary = storagePoolMgr.getStoragePool(command.getPool().getType(), command.getPrimaryStoragePoolNameLabel()); + } catch (final CloudRuntimeException e) { + if (e.getMessage().contains("not found")) { + primary = + storagePoolMgr.createStoragePool(command.getPool().getUuid(), command.getPool().getHost(), command.getPool().getPort(), command.getPool().getPath(), + command.getPool().getUserInfo(), command.getPool().getType()); + } else { + return new CreatePrivateTemplateAnswer(command, false, e.getMessage()); + } + } + + final KVMPhysicalDisk disk = primary.getPhysicalDisk(command.getVolumePath()); + final String tmpltPath = secondaryStorage.getLocalPath() + File.separator + templateInstallFolder; + final StorageLayer storage = libvirtComputingResource.getStorage(); + storage.mkdirs(tmpltPath); + + if (primary.getType() != StoragePoolType.RBD) { + final String createTmplPath = libvirtComputingResource.createTmplPath(); + final int cmdsTimeout = libvirtComputingResource.getCmdsTimeout(); + + final Script scriptCommand = new Script(createTmplPath, cmdsTimeout, s_logger); + scriptCommand.add("-f", disk.getPath()); + scriptCommand.add("-t", tmpltPath); + scriptCommand.add("-n", command.getUniqueName() + ".qcow2"); + + final String result = scriptCommand.execute(); + + if (result != null) { + s_logger.debug("failed to create template: " + result); + return new CreatePrivateTemplateAnswer(command, false, result); + } + } else { + s_logger.debug("Converting RBD disk " + disk.getPath() + " into template " + command.getUniqueName()); + + final QemuImgFile srcFile = + new QemuImgFile(KVMPhysicalDisk.RBDStringBuilder(primary.getSourceHost(), primary.getSourcePort(), primary.getAuthUserName(), + primary.getAuthSecret(), disk.getPath())); + srcFile.setFormat(PhysicalDiskFormat.RAW); + + final QemuImgFile destFile = new QemuImgFile(tmpltPath + "/" + command.getUniqueName() + ".qcow2"); + destFile.setFormat(PhysicalDiskFormat.QCOW2); + + final QemuImg q = new QemuImg(0); + try { + q.convert(srcFile, destFile); + } catch (final QemuImgException e) { + s_logger.error("Failed to create new template while converting " + srcFile.getFileName() + " to " + destFile.getFileName() + " the error was: " + + e.getMessage()); + } + + final File templateProp = new File(tmpltPath + "/template.properties"); + if (!templateProp.exists()) { + templateProp.createNewFile(); + } + + String templateContent = "filename=" + command.getUniqueName() + ".qcow2" + System.getProperty("line.separator"); + + final DateFormat dateFormat = new SimpleDateFormat("MM_dd_yyyy"); + final Date date = new Date(); + templateContent += "snapshot.name=" + dateFormat.format(date) + System.getProperty("line.separator"); + + try(FileOutputStream templFo = new FileOutputStream(templateProp);) { + templFo.write(templateContent.getBytes()); + templFo.flush(); + }catch(final IOException ex) + { + s_logger.error("CreatePrivateTemplateAnswer:Exception:"+ex.getMessage()); + } + + } + + final Map params = new HashMap(); + params.put(StorageLayer.InstanceConfigKey, storage); + final Processor qcow2Processor = new QCOW2Processor(); + + qcow2Processor.configure("QCOW2 Processor", params); + + final FormatInfo info = qcow2Processor.process(tmpltPath, null, command.getUniqueName()); + + final TemplateLocation loc = new TemplateLocation(storage, tmpltPath); + loc.create(1, true, command.getUniqueName()); + loc.addFormat(info); + loc.save(); + + return new CreatePrivateTemplateAnswer(command, true, null, templateInstallFolder + command.getUniqueName() + ".qcow2", info.virtualSize, info.size, + command.getUniqueName(), ImageFormat.QCOW2); + } catch (final InternalErrorException e) { + return new CreatePrivateTemplateAnswer(command, false, e.toString()); + } catch (final IOException e) { + return new CreatePrivateTemplateAnswer(command, false, e.toString()); + } catch (final ConfigurationException e) { + return new CreatePrivateTemplateAnswer(command, false, e.toString()); + } catch (final CloudRuntimeException e) { + return new CreatePrivateTemplateAnswer(command, false, e.toString()); + } finally { + if (secondaryStorage != null) { + storagePoolMgr.deleteStoragePool(secondaryStorage.getType(), secondaryStorage.getUuid()); + } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cloudstack/blob/bcf78d3b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtManageSnapshotCommandWrapper.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtManageSnapshotCommandWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtManageSnapshotCommandWrapper.java new file mode 100644 index 0000000..9a991a1 --- /dev/null +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtManageSnapshotCommandWrapper.java @@ -0,0 +1,163 @@ +// +// 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.wrapper; + +import java.io.File; +import java.text.MessageFormat; + +import org.apache.log4j.Logger; +import org.libvirt.Connect; +import org.libvirt.Domain; +import org.libvirt.DomainInfo.DomainState; +import org.libvirt.DomainSnapshot; +import org.libvirt.LibvirtException; + +import com.ceph.rados.IoCTX; +import com.ceph.rados.Rados; +import com.ceph.rbd.Rbd; +import com.ceph.rbd.RbdImage; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.ManageSnapshotAnswer; +import com.cloud.agent.api.ManageSnapshotCommand; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk; +import com.cloud.hypervisor.kvm.storage.KVMStoragePool; +import com.cloud.resource.CommandWrapper; +import com.cloud.storage.Storage.StoragePoolType; +import com.cloud.utils.script.Script; + +public final class LibvirtManageSnapshotCommandWrapper extends CommandWrapper { + + private static final Logger s_logger = Logger.getLogger(LibvirtManageSnapshotCommandWrapper.class); + + @Override + public Answer execute(final ManageSnapshotCommand command, final LibvirtComputingResource libvirtComputingResource) { + final String snapshotName = command.getSnapshotName(); + final String snapshotPath = command.getSnapshotPath(); + final String vmName = command.getVmName(); + try { + final LibvirtConnectionWrapper libvirtConnectionWrapper = libvirtComputingResource.getLibvirtConnectionWrapper(); + final Connect conn = libvirtConnectionWrapper.getConnectionByVmName(vmName); + DomainState state = null; + Domain vm = null; + if (vmName != null) { + try { + vm = libvirtComputingResource.getDomain(conn, command.getVmName()); + state = vm.getInfo().state; + } catch (final LibvirtException e) { + s_logger.trace("Ignoring libvirt error.", e); + } + } + + final KVMStoragePool primaryPool = libvirtComputingResource.getStoragePoolMgr().getStoragePool(command.getPool().getType(), command.getPool().getUuid()); + + final KVMPhysicalDisk disk = primaryPool.getPhysicalDisk(command.getVolumePath()); + if (state == DomainState.VIR_DOMAIN_RUNNING && !primaryPool.isExternalSnapshot()) { + + final MessageFormat snapshotXML = new MessageFormat(" " + " {0}" + " " + + " {1}" + " " + " "); + + final String vmUuid = vm.getUUIDString(); + final Object[] args = new Object[] {snapshotName, vmUuid}; + final String snapshot = snapshotXML.format(args); + s_logger.debug(snapshot); + if (command.getCommandSwitch().equalsIgnoreCase(ManageSnapshotCommand.CREATE_SNAPSHOT)) { + vm.snapshotCreateXML(snapshot); + } else { + final DomainSnapshot snap = vm.snapshotLookupByName(snapshotName); + snap.delete(0); + } + + /* + * libvirt on RHEL6 doesn't handle resume event emitted from + * qemu + */ + vm = libvirtComputingResource.getDomain(conn, command.getVmName()); + state = vm.getInfo().state; + if (state == DomainState.VIR_DOMAIN_PAUSED) { + vm.resume(); + } + } else { + /** + * For RBD we can't use libvirt to do our snapshotting or any Bash scripts. + * libvirt also wants to store the memory contents of the Virtual Machine, + * but that's not possible with RBD since there is no way to store the memory + * contents in RBD. + * + * So we rely on the Java bindings for RBD to create our snapshot + * + * This snapshot might not be 100% consistent due to writes still being in the + * memory of the Virtual Machine, but if the VM runs a kernel which supports + * barriers properly (>2.6.32) this won't be any different then pulling the power + * cord out of a running machine. + */ + if (primaryPool.getType() == StoragePoolType.RBD) { + try { + final Rados r = new Rados(primaryPool.getAuthUserName()); + r.confSet("mon_host", primaryPool.getSourceHost() + ":" + primaryPool.getSourcePort()); + r.confSet("key", primaryPool.getAuthSecret()); + r.confSet("client_mount_timeout", "30"); + r.connect(); + s_logger.debug("Succesfully connected to Ceph cluster at " + r.confGet("mon_host")); + + final IoCTX io = r.ioCtxCreate(primaryPool.getSourceDir()); + final Rbd rbd = new Rbd(io); + final RbdImage image = rbd.open(disk.getName()); + + if (command.getCommandSwitch().equalsIgnoreCase(ManageSnapshotCommand.CREATE_SNAPSHOT)) { + s_logger.debug("Attempting to create RBD snapshot " + disk.getName() + "@" + snapshotName); + image.snapCreate(snapshotName); + } else { + s_logger.debug("Attempting to remove RBD snapshot " + disk.getName() + "@" + snapshotName); + image.snapRemove(snapshotName); + } + + rbd.close(image); + r.ioCtxDestroy(io); + } catch (final Exception e) { + s_logger.error("A RBD snapshot operation on " + disk.getName() + " failed. The error was: " + e.getMessage()); + } + } else { + /* VM is not running, create a snapshot by ourself */ + final int cmdsTimeout = libvirtComputingResource.getCmdsTimeout(); + final String manageSnapshotPath = libvirtComputingResource.manageSnapshotPath(); + + final Script scriptCommand = new Script(manageSnapshotPath, cmdsTimeout, s_logger); + if (command.getCommandSwitch().equalsIgnoreCase(ManageSnapshotCommand.CREATE_SNAPSHOT)) { + scriptCommand.add("-c", disk.getPath()); + } else { + scriptCommand.add("-d", snapshotPath); + } + + scriptCommand.add("-n", snapshotName); + final String result = scriptCommand.execute(); + if (result != null) { + s_logger.debug("Failed to manage snapshot: " + result); + return new ManageSnapshotAnswer(command, false, "Failed to manage snapshot: " + result); + } + } + } + return new ManageSnapshotAnswer(command, command.getSnapshotId(), disk.getPath() + File.separator + snapshotName, true, null); + } catch (final LibvirtException e) { + s_logger.debug("Failed to manage snapshot: " + e.toString()); + return new ManageSnapshotAnswer(command, false, "Failed to manage snapshot: " + e.toString()); + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/cloudstack/blob/bcf78d3b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRequestWrapper.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRequestWrapper.java b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRequestWrapper.java index 148075b..2656037 100644 --- a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRequestWrapper.java +++ b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRequestWrapper.java @@ -23,12 +23,14 @@ import java.util.Hashtable; import com.cloud.agent.api.Answer; import com.cloud.agent.api.AttachIsoCommand; import com.cloud.agent.api.AttachVolumeCommand; +import com.cloud.agent.api.BackupSnapshotCommand; import com.cloud.agent.api.CheckHealthCommand; import com.cloud.agent.api.CheckNetworkCommand; import com.cloud.agent.api.CheckOnHostCommand; import com.cloud.agent.api.CheckVirtualMachineCommand; import com.cloud.agent.api.CleanupNetworkRulesCmd; import com.cloud.agent.api.Command; +import com.cloud.agent.api.CreatePrivateTemplateFromVolumeCommand; import com.cloud.agent.api.CreateStoragePoolCommand; import com.cloud.agent.api.CreateVolumeFromSnapshotCommand; import com.cloud.agent.api.DeleteStoragePoolCommand; @@ -39,6 +41,7 @@ import com.cloud.agent.api.GetVmDiskStatsCommand; import com.cloud.agent.api.GetVmStatsCommand; import com.cloud.agent.api.GetVncPortCommand; import com.cloud.agent.api.MaintainCommand; +import com.cloud.agent.api.ManageSnapshotCommand; import com.cloud.agent.api.MigrateCommand; import com.cloud.agent.api.ModifySshKeysCommand; import com.cloud.agent.api.ModifyStoragePoolCommand; @@ -136,6 +139,9 @@ public class LibvirtRequestWrapper extends RequestWrapper { linbvirtCommands.put(PlugNicCommand.class, new LibvirtPlugNicCommandWrapper()); linbvirtCommands.put(UnPlugNicCommand.class, new LibvirtUnPlugNicCommandWrapper()); linbvirtCommands.put(NetworkUsageCommand.class, new LibvirtNetworkUsageCommandWrapper()); + linbvirtCommands.put(CreatePrivateTemplateFromVolumeCommand.class, new LibvirtCreatePrivateTemplateFromVolumeCommandWrapper()); + linbvirtCommands.put(ManageSnapshotCommand.class, new LibvirtManageSnapshotCommandWrapper()); + linbvirtCommands.put(BackupSnapshotCommand.class, new LibvirtBackupSnapshotCommandWrapper()); resources.put(LibvirtComputingResource.class, linbvirtCommands); } http://git-wip-us.apache.org/repos/asf/cloudstack/blob/bcf78d3b/plugins/hypervisors/kvm/test/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/kvm/test/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java b/plugins/hypervisors/kvm/test/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java index 9b0be08..b0c2734 100644 --- a/plugins/hypervisors/kvm/test/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java +++ b/plugins/hypervisors/kvm/test/com/cloud/hypervisor/kvm/resource/LibvirtComputingResourceTest.java @@ -66,11 +66,13 @@ import org.xml.sax.SAXException; import com.cloud.agent.api.Answer; import com.cloud.agent.api.AttachIsoCommand; import com.cloud.agent.api.AttachVolumeCommand; +import com.cloud.agent.api.BackupSnapshotCommand; import com.cloud.agent.api.CheckHealthCommand; import com.cloud.agent.api.CheckNetworkCommand; import com.cloud.agent.api.CheckOnHostCommand; import com.cloud.agent.api.CheckVirtualMachineCommand; import com.cloud.agent.api.CleanupNetworkRulesCmd; +import com.cloud.agent.api.CreatePrivateTemplateFromVolumeCommand; import com.cloud.agent.api.CreateStoragePoolCommand; import com.cloud.agent.api.CreateVolumeFromSnapshotCommand; import com.cloud.agent.api.DeleteStoragePoolCommand; @@ -81,6 +83,7 @@ import com.cloud.agent.api.GetVmDiskStatsCommand; import com.cloud.agent.api.GetVmStatsCommand; import com.cloud.agent.api.GetVncPortCommand; import com.cloud.agent.api.MaintainCommand; +import com.cloud.agent.api.ManageSnapshotCommand; import com.cloud.agent.api.MigrateCommand; import com.cloud.agent.api.ModifySshKeysCommand; import com.cloud.agent.api.ModifyStoragePoolCommand; @@ -3313,7 +3316,6 @@ public class LibvirtComputingResourceTest { final Answer answer = wrapper.execute(command, libvirtComputingResource); assertTrue(answer.getResult()); - //Being called twice, although I did not find the second place yet. verify(libvirtComputingResource, times(1)).networkUsage(command.getPrivateIP(), "create", null); } @@ -3337,7 +3339,6 @@ public class LibvirtComputingResourceTest { final Answer answer = wrapper.execute(command, libvirtComputingResource); assertTrue(answer.getResult()); - //Being called twice, although I did not find the second place yet. verify(libvirtComputingResource, times(1)).configureVPCNetworkUsage(command.getPrivateIP(), command.getGatewayIP(), "create", command.getVpcCIDR()); } @@ -3360,7 +3361,6 @@ public class LibvirtComputingResourceTest { final Answer answer = wrapper.execute(command, libvirtComputingResource); assertTrue(answer.getResult()); - //Being called twice, although I did not find the second place yet. verify(libvirtComputingResource, times(1)).getVPCNetworkStats(command.getPrivateIP(), command.getGatewayIP(), command.getOption()); } @@ -3383,7 +3383,6 @@ public class LibvirtComputingResourceTest { final Answer answer = wrapper.execute(command, libvirtComputingResource); assertTrue(answer.getResult()); - //Being called twice, although I did not find the second place yet. verify(libvirtComputingResource, times(1)).getVPCNetworkStats(command.getPrivateIP(), command.getGatewayIP(), command.getOption()); } @@ -3406,7 +3405,135 @@ public class LibvirtComputingResourceTest { final Answer answer = wrapper.execute(command, libvirtComputingResource); assertTrue(answer.getResult()); - //Being called twice, although I did not find the second place yet. verify(libvirtComputingResource, times(1)).configureVPCNetworkUsage(command.getPrivateIP(), command.getGatewayIP(), command.getOption(), command.getVpcCIDR()); } + + @SuppressWarnings("unchecked") + @Test + public void testCreatePrivateTemplateFromVolumeCommand() { + //Simple test used to make sure the flow (LibvirtComputingResource => Request => CommandWrapper) is working. + //The code is way to big and complex. Will finish the refactor and come back to this to add more cases. + + final StoragePool pool = Mockito.mock(StoragePool.class);; + final String secondaryStorageUrl = "nfs:/192.168.2.2/storage/secondary"; + final long templateId = 1l; + final long accountId = 1l; + final String userSpecifiedName = "User"; + final String uniqueName = "Unique"; + final String volumePath = "/123/vol"; + final String vmName = "Test"; + final int wait = 0; + + final CreatePrivateTemplateFromVolumeCommand command = new CreatePrivateTemplateFromVolumeCommand(pool, secondaryStorageUrl, templateId, accountId, userSpecifiedName, uniqueName, volumePath, vmName, wait); + + final KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class); + final KVMStoragePool secondaryStorage = Mockito.mock(KVMStoragePool.class); + //final KVMStoragePool primary = Mockito.mock(KVMStoragePool.class); + + when(libvirtComputingResource.getStoragePoolMgr()).thenReturn(storagePoolMgr); + when(storagePoolMgr.getStoragePoolByURI(secondaryStorageUrl)).thenReturn(secondaryStorage); + when(storagePoolMgr.getStoragePool(command.getPool().getType(), command.getPrimaryStoragePoolNameLabel())).thenThrow(new CloudRuntimeException("error")); + + final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance(); + assertNotNull(wrapper); + + final Answer answer = wrapper.execute(command, libvirtComputingResource); + assertFalse(answer.getResult()); + + verify(libvirtComputingResource, times(1)).getStoragePoolMgr(); + verify(storagePoolMgr, times(1)).getStoragePoolByURI(secondaryStorageUrl); + verify(storagePoolMgr, times(1)).getStoragePool(command.getPool().getType(), command.getPrimaryStoragePoolNameLabel()); + } + + @SuppressWarnings("unchecked") + @Test + public void testManageSnapshotCommandLibvirtException() { + //Simple test used to make sure the flow (LibvirtComputingResource => Request => CommandWrapper) is working. + //The code is way to big and complex. Will finish the refactor and come back to this to add more cases. + + final StoragePool pool = Mockito.mock(StoragePool.class);; + final String volumePath = "/123/vol"; + final String vmName = "Test"; + + final long snapshotId = 1l; + final String preSnapshotPath = "/snapshot/path"; + final String snapshotName = "snap"; + + final ManageSnapshotCommand command = new ManageSnapshotCommand(snapshotId, volumePath, pool, preSnapshotPath, snapshotName, vmName); + + final LibvirtConnectionWrapper libvirtConnectionWrapper = Mockito.mock(LibvirtConnectionWrapper.class); + //final Connect conn = Mockito.mock(Connect.class); + + when(libvirtComputingResource.getLibvirtConnectionWrapper()).thenReturn(libvirtConnectionWrapper); + + try { + when(libvirtConnectionWrapper.getConnectionByVmName(command.getVmName())).thenThrow(LibvirtException.class); + } catch (final LibvirtException e) { + fail(e.getMessage()); + } + + final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance(); + assertNotNull(wrapper); + + final Answer answer = wrapper.execute(command, libvirtComputingResource); + assertFalse(answer.getResult()); + + verify(libvirtComputingResource, times(1)).getLibvirtConnectionWrapper(); + try { + verify(libvirtConnectionWrapper, times(1)).getConnectionByVmName(command.getVmName()); + } catch (final LibvirtException e) { + fail(e.getMessage()); + } + } + + @SuppressWarnings("unchecked") + @Test + public void testBackupSnapshotCommandLibvirtException() { + //Simple test used to make sure the flow (LibvirtComputingResource => Request => CommandWrapper) is working. + //The code is way to big and complex. Will finish the refactor and come back to this to add more cases. + + final StoragePool pool = Mockito.mock(StoragePool.class);; + final String secondaryStorageUrl = "nfs:/192.168.2.2/storage/secondary"; + final long accountId = 1l; + final String volumePath = "/123/vol"; + final String vmName = "Test"; + final int wait = 0; + + final long snapshotId = 1l; + final String snapshotName = "snap"; + + final Long dcId = 1l; + final Long volumeId = 1l; + final Long secHostId = 1l; + final String snapshotUuid = "9a0afe7c-26a7-4585-bf87-abf82ae106d9"; + final String prevBackupUuid = "003a0cc2-2e04-417a-bee0-534ef1724561"; + final boolean isVolumeInactive = false; + final String prevSnapshotUuid = "1791efae-f22d-474b-87c6-92547d6c5877"; + + final BackupSnapshotCommand command = new BackupSnapshotCommand(secondaryStorageUrl, dcId, accountId, volumeId, snapshotId, secHostId, volumePath, pool, snapshotUuid, snapshotName, prevSnapshotUuid, prevBackupUuid, isVolumeInactive, vmName, wait); + + final LibvirtConnectionWrapper libvirtConnectionWrapper = Mockito.mock(LibvirtConnectionWrapper.class); + //final Connect conn = Mockito.mock(Connect.class); + + when(libvirtComputingResource.getLibvirtConnectionWrapper()).thenReturn(libvirtConnectionWrapper); + + try { + when(libvirtConnectionWrapper.getConnectionByVmName(command.getVmName())).thenThrow(LibvirtException.class); + } catch (final LibvirtException e) { + fail(e.getMessage()); + } + + final LibvirtRequestWrapper wrapper = LibvirtRequestWrapper.getInstance(); + assertNotNull(wrapper); + + final Answer answer = wrapper.execute(command, libvirtComputingResource); + assertFalse(answer.getResult()); + + verify(libvirtComputingResource, times(1)).getLibvirtConnectionWrapper(); + try { + verify(libvirtConnectionWrapper, times(1)).getConnectionByVmName(command.getVmName()); + } catch (final LibvirtException e) { + fail(e.getMessage()); + } + } } \ No newline at end of file