diff --git a/core/src/main/java/org/apache/cloudstack/backup/BackupAnswer.java b/core/src/main/java/org/apache/cloudstack/backup/BackupAnswer.java index c73ad3ab082..c5e52850a33 100644 --- a/core/src/main/java/org/apache/cloudstack/backup/BackupAnswer.java +++ b/core/src/main/java/org/apache/cloudstack/backup/BackupAnswer.java @@ -21,16 +21,15 @@ package org.apache.cloudstack.backup; import com.cloud.agent.api.Answer; +import java.util.Map; + public class BackupAnswer extends Answer { Long size; Long virtualSize; - String path; + Map volumes; - public BackupAnswer(TakeBackupCommand command, Long size, Long virtualSize, String path) { - super(command); - this.size = size; - this.virtualSize = virtualSize; - this.path = path; + public BackupAnswer(final TakeBackupCommand command, final boolean success, final String details) { + super(command, success, details); } public Long getSize() { @@ -49,11 +48,11 @@ public class BackupAnswer extends Answer { this.virtualSize = virtualSize; } - public String getPath() { - return path; + public Map getVolumes() { + return volumes; } - public void setPath(String path) { - this.path = path; + public void setVolumes(Map volumes) { + this.volumes = volumes; } } diff --git a/core/src/main/java/org/apache/cloudstack/backup/TakeBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/TakeBackupCommand.java index a72c6744e0b..91a49f1ce30 100644 --- a/core/src/main/java/org/apache/cloudstack/backup/TakeBackupCommand.java +++ b/core/src/main/java/org/apache/cloudstack/backup/TakeBackupCommand.java @@ -26,13 +26,15 @@ import java.util.Map; public class TakeBackupCommand extends Command { private String vmName; + private String backupPath; private String backupStoragePath; @LogLevel(LogLevel.Log4jLevel.Off) private Map details; - public TakeBackupCommand(String vmName, String backupStoragePath, Map details) { + public TakeBackupCommand(String vmName, String backupPath, String backupStoragePath, Map details) { super(); this.vmName = vmName; + this.backupPath = backupPath; this.backupStoragePath = backupStoragePath; this.details = details; } @@ -45,6 +47,14 @@ public class TakeBackupCommand extends Command { this.vmName = vmName; } + public String getBackupPath() { + return backupPath; + } + + public void setBackupPath(String backupPath) { + this.backupPath = backupPath; + } + public String getBackupStoragePath() { return backupStoragePath; } diff --git a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java index d18ab7aa7a3..5dfaf4f3008 100644 --- a/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java +++ b/plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java @@ -43,6 +43,7 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; import javax.inject.Inject; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -139,19 +140,21 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co @Override public boolean takeBackup(VirtualMachine vm) { - Host host = getRunningVMHypervisorHost(vm); + final Host host = getRunningVMHypervisorHost(vm); if (host == null || !Status.Up.equals(host.getStatus()) || !Hypervisor.HypervisorType.KVM.equals(host.getHypervisorType())) { throw new CloudRuntimeException("Unable to contact backend control plane to initiate backup"); } - BackupOfferingVO backupOffering = new BackupOfferingDaoImpl().findById(vm.getBackupOfferingId()); + final BackupOfferingVO backupOffering = new BackupOfferingDaoImpl().findById(vm.getBackupOfferingId()); + final String backupStoragePath = getBackupStoragePath(vm.getDataCenterId()); final String nasType = getNasType(vm.getDataCenterId()); final Map backupDetails = Map.of( "type", nasType ); + final String backupPath = new SimpleDateFormat("yyyy.MM.dd.HH.mm.ss").format(new java.util.Date()); - TakeBackupCommand command = new TakeBackupCommand(vm.getInstanceName(), backupStoragePath, backupDetails); + TakeBackupCommand command = new TakeBackupCommand(vm.getInstanceName(), backupPath, backupStoragePath, backupDetails); BackupAnswer answer = null; try { @@ -165,7 +168,7 @@ public class NASBackupProvider extends AdapterBase implements BackupProvider, Co if (answer != null) { BackupVO backup = new BackupVO(); backup.setVmId(vm.getId()); - backup.setExternalId(String.format("%s|%s|%s", nasType, backupStoragePath, answer.getPath())); + backup.setExternalId(String.format("%s|%s|%s", nasType, backupStoragePath, backupPath)); backup.setType("FULL"); backup.setDate(new Date()); backup.setSize(answer.getSize()); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java index dec7f70e62f..7ed6e7203de 100644 --- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java @@ -326,6 +326,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv private String createTmplPath; private String heartBeatPath; private String vmActivityCheckPath; + private String nasBackupPath; private String securityGroupPath; private String ovsPvlanDhcpHostPath; private String ovsPvlanVmPath; @@ -714,6 +715,10 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv return vmActivityCheckPath; } + public String getNasBackupPath() { + return nasBackupPath; + } + public String getOvsPvlanDhcpHostPath() { return ovsPvlanDhcpHostPath; } @@ -984,6 +989,11 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv throw new ConfigurationException("Unable to find kvmvmactivity.sh"); } + nasBackupPath = Script.findScript(kvmScriptsDir, "nasbackup.sh"); + if (nasBackupPath == null) { + throw new ConfigurationException("Unable to find nasbackup.sh"); + } + createTmplPath = Script.findScript(storageScriptsDir, "createtmplt.sh"); if (createTmplPath == null) { throw new ConfigurationException("Unable to find the createtmplt.sh"); diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtTakeBackupCommandWrapper.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtTakeBackupCommandWrapper.java new file mode 100644 index 00000000000..740749376b4 --- /dev/null +++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtTakeBackupCommandWrapper.java @@ -0,0 +1,52 @@ +// +// 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 com.cloud.agent.api.Answer; +import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource; +import com.cloud.resource.CommandWrapper; +import com.cloud.resource.ResourceWrapper; +import com.cloud.utils.script.Script; +import org.apache.cloudstack.backup.BackupAnswer; +import org.apache.cloudstack.backup.TakeBackupCommand; + +import java.io.File; + +@ResourceWrapper(handles = TakeBackupCommand.class) +public class LibvirtTakeBackupCommandWrapper extends CommandWrapper { + @Override + public Answer execute(TakeBackupCommand command, LibvirtComputingResource libvirtComputingResource) { + final String vmName = command.getVmName(); + final String backupStoragePath = command.getBackupStoragePath(); + final String backupFolder = command.getBackupPath(); + Script cmd = new Script(libvirtComputingResource.getNasBackupPath(), libvirtComputingResource.getCmdsTimeout(), logger); + cmd.add("-b", vmName); + cmd.add("-s", backupStoragePath); + cmd.add("-p", String.format("%s%s%s", vmName, File.separator, backupFolder)); + String result = cmd.execute(); + if (result == null) { + logger.debug("Failed to take VM backup: " + result); + return new BackupAnswer(command, false, result); + } + BackupAnswer answer = new BackupAnswer(command, true, null); + answer.setSize(Long.valueOf(result)); + return answer; + } +} diff --git a/scripts/vm/hypervisor/kvm/nasbackup.sh b/scripts/vm/hypervisor/kvm/nasbackup.sh index 053c97243cc..d354e991b74 100755 --- a/scripts/vm/hypervisor/kvm/nasbackup.sh +++ b/scripts/vm/hypervisor/kvm/nasbackup.sh @@ -16,30 +16,89 @@ ## specific language governing permissions and limitations ## under the License. -# CloudStack B&R NAS (KVM) Backup and Recovery Tool +# CloudStack B&R NAS Backup and Recovery Tool for KVM -# TODO: do libvirt version & other dependency checks +# TODO: do libvirt/logging etc checks -# TODO: logging & args/opts handling +backup_vm() { + vm=$1 + path=$2 + storage=$3 -backup_domain() { - domain=$1 # TODO: get as opt? - # TODO: santiy checks on domain - # get domblklist - # gather external snapshot diskspec/target - # ensure the target nas is mounted / path is known or available - # virsh snapshot-create-as -> snapshot domain with --no-metadata--atomic --quiesce (?) --disk-only - # merge/commit target -> domblklist; blockcommit - # backup this domain & its disks, xml completely - # cleanup snapshot(s) + mount_point=$(mktemp -d -t csbackup.XXXXX) + dest="$mount_point/$path" + + mount -t nfs $storage $mount_point + mkdir -p $dest + + echo "" > $dest/backup.xml + for disk in $(virsh -c qemu:///system domblklist $vm --details | awk '/disk/{print$3}'); do + echo "" >> $dest/backup.xml + done + echo "" > $dest/backup.xml + + virsh -c qemu:///system backup-begin --domain $vm --backupxml $dest/backup.xml + virsh -c qemu:///system dumpxml $vm > $dest/domain-$vm.xml + sync + + # Print directory size + du -sb $dest | cut -f1 + umount $mount_point + rmdir $mount_point } -restore_all_volumes() { - # check and mount nas target & copy files - # check and restore all volumes +OP="" +VM="" +PATH="" +NAS="" +TYPE="" + +function usage { + echo "" + echo "Usage: $0 -b -s -p " + echo "" + exit 1 } -restore_volume() { - # check and mount nas target & copy files - # check and restore specific volume (qcow2) -} +while [[ $# -gt 0 ]]; do + case $1 in + -b|--backup) + OP="backup" + VM="$2" + shift + shift + ;; + -s|--storage) + NAS="$2" + TYPE="nfs" + shift + shift + ;; + -p|--path) + PATH="$2" + shift + shift + ;; + -r|--recover) + OP="recover" + shift + ;; + -rv|--recover) + OP="recover-volume" + shift + ;; + -h|--help) + usage + shift + ;; + *) + echo "Invalid option: $1" + usage + ;; + esac +done + +if [ "$OP" = "backup" ]; then + backup_vm $VM $PATH $NAS +fi +