prototype backup e2e

Signed-off-by: Rohit Yadav <rohit.yadav@shapeblue.com>
This commit is contained in:
Rohit Yadav 2024-08-01 14:31:13 +05:30
parent f3bc03955a
commit f13769d33d
6 changed files with 168 additions and 35 deletions

View File

@ -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<String, String> 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<String, String> getVolumes() {
return volumes;
}
public void setPath(String path) {
this.path = path;
public void setVolumes(Map<String, String> volumes) {
this.volumes = volumes;
}
}

View File

@ -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<String, String> details;
public TakeBackupCommand(String vmName, String backupStoragePath, Map<String, String> details) {
public TakeBackupCommand(String vmName, String backupPath, String backupStoragePath, Map<String, String> 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;
}

View File

@ -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<String, String> 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());

View File

@ -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");

View File

@ -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<TakeBackupCommand, Answer, LibvirtComputingResource> {
@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;
}
}

View File

@ -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 "<domainbackup mode='push'><disks>" > $dest/backup.xml
for disk in $(virsh -c qemu:///system domblklist $vm --details | awk '/disk/{print$3}'); do
echo "<disk name='$disk' backup='yes' type='file' backupmode='full'><driver type='qcow2'/><target file='$dest/$disk' /></disk>" >> $dest/backup.xml
done
echo "</disks></domainbackup>" > $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 <domain> -s <NAS storage mount path> -p <backup dest path>"
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