wido commented on code in PR #14386:
URL: https://github.com/apache/cloudstack/pull/14386#discussion_r4269414937
##########
core/src/main/java/org/apache/cloudstack/backup/TakeBackupCommand.java:
##########
@@ -38,6 +38,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) or "content"
(pull mode); null => "qcow2"
Review Comment:
The comment documents a "content" method but not "rbd", which is the one
this PR adds.
##########
plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java:
##########
@@ -381,6 +449,173 @@ private boolean
replaceBlockDeviceWithBackup(KVMStoragePoolManager storagePoolMg
return true;
}
+ /**
+ * Rebuild an RBD image by replaying a chain of {@code rbd export-diff}
streams into it: the
+ * chain's full anchor first, then each incremental in the order it was
taken.
+ *
+ * <p>RBD images are raw and cannot hold a backing-file reference, so —
unlike the qcow2 path —
+ * a diff carries no pointer to the one before it and the chain cannot be
flattened by
+ * {@code qemu-img}. Ceph applies the deltas natively instead: each diff
ends at the snapshot
+ * the backup was taken from, {@code import-diff} recreates that snapshot
on the target, and the
+ * next diff in the chain is validated against it. A truncated or
out-of-order diff is therefore
+ * rejected by Ceph rather than silently producing a half-restored
image.</p>
+ *
+ * <p>The image is recreated first. A full diff only carries the extents
that were allocated
+ * when it was taken, so any block still allocated in the current image
would otherwise show
+ * through the restore as stale data. Recreating the image also drops any
RBD snapshots it
+ * carried, including CloudStack volume snapshots kept on primary storage
— a restore rewinds
+ * the volume wholesale, and there is no way to guarantee the first
requirement while keeping
+ * snapshots of content that is being replaced.</p>
+ */
+ private boolean restoreRbdImageFromDiffChain(KVMStoragePoolManager
storagePoolMgr, PrimaryDataStoreTO volumePool,
+ String volumePath,
List<String> diffFiles, Long size) {
+ if (!Storage.StoragePoolType.RBD.equals(volumePool.getPoolType())) {
+ logger.error("Backup [{}] holds RBD diffs but volume [{}] is on {}
storage; it cannot be restored there.",
+ diffFiles.get(0), volumePath, volumePool.getPoolType());
+ return false;
+ }
+ KVMStoragePool pool =
storagePoolMgr.getStoragePool(volumePool.getPoolType(), volumePool.getUuid());
+ Path keyFile = null;
+ try {
+ keyFile = writeCephKeyFile(pool.getAuthSecret());
+ List<String> connection = rbdConnectionArgs(pool, keyFile);
+ // Volume paths of RBD volumes are already "<ceph pool>/<image>".
+ String image = volumePath;
+
+ // Prefer the current image's own size: it is what the VM's disk
was sized to. Only a
+ // restore that creates a brand new volume has no image to read it
from.
+ Long imageSize = rbdImageSize(connection, image);
+ if (imageSize == null) {
+ imageSize = size;
+ }
+ if (imageSize == null || imageSize <= 0) {
+ logger.error("Unable to determine the size to recreate RBD
image {} with", image);
+ return false;
+ }
+
+ if (!recreateRbdImage(connection, image, imageSize,
rbdDataPool(pool))) {
+ return false;
+ }
+
+ for (String diff : diffFiles) {
+ logger.debug("Applying backup diff {} to RBD image {}", diff,
image);
+ if (Script.executeCommandForExitValue(rbdCommand(connection,
"import-diff", diff, image)) != 0) {
+ logger.error("Failed to apply backup diff {} to RBD image
{}", diff, image);
+ return false;
+ }
+ }
+
+ // The snapshots import-diff recreated only existed to chain the
diffs together; the
+ // restored volume should look like any other, so drop them.
+ if (Script.executeCommandForExitValue(rbdCommand(connection,
"snap", "purge", image)) != 0) {
+ logger.warn("Failed to purge the snapshots left behind by the
restore of RBD image {}", image);
+ }
+ logger.debug("Successfully restored RBD image {} from {} backup
diff(s)", image, diffFiles.size());
+ return true;
+ } catch (IOException e) {
+ logger.error("Failed to prepare the Ceph credentials needed to
restore volume [{}]", volumePath, e);
+ return false;
+ } finally {
+ deleteCephKeyFile(keyFile);
+ }
+ }
+
+ /**
+ * Hand the cephx secret to the {@code rbd} CLI through a 0600 temporary
file rather than an
+ * argument, so it never shows up in the host's process list.
+ */
+ private Path writeCephKeyFile(String authSecret) throws IOException {
+ if (StringUtils.isBlank(authSecret)) {
+ return null;
+ }
+ Path keyFile = Files.createTempFile("csrestore-ceph-", ".key",
+
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-------")));
+ Files.write(keyFile, authSecret.getBytes(StandardCharsets.UTF_8));
+ return keyFile;
+ }
+
+ private void deleteCephKeyFile(Path keyFile) {
+ if (keyFile == null) {
+ return;
+ }
+ try {
+ Files.deleteIfExists(keyFile);
+ } catch (IOException e) {
+ logger.warn("Failed to delete the temporary Ceph key file [{}].",
keyFile, e);
+ }
+ }
+
+ private List<String> rbdConnectionArgs(KVMStoragePool pool, Path keyFile) {
+ List<String> args = new ArrayList<>();
+ if (StringUtils.isNotBlank(pool.getSourceHost())) {
+ List<String> monitors = new ArrayList<>();
+ for (String host : pool.getSourceHost().split(",")) {
+ host = host.trim().replace("[", "").replace("]", "");
Review Comment:
This strips the brackets and then appends the port, so an IPv6 monitor
becomes `fd00::1:6789`, which rbd cannot parse. The script side keeps the
brackets and works. This is also one more copy of the host-list-plus-port logic
(see my remark on #14320); one shared helper would serve both PRs.
##########
plugins/backup/nas/src/main/java/org/apache/cloudstack/backup/NASBackupChainKeys.java:
##########
@@ -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";
Review Comment:
`METHOD_CONTENT` has no user in this PR. Leftover from another branch?
##########
scripts/vm/hypervisor/kvm/nasbackup.sh:
##########
@@ -135,6 +138,282 @@ get_linstor_uuid_from_device() {
return 1
}
+# Globals populated by parse_rbd_opts() for the disk it was last called with.
+RBD_IMAGE="" # "<pool>/<image>"
+RBD_ARGS=() # connection arguments to pass to every rbd invocation
+RBD_KEYFILE="" # temporary cephx keyfile, removed by rbd_cleanup_keyfile
+# Prefix NASBackupProvider names every chain anchor with. Snapshots carrying
it belong to this
+# tool and no other, which is what makes the stale-anchor sweep safe.
+RBD_SNAPSHOT_PREFIX="csbackup-"
+
+rbd_cleanup_keyfile() {
+ if [[ -n "$RBD_KEYFILE" ]]; then
+ rm -f "$RBD_KEYFILE"
+ RBD_KEYFILE=""
+ fi
+}
+
+# Split an RBD disk string produced by KVMPhysicalDisk.RBDStringBuilder into
the image name and
+# the connection arguments the rbd CLI needs:
+#
rbd:<pool>/<image>:mon_host=<h1>\;<h2>:auth_client_required=cephx:id=<user>:key=<secret>:...
+# ':' and ';' are backslash-escaped inside mon_host so that option splitting
stays unambiguous;
+# they are masked before the split and restored afterwards.
+parse_rbd_opts() {
+ local s="${1#rbd:}"
+ s="${s//\\:/$'\x01'}"
+ s="${s//\\;/$'\x02'}"
+ local -a parts=()
+ IFS=':' read -ra parts <<< "$s"
+
+ rbd_cleanup_keyfile
+ RBD_IMAGE="${parts[0]}"
+ RBD_ARGS=()
+
+ local p monHost="" authId="" authKey=""
+ for p in "${parts[@]:1}"; do
+ case "$p" in
+ mon_host=*) monHost="${p#mon_host=}" ;;
+ id=*) authId="${p#id=}" ;;
+ key=*) authKey="${p#key=}" ;;
+ esac
+ done
+ monHost="${monHost//$'\x01'/:}" # host\:port -> host:port
+ monHost="${monHost//$'\x02'/,}" # monitor list -> the comma-separated form
rbd -m accepts
+
+ if [[ -n "$monHost" ]]; then
+ RBD_ARGS+=(-m "$monHost")
+ fi
+ if [[ -n "$authId" ]]; then
+ RBD_ARGS+=(--id "$authId")
+ if [[ -n "$authKey" ]]; then
+ RBD_KEYFILE=$(mktemp -t csbackup-ceph.XXXXXX)
+ chmod 600 "$RBD_KEYFILE"
+ printf '%s' "$authKey" > "$RBD_KEYFILE"
+ RBD_ARGS+=(--keyfile "$RBD_KEYFILE")
+ fi
Review Comment:
There is no trap, so when the agent times out and kills the script, this key
file stays in /tmp. The new snapshot stays as well, but that one is swept by
the next full. A `trap rbd_cleanup_keyfile EXIT` would cover the key file.
##########
scripts/vm/hypervisor/kvm/nasbackup.sh:
##########
@@ -135,6 +138,282 @@ get_linstor_uuid_from_device() {
return 1
}
+# Globals populated by parse_rbd_opts() for the disk it was last called with.
+RBD_IMAGE="" # "<pool>/<image>"
+RBD_ARGS=() # connection arguments to pass to every rbd invocation
+RBD_KEYFILE="" # temporary cephx keyfile, removed by rbd_cleanup_keyfile
+# Prefix NASBackupProvider names every chain anchor with. Snapshots carrying
it belong to this
+# tool and no other, which is what makes the stale-anchor sweep safe.
+RBD_SNAPSHOT_PREFIX="csbackup-"
+
+rbd_cleanup_keyfile() {
+ if [[ -n "$RBD_KEYFILE" ]]; then
+ rm -f "$RBD_KEYFILE"
+ RBD_KEYFILE=""
+ fi
+}
+
+# Split an RBD disk string produced by KVMPhysicalDisk.RBDStringBuilder into
the image name and
+# the connection arguments the rbd CLI needs:
+#
rbd:<pool>/<image>:mon_host=<h1>\;<h2>:auth_client_required=cephx:id=<user>:key=<secret>:...
+# ':' and ';' are backslash-escaped inside mon_host so that option splitting
stays unambiguous;
+# they are masked before the split and restored afterwards.
+parse_rbd_opts() {
+ local s="${1#rbd:}"
+ s="${s//\\:/$'\x01'}"
+ s="${s//\\;/$'\x02'}"
+ local -a parts=()
+ IFS=':' read -ra parts <<< "$s"
+
+ rbd_cleanup_keyfile
+ RBD_IMAGE="${parts[0]}"
+ RBD_ARGS=()
+
+ local p monHost="" authId="" authKey=""
+ for p in "${parts[@]:1}"; do
+ case "$p" in
+ mon_host=*) monHost="${p#mon_host=}" ;;
+ id=*) authId="${p#id=}" ;;
+ key=*) authKey="${p#key=}" ;;
+ esac
+ done
+ monHost="${monHost//$'\x01'/:}" # host\:port -> host:port
+ monHost="${monHost//$'\x02'/,}" # monitor list -> the comma-separated form
rbd -m accepts
+
+ if [[ -n "$monHost" ]]; then
+ RBD_ARGS+=(-m "$monHost")
+ fi
+ if [[ -n "$authId" ]]; then
+ RBD_ARGS+=(--id "$authId")
+ if [[ -n "$authKey" ]]; then
+ RBD_KEYFILE=$(mktemp -t csbackup-ceph.XXXXXX)
+ chmod 600 "$RBD_KEYFILE"
+ printf '%s' "$authKey" > "$RBD_KEYFILE"
+ RBD_ARGS+=(--keyfile "$RBD_KEYFILE")
+ fi
+ fi
+}
+
+# Best-effort removal of snapshot $1 from each of the remaining disk
arguments. Used both to roll
+# back a half-created anchor and to drop the previous anchor once its delta
has been exported.
+rbd_remove_snapshots() {
+ local snapName="$1"
+ shift
+ local d
+ for d in "$@"; do
+ [[ -z "$d" ]] && continue
+ parse_rbd_opts "$d"
+ if ! rbd "${RBD_ARGS[@]}" snap rm "$RBD_IMAGE@$snapName" >> "$logFile"
2>&1; then
+ log -e "cleanup: failed to remove RBD snapshot $RBD_IMAGE@$snapName
(non-fatal)"
+ fi
+ done
+ rbd_cleanup_keyfile
+}
+
+# Remove every snapshot carrying the backup prefix except $1, across the
remaining disk arguments.
+# Only snapshots this tool created are ever touched: user and CloudStack
volume snapshots do not
+# use the prefix.
+rbd_sweep_stale_anchors() {
+ local keepSnap="$1"
+ shift
+ local d stale
+ for d in "$@"; do
+ [[ -z "$d" ]] && continue
+ parse_rbd_opts "$d"
+ while read -r stale; do
+ [[ -z "$stale" || "$stale" == "$keepSnap" ]] && continue
+ if ! rbd "${RBD_ARGS[@]}" snap rm "$RBD_IMAGE@$stale" >> "$logFile"
2>&1; then
+ log -e "cleanup: failed to remove stale RBD snapshot $RBD_IMAGE@$stale
(non-fatal)"
+ fi
+ done < <(
+ rbd "${RBD_ARGS[@]}" snap ls "$RBD_IMAGE" --format json 2>>"$logFile" |
python3 -c '
+import sys, json
+try:
+ snaps = json.load(sys.stdin)
+except Exception:
+ sys.exit(0)
+for s in snaps or []:
+ name = (s or {}).get("name") or ""
+ if name.startswith(sys.argv[1]):
+ print(name)
+' "$RBD_SNAPSHOT_PREFIX" 2>/dev/null || true
+ )
+ done
+ rbd_cleanup_keyfile
+}
+
+rbd_snapshot_exists() {
+ local snapName="$1"
+ rbd "${RBD_ARGS[@]}" snap ls "$RBD_IMAGE" --format json 2>>"$logFile" |
python3 -c '
+import sys, json
+try:
+ snaps = json.load(sys.stdin)
+except Exception:
+ sys.exit(1)
+sys.exit(0 if any((s or {}).get("name") == sys.argv[1] for s in snaps or [])
else 1)
+' "$snapName"
+}
+
+# Incremental backup of Ceph/RBD volumes.
+#
+# RBD images are raw, so they have nowhere to persist a QEMU dirty bitmap and
cannot take part in
+# the libvirt-checkpoint scheme backup_running_vm uses. Ceph provides the
equivalent natively: an
+# `rbd snap` per cycle as the anchor, and `rbd export-diff --from-snap` to
stream out exactly the
+# extents that changed between two anchors (computed inside Ceph from the
image object-map).
+#
+# Because the snapshot lives in Ceph rather than in the QEMU process, this
works identically for
+# running and stopped VMs, and survives a live migration of the VM between
hosts.
+#
+# Only the newest anchor is kept on each image: once this run's delta is
exported, the parent
+# snapshot is removed. A full additionally sweeps every other snapshot
carrying the backup prefix,
+# so anchors abandoned by a deleted chain, an unassigned offering or a restore
cannot accumulate.
+backup_rbd_vm() {
+ if [[ -z "$DISK_PATHS" ]]; then
+ echo "RBD backup mode requires the volume paths (-d|--diskpaths)"
+ exit 1
+ fi
+ if [[ -z "$BITMAP_NEW" ]]; then
+ echo "RBD backup mode requires --bitmap-new (the snapshot to create)"
+ exit 1
+ fi
+
+ local effective_mode="$MODE"
+ case "$effective_mode" in
+ full|incremental)
+ ;;
+ *)
+ echo "Mode $effective_mode is not supported by the rbd method"
+ cleanup
+ exit 1
+ ;;
+ esac
+ local -a disks=()
+ IFS=',' read -ra disks <<< "$DISK_PATHS"
+
+ local d
+ for d in "${disks[@]}"; do
+ if [[ "$d" != rbd:* ]]; then
+ echo "RBD backup mode was requested but disk $d is not an RBD volume"
+ exit 1
+ fi
+ done
+
+ # The parent anchor must still be present on EVERY disk. A volume migration
or an out-of-band
+ # snapshot purge can drop it; fall back to a full rather than letting
export-diff fail. Checked
+ # before anything is mounted or created so the fallback costs nothing.
+ if [[ "$effective_mode" == "incremental" ]]; then
+ for d in "${disks[@]}"; do
+ parse_rbd_opts "$d"
+ if ! rbd_snapshot_exists "$BITMAP_PARENT"; then
+ log -e "rbd incremental: parent snapshot $BITMAP_PARENT missing on
$RBD_IMAGE — falling back to full"
+ echo "INCREMENTAL_FALLBACK=true"
+ effective_mode="full"
+ break
+ fi
+ done
+ rbd_cleanup_keyfile
+ fi
+
+ mount_operation
+ mkdir -p "$dest" || { echo "Failed to create backup directory $dest"; exit
1; }
+
+ # Freezing the guest filesystems makes the snapshot set
filesystem-consistent instead of merely
+ # crash-consistent. Only possible while the VM runs and its guest agent
answers.
+ local thaw=0
+ local vm_state
+ vm_state=$(virsh -c qemu:///system domstate "$VM" 2>/dev/null || true)
+ if [[ ${QUIESCE} == "true" && "$vm_state" == "running" ]]; then
+ if virsh -c qemu:///system qemu-agent-command "$VM"
'{"execute":"guest-fsfreeze-freeze"}' > /dev/null 2>/dev/null; then
+ thaw=1
+ fi
+ fi
+
+ # Create this cycle's anchor on every disk back to back, so the set is as
close to a single
+ # point in time for the VM as a whole as Ceph allows.
+ local -a snapped=()
+ local snap_failed=0
+ for d in "${disks[@]}"; do
+ parse_rbd_opts "$d"
+ if rbd "${RBD_ARGS[@]}" snap create "$RBD_IMAGE@$BITMAP_NEW" >> "$logFile"
2>&1; then
Review Comment:
The agent now needs the rbd CLI for both backup and restore, but ceph-common
is not added to debian/control or the spec files. Without it this fails with
"Failed to create RBD snapshot" and a restore with a non-zero exit, neither
naming the cause. Either add the dependency as #14320 did for the python
bindings, or check for the binary up front and fail with a clear message.
##########
plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRestoreBackupCommandWrapper.java:
##########
@@ -381,6 +449,173 @@ private boolean
replaceBlockDeviceWithBackup(KVMStoragePoolManager storagePoolMg
return true;
}
+ /**
+ * Rebuild an RBD image by replaying a chain of {@code rbd export-diff}
streams into it: the
+ * chain's full anchor first, then each incremental in the order it was
taken.
+ *
+ * <p>RBD images are raw and cannot hold a backing-file reference, so —
unlike the qcow2 path —
+ * a diff carries no pointer to the one before it and the chain cannot be
flattened by
+ * {@code qemu-img}. Ceph applies the deltas natively instead: each diff
ends at the snapshot
+ * the backup was taken from, {@code import-diff} recreates that snapshot
on the target, and the
+ * next diff in the chain is validated against it. A truncated or
out-of-order diff is therefore
+ * rejected by Ceph rather than silently producing a half-restored
image.</p>
+ *
+ * <p>The image is recreated first. A full diff only carries the extents
that were allocated
+ * when it was taken, so any block still allocated in the current image
would otherwise show
+ * through the restore as stale data. Recreating the image also drops any
RBD snapshots it
+ * carried, including CloudStack volume snapshots kept on primary storage
— a restore rewinds
+ * the volume wholesale, and there is no way to guarantee the first
requirement while keeping
+ * snapshots of content that is being replaced.</p>
+ */
+ private boolean restoreRbdImageFromDiffChain(KVMStoragePoolManager
storagePoolMgr, PrimaryDataStoreTO volumePool,
+ String volumePath,
List<String> diffFiles, Long size) {
+ if (!Storage.StoragePoolType.RBD.equals(volumePool.getPoolType())) {
+ logger.error("Backup [{}] holds RBD diffs but volume [{}] is on {}
storage; it cannot be restored there.",
+ diffFiles.get(0), volumePath, volumePool.getPoolType());
+ return false;
+ }
+ KVMStoragePool pool =
storagePoolMgr.getStoragePool(volumePool.getPoolType(), volumePool.getUuid());
+ Path keyFile = null;
+ try {
+ keyFile = writeCephKeyFile(pool.getAuthSecret());
+ List<String> connection = rbdConnectionArgs(pool, keyFile);
+ // Volume paths of RBD volumes are already "<ceph pool>/<image>".
+ String image = volumePath;
+
+ // Prefer the current image's own size: it is what the VM's disk
was sized to. Only a
+ // restore that creates a brand new volume has no image to read it
from.
+ Long imageSize = rbdImageSize(connection, image);
+ if (imageSize == null) {
+ imageSize = size;
+ }
+ if (imageSize == null || imageSize <= 0) {
+ logger.error("Unable to determine the size to recreate RBD
image {} with", image);
+ return false;
+ }
+
+ if (!recreateRbdImage(connection, image, imageSize,
rbdDataPool(pool))) {
+ return false;
+ }
+
+ for (String diff : diffFiles) {
+ logger.debug("Applying backup diff {} to RBD image {}", diff,
image);
+ if (Script.executeCommandForExitValue(rbdCommand(connection,
"import-diff", diff, image)) != 0) {
+ logger.error("Failed to apply backup diff {} to RBD image
{}", diff, image);
+ return false;
+ }
+ }
+
+ // The snapshots import-diff recreated only existed to chain the
diffs together; the
+ // restored volume should look like any other, so drop them.
+ if (Script.executeCommandForExitValue(rbdCommand(connection,
"snap", "purge", image)) != 0) {
+ logger.warn("Failed to purge the snapshots left behind by the
restore of RBD image {}", image);
+ }
+ logger.debug("Successfully restored RBD image {} from {} backup
diff(s)", image, diffFiles.size());
+ return true;
+ } catch (IOException e) {
+ logger.error("Failed to prepare the Ceph credentials needed to
restore volume [{}]", volumePath, e);
+ return false;
+ } finally {
+ deleteCephKeyFile(keyFile);
+ }
+ }
+
+ /**
+ * Hand the cephx secret to the {@code rbd} CLI through a 0600 temporary
file rather than an
+ * argument, so it never shows up in the host's process list.
+ */
+ private Path writeCephKeyFile(String authSecret) throws IOException {
+ if (StringUtils.isBlank(authSecret)) {
+ return null;
+ }
+ Path keyFile = Files.createTempFile("csrestore-ceph-", ".key",
+
PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rw-------")));
+ Files.write(keyFile, authSecret.getBytes(StandardCharsets.UTF_8));
+ return keyFile;
+ }
+
+ private void deleteCephKeyFile(Path keyFile) {
+ if (keyFile == null) {
+ return;
+ }
+ try {
+ Files.deleteIfExists(keyFile);
+ } catch (IOException e) {
+ logger.warn("Failed to delete the temporary Ceph key file [{}].",
keyFile, e);
+ }
+ }
+
+ private List<String> rbdConnectionArgs(KVMStoragePool pool, Path keyFile) {
+ List<String> args = new ArrayList<>();
+ if (StringUtils.isNotBlank(pool.getSourceHost())) {
+ List<String> monitors = new ArrayList<>();
+ for (String host : pool.getSourceHost().split(",")) {
+ host = host.trim().replace("[", "").replace("]", "");
+ monitors.add(pool.getSourcePort() > 0 ? host + ":" +
pool.getSourcePort() : host);
+ }
+ args.add("-m");
+ args.add(String.join(",", monitors));
+ }
+ if (StringUtils.isNotBlank(pool.getAuthUserName())) {
+ args.add("--id");
+ args.add(pool.getAuthUserName());
+ if (keyFile != null) {
+ args.add("--keyfile");
+ args.add(keyFile.toString());
+ }
+ }
+ return args;
+ }
+
+ private String[] rbdCommand(List<String> connection, String... arguments) {
+ List<String> command = new ArrayList<>();
+ command.add(Script.getExecutableAbsolutePath("rbd"));
+ command.addAll(connection);
+ command.addAll(Arrays.asList(arguments));
+ return command.toArray(new String[0]);
+ }
+
+ /** Size of {@code image} in bytes, or {@code null} when the image does
not exist. */
+ private Long rbdImageSize(List<String> connection, String image) {
+ Pair<Integer, String> result = Script.executePipedCommands(
+ Collections.singletonList(rbdCommand(connection, "info",
image, "--format", "json")), 0);
+ if (result.first() == null || result.first() != 0 || result.second()
== null) {
+ return null;
+ }
+ Matcher matcher = RBD_INFO_SIZE.matcher(result.second());
+ return matcher.find() ? Long.valueOf(matcher.group(1)) : null;
+ }
+
+ private boolean recreateRbdImage(List<String> connection, String image,
long sizeBytes, String dataPool) {
Review Comment:
A volume that has a protected snapshot with clones, which is what creating a
volume from a snapshot kept on primary produces, cannot be removed here, so the
restore fails with "Failed to remove RBD image before restoring it". Fine as a
limitation, but worth a message that says why, and a line in the docs.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]