Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 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
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
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
2 changes: 1 addition & 1 deletion debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Description: CloudStack server library

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
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, python3-rados, python3-rbd, ceph-common, socat

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we add python3-rados and rbd in this PR? Because those Python libraries aren't used here

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is just to be consistent with the #14320 which requires the python libraries.
we can remove them

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guessed so. Technically its not part of this PR, but I doesn't hurt that much.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree. ideally nasbackup.sh is rewritten with python, it is a big change

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking of moving most of the nasbackup.sh functionality to Java, with some unavoidable code paths still in shell (or python)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But having it in a script has its use in quickly fixing an issue directly on a deployment.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is ceph-common available by default on all distros? specifically ubuntu?

Recommends: init-system-helpers
Conflicts: cloud-agent, cloud-agent-libs, cloud-agent-deps, cloud-agent-scripts
Description: CloudStack agent
Expand Down
3 changes: 3 additions & 0 deletions packaging/el8/cloud.spec
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ Requires: (libgcrypt > 1.8.3 or libgcrypt20)
Requires: (selinux-tools if selinux-tools)
Requires: sysstat
Requires: python3-libnbd
Requires: python3-rados
Requires: python3-rbd

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Requires: ceph-common
Requires: socat
Provides: cloud-agent
Group: System Environment/Libraries
Expand Down
3 changes: 3 additions & 0 deletions packaging/suse15/cloud.spec
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ Requires: cifs-utils
Requires: (edk2-ovmf or qemu-ovmf-x86_64)
Requires: swtpm
Requires: (python3-libvirt or python3-libvirt-python)
Requires: (python3-rados or python3-ceph-common)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And for these as well, tools aren't used in this PR

Requires: (python3-rbd or python3-ceph-common)
Requires: 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