mirror of https://github.com/apache/cloudstack.git
Fix backup after adding a volume
This commit is contained in:
parent
77d7d43a4f
commit
d3798e1251
|
|
@ -19,12 +19,18 @@ package com.cloud.hypervisor.kvm.resource.wrapper;
|
|||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cloudstack.backup.StartBackupAnswer;
|
||||
import org.apache.cloudstack.backup.StartBackupCommand;
|
||||
import org.apache.cloudstack.utils.qemu.QemuCommand;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
import org.libvirt.Domain;
|
||||
import org.libvirt.LibvirtException;
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
import com.cloud.resource.CommandWrapper;
|
||||
|
|
@ -150,31 +156,47 @@ public class LibvirtStartBackupCommandWrapper extends CommandWrapper<StartBackup
|
|||
return xml.toString();
|
||||
}
|
||||
|
||||
private String createBackupXml(StartBackupCommand cmd, String fromCheckpointId, String socket, LibvirtComputingResource resource) {
|
||||
private String createBackupXml(StartBackupCommand cmd, String fromCheckpointId, String socket, LibvirtComputingResource resource) throws LibvirtException {
|
||||
StringBuilder xml = new StringBuilder();
|
||||
xml.append("<domainbackup mode=\"pull\">\n");
|
||||
|
||||
if (StringUtils.isNotBlank(fromCheckpointId)) {
|
||||
xml.append(" <incremental>").append(fromCheckpointId).append("</incremental>\n");
|
||||
}
|
||||
|
||||
xml.append(String.format(" <server transport=\"unix\" socket=\"/tmp/imagetransfer/%s.sock\"/>\n", socket));
|
||||
|
||||
xml.append(" <disks>\n");
|
||||
|
||||
Map<String, String> diskPathUuidMap = cmd.getDiskPathUuidMap();
|
||||
Map<String, String> diskPathLabelMap = resource.getDiskPathLabelMap(cmd.getVmName());
|
||||
Map<String, Boolean> diskPathHasFromCheckpointMap = new HashMap<>();
|
||||
if (StringUtils.isNotBlank(fromCheckpointId)) {
|
||||
Domain vm = null;
|
||||
try {
|
||||
vm = resource.getDomain(resource.getLibvirtUtilitiesHelper().getConnection(), cmd.getVmName());
|
||||
if (vm != null) {
|
||||
diskPathHasFromCheckpointMap = getVmDiskPathHasFromCheckpointMap(vm, fromCheckpointId);
|
||||
} else {
|
||||
logger.warn("Failed to get domain for VM [{}] while evaluating export bitmap [{}]. Falling back to full Backup",
|
||||
cmd.getVmName(), fromCheckpointId);
|
||||
}
|
||||
} finally {
|
||||
if (vm != null) {
|
||||
vm.free();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<String, String> entry : diskPathLabelMap.entrySet()) {
|
||||
if (!diskPathUuidMap.containsKey(entry.getKey())) {
|
||||
String diskPath = entry.getKey();
|
||||
if (!diskPathUuidMap.containsKey(diskPath)) {
|
||||
continue;
|
||||
}
|
||||
String diskName = entry.getValue();
|
||||
String export = diskPathUuidMap.get(entry.getKey());
|
||||
String export = diskPathUuidMap.get(diskPath);
|
||||
String scratchFile = "/var/tmp/scratch-" + export + ".qcow2";
|
||||
xml.append(" <disk name=\"").append(diskName).append("\" type=\"file\" exportname=\"").append(export);
|
||||
if (StringUtils.isNotBlank(fromCheckpointId)) {
|
||||
xml.append("\" exportbitmap=\"").append(fromCheckpointId);
|
||||
if (StringUtils.isNotBlank(fromCheckpointId) && Boolean.TRUE.equals(diskPathHasFromCheckpointMap.get(diskPath))) {
|
||||
xml.append("\" backupmode=\"incremental\"")
|
||||
.append(" incremental=\"").append(fromCheckpointId)
|
||||
.append("\" exportbitmap=\"").append(fromCheckpointId);
|
||||
}
|
||||
xml.append("\">\n");
|
||||
xml.append(" <scratch file=\"").append(scratchFile).append("\"/>\n");
|
||||
|
|
@ -194,7 +216,6 @@ public class LibvirtStartBackupCommandWrapper extends CommandWrapper<StartBackup
|
|||
}
|
||||
|
||||
private Answer handleStoppedVmBackup(StartBackupCommand cmd, String toCheckpointId) {
|
||||
String vmName = cmd.getVmName();
|
||||
Map<String, String> diskPathUuidMap = cmd.getDiskPathUuidMap();
|
||||
for (Map.Entry<String, String> entry : diskPathUuidMap.entrySet()) {
|
||||
String diskPath = entry.getKey();
|
||||
|
|
@ -218,4 +239,43 @@ public class LibvirtStartBackupCommandWrapper extends CommandWrapper<StartBackup
|
|||
private long getCheckpointCreateTime() {
|
||||
return System.currentTimeMillis() / 1000;
|
||||
}
|
||||
|
||||
private Map<String, Boolean> getVmDiskPathHasFromCheckpointMap(Domain vm, String fromCheckpointId) throws LibvirtException {
|
||||
Map<String, Boolean> diskPathHasFromCheckpointMap = new HashMap<>();
|
||||
String queryBlock = vm.qemuMonitorCommand(QemuCommand.buildQemuCommand("query-block", null), 0);
|
||||
JSONObject response = new JSONObject(queryBlock);
|
||||
JSONArray blocks = response.optJSONArray("return");
|
||||
if (blocks == null) {
|
||||
logger.warn("Couldn't get bitmap information for the VM [{}]. Falling back to full Backup", vm.getName());
|
||||
return diskPathHasFromCheckpointMap;
|
||||
}
|
||||
for (int i = 0; i < blocks.length(); i++) {
|
||||
JSONObject block = blocks.getJSONObject(i);
|
||||
JSONObject inserted = block.optJSONObject("inserted");
|
||||
if (inserted == null) {
|
||||
continue;
|
||||
}
|
||||
String file = inserted.optString("file");
|
||||
if (StringUtils.isBlank(file)) {
|
||||
continue;
|
||||
}
|
||||
JSONArray dirtyBitmaps = inserted.optJSONArray("dirty-bitmaps");
|
||||
boolean hasFromCheckpointBitmap = false;
|
||||
if (dirtyBitmaps != null) {
|
||||
for (int j = 0; j < dirtyBitmaps.length(); j++) {
|
||||
JSONObject dirtyBitmap = dirtyBitmaps.optJSONObject(j);
|
||||
if (dirtyBitmap == null) {
|
||||
continue;
|
||||
}
|
||||
String bitmapName = dirtyBitmap.optString("name");
|
||||
if (fromCheckpointId.equals(bitmapName)) {
|
||||
hasFromCheckpointBitmap = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
diskPathHasFromCheckpointMap.put(file, hasFromCheckpointBitmap);
|
||||
}
|
||||
return diskPathHasFromCheckpointMap;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import org.apache.cloudstack.backup.StartNBDServerAnswer;
|
|||
import org.apache.cloudstack.backup.StartNBDServerCommand;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import com.cloud.agent.api.Answer;
|
||||
import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
|
||||
|
|
@ -68,6 +70,11 @@ public class LibvirtStartNBDServerCommandWrapper extends CommandWrapper<StartNBD
|
|||
}
|
||||
|
||||
String socketName = "/tmp/imagetransfer/" + socket + ".sock";
|
||||
String bitmapArg = "";
|
||||
if (StringUtils.isNotBlank(cmd.getFromCheckpointId())
|
||||
&& isBitmapPresentOnDisk(volumePath, cmd.getFromCheckpointId())) {
|
||||
bitmapArg = "-B " + cmd.getFromCheckpointId();
|
||||
}
|
||||
// --persistent: Don't stop the service when the last client disconnects.
|
||||
// --shared=NUM: Allow up to NUM clients to share the device (default 1), 0 for unlimited. Number of parallel connections is managed by the image server.
|
||||
String systemdRunCmd = String.format(
|
||||
|
|
@ -75,7 +82,7 @@ public class LibvirtStartNBDServerCommandWrapper extends CommandWrapper<StartNBD
|
|||
unitName,
|
||||
exportName,
|
||||
socketName,
|
||||
cmd.getFromCheckpointId() != null ? "-B " + cmd.getFromCheckpointId() : "",
|
||||
bitmapArg,
|
||||
cmd.getDirection().equals("download") ? "--read-only" : "",
|
||||
volumePath
|
||||
);
|
||||
|
|
@ -125,4 +132,40 @@ public class LibvirtStartNBDServerCommandWrapper extends CommandWrapper<StartNBD
|
|||
return new StartNBDServerAnswer(cmd, true, "qemu-nbd service started for upload",
|
||||
transferId, transferUrl);
|
||||
}
|
||||
|
||||
private boolean isBitmapPresentOnDisk(String volumePath, String fromCheckpointId) {
|
||||
String qemuImgInfo = Script.runBashScriptIgnoreExitValue(
|
||||
String.format("qemu-img info --output=json %s", volumePath), 0);
|
||||
if (StringUtils.isBlank(qemuImgInfo)) {
|
||||
logger.warn("Unable to read qemu-img info output for disk path [{}].", volumePath);
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
JSONObject info = new JSONObject(qemuImgInfo);
|
||||
JSONObject formatSpecific = info.optJSONObject("format-specific");
|
||||
if (formatSpecific == null) {
|
||||
return false;
|
||||
}
|
||||
JSONObject formatData = formatSpecific.optJSONObject("data");
|
||||
if (formatData == null) {
|
||||
return false;
|
||||
}
|
||||
JSONArray bitmaps = formatData.optJSONArray("bitmaps");
|
||||
if (bitmaps == null) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < bitmaps.length(); i++) {
|
||||
JSONObject bitmap = bitmaps.optJSONObject(i);
|
||||
if (bitmap == null) {
|
||||
continue;
|
||||
}
|
||||
if (fromCheckpointId.equals(bitmap.optString("name"))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to parse qemu-img info output for disk path [{}].", volumePath, e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue