Skip to content

Commit a8fddbf

Browse files
committed
nasbackup: carry the backup method separately from the mode
The RBD path overloaded ChainDecision.mode with rbd-full and rbd-incremental, the same way the content-based path upstream overloaded it with content-full and content-incremental. Split the two concerns as that path already did: mode stays full or incremental, and a new method field says how the backup is taken -- qcow2 (push mode with dirty bitmaps) or rbd (Ceph-native, anchored on an rbd snapshot and exported with rbd export-diff). TakeBackupCommand gets a method field and nasbackup.sh a --method option. Both default to qcow2 when absent, so a management server that does not send a method keeps working against an upgraded agent. legacy-full is unchanged: rewriting it as full plus a "none" method would break incremental-disabled backups on agents that have not been upgraded yet. CHAIN_FORMAT stays as it is. The method says how a backup is taken and lives only for the duration of the request; the chain format is persisted and says what is on the repository, which is what restore and delete read later. The two coincide for the rbd method today but answer different questions. This mirrors c144d09 from #14194, which introduced the method for the content path and named an RBD diff as the next one to adopt it. Kept as close to that commit as the branch allows so the two merge cleanly: this branch carries the RBD path but not the content one, so the METHOD_CONTENT token is defined without a user here.
1 parent 3321f0a commit a8fddbf

6 files changed

Lines changed: 130 additions & 60 deletions

File tree

‎core/src/main/java/org/apache/cloudstack/backup/TakeBackupCommand.java‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public class TakeBackupCommand extends Command {
3838

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

@@ -125,6 +126,14 @@ public void setMode(String mode) {
125126
this.mode = mode;
126127
}
127128

129+
public String getMethod() {
130+
return method;
131+
}
132+
133+
public void setMethod(String method) {
134+
this.method = method;
135+
}
136+
128137
public String getBitmapNew() {
129138
return bitmapNew;
130139
}

‎plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupChainKeys.java‎

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,18 +55,36 @@ public final class NASBackupChainKeys {
5555
*/
5656
public static final String TYPE_FULL = "full";
5757
public static final String TYPE_INCREMENTAL = "incremental";
58-
/**
59-
* Ceph/RBD equivalents of {@link #TYPE_FULL} / {@link #TYPE_INCREMENTAL}. RBD images are raw
60-
* and cannot carry a QEMU dirty bitmap, so these anchor each cycle on an RBD snapshot and
61-
* export the delta with {@code rbd export-diff} instead of a libvirt checkpoint + push backup.
62-
*/
63-
public static final String TYPE_RBD_FULL = "rbd-full";
64-
public static final String TYPE_RBD_INCREMENTAL = "rbd-incremental";
6558
// Feature disabled: behave exactly like the pre-incremental full-only backup — no QEMU
6659
// bitmap/checkpoint is created and no chain metadata is persisted. Matches nasbackup.sh's
6760
// "legacy-full" mode token (which sets make_checkpoint=0).
6861
public static final String TYPE_LEGACY_FULL = "legacy-full";
6962

63+
/**
64+
* In-memory backup-method sentinels used by {@code ChainDecision.method}: how a backup is
65+
* taken, independent of whether it is a full or an incremental ({@code ChainDecision.mode}).
66+
*
67+
* <p>{@link #METHOD_QCOW2}: libvirt push-mode backup; incrementals come from QEMU persistent
68+
* dirty bitmaps (libvirt checkpoints), which only qcow2 volumes can carry.</p>
69+
*/
70+
public static final String METHOD_QCOW2 = "qcow2";
71+
72+
/**
73+
* Content-based libvirt pull-mode backup, used for raw block-device storage such as
74+
* LINSTOR/DRBD which cannot carry persistent dirty bitmaps. A full is written as a sparse
75+
* qcow2 in a single pass; an incremental is a qcow2 overlay on the disk's NBD export,
76+
* safe-rebased onto the parent so it holds only the clusters that differ.
77+
*/
78+
public static final String METHOD_CONTENT = "content";
79+
80+
/**
81+
* Ceph-native backup, used for RBD volumes. They are raw and cannot carry a persistent dirty
82+
* bitmap, so each cycle anchors on an RBD snapshot and exports the delta with
83+
* {@code rbd export-diff}. Unlike the other methods this one writes {@link #FORMAT_RBD_DIFF}
84+
* streams rather than qcow2 files, which is what {@link #CHAIN_FORMAT} records for restore.
85+
*/
86+
public static final String METHOD_RBD = "rbd";
87+
7088
/**
7189
* VM-scoped detail (stored in {@code vm_instance_details}) holding the QEMU dirty-bitmap
7290
* name that currently exists on the running VM and is therefore the only valid parent

‎plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupProvider.java‎

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,12 @@ protected Host getVMHypervisorHost(VirtualMachine vm) {
217217

218218
/**
219219
* Returned by {@link #decideChain(VirtualMachine)} to describe the next backup's place in
220-
* the chain: full vs incremental, the bitmap name to create, and (for incrementals) the
221-
* parent bitmap and parent file path.
220+
* the chain: full vs incremental, how it is taken, the bitmap name to create, and (for
221+
* incrementals) the parent bitmap and parent file path.
222222
*/
223223
static final class ChainDecision {
224-
final String mode; // "full"/"incremental", or their "rbd-" prefixed equivalents
224+
final String mode; // "full" or "incremental"
225+
final String method; // NASBackupChainKeys.METHOD_QCOW2, METHOD_CONTENT or METHOD_RBD
225226
// On-NAS format of the chain this backup belongs to: NASBackupChainKeys.FORMAT_QCOW2 for
226227
// the libvirt-checkpoint path, FORMAT_RBD_DIFF for the Ceph-native rbd export-diff path.
227228
final String format;
@@ -234,9 +235,10 @@ static final class ChainDecision {
234235
final String chainId; // chain identifier this backup belongs to
235236
final int chainPosition; // 0 for full, N for the Nth incremental in the chain
236237

237-
private ChainDecision(String mode, String format, String bitmapNew, String bitmapParent, List<String> parentPaths,
238-
String chainId, int chainPosition) {
238+
private ChainDecision(String mode, String method, String format, String bitmapNew, String bitmapParent,
239+
List<String> parentPaths, String chainId, int chainPosition) {
239240
this.mode = mode;
241+
this.method = method;
240242
this.format = format;
241243
this.bitmapNew = bitmapNew;
242244
this.bitmapParent = bitmapParent;
@@ -246,8 +248,8 @@ private ChainDecision(String mode, String format, String bitmapNew, String bitma
246248
}
247249

248250
static ChainDecision fullStart(String bitmapName) {
249-
return new ChainDecision(NASBackupChainKeys.TYPE_FULL, NASBackupChainKeys.FORMAT_QCOW2, bitmapName,
250-
null, null, UUID.randomUUID().toString(), 0);
251+
return new ChainDecision(NASBackupChainKeys.TYPE_FULL, NASBackupChainKeys.METHOD_QCOW2,
252+
NASBackupChainKeys.FORMAT_QCOW2, bitmapName, null, null, UUID.randomUUID().toString(), 0);
251253
}
252254

253255
/**
@@ -257,8 +259,8 @@ static ChainDecision fullStart(String bitmapName) {
257259
* deleted backup, an unassigned offering, a restore) cannot leave snapshots behind.
258260
*/
259261
static ChainDecision rbdFullStart(String snapshotName) {
260-
return new ChainDecision(NASBackupChainKeys.TYPE_RBD_FULL, NASBackupChainKeys.FORMAT_RBD_DIFF,
261-
snapshotName, null, null, UUID.randomUUID().toString(), 0);
262+
return new ChainDecision(NASBackupChainKeys.TYPE_FULL, NASBackupChainKeys.METHOD_RBD,
263+
NASBackupChainKeys.FORMAT_RBD_DIFF, snapshotName, null, null, UUID.randomUUID().toString(), 0);
262264
}
263265

264266
/**
@@ -267,8 +269,8 @@ static ChainDecision rbdFullStart(String snapshotName) {
267269
* diff stream is not rebased onto its parent, the chain is replayed in order on restore.
268270
*/
269271
static ChainDecision rbdIncremental(String snapshotName, String parentSnapshot, String chainId, int chainPosition) {
270-
return new ChainDecision(NASBackupChainKeys.TYPE_RBD_INCREMENTAL, NASBackupChainKeys.FORMAT_RBD_DIFF,
271-
snapshotName, parentSnapshot, null, chainId, chainPosition);
272+
return new ChainDecision(NASBackupChainKeys.TYPE_INCREMENTAL, NASBackupChainKeys.METHOD_RBD,
273+
NASBackupChainKeys.FORMAT_RBD_DIFF, snapshotName, parentSnapshot, null, chainId, chainPosition);
272274
}
273275

274276
/**
@@ -277,27 +279,26 @@ static ChainDecision rbdIncremental(String snapshotName, String parentSnapshot,
277279
* is sent to the agent or persisted. Keeps the feature-off path byte-for-byte legacy.
278280
*/
279281
static ChainDecision legacyFull() {
280-
return new ChainDecision(NASBackupChainKeys.TYPE_LEGACY_FULL, NASBackupChainKeys.FORMAT_QCOW2,
281-
null, null, null, null, 0);
282+
return new ChainDecision(NASBackupChainKeys.TYPE_LEGACY_FULL, NASBackupChainKeys.METHOD_QCOW2,
283+
NASBackupChainKeys.FORMAT_QCOW2, null, null, null, null, 0);
282284
}
283285

284286
static ChainDecision incremental(String bitmapNew, String bitmapParent, List<String> parentPaths,
285287
String chainId, int chainPosition) {
286-
return new ChainDecision(NASBackupChainKeys.TYPE_INCREMENTAL, NASBackupChainKeys.FORMAT_QCOW2,
287-
bitmapNew, bitmapParent, parentPaths, chainId, chainPosition);
288+
return new ChainDecision(NASBackupChainKeys.TYPE_INCREMENTAL, NASBackupChainKeys.METHOD_QCOW2,
289+
NASBackupChainKeys.FORMAT_QCOW2, bitmapNew, bitmapParent, parentPaths, chainId, chainPosition);
288290
}
289291

290292
boolean isIncremental() {
291-
return NASBackupChainKeys.TYPE_INCREMENTAL.equals(mode)
292-
|| NASBackupChainKeys.TYPE_RBD_INCREMENTAL.equals(mode);
293+
return NASBackupChainKeys.TYPE_INCREMENTAL.equals(mode);
293294
}
294295

295296
boolean isLegacyFull() {
296297
return NASBackupChainKeys.TYPE_LEGACY_FULL.equals(mode);
297298
}
298299

299300
boolean isRbd() {
300-
return NASBackupChainKeys.FORMAT_RBD_DIFF.equals(format);
301+
return NASBackupChainKeys.METHOD_RBD.equals(method);
301302
}
302303

303304
/**
@@ -701,6 +702,7 @@ public Pair<Boolean, Backup> takeBackup(final VirtualMachine vm, Boolean quiesce
701702
command.setMountOptions(backupRepository.getMountOptions());
702703
command.setQuiesce(quiesceVM);
703704
command.setMode(decision.mode);
705+
command.setMethod(decision.method);
704706
command.setBitmapNew(decision.bitmapNew);
705707
command.setBitmapParent(decision.bitmapParent);
706708
command.setParentPaths(decision.parentPaths);

‎plugins/backup/nas/src/test/java/org/apache/cloudstack/backup/NASBackupProviderTest.java‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,8 @@ public void decideChainStartsAnRbdChainForRbdBackedVm() {
486486

487487
NASBackupProvider.ChainDecision decision = nasBackupProvider.decideChain(vm);
488488

489-
Assert.assertEquals(NASBackupChainKeys.TYPE_RBD_FULL, decision.mode);
489+
Assert.assertEquals(NASBackupChainKeys.TYPE_FULL, decision.mode);
490+
Assert.assertEquals(NASBackupChainKeys.METHOD_RBD, decision.method);
490491
Assert.assertEquals(NASBackupChainKeys.FORMAT_RBD_DIFF, decision.format);
491492
Assert.assertTrue("RBD chains anchor on an RBD snapshot, not a QEMU bitmap",
492493
decision.bitmapNew.startsWith("csbackup-"));
@@ -513,7 +514,8 @@ public void decideChainKeepsStoppedRbdVmOnTheRbdPath() {
513514

514515
NASBackupProvider.ChainDecision decision = nasBackupProvider.decideChain(vm);
515516

516-
Assert.assertEquals(NASBackupChainKeys.TYPE_RBD_FULL, decision.mode);
517+
Assert.assertEquals(NASBackupChainKeys.TYPE_FULL, decision.mode);
518+
Assert.assertEquals(NASBackupChainKeys.METHOD_RBD, decision.method);
517519
Assert.assertEquals(NASBackupChainKeys.FORMAT_RBD_DIFF, decision.format);
518520
}
519521

@@ -529,7 +531,8 @@ public void asFullRestartKeepsTheRbdFlavour() {
529531

530532
NASBackupProvider.ChainDecision restarted = incremental.asFullRestart();
531533

532-
Assert.assertEquals(NASBackupChainKeys.TYPE_RBD_FULL, restarted.mode);
534+
Assert.assertEquals(NASBackupChainKeys.TYPE_FULL, restarted.mode);
535+
Assert.assertEquals(NASBackupChainKeys.METHOD_RBD, restarted.method);
533536
Assert.assertEquals(NASBackupChainKeys.FORMAT_RBD_DIFF, restarted.format);
534537
Assert.assertEquals("csbackup-2", restarted.bitmapNew);
535538
Assert.assertEquals(0, restarted.chainPosition);

‎plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtTakeBackupCommandWrapper.java‎

Lines changed: 40 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,13 @@ public class LibvirtTakeBackupCommandWrapper extends CommandWrapper<TakeBackupCo
5252
// Incremental feature disabled: plain full backup with no QEMU bitmap/checkpoint and no
5353
// chain metadata. Matches nasbackup.sh's "legacy-full" mode (make_checkpoint=0).
5454
private static final String MODE_LEGACY_FULL = "legacy-full";
55-
// Ceph/RBD chains: anchored on an `rbd snap` per cycle and exported with `rbd export-diff`
56-
// instead of a libvirt checkpoint + push backup. bitmapNew/bitmapParent carry RBD snapshot
57-
// names rather than QEMU bitmap names; no parent file paths, nothing is rebased.
58-
private static final String MODE_RBD_FULL = "rbd-full";
59-
private static final String MODE_RBD_INCREMENTAL = "rbd-incremental";
55+
56+
// Backup methods, orthogonal to the mode. qcow2 is the push-mode path with QEMU dirty bitmaps,
57+
// also assumed when the management server sends none. rbd is the Ceph-native path: it anchors
58+
// each cycle on an `rbd snap` and exports the delta with `rbd export-diff`, so bitmapNew and
59+
// bitmapParent carry RBD snapshot names rather than QEMU bitmap names, and nothing is rebased.
60+
private static final String METHOD_QCOW2 = "qcow2";
61+
private static final String METHOD_RBD = "rbd";
6062

6163
@Override
6264
public Answer execute(TakeBackupCommand command, LibvirtComputingResource libvirtComputingResource) {
@@ -163,6 +165,10 @@ private Pair<Integer, String> runBackupScript(LibvirtComputingResource libvirtCo
163165
argv.add("-M");
164166
argv.add(mode);
165167
}
168+
if (StringUtils.isNotBlank(command.getMethod())) {
169+
argv.add("--method");
170+
argv.add(command.getMethod());
171+
}
166172
if (StringUtils.isNotBlank(bitmapNew)) {
167173
argv.add("--bitmap-new");
168174
argv.add(bitmapNew);
@@ -187,6 +193,13 @@ private Pair<Integer, String> runBackupScript(LibvirtComputingResource libvirtCo
187193
*/
188194
private String validateBackupArgs(TakeBackupCommand command) {
189195
String mode = command.getMode();
196+
String method = StringUtils.defaultIfBlank(command.getMethod(), METHOD_QCOW2);
197+
if (METHOD_RBD.equals(method)) {
198+
return validateRbdBackupArgs(command, mode);
199+
}
200+
if (!METHOD_QCOW2.equals(method)) {
201+
return "Unknown backup method: " + method;
202+
}
190203
if (StringUtils.isBlank(mode)) {
191204
return null; // legacy full-only, no extra args expected
192205
}
@@ -211,22 +224,31 @@ private String validateBackupArgs(TakeBackupCommand command) {
211224
if (MODE_LEGACY_FULL.equals(mode)) {
212225
return null; // feature-off full backup, no bitmap or chain args expected
213226
}
214-
if (MODE_RBD_FULL.equals(mode) || MODE_RBD_INCREMENTAL.equals(mode)) {
215-
if (command.getBitmapNew() == null || command.getBitmapNew().isEmpty()) {
216-
return mode + " mode requires bitmapNew (the RBD snapshot to create as the chain anchor)";
217-
}
218-
if (MODE_RBD_INCREMENTAL.equals(mode)
219-
&& (command.getBitmapParent() == null || command.getBitmapParent().isEmpty())) {
220-
return "rbd-incremental mode requires bitmapParent (the RBD snapshot to diff against)";
221-
}
222-
if (command.getVolumePaths() == null || command.getVolumePaths().isEmpty()) {
223-
return mode + " mode requires the VM's volume paths";
224-
}
225-
return null;
226-
}
227227
return "Unknown backup mode: " + mode;
228228
}
229229

230+
/**
231+
* An RBD chain anchors each cycle on an {@code rbd snap} whose name travels in bitmapNew, and
232+
* an incremental diffs it against the previous anchor in bitmapParent. No parent file paths:
233+
* the diff stream is replayed on restore rather than rebased onto its parent.
234+
*/
235+
private String validateRbdBackupArgs(TakeBackupCommand command, String mode) {
236+
if (!MODE_FULL.equals(mode) && !MODE_INCREMENTAL.equals(mode)) {
237+
return "Unknown backup mode for the rbd method: " + mode;
238+
}
239+
if (command.getBitmapNew() == null || command.getBitmapNew().isEmpty()) {
240+
return mode + " mode requires bitmapNew (the RBD snapshot to create as the chain anchor)";
241+
}
242+
if (MODE_INCREMENTAL.equals(mode)
243+
&& (command.getBitmapParent() == null || command.getBitmapParent().isEmpty())) {
244+
return "incremental mode requires bitmapParent (the RBD snapshot to diff against)";
245+
}
246+
if (command.getVolumePaths() == null || command.getVolumePaths().isEmpty()) {
247+
return mode + " mode requires the VM's volume paths";
248+
}
249+
return null;
250+
}
251+
230252
/**
231253
* Sum the per-disk size lines emitted by nasbackup.sh. Single-volume mode emits one
232254
* line containing just the byte count; multi-volume mode emits one line per disk

0 commit comments

Comments
 (0)