Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
68a403c
test: support Ceph/RBD primary storage in NAS backup smoke test
weizhouapache Sep 8, 2026
5fa0054
KVM: support incremental NAS backups of RBD volumes
weizhouapache Oct 12, 2026
0cdbf56
test: run the NAS backup smoke test on a cloud-init template
weizhouapache Oct 12, 2026
a4a8c27
test: validate a whole backup chain, including across a mid-chain delete
weizhouapache Oct 12, 2026
3321f0a
test: verify the guest of a VM created from a backup, not just that i…
weizhouapache Oct 12, 2026
a8fddbf
nasbackup: carry the backup method separately from the mode
weizhouapache Oct 14, 2026
935adf0
utils: add RbdUtils for the Ceph monitors of an RBD pool
weizhouapache Oct 14, 2026
5b393a1
kvm: build the Ceph monitors of the restore backup command with RbdUtils
weizhouapache Oct 14, 2026
27012a3
backup: name the rbd method in the TakeBackupCommand comment
weizhouapache Oct 14, 2026
88d1990
Add package dependencies
weizhouapache Oct 13, 2026
a74c7c4
Add ceph-common as dependencies
weizhouapache Oct 14, 2026
9786f67
kvm: refuse an RBD restore that would destroy a snapshot we did not take
weizhouapache Oct 14, 2026
8d8cd5e
kvm: drop the cephx key file from a trap in nasbackup.sh
weizhouapache Oct 14, 2026
39cc104
utils: test the escaped colons of an IPv6 Ceph monitor without a port
weizhouapache Oct 15, 2026
bafa96b
kvm: remove the backup anchor once the chain it anchors is gone
weizhouapache Oct 21, 2026
38d5fb7
nas: decide a chain from its flavour rather than from an rbd flag
weizhouapache Oct 21, 2026
eb41943
Merge remote-tracking branch 'apache/main' into 24-incremental-backup…
weizhouapache Oct 22, 2026
95a7402
packaging: suggest ceph-common instead of requiring it
weizhouapache Oct 22, 2026
729de6e
nas: address review of the RBD incremental backups
weizhouapache Oct 22, 2026
fcceb02
nas: run RBD backups on a host that has the rbd command
weizhouapache Oct 23, 2026
5bd6a26
nas: restore RBD backups on a host that has the rbd command
weizhouapache Oct 23, 2026
beb3952
import com.cloud.utils.exception.CloudRuntimeException
weizhouapache Oct 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/src/main/java/com/cloud/host/Host.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ public static String[] toStrings(Host.Type... types) {
String HOST_KVM_DISK_ONLY_VM_SNAPSHOT_NVRAM = "host.kvm.diskonlyvmsnapshot.nvram";
String HOST_VOLUME_ENCRYPTION = "host.volume.encryption";
String HOST_RBD_VOLUME_ENCRYPTION = "host.volume.encryption.rbd";
String HOST_RBD_CLI = "host.rbd.cli";
String HOST_INSTANCE_CONVERSION = "host.instance.conversion";
String HOST_VDDK_SUPPORT = "host.vddk.support";
String HOST_VDDK_LIB_DIR = "vddk.lib.dir";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//
// 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 org.apache.cloudstack.backup;

import java.util.List;

import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;

import com.cloud.agent.api.Command;

/**
* Remove the backup anchors of an Instance's volumes: the RBD snapshots an incremental chain is
* exported from. An anchor is only kept to export the next incremental from it, so once the chain
* is gone the next backup is a full one and the anchor would only keep the data it references
* allocated in the pool.
*/
public class RemoveBackupAnchorsCommand extends Command {
private List<PrimaryDataStoreTO> volumePools;
private List<String> volumePaths;

public RemoveBackupAnchorsCommand(List<PrimaryDataStoreTO> volumePools, List<String> volumePaths) {
super();
this.volumePools = volumePools;
this.volumePaths = volumePaths;
}

public List<PrimaryDataStoreTO> getVolumePools() {
return volumePools;
}

public void setVolumePools(List<PrimaryDataStoreTO> volumePools) {
this.volumePools = volumePools;
}

public List<String> getVolumePaths() {
return volumePaths;
}

public void setVolumePaths(List<String> volumePaths) {
this.volumePaths = volumePaths;
}

@Override
public boolean executeInSequence() {
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ public class RestoreBackupCommand extends Command {
private VirtualMachine.State vmState;
private Integer mountTimeout;

/**
* On-NAS format of the backup files: {@code qcow2} (default, also when null) or {@code rbddiff}
* for Ceph/RBD chains written with {@code rbd export-diff}. Decides both the file extension the
* agent looks for and how the volume is reconstructed.
*/
private String backupFormat;

/**
* For {@code rbddiff} backups: every backup directory of the chain from its full anchor up to
* and including the backup being restored, ordered root-first. {@code rbd import-diff} streams
* have no backing-file metadata, so the chain cannot be self-describing the way rebased qcow2
* files are — the agent has to be told which diffs to replay and in which order. Null/empty for
* qcow2 backups.
*/
private List<String> backupChainPaths;

protected RestoreBackupCommand() {
super();
}
Expand Down Expand Up @@ -164,4 +180,20 @@ public Integer getMountTimeout() {
public void setMountTimeout(Integer mountTimeout) {
this.mountTimeout = mountTimeout;
}

public String getBackupFormat() {
return backupFormat;
}

public void setBackupFormat(String backupFormat) {
this.backupFormat = backupFormat;
}

public List<String> getBackupChainPaths() {
return backupChainPaths;
}

public void setBackupChainPaths(List<String> backupChainPaths) {
this.backupChainPaths = backupChainPaths;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class TakeBackupCommand extends Command {

// Incremental backup fields (NAS provider; null/empty for legacy full-only callers).
private String mode; // "full" or "incremental"; null => legacy behaviour (script default)
private String method; // "qcow2" (bitmaps, push mode), "content" (pull mode) or "rbd" (Ceph export-diff); null => "qcow2"
private String bitmapNew; // Checkpoint/bitmap name to create with this backup (timestamp-based)
private String bitmapParent; // Incremental: parent bitmap to read changes since

Expand Down Expand Up @@ -134,6 +135,14 @@ public void setMode(String mode) {
this.mode = mode;
}

public String getMethod() {
return method;
}

public void setMethod(String method) {
this.method = method;
}

public String getBitmapNew() {
return bitmapNew;
}
Expand Down
1 change: 1 addition & 0 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Package: cloudstack-agent
Architecture: all
Depends: ${python:Depends}, ${python3:Depends}, openjdk-17-jre-headless | java17-runtime-headless | java17-runtime | zulu-17, cloudstack-common (= ${source:Version}), lsb-base (>= 9), openssh-client, qemu-kvm (>= 2.5) | qemu-system-x86 (>= 5.2), libvirt-bin (>= 1.3) | libvirt-daemon-system (>= 3.0), iproute2, ebtables, vlan, ipset, python3-libvirt, ethtool, iptables, cryptsetup, rng-tools, rsync, ovmf, swtpm, lsb-release, ufw, apparmor, cpu-checker, libvirt-daemon-driver-storage-rbd, sysstat, python3-libnbd, socat
Recommends: init-system-helpers
Suggests: ceph-common
Conflicts: cloud-agent, cloud-agent-libs, cloud-agent-deps, cloud-agent-scripts
Description: CloudStack agent
The CloudStack agent is in charge of managing shared computing resources in
Expand Down
1 change: 1 addition & 0 deletions packaging/el8/cloud.spec
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ Requires: (libgcrypt > 1.8.3 or libgcrypt20)
Requires: (selinux-tools if selinux-tools)
Requires: sysstat
Requires: python3-libnbd
Suggests: ceph-common
Requires: socat
Provides: cloud-agent
Group: System Environment/Libraries
Expand Down
1 change: 1 addition & 0 deletions packaging/suse15/cloud.spec
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ Requires: cifs-utils
Requires: (edk2-ovmf or qemu-ovmf-x86_64)
Requires: swtpm
Requires: (python3-libvirt or python3-libvirt-python)
Suggests: ceph-common
Requires: (qemu-img or qemu-tools)
Requires: qemu-kvm
Requires: cryptsetup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,21 @@ public final class NASBackupChainKeys {
/** UUID of the parent backup (full or previous incremental). Empty for full backups. */
public static final String PARENT_BACKUP_ID = "nas.parent_backup_id";

/** QEMU dirty-bitmap name created by this backup, used as the {@code <incremental>} reference for the next one. */
/**
* Name of the point-in-time anchor this backup created, used as the parent reference for the
* next backup in the chain. Its meaning depends on the chain format: for {@link #FORMAT_QCOW2}
* chains it is the QEMU dirty-bitmap name used as the {@code <incremental>} reference; for
* {@link #FORMAT_RBD_DIFF} chains it is the RBD snapshot name used as {@code --from-snap}.
*/
public static final String BITMAP_NAME = "nas.bitmap_name";

/**
* On-NAS format of the chain this backup belongs to: {@link #FORMAT_QCOW2} (rebased qcow2
* files, the file-based storage path) or {@link #FORMAT_RBD_DIFF} ({@code rbd export-diff}
* streams, the Ceph/RBD path). Absent on legacy-full backups, which are always qcow2.
*/
public static final String CHAIN_FORMAT = "nas.chain_format";

/** Identifier shared by every backup in the same chain (the full anchors a chain; its incrementals inherit the id). */
public static final String CHAIN_ID = "nas.chain_id";

Expand All @@ -48,6 +60,31 @@ public final class NASBackupChainKeys {
// "legacy-full" mode token (which sets make_checkpoint=0).
public static final String TYPE_LEGACY_FULL = "legacy-full";

/**
* In-memory backup-method sentinels used by {@code ChainDecision.method}: how a backup is
* taken, independent of whether it is a full or an incremental ({@code ChainDecision.mode}).
*
* <p>{@link #METHOD_QCOW2}: libvirt push-mode backup; incrementals come from QEMU persistent
* dirty bitmaps (libvirt checkpoints), which only qcow2 volumes can carry.</p>
*/
public static final String METHOD_QCOW2 = "qcow2";

/**
* Content-based libvirt pull-mode backup, used for raw block-device storage such as
* LINSTOR/DRBD which cannot carry persistent dirty bitmaps. A full is written as a sparse
* qcow2 in a single pass; an incremental is a qcow2 overlay on the disk's NBD export,
* safe-rebased onto the parent so it holds only the clusters that differ.
*/
public static final String METHOD_CONTENT = "content";
Comment thread
weizhouapache marked this conversation as resolved.

/**
* Ceph-native backup, used for RBD volumes. They are raw and cannot carry a persistent dirty
* bitmap, so each cycle anchors on an RBD snapshot and exports the delta with
* {@code rbd export-diff}. Unlike the other methods this one writes {@link #FORMAT_RBD_DIFF}
* streams rather than qcow2 files, which is what {@link #CHAIN_FORMAT} records for restore.
*/
public static final String METHOD_RBD = "rbd";

/**
* VM-scoped detail (stored in {@code vm_instance_details}) holding the QEMU dirty-bitmap
* name that currently exists on the running VM and is therefore the only valid parent
Expand All @@ -57,6 +94,10 @@ public final class NASBackupChainKeys {
*/
public static final String VM_ACTIVE_CHECKPOINT_ID = "nas.active_checkpoint_id";

/** Chain format values persisted under {@link #CHAIN_FORMAT}; also the on-NAS file extension. */
public static final String FORMAT_QCOW2 = "qcow2";
public static final String FORMAT_RBD_DIFF = "rbddiff";

private NASBackupChainKeys() {
}
}
Loading
Loading