Skip to content

kvm: restore backups into the volume they were taken from - #8

Open
calvix wants to merge 56 commits into
4.22from
fix/nas-backup-restore-volume-mapping
Open

calvix wants to merge 56 commits into
4.22from
fix/nas-backup-restore-volume-mapping

Conversation

@calvix

@calvix calvix commented Aug 19, 2026 •

Copy link
Copy Markdown
Owner

Description

restoreBackup of a KVM instance with more than one data disk can write a
backed up disk into a different volume than the one it was taken from. This happens only on a second or further restore of a VM from a backup that was taken. ie: create backup of VM, restore 1, then restore 2 again.

  • equal-sized data disks → the volume contents are silently exchanged, and the API returns success: true, but the appearance of the bug is not 100% because the enumeration can randomly be the same as before. I recommend trying instances with 5 or 7 data disks for testing where occurrence is very likely.
  • differently sized data disks → the smaller image is written over the larger volume, the restore then
    fails converting the larger image into the smaller volume, and returns an error after the data is already
    destroyed
    , with no rollback
  • the ROOT disk is never affected (always re-attached at device id 0, so it sorts first in both lists)
  • createVMFromBackup and restoreVolumeFromBackupAndAttachToVM are not affected

Reproduced on 4.22.1.0 with KVM + Ceph/RBD primary storage and the NAS backup provider.

At first, this can be a very silent error, especially if the VM uses mount by LABEL/UUID, as the VM will mount the data to the right mount paths, but volume A now contains the data of volume B ( including LABELs) and volume B contains data for volume A.

Logs

case 1 - different disk sizes

The logs show output showing this bug on the VM, with 2 data volumes diska, diskb - all stored on Ceph primary storage and backed up via backup and recovery plugin to cephfs storage. diskcheck is a simple bash util that prints useful data from the disks on a remote machine and connects via ssh.

 #:   diskcheck 10.3.77.212
DEVICE SIZE   LABEL   MOUNT        FS-SIZE  FILE
vdb    1G     DISKA   /mnt/diska   974M     DISKA=DISKA
vdc    2G     DISKB   /mnt/diskb   2.0G     DISKB=DISKB

 #: cmk -p stable list volumes virtualmachineid=3d602d8a-c00c-4f49-a432-8c8f82237b3d filter=name,deviceid,size
{
  "count": 3,
  "volume": [
    {
      "deviceid": 0,
      "name": "ROOT-959",
      "size": 630159872
    },
    {
      "deviceid": 1,
      "name": "demo-disk-a",
      "size": 1073741824
    },
    {
      "deviceid": 2,
      "name": "demo-disk-b",
      "size": 2147483648
    }
  ]
}

 #: cmk -p stable stop virtualmachine id=3d602d8a-c00c-4f49-a432-8c8f82237b3d
{
 ...... redacted
}

 #: cmk -p stable restore backup id=557b755b-6426-4daa-9c96-871281a4112f
{
  "success": true
}
 # Below we can see the deviceID changed for both data disks
 #: cmk -p stable list volumes virtualmachineid=3d602d8a-c00c-4f49-a432-8c8f82237b3d filter=name,deviceid
{
  "count": 3,
  "volume": [
    {
      "deviceid": 0,
      "name": "ROOT-959"
    },
    {
      "deviceid": 4,
      "name": "demo-disk-a"
    },
    {
      "deviceid": 1,
      "name": "demo-disk-b"
    }
  ]
}
 #: cmk -p stable start virtualmachine id=3d602d8a-c00c-4f49-a432-8c8f82237b3d
{
...
}
 # Here we can see that disk ordering changed - this will basically break any further restore 
 #: diskcheck 10.3.77.212
DEVICE SIZE   LABEL   MOUNT        FS-SIZE  FILE
vdb    2G     DISKB   /mnt/diskb   2.0G     DISKB=DISKB
vdc    1G     DISKA   /mnt/diska   974M     DISKA=DISKA



 #: cmk -p stable stop virtualmachine id=3d602d8a-c00c-4f49-a432-8c8f82237b3d
{
  ...
  
}
 # The second restore fails because it cannot restore 2GB disk into 1 GB volume
 #: cmk -p stable restore backup id=557b755b-6426-4daa-9c96-871281a4112f
{
  "account": "admin",
  "accountid": "65fed4ca-48b6-11f1-a321-bc24119cb31e",
  "cmd": "org.apache.cloudstack.api.command.user.backup.RestoreBackupCmd",
  "completed": "2026-08-19T11:35:55+0200",
  "created": "2026-08-19T11:35:44+0200",
  "domainid": "176895fd-48b6-11f1-a321-bc24119cb31e",
  "domainpath": "ROOT",
  "jobid": "3c7c45dc-68f1-4152-8276-a1f4cf03678f",
  "jobprocstatus": 0,
  "jobresult": {
    "errorcode": 530,
    "errortext": "Error restoring VM from backup [{\"externalId\":\"i-2-959-VM\\/2026.08.19.11.28.52\",\"name\":\"demo\",\"uuid\":\"557b755b-6426-4daa-9c96-871281a4112f\",\"vmId\":959}]."
  },
  "jobresultcode": 530,
  "jobresulttype": "object",
  "jobstatus": 2,
  "userid": "65ff40f2-48b6-11f1-a321-bc24119cb31e"
}

If the disk sizes are the same, the second restore works but disk can be swapped depending on the scenario how disk are renumbered.

Root cause

NASBackupProvider.restoreVMBackup builds two lists the backed up volumes and the instance's current
volumes, each sorted by device id, and LibvirtRestoreBackupCommandWrapper.restoreVolumesOfExistingVM
consumes them index by index:

BackupManagerImpl.restoreBackup - importRestoredVM - KVMGuru.importVirtualMachineFromBackup
re-attaches every data disk with getNextAvailableDeviceId(). The volumes are never detached first, so
their current ids still count as "in use" and the helper can never return the id a volume already holds —
every data disk is shifted by one slot on every restore.

A restore is therefore only correct while the instance's device ids still match the ones recorded in the
backup. The first restore satisfies that and then breaks it for the next one.

Behaviour, restore by restore

The first restore is always correct. It is the restore itself that can plant the fault, so the damage only
appears on second restore.

Restore #1 — succeeds, but renumbers the disks

The device ids recorded in the backup still match the instance, so the two sorted lists line up and every
backup lands in its own volume. Verified on a 2-data-disk instance by reading the volumes directly from
primary storage — each still held its own filesystem.

But on completion KVMGuru.importVirtualMachineFromBackup re-attaches the data disks with
getNextAvailableDeviceId():

t

before restore #1:   ROOT=0   demo-disk-a=1   demo-disk-b=2
after  restore #1:   ROOT=0    ddemo-disk-a=4  emo-disk-b=1         <-- renumbered
after  restore #2:   ROOT=0   demo-disk-a=2   demo-disk-b=4      <-- renumbered again

After the first restore, diskA gets id 4 because ids 1 and 2 are taken; id 3 is cdrom, so the next free id is 4. diskb gets id 1 because the id was just freed.

Restore #2 — the damage, and what it looks like depends on the disk sizes

Sorted by device id the instance is now [ROOT, demo-disk-b, demo-disk-a] while the backup is
[ROOT, demo-disk-a, demo-disk-b], so index 1 and 2 point at each other's volumes.

Equal-sized data disks (1 GB + 1 GB) — silent corruption. Both images fit their new targets, so
qemu-img succeeds and the API returns success: true. The volume contents are simply exchanged:

demo-disk-a  contains ext4 label: DISKB      <-- swapped
demo-disk-b  contains ext4 label: DISKA

A guest that mounts by LABEL=/UUID= still mounts everything at the right paths, because the labels
travel with the filesystems — so nothing looks wrong from inside the instance, while CloudStack's
volume-to-content mapping is now wrong for every per-volume operation (detach, delete, snapshot, resize,
restore single volume).

Differently sized data disks (1 GB + 2 GB), data loss, reported as a failure.
The loop processes the disks in order and only aborts on the second pairing, so the first one has already been written.
The API returns:

"errorcode": 530,
"errortext": "Error restoring VM from backup [{\"externalId\":\"i-2-959-VM/2026.08.19.11.28.52\", ...}]"

and the agent log shows the real cause — the 2 GB backup being written into the 1 GB volume:

qemu-img convert -n -O raw --image-opts
  driver=qcow2,file.filename=.../datadisk.806fa24b-a2da-45b1-9bd4-5fd51d0cc78b.qcow2   <- backup of disk B (2 GB)
  rbd:cloudstack/da2029ad-8517-4f40-af03-bd5f3ec5d959                                   <- volume of disk A (1 GB)
encountered the error: [qemu-img: output file is smaller than input]

State afterwards — the restore is reported as failed, but one volume is already gone, with no rollback:

demo-disk-a (1 GiB)   ext4-label = DISKA     <- untouched, the write into it failed
demo-disk-b (2 GiB)   ext4-label = DISKA     <- OVERWRITTEN, its own data now exists only in the backup

Both volumes still report Ready in CloudStack. An operator seeing error 530 would reasonably assume
nothing happened.

How to reproduce

Instance with two data disks, a NAS backup offering, KVM primary storage. Use two different sizes to get
the loud failure, if you want to reproduce the disk swap, I recommend using more data disks, as there is a chance that reumbering accdientaly get right and the bug does not occur. So trying like 5 or 7 data disks should most likely show the bug.

# 1. baseline: device ids 1 and 2
cmk list volumes virtualmachineid=$VM filter=name,deviceid
#   ROOT deviceid=0 | demo-disk-a deviceid=1 | demo-disk-b deviceid=2

# 2. label the filesystems so they can be told apart
#    mkfs.ext4 -L DISKA /dev/vdb ; mkfs.ext4 -L DISKB /dev/vdc

cmk create backup virtualmachineid=$VM name=demo
cmk stop virtualmachine id=$VM

# 3. FIRST restore - correct, but renumbers the disks
cmk restore backup id=$B
cmk list volumes virtualmachineid=$VM filter=name,deviceid
#   ROOT deviceid=0 | demo-disk-b deviceid=1 | demo-disk-a deviceid=4     <-- misaligned from here on

# 4. SECOND restore - silent swap (equal sizes) or error 530 after destroying a volume (different sizes)
cmk restore backup id=$B

# if you have the same sizes, you could try from UI detach diskA ( after either turning off teh safety config or disabling backups) and see that in OS the different disk vanishes.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Enhancement (improves an existing feature and functionality)
  • Cleanup (Code refactoring and cleanup, that may add test cases)
  • build/CI

How Has This Been Tested?

Unit tests — three added, all of which fail without the patch:

with the fix without the fix
KVMGuruTest 30 tests, 0 failures 30 tests, 1 error
LibvirtRestoreBackupCommandWrapperTest 13 tests, 0 failures 13 tests, 1 failure, 1 error

Every pre-existing test in both classes passes either way.

  • LibvirtRestoreBackupCommandWrapperTest.testRestoreOfExistingVmMapsBackupsToVolumesByUuid — the
    instance's volumes arrive in a different order than the backed up volumes; each backup must still be
    written into the volume it was taken from. Also asserts with never() that neither data disk's backup is
    written into the other one.
  • LibvirtRestoreBackupCommandWrapperTest.testRestoreOfExistingVmFailsWhenBackedUpVolumeIsNoLongerAttached
  • KVMGuruTest.testImportVirtualMachineFromBackupReinstatesRecordedDeviceIds — a data disk recorded on
    device id 5 must be re-attached on device id 5, and getNextAvailableDeviceId() must not be consulted.

Live testing — a patched build was deployed to a KVM + Ceph/RBD zone (management server and KVM
agents). The same 2-data-disk instance was restored three times in place:

  • device ids stayed 0,1,2 on every restore (unpatched: 1,2 → 4,1 after the first)
  • both volumes kept their own filesystem, verified with rbd export and from inside the guest
  • a 5-data-disk instance backed up and restored with all checksums intact
  • createVMFromBackup verified unaffected

calvix added 2 commits August 19, 2026 11:40
An in-place restore of a KVM instance with more than one data disk can write
a backed up disk into a different volume than the one it came from.

NASBackupProvider.restoreVMBackup pairs the backed up volumes with the
instance's current volumes by position in two lists that are each sorted by
device id, and LibvirtRestoreBackupCommandWrapper.restoreVolumesOfExistingVM
consumes those lists index by index. The volume UUID is carried along but only
used to build error messages.

That pairing is only correct while the instance's device ids still match the
ones recorded in the backup, and the restore itself breaks that: on completion
KVMGuru.importVirtualMachineFromBackup re-attaches every data disk with
getNextAvailableDeviceId(). The volumes are never detached first, so their
current ids still count as in use and the helper cannot return the id a volume
already holds - every data disk is shifted by one slot on each restore. The
root disk is unaffected because it is always re-attached at device id 0.

A second restore therefore maps the backup files onto the wrong volumes. With
equally sized disks the contents are silently exchanged and the API reports
success. With disks of different sizes the first volume is overwritten and the
restore then fails converting an image into a smaller volume, leaving that
volume half rewritten with no rollback.

Three changes:

- KVMGuru: re-attach data disks with the device id recorded in the backup,
  falling back to the next free id only when the backup has none.
- LibvirtRestoreBackupCommandWrapper: select the target volume by UUID instead
  of by list position, and fail explicitly when a backed up volume is no longer
  attached to the instance.
- NASBackupProvider: derive the UUID list and the backup file list from the
  same sorted collection so the two cannot drift apart.

The create-instance-from-backup path (restoreVolumesOfDestroyedVMs) is left
alone: it provisions new volumes with new UUIDs, so it has nothing to match on.
LibvirtRestoreBackupCommandWrapperTest: restoring an instance whose volumes
arrive in a different order than the backed up volumes must still write each
backup into the volume it was taken from, and must fail explicitly when a
backed up volume is no longer attached to the instance.

KVMGuruTest: importing an instance from a backup must re-attach its data disks
on the device ids recorded in the backup rather than allocating new ones.
@calvix
calvix force-pushed the fix/nas-backup-restore-volume-mapping branch from 8f011e1 to 4722bd4 Compare August 19, 2026 12:00
calvix and others added 27 commits August 21, 2026 09:00
… uuid

Matching backed up volumes to the instance's volumes by uuid breaks
creating an instance from a backup: that instance gets brand new volumes,
so none of the uuids recorded in the backup can ever match and the
restore always failed with the volume 'is not attached to the instance
any more' error.

When none of the recorded uuids match, the volumes are new and the device
id ordering both lists already carry is the only mapping available, so
fall back to it. When some of them do match, a missing one really is a
detached volume and is still rejected, which is what the uuid matching was
added for. The fallback also refuses to run if the two lists differ in
length.
…e#13779)

* Add NULL check during restoreVM operation when host is removed (apache#571)

* Add NULL check during restore VM operation when host is not available/removed

* fix line ending pre commit failure

* update logging with details of removed host and vm

---------

Co-authored-by: Sachin R Doddaguni <s_rudrappadoddagu@apple.com>
(cherry picked from commit 1dbda12ccdca1aaf86ef8f9b185986c9d22b567e)

* Handle null host in VM restore to prevent NPE on deleted host records

* Fix build

* Fix unit test

---------

Co-authored-by: Sachin R <32716246+sachindoddaguni@users.noreply.github.com>
Co-authored-by: Sachin R Doddaguni <s_rudrappadoddagu@apple.com>
Co-authored-by: mprokopchuk <mprokopchuk@apple.com>
…s list (apache#13931)

Co-authored-by: mprokopchuk <mprokopchuk@apple.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…VM (apache#13558)

Co-authored-by: Sachin R <32716246+sachindoddaguni@users.noreply.github.com>
Co-authored-by: dahn <daan@onecht.net>
Co-authored-by: Daan Hoogland <dahn@apache.org>
Co-authored-by: Suresh Kumar Anaparti <sureshkumar.anaparti@gmail.com>
…3144)

Co-authored-by: toni.zamparetti <toni.zamparetti@scclouds.com.br>
…etworks with dedicated vlan (apache#13379)

Co-authored-by: Pearl Dsilva <pearl1954@gmail.com>
…che#14022)

setVpcLimit ignored its parameter and copied the networkLimit field into
vpcLimit (this.vpcLimit = networkLimit), so a projects reported vpclimit
always mirrored its networklimit instead of the real VPC limit (and would be
null if setVpcLimit ran before setNetworkLimit). The sibling setters all
assign their own parameter.

Assign the vpcLimit parameter.

Adds a regression test asserting setVpcLimit stores its own value and leaves
networkLimit untouched.
Change input type for code entry field

Replaced a-input-password with a-input for better user experience.
…pache#13611)

When createVolumeAsync fails, createVolumeCallback resets the volume's
pool_id only when volume.getPodId() != null. Zone-wide primary storage
pools have no pod, so such a volume keeps a stale pool_id while it is
reverted to Allocated. On the next create/attach, findStoragePool then
returns no suitable pool because storagePoolCompatibleWithVolumePool
rejects the volume (its state is not Ready), and the operation fails with
"Unable to find suitable primary storage" even though the pool has plenty
of capacity.

Guard the reset on the field that is actually being cleared
(getPoolId() != null) instead of getPodId(), so it also applies to
zone-wide (and local) storage. The same guard is fixed in
destroyAndReallocateManagedVolume. ensureVolumeIsExpungeReady is left
unchanged as it legitimately clears pod_id.

Regression from apache#10757.
…emoval race (apache#13700)

removeNicFromVmThroughJobQueue looked up pending work jobs by
(vmType, vmId, commandName) only, so the nic was not part of the dedup
key. A second removeNicFromVirtualMachine request for a different nic on
the same vm matched the first still-pending VmWorkRemoveNicFromVm job and
joined it instead of submitting its own. That job removes only the nic it
was created for, yet both callers wait on the same job id and both receive
its success, leaving the second nic silently attached while its API call
reports success.

Make the nic uuid part of the lookup key, mirroring
addVmToNetworkThroughJobQueue which was fixed the same way in apache#5658:

  - look up pending jobs with the 4-arg
    listPendingWorkJobs(Instance, vmId, cmd, nic.getUuid())
  - fail fast with CloudRuntimeException if more than one job matches
  - stamp new jobs with setSecondaryObjectIdentifier(nic.getUuid())
    before submitting

Genuine duplicates, two requests for the same nic, still dedup as before.
Adds three regression tests to VirtualMachineManagerImplTest covering the
cross-nic race, same-nic dedup, and the multiple-pending-jobs guard.

Fixes: apache#13699
Generated-by: Claude Code (Anthropic)

Signed-off-by: Ayush Sinha <ayushsinha3199@gmail.com>
…pache#13835)

* kvm: fix RBD exclusive-lock leak that breaks revertSnapshot on Ceph

takeRbdVolumeSnapshotOfStoppedVm() called image.snapCreate(snapshotName)
twice. The first call creates the RBD snapshot, the second one always
throws RbdException ("Failed to create snapshot <uuid>") because the
snapshot already exists.

The duplicate is a merge artifact: 30d3066 ("Merge branch '4.20' into
4.22") resolved a conflict by keeping the call from both sides - each
parent had exactly one.

Because there was no finally block, that exception skipped rbd.close(image)
and r.ioCtxDestroy(io), so the agent kept the image open and held its RBD
exclusive-lock indefinitely. The exception is only logged, so the snapshot
job still reported success and the fault stayed invisible.

Consequences observed on a KVM + Ceph/RBD cluster:

- revertSnapshot fails with "com.ceph.rbd.RbdException: Failed to rollback
  snapshot <uuid>". librbd returns EROFS because a live peer holds the
  exclusive-lock; 'rbd snap rollback' only succeeds once that client dies
  and librbd can break the lock, which makes the failure look intermittent.
- getRbdSnapshotSize() is never reached, so every snapshot is reported with
  physical size 0 when snapshot.backup.to.secondary is false.
- The leaked watchers keep the image busy, so 'rbd rm' fails and the volume
  cannot be expunged - it stays stuck in state Destroy.

Note the method also runs for RUNNING VMs: createSnapshot() branches on
"RUNNING && !primaryPool.isExternalSnapshot()", and RBD is an
external-snapshot pool, so every RBD volume snapshot took this path.

Remove the duplicated call and move the image/IO-context cleanup into a
finally block so the lock is released even if the snapshot itself fails.

* kvm: release RBD handles on every path when cloning a volume from a snapshot

createRBDvolumeFromRBDSnapshot() closed the source image, the cloned image
and the RADOS IO context only on the success path, and called snapUnprotect()
only there too. Two paths escaped that cleanup:

- the early "Could not find snapshot ... on RBD" return, and
- any RadosException/RbdException from clone(), resize() or flatten(), which
  is caught and turned into a null disk.

Both leave the images open, so this client keeps the RBD exclusive-lock. That
later makes 'rbd snap rollback' (revertSnapshot) fail with EROFS from another
host, and keeps the image busy so 'rbd rm' cannot remove it - the volume then
stays stuck in state Destroy.

The failure paths after snapProtect() are worse: the snapshot stays protected,
and a protected snapshot can be deleted neither on its own nor together with
its volume.

Move the cleanup into a finally block, tracking whether the snapshot was
actually protected so it is unprotected exactly when it needs to be. Failures
during cleanup are logged and never mask the original outcome; a failed
snapUnprotect is logged at ERROR since it needs manual intervention.

This is the same class of defect as the leak fixed in
takeRbdVolumeSnapshotOfStoppedVm(); no behaviour changes on the success path.

* kvm: add regression tests for the RBD snapshot handle leak

Two tests around takeRbdVolumeSnapshotOfStoppedVm, using the MockedConstruction
pattern already used in this test class (the Rbd instance is created inside the
method under test, so it cannot be injected):

- createsSnapshotExactlyOnce guards the duplicated snapCreate call from coming
  back, and checks the image and IO context are released.
- releasesHandlesWhenSnapshotFails makes snapCreate throw and asserts the image
  is still closed and the IO context destroyed, so a future failure cannot leak
  the RBD exclusive-lock again.

takeRbdVolumeSnapshotOfStoppedVm, radosConnect and getRbdSnapshotSize widened
from private to protected so the test can stub the Ceph interactions.

* kvm: extract RBD handle teardown into null-safe helper methods

Address review feedback: the nested try-catch teardown blocks in the
finally clauses of takeRbdVolumeSnapshotOfStoppedVm and
createRBDvolumeFromRBDSnapshot are extracted into two reusable,
null-safe helpers that log but never throw:

- closeRbdImage (3 call sites)
- destroyRadosIoCtx (2 call sites)

No behavior change.

* kvm: extract RBD snapshot unprotect into a helper method

Follow-up to the teardown helpers: the snapUnprotect block in the
finally clause of createRBDvolumeFromRBDSnapshot moves into a
never-throwing unprotectRbdSnapshot helper, so the finally clause is
now free of inline try-catch constructions entirely.

No behavior change.

---------

Co-authored-by: calvix <7136358+calvix@users.noreply.github.com>
erikbocks and others added 27 commits September 8, 2026 16:30
* Add VR system offering to network offerings listing

* Change method to display order column as the last one

* Remove VR's service offering id and name from non-admin users

* Removal of domain column and addition of Egress default policy column

* Revert domain removal
* Allow cleaning up of networks stuck in Implementing state

* extract code
…ePublicLoadBalancerRule (apache#14027)

createPublicLoadBalancerRule resolved ipVO only when an ipAddrId was supplied,
then at the port-53 check did (srcPortStart == DNS_PORT && ipVO.isSourceNat()).
For an elastic-LB rule created without an explicit IP (ipAddrId == null) the
system IP is allocated later, so ipVO was still null and creating a rule on
port 53 threw a NullPointerException. The ipVO == null validation only runs
further down.

Guard the check with ipVO != null so the DNS/Source NAT conflict test is
skipped when there is no IP yet; the flow then reaches the existing
can't-find-source-IP parameter error.

Adds a regression test creating a port-53 rule with a null ipAddrId
(NullPointerException before the fix).
Restoring a volume from a backup and attaching it to a VM has been broken
since the restore commands were changed to run without a shell, in three
independent ways.

getDeviceToAttachDisk pipes virsh domblklist through awk, but passes the awk
program still wrapped in the single quotes a shell would have stripped. Run
directly, awk fails with "invalid char" and returns nothing, so the device
name is empty and charAt throws StringIndexOutOfBoundsException before any
attach is attempted. This affects every storage type. The exit value was also
never checked, and the output not trimmed, so even a working awk would leave
the trailing line separator and increment that instead of the device letter.

The RBD branch passes the literal string "<<EOF%sEOF" as a virsh argument. The
placeholder is never substituted with the disk XML, and a here-document cannot
work without a shell, so virsh is handed a bogus argument and fails. The XML is
now written to a temporary file that virsh reads.

The Linstor branch declares "--subdriver qcow2", inverting the previous
behaviour where Linstor got a raw attach and every other pool got qcow2. A
Linstor volume is a raw DRBD block device, so libvirt rejects it with "Image is
not in qcow2 format". The condition is restored, along with the "--driver qemu"
that was dropped.
…ache#14064)

The "Migrate instance to another host" wizard lets an operator pick a destination host and, optionally, a destination
primary storage. Today the storage list is not filtered by the selected host, so it offers primary storages the host cannot reach. This filters that list down to the storages actually accessible to the selected host.
…#14050)

createOrupdateConfigObject created a missing configuration subgroup with the
no-arg ConfigurationSubGroupVO constructor, so the row was written with a null
name and null group_id. Because the name stayed null, the next
findByNameAndGroup lookup missed again and inserted another null row on every
management-server restart. Build the subgroup with its name and precedence and
set its group id, matching the sibling configuration-group branch.
Signed-off-by: kunal.behbudzade <kunal.behbudzade@btsgrp.com>
Co-authored-by: kunal.behbudzade <kunal.behbudzade@btsgrp.com>
…apache#14023)

equals() returned false when the two responses had the SAME value and true
when the values DIFFERED (the value branch was inverted):

    else if (this.getValue().equals(other.getValue()))
        return false;

So two identical details were treated as unequal and two details differing
only by value were treated as equal, corrupting any Set/Map/dedup keyed on
ImageStoreDetailResponse. It also NPEd when value was null.

Compare with !Objects.equals(getValue(), other.getValue()), which restores
the correct result and is null-safe.

Adds tests for equal name+value, differing value, and differing name.
…essions sockets and introduce reconnection window for console sessions (apache#13683)

* Add lock and synchronize allowed sessions

* Fix closing connections to VNC ports on console close

* Add reconnection grant window to prevent network connectivity issues after acquiring a session

* Introduce zone setting to control the reconnection window for console sessions
* Routed: get vm network statistics on Routed network

* Potential fix for pull request finding

* Create method isNetworkEligibleForNetworkStats and add unit tests
Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
…BD (apache#13991)

Ceph Tentacle 20.2.4 removed the legacy `auth_supported` librados option.
CloudStack builds RBD connection strings with `auth_supported=cephx`/`none`,
so every RBD operation through the KVM agent fails with
"failed to set RADOS option: auth_supported".

Switch the RBD string builder to `auth_client_required`, the modern option
used by the qemu rbd driver and accepted by current Ceph releases.

Co-authored-by: Wei Zhou <weizhou@apache.org>
…les (apache#7562)

* Add basic `.editorconfig` file to help maintain consistent coding styles
…ypervisors (apache#13930)

* fix NPE when accessing systemvm templates for unsupported hypervisors in deploySharedFSVM()

* systemvm -> SystemVM

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* Add unit test for skip-hypervisor-without-template fix in deploySharedFSVM()

* sharedfs: drop duplicate skip-hypervisor test after 4.22 merge

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
… on RBD (apache#13361)

RBD erasure-coded pool support (apache#9808) added handling of the
rbd_default_data_pool storage-pool detail to RBDStringBuilder (qemu-img
path) and to createPhysicalDisk (blank volumes), but not to
createDiskFromTemplateOnRBD. As a result, ROOT volumes created from a
template via rados-java rbd.clone()/rbd.create() are created without a
data pool: all of their data objects land in the (replicated) metadata
pool instead of the erasure-coded data pool, defeating the point of EC
and consuming ~3x raw space.

Set rbd_default_data_pool on the Rados connection (before connect) in
both the same-cluster clone/copy branch and the cross-cluster copy
branch of createDiskFromTemplateOnRBD, using the destination pool's
detail. librbd then honors it as the default data pool when the new
image is created, so template-derived volumes get data_pool set, the
same way blank volumes already do.
…storeBackupCommandWrapper (apache#14006)

* kvm: detect mount failures and honour the configured timeouts on backup restore

Script.executeCommand returns null when the command fails, it does not throw,
so the try/catch around the mount and umount of the backup repository could
never fire and the return value was discarded. A repository that fails to mount
was therefore treated as mounted, and the restore carried on against an empty
directory until it failed later with a misleading "backup file not found". A
failed umount was ignored the same way, leaking the mount. Both now go through
executeCommandForExitValue and check the exit value.

The same refactor also dropped the timeouts. mountTimeout was still passed into
mountBackupDirectory but never used, and the rsync of the volume lost the
command timeout, so both fell back to the one hour default in Script instead of
the configured values. An unresponsive repository could hold a restore up for
an hour rather than failing after nas.backup.restore.mount.timeout seconds.

* kvm: clean up after a failed mount and bound the unmount of a backup repository

The directory created for the mount is removed by the caller in a finally block,
but that block is only reached once the mount has succeeded, so a repository
that cannot be mounted left an empty directory behind on every attempt. It is
now removed before the failure is reported, best effort and logged if it cannot
be.

The unmount ran without a timeout and so fell back to the one hour default in
Script. Unmounting a repository that has become unreachable blocks as easily as
mounting one, and this runs in the cleanup path of a restore, so it is now
bounded by the configured mount timeout like the mount itself.
Conflicts:
- LibvirtRestoreBackupCommandWrapperTest.java: apache#14006 appended three mount/unmount
  timeout tests at the end of the class, where the volume mapping tests end too;
  kept both.
…eouts

apache#14006 runs the mount, the umount and the rsync of a restore through
Script.executeCommandForExitValue(long, String...) so that they honour the
configured timeouts. The volume mapping tests still stubbed and verified the
overloads without a timeout. The two tests asserting where a backup is written
therefore failed, and the checks that a backup is never written into the wrong
volume passed without verifying anything, as nothing called that overload any
more. Stub and verify the timeout variants instead, as apache#14006 does for the
existing tests.
The incremental NAS backup backport (apache#13877) makes a restore first ask
qemu-img whether the backup references a parent, and flattens the chain with
qemu-img convert when it does; only a backup without a backing file is
rsynced. The volume mapping tests stubbed runSimpleBashScriptForExitValue to
return 0 for every command, so the probe reported a backing chain, the restore
took the convert path, and the rsync invocations these tests assert on never
happened. Answer the probe with "no backing chain", as the existing restore
tests do, and keep 0 for the other checks.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.