mirror of
https://github.com/lxc/incus
synced 2026-08-02 05:26:46 +00:00
Merge pull request #3740 from stgraber/storage
Some checks are pending
Build / Build (${{ matrix.architecture }}) (amd64) (push) Waiting to run
Build / Build (${{ matrix.architecture }}) (arm64) (push) Waiting to run
Tests / Code (oldstable) (push) Waiting to run
Tests / Code (stable) (push) Waiting to run
Tests / Code (tip) (push) Waiting to run
Tests / System (btrfs, stable, ubuntu-24.04, cluster) (push) Waiting to run
Tests / System (btrfs, stable, ubuntu-24.04, standalone_storage) (push) Waiting to run
Tests / System (ceph, stable, ubuntu-24.04, cluster) (push) Waiting to run
Tests / System (ceph, stable, ubuntu-24.04, standalone_storage) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04, cluster) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04, standalone_container) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04, standalone_core) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04, standalone_network) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04, standalone_storage) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04-arm, cluster) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04-arm, standalone_container) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04-arm, standalone_core) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04-arm, standalone_network) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04-arm, standalone_storage) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04, cluster) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04, standalone_container) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04, standalone_core) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04, standalone_network) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04, standalone_storage) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04-arm, cluster) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04-arm, standalone_container) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04-arm, standalone_core) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04-arm, standalone_network) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04-arm, standalone_storage) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04, cluster) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04, standalone_container) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04, standalone_core) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04, standalone_network) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04, standalone_storage) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04-arm, cluster) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04-arm, standalone_container) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04-arm, standalone_core) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04-arm, standalone_network) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04-arm, standalone_storage) (push) Waiting to run
Tests / System (linstor, stable, ubuntu-24.04, cluster) (push) Waiting to run
Tests / System (linstor, stable, ubuntu-24.04, standalone_storage) (push) Waiting to run
Tests / System (lvm, stable, ubuntu-24.04, cluster) (push) Waiting to run
Tests / System (lvm, stable, ubuntu-24.04, standalone_storage) (push) Waiting to run
Tests / System (random, stable, ubuntu-24.04, cluster) (push) Waiting to run
Tests / System (random, stable, ubuntu-24.04, standalone_storage) (push) Waiting to run
Tests / System (zfs, stable, ubuntu-24.04, cluster) (push) Waiting to run
Tests / System (zfs, stable, ubuntu-24.04, standalone_storage) (push) Waiting to run
Tests / Client (oldstable, macos-latest) (push) Waiting to run
Tests / Client (oldstable, ubuntu-latest) (push) Waiting to run
Tests / Client (oldstable, windows-latest) (push) Waiting to run
Tests / Client (stable, macos-latest) (push) Waiting to run
Tests / Client (stable, ubuntu-latest) (push) Waiting to run
Tests / Client (stable, windows-latest) (push) Waiting to run
Tests / Documentation (push) Waiting to run
Some checks are pending
Build / Build (${{ matrix.architecture }}) (amd64) (push) Waiting to run
Build / Build (${{ matrix.architecture }}) (arm64) (push) Waiting to run
Tests / Code (oldstable) (push) Waiting to run
Tests / Code (stable) (push) Waiting to run
Tests / Code (tip) (push) Waiting to run
Tests / System (btrfs, stable, ubuntu-24.04, cluster) (push) Waiting to run
Tests / System (btrfs, stable, ubuntu-24.04, standalone_storage) (push) Waiting to run
Tests / System (ceph, stable, ubuntu-24.04, cluster) (push) Waiting to run
Tests / System (ceph, stable, ubuntu-24.04, standalone_storage) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04, cluster) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04, standalone_container) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04, standalone_core) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04, standalone_network) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04, standalone_storage) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04-arm, cluster) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04-arm, standalone_container) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04-arm, standalone_core) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04-arm, standalone_network) (push) Waiting to run
Tests / System (dir, oldstable, ubuntu-24.04-arm, standalone_storage) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04, cluster) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04, standalone_container) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04, standalone_core) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04, standalone_network) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04, standalone_storage) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04-arm, cluster) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04-arm, standalone_container) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04-arm, standalone_core) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04-arm, standalone_network) (push) Waiting to run
Tests / System (dir, stable, ubuntu-24.04-arm, standalone_storage) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04, cluster) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04, standalone_container) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04, standalone_core) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04, standalone_network) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04, standalone_storage) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04-arm, cluster) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04-arm, standalone_container) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04-arm, standalone_core) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04-arm, standalone_network) (push) Waiting to run
Tests / System (dir, tip, ubuntu-24.04-arm, standalone_storage) (push) Waiting to run
Tests / System (linstor, stable, ubuntu-24.04, cluster) (push) Waiting to run
Tests / System (linstor, stable, ubuntu-24.04, standalone_storage) (push) Waiting to run
Tests / System (lvm, stable, ubuntu-24.04, cluster) (push) Waiting to run
Tests / System (lvm, stable, ubuntu-24.04, standalone_storage) (push) Waiting to run
Tests / System (random, stable, ubuntu-24.04, cluster) (push) Waiting to run
Tests / System (random, stable, ubuntu-24.04, standalone_storage) (push) Waiting to run
Tests / System (zfs, stable, ubuntu-24.04, cluster) (push) Waiting to run
Tests / System (zfs, stable, ubuntu-24.04, standalone_storage) (push) Waiting to run
Tests / Client (oldstable, macos-latest) (push) Waiting to run
Tests / Client (oldstable, ubuntu-latest) (push) Waiting to run
Tests / Client (oldstable, windows-latest) (push) Waiting to run
Tests / Client (stable, macos-latest) (push) Waiting to run
Tests / Client (stable, ubuntu-latest) (push) Waiting to run
Tests / Client (stable, windows-latest) (push) Waiting to run
Tests / Documentation (push) Waiting to run
Storage fixes
This commit is contained in:
commit
7ec79d0778
@ -1090,11 +1090,14 @@ func cleanupDependentDisks(s *state.State, inst instance.Instance, deviceOverrid
|
||||
return fmt.Errorf("Failed loading storage pool: %w", err)
|
||||
}
|
||||
|
||||
volName, _ := internalInstance.SplitVolumeSource(dev.Config["source"])
|
||||
|
||||
// If new disk was created than delete source volume.
|
||||
override, ok := deviceOverrides[dev.Name]
|
||||
if ok {
|
||||
if (override["source"] != "" && override["source"] != dev.Config["source"]) || (override["pool"] != "" && override["pool"] != dev.Config["pool"]) {
|
||||
_ = diskPool.DeleteCustomVolume(inst.Project().Name, dev.Config["source"], op)
|
||||
overrideVolName, _ := internalInstance.SplitVolumeSource(override["source"])
|
||||
if (override["source"] != "" && overrideVolName != volName) || (override["pool"] != "" && override["pool"] != dev.Config["pool"]) {
|
||||
_ = diskPool.DeleteCustomVolume(inst.Project().Name, volName, op)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1103,7 +1106,7 @@ func cleanupDependentDisks(s *state.State, inst instance.Instance, deviceOverrid
|
||||
return nil
|
||||
}
|
||||
|
||||
_ = diskPool.DeleteCustomVolume(inst.Project().Name, dev.Config["source"], op)
|
||||
_ = diskPool.DeleteCustomVolume(inst.Project().Name, volName, op)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
@ -561,7 +561,9 @@ func validateDependentVolumes(source instance.Instance, req *api.InstancesPost)
|
||||
}
|
||||
|
||||
// Check if the source was overridden.
|
||||
if oldDevice["source"] == newDevice["source"] {
|
||||
oldVolName, _ := internalInstance.SplitVolumeSource(oldDevice["source"])
|
||||
newVolName, _ := internalInstance.SplitVolumeSource(newDevice["source"])
|
||||
if oldVolName == newVolName {
|
||||
return fmt.Errorf("Device source name should be different during copy for dependent disk: %s", key)
|
||||
}
|
||||
}
|
||||
|
||||
@ -4579,7 +4579,8 @@ func (d *lxc) delete(force bool, cleanupDependencies bool) error {
|
||||
return fmt.Errorf("Failed loading storage pool: %w", err)
|
||||
}
|
||||
|
||||
err = diskPool.DeleteCustomVolume(d.Project().Name, dev.Config["source"], nil)
|
||||
volName, _ := internalInstance.SplitVolumeSource(dev.Config["source"])
|
||||
err = diskPool.DeleteCustomVolume(d.Project().Name, volName, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -1048,7 +1048,7 @@ func (d *qemu) receiveMigrationSnapshot(monitor *qmp.Monitor, blockExport string
|
||||
_ = monitor.NBDServerStop()
|
||||
}()
|
||||
|
||||
err = monitor.NBDBlockExportAdd(blockExport, blockExport, true, nil)
|
||||
err = monitor.NBDBlockExportAdd(blockExport, blockExport, true, "", nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed adding root disk to NBD server: %w", err)
|
||||
}
|
||||
@ -7675,7 +7675,8 @@ func (d *qemu) delete(force bool, cleanupDependencies bool) error {
|
||||
return fmt.Errorf("Failed loading storage pool: %w", err)
|
||||
}
|
||||
|
||||
err = diskPool.DeleteCustomVolume(d.Project().Name, dev.Config["source"], nil)
|
||||
volName, _ := internalInstance.SplitVolumeSource(dev.Config["source"])
|
||||
err = diskPool.DeleteCustomVolume(d.Project().Name, volName, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -8297,7 +8298,9 @@ func (d *qemu) MigrateSend(args instance.MigrateSendArgs) error {
|
||||
}
|
||||
|
||||
// prepareEphemeralSnapshot sets up an overlay block device suitable for short lived operations.
|
||||
func (d *qemu) prepareEphemeralSnapshot(monitor *qmp.Monitor, diskName string, diskSize int64) (string, string, func(), error) {
|
||||
// When backed is true, the overlay is opened with the disk's current top node as its backing
|
||||
// node, as needed by copy-before-write overlays.
|
||||
func (d *qemu) prepareEphemeralSnapshot(monitor *qmp.Monitor, diskName string, diskSize int64, backed bool) (string, string, func(), error) {
|
||||
snapshotDiskName := ephemeralSnapshotName(diskName)
|
||||
|
||||
// Create snapshot of the disk.
|
||||
@ -8344,33 +8347,7 @@ func (d *qemu) prepareEphemeralSnapshot(monitor *qmp.Monitor, diskName string, d
|
||||
|
||||
_ = snapFile.Close() // Don't prevent clean unmount when instance is stopped.
|
||||
|
||||
// Add the snapshot file as a block device (not visible to the guest OS).
|
||||
err = monitor.AddBlockDevice(map[string]any{
|
||||
"driver": "qcow2",
|
||||
"node-name": snapshotDiskName,
|
||||
"read-only": false,
|
||||
"file": map[string]any{
|
||||
"driver": "file",
|
||||
"filename": fmt.Sprintf("/dev/fdset/%d", info.ID),
|
||||
},
|
||||
}, nil, false)
|
||||
if err != nil {
|
||||
return "", "", nil, fmt.Errorf("Failed adding migration storage snapshot block device: %w", err)
|
||||
}
|
||||
|
||||
reverter := revert.New()
|
||||
defer reverter.Fail()
|
||||
|
||||
removeOverlay := func() {
|
||||
err := monitor.RemoveBlockDevice(snapshotDiskName)
|
||||
if err != nil {
|
||||
d.logger.Error("Failed removing temporary snapshot disk device", logger.Ctx{"err": err})
|
||||
}
|
||||
}
|
||||
|
||||
reverter.Add(removeOverlay)
|
||||
|
||||
// Find the base block device that writes should be redirected away from.
|
||||
// Find the disk's current top node, the base of the new overlay.
|
||||
blockDevs, err := d.fetchBlockDeviceChain(monitor, diskName)
|
||||
if err != nil {
|
||||
return "", "", nil, fmt.Errorf("Failed fetching block device chain: %w", err)
|
||||
@ -8378,7 +8355,32 @@ func (d *qemu) prepareEphemeralSnapshot(monitor *qmp.Monitor, diskName string, d
|
||||
|
||||
blockDevName := blockDevs[len(blockDevs)-1]
|
||||
|
||||
reverter.Success()
|
||||
blockDev := map[string]any{
|
||||
"driver": "qcow2",
|
||||
"node-name": snapshotDiskName,
|
||||
"read-only": false,
|
||||
"file": map[string]any{
|
||||
"driver": "file",
|
||||
"filename": fmt.Sprintf("/dev/fdset/%d", info.ID),
|
||||
},
|
||||
}
|
||||
|
||||
if backed {
|
||||
blockDev["backing"] = blockDevName
|
||||
}
|
||||
|
||||
// Add the snapshot file as a block device (not visible to the guest OS).
|
||||
err = monitor.AddBlockDevice(blockDev, nil, false)
|
||||
if err != nil {
|
||||
return "", "", nil, fmt.Errorf("Failed adding migration storage snapshot block device: %w", err)
|
||||
}
|
||||
|
||||
removeOverlay := func() {
|
||||
err := monitor.RemoveBlockDevice(snapshotDiskName)
|
||||
if err != nil {
|
||||
d.logger.Error("Failed removing temporary snapshot disk device", logger.Ctx{"err": err})
|
||||
}
|
||||
}
|
||||
|
||||
return snapshotDiskName, blockDevName, removeOverlay, nil
|
||||
}
|
||||
@ -8420,6 +8422,22 @@ func (d *qemu) mergeEphemeralSnapshot(monitor *qmp.Monitor, overlayNode string)
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeEphemeralOverlay tears down a copy-before-write overlay, cancelling its backup job first.
|
||||
func (d *qemu) removeEphemeralOverlay(monitor *qmp.Monitor, overlayNode string) error {
|
||||
// Cancel the copy-before-write job if it is still running.
|
||||
err := monitor.BlockJobCancelWait(overlayNode)
|
||||
if err != nil {
|
||||
d.logger.Debug("Failed cancelling overlay block job", logger.Ctx{"overlay": overlayNode, "err": err})
|
||||
}
|
||||
|
||||
err = monitor.RemoveBlockDevice(overlayNode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed removing temporary snapshot overlay %q: %w", overlayNode, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createEphemeralSnapshot creates a temporary snapshot of the disk that is intended for short-lived operations.
|
||||
func (d *qemu) createEphemeralSnapshot(diskName string, diskSize int64) (func(), error) {
|
||||
monitor, err := d.qmpConnect()
|
||||
@ -8427,7 +8445,7 @@ func (d *qemu) createEphemeralSnapshot(diskName string, diskSize int64) (func(),
|
||||
return nil, err
|
||||
}
|
||||
|
||||
snapshotDiskName, blockDevName, removeOverlay, err := d.prepareEphemeralSnapshot(monitor, diskName, diskSize)
|
||||
snapshotDiskName, blockDevName, removeOverlay, err := d.prepareEphemeralSnapshot(monitor, diskName, diskSize, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -11676,7 +11694,7 @@ func (d *qemu) ExportQcow2Block(diskName string, blockIndex int) (func(), string
|
||||
|
||||
exportDiskPath := fmt.Sprintf("nbd+unix:///%s?socket=%s", exportBlockName, shortSocketPath)
|
||||
|
||||
err = monitor.NBDBlockExportAdd(exportBlockName, exportBlockName, false, nil)
|
||||
err = monitor.NBDBlockExportAdd(exportBlockName, exportBlockName, false, "", nil)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("Failed adding disk to NBD server: %w", err)
|
||||
}
|
||||
@ -11828,10 +11846,19 @@ func (d *qemu) ConnectNBD(diskName string, volSize int64, writable bool) (net.Co
|
||||
reverter := revert.New()
|
||||
defer reverter.Fail()
|
||||
|
||||
overlayNode := ""
|
||||
|
||||
disconnect := func() {
|
||||
d.logger.Debug("User requested NBD server stopped")
|
||||
_ = nbdConn.Close()
|
||||
_ = monitor.NBDServerStop()
|
||||
|
||||
if overlayNode != "" {
|
||||
err := d.removeEphemeralOverlay(monitor, overlayNode)
|
||||
if err != nil {
|
||||
d.logger.Error("Failed removing temporary snapshot overlay", logger.Ctx{"overlay": overlayNode, "err": err})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reverter.Add(disconnect)
|
||||
@ -11859,17 +11886,27 @@ func (d *qemu) ConnectNBD(diskName string, volSize int64, writable bool) (net.Co
|
||||
}
|
||||
|
||||
blockExport := blockDevs[len(blockDevs)-1]
|
||||
exportNode := blockExport
|
||||
|
||||
if !writable {
|
||||
cleanupSnapshot, err := d.createEphemeralSnapshot(blockExport, volSize)
|
||||
// Expose a frozen view of the disk through a copy-before-write overlay
|
||||
// (see ConnectNBDAllDisks).
|
||||
snapNode, baseNode, removeOverlay, err := d.prepareEphemeralSnapshot(monitor, blockExport, volSize, true)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Failed creating temporary snapshot: %w", err)
|
||||
}
|
||||
|
||||
reverter.Add(cleanupSnapshot)
|
||||
err = monitor.BlockDevBackupTransaction([]qmp.BlockDevBackupTarget{{Device: baseNode, Target: snapNode, Sync: "none", JobID: snapNode}})
|
||||
if err != nil {
|
||||
removeOverlay()
|
||||
return nil, nil, fmt.Errorf("Failed creating temporary snapshot: %w", err)
|
||||
}
|
||||
|
||||
overlayNode = snapNode
|
||||
exportNode = snapNode
|
||||
}
|
||||
|
||||
err = monitor.NBDBlockExportAdd(blockExport, "", writable, bitmapNames)
|
||||
err = monitor.NBDBlockExportAdd(exportNode, "", writable, blockExport, bitmapNames)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Failed adding disk to NBD server: %w", err)
|
||||
}
|
||||
@ -11997,9 +12034,9 @@ func (d *qemu) ConnectNBDAllDisks(reuse bool) (net.Conn, func(), error) {
|
||||
continue
|
||||
}
|
||||
|
||||
err = d.mergeEphemeralSnapshot(monitor, overlayNode)
|
||||
err = d.removeEphemeralOverlay(monitor, overlayNode)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Failed recovering disk %q from an earlier failed snapshot merge: %w", devName, err)
|
||||
return nil, nil, fmt.Errorf("Failed recovering disk %q from an earlier failed teardown: %w", devName, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -12028,14 +12065,17 @@ func (d *qemu) ConnectNBDAllDisks(reuse bool) (net.Conn, func(), error) {
|
||||
type exportTarget struct {
|
||||
deviceName string
|
||||
exportNode string
|
||||
bitmapNode string
|
||||
bitmaps []string
|
||||
}
|
||||
|
||||
targets := make([]exportTarget, 0, len(deviceNames))
|
||||
snapshots := make([]qmp.BlockDevSnapshotTarget, 0, len(deviceNames))
|
||||
backups := make([]qmp.BlockDevBackupTarget, 0, len(deviceNames))
|
||||
overlays := make([]string, 0, len(deviceNames))
|
||||
|
||||
// Prepare an overlay for each disk so the guest keeps running while we export a frozen view.
|
||||
// Prepare a copy-before-write overlay for each disk, exposing a frozen view of it while the
|
||||
// guest keeps writing to the disk itself. Snapshotting the disk instead would reopen it and
|
||||
// its persistent dirty bitmaps read-only, making the eventual merge of the snapshot fail.
|
||||
for _, devName := range deviceNames {
|
||||
nodeName := d.blockNodeName(linux.PathNameEncode(devName))
|
||||
|
||||
@ -12058,26 +12098,26 @@ func (d *qemu) ConnectNBDAllDisks(reuse bool) (net.Conn, func(), error) {
|
||||
return nil, nil, fmt.Errorf("Failed fetching size for %q: %w", devName, err)
|
||||
}
|
||||
|
||||
overlayNode, baseNode, removeOverlay, err := d.prepareEphemeralSnapshot(monitor, nodeName, diskSize)
|
||||
overlayNode, baseNode, removeOverlay, err := d.prepareEphemeralSnapshot(monitor, nodeName, diskSize, true)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Failed creating temporary snapshot for %q: %w", devName, err)
|
||||
}
|
||||
|
||||
reverter.Add(removeOverlay)
|
||||
|
||||
snapshots = append(snapshots, qmp.BlockDevSnapshotTarget{Node: baseNode, Overlay: overlayNode})
|
||||
targets = append(targets, exportTarget{deviceName: devName, exportNode: baseNode, bitmaps: bitmapNames})
|
||||
backups = append(backups, qmp.BlockDevBackupTarget{Device: baseNode, Target: overlayNode, Sync: "none", JobID: overlayNode})
|
||||
targets = append(targets, exportTarget{deviceName: devName, exportNode: overlayNode, bitmapNode: baseNode, bitmaps: bitmapNames})
|
||||
overlays = append(overlays, overlayNode)
|
||||
}
|
||||
|
||||
// Create all overlays atomically so the exported disks share a consistent point in time.
|
||||
err = monitor.BlockDevSnapshotTransaction(snapshots)
|
||||
// Start all copy-before-write jobs atomically so the exported disks share a consistent
|
||||
// point in time.
|
||||
err = monitor.BlockDevBackupTransaction(backups)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Failed creating consistent storage snapshot: %w", err)
|
||||
}
|
||||
|
||||
// The overlays are now active and hold the guest's ongoing writes, so they must be committed
|
||||
// back rather than simply removed. Take over cleanup from the reverter.
|
||||
// The overlays never hold guest writes, teardown cancels the jobs and drops them.
|
||||
reverter.Success()
|
||||
|
||||
stop := func() {
|
||||
@ -12092,13 +12132,16 @@ func (d *qemu) ConnectNBDAllDisks(reuse bool) (net.Conn, func(), error) {
|
||||
_ = os.Remove(d.nbdPath())
|
||||
|
||||
for _, overlayNode := range overlays {
|
||||
_ = d.mergeEphemeralSnapshot(monitor, overlayNode)
|
||||
err := d.removeEphemeralOverlay(monitor, overlayNode)
|
||||
if err != nil {
|
||||
d.logger.Error("Failed removing temporary snapshot overlay", logger.Ctx{"overlay": overlayNode, "err": err})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add an NBD export per disk, using the Incus device name as the export name.
|
||||
for _, target := range targets {
|
||||
err = monitor.NBDBlockExportAdd(target.exportNode, target.deviceName, false, target.bitmaps)
|
||||
err = monitor.NBDBlockExportAdd(target.exportNode, target.deviceName, false, target.bitmapNode, target.bitmaps)
|
||||
if err != nil {
|
||||
stop()
|
||||
return nil, nil, fmt.Errorf("Failed adding disk %q to NBD server: %w", target.deviceName, err)
|
||||
|
||||
@ -1259,8 +1259,9 @@ func (m *Monitor) NBDServerStop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NBDBlockExportAdd exports a writable device via the NBD server.
|
||||
func (m *Monitor) NBDBlockExportAdd(deviceNodeName string, exportName string, writable bool, bitmapNames []string) error {
|
||||
// NBDBlockExportAdd adds a block export to the NBD server. Bitmaps are looked up on
|
||||
// bitmapNode, which defaults to the exported node when empty.
|
||||
func (m *Monitor) NBDBlockExportAdd(deviceNodeName string, exportName string, writable bool, bitmapNode string, bitmapNames []string) error {
|
||||
var args struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
@ -1279,12 +1280,16 @@ func (m *Monitor) NBDBlockExportAdd(deviceNodeName string, exportName string, wr
|
||||
args.Name = exportName
|
||||
args.Writable = writable
|
||||
|
||||
if bitmapNode == "" {
|
||||
bitmapNode = deviceNodeName
|
||||
}
|
||||
|
||||
for _, b := range bitmapNames {
|
||||
args.Bitmaps = append(args.Bitmaps, struct {
|
||||
Node string `json:"node"`
|
||||
Name string `json:"name"`
|
||||
}{
|
||||
Node: deviceNodeName,
|
||||
Node: bitmapNode,
|
||||
Name: b,
|
||||
})
|
||||
}
|
||||
@ -1403,25 +1408,27 @@ func (m *Monitor) BlockDevSnapshot(deviceNodeName string, snapshotNodeName strin
|
||||
return nil
|
||||
}
|
||||
|
||||
// BlockDevSnapshotTarget describes a single blockdev-snapshot action.
|
||||
type BlockDevSnapshotTarget struct {
|
||||
Node string `json:"node"`
|
||||
Overlay string `json:"overlay"`
|
||||
// BlockDevBackupTarget describes a single blockdev-backup action.
|
||||
type BlockDevBackupTarget struct {
|
||||
Device string `json:"device"`
|
||||
Target string `json:"target"`
|
||||
Sync string `json:"sync"`
|
||||
JobID string `json:"job-id"`
|
||||
}
|
||||
|
||||
// BlockDevSnapshotTransaction atomically creates the given device snapshots in a single
|
||||
// transaction so that all overlays are taken at the same point in time.
|
||||
func (m *Monitor) BlockDevSnapshotTransaction(snapshots []BlockDevSnapshotTarget) error {
|
||||
// BlockDevBackupTransaction atomically starts the given blockdev-backup jobs in a single
|
||||
// transaction so that all targets share the same point in time.
|
||||
func (m *Monitor) BlockDevBackupTransaction(backups []BlockDevBackupTarget) error {
|
||||
type action struct {
|
||||
Type string `json:"type"`
|
||||
Data BlockDevSnapshotTarget `json:"data"`
|
||||
Type string `json:"type"`
|
||||
Data BlockDevBackupTarget `json:"data"`
|
||||
}
|
||||
|
||||
actions := make([]action, 0, len(snapshots))
|
||||
for _, snapshot := range snapshots {
|
||||
actions := make([]action, 0, len(backups))
|
||||
for _, backup := range backups {
|
||||
actions = append(actions, action{
|
||||
Type: "blockdev-snapshot",
|
||||
Data: snapshot,
|
||||
Type: "blockdev-backup",
|
||||
Data: backup,
|
||||
})
|
||||
}
|
||||
|
||||
@ -1632,6 +1639,21 @@ func (m *Monitor) BlockJobCancel(deviceNodeName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// BlockJobCancelWait cancels an ongoing block job and waits until it is gone.
|
||||
func (m *Monitor) BlockJobCancelWait(jobID string) error {
|
||||
err := m.BlockJobCancel(jobID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = m.blockJobWait(jobID, false, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BlockJobComplete completes a block job that is in ready state.
|
||||
func (m *Monitor) BlockJobComplete(deviceNodeName string) error {
|
||||
var args struct {
|
||||
|
||||
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
internalInstance "github.com/lxc/incus/v7/internal/instance"
|
||||
"github.com/lxc/incus/v7/internal/migration"
|
||||
backupConfig "github.com/lxc/incus/v7/internal/server/backup/config"
|
||||
"github.com/lxc/incus/v7/internal/server/operations"
|
||||
@ -311,7 +312,7 @@ func ProtobufToDependentVolume(volume *migration.DependentVolume, migrationType
|
||||
}
|
||||
|
||||
if overrides["source"] != "" {
|
||||
volName = overrides["source"]
|
||||
volName, _ = internalInstance.SplitVolumeSource(overrides["source"])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1262,7 +1262,9 @@ func (b *backend) CreateInstanceFromCopy(inst instance.Instance, src instance.In
|
||||
return fmt.Errorf("Failed loading storage pool: %w", err)
|
||||
}
|
||||
|
||||
err = diskPool.CreateCustomVolumeFromCopy(inst.Project().Name, src.Project().Name, newDevices[dev.Name]["source"], "", nil, dev.Config["pool"], dev.Config["source"], snapshots, op)
|
||||
newVolName, _ := internalInstance.SplitVolumeSource(newDevices[dev.Name]["source"])
|
||||
srcVolName, _ := internalInstance.SplitVolumeSource(dev.Config["source"])
|
||||
err = diskPool.CreateCustomVolumeFromCopy(inst.Project().Name, src.Project().Name, newVolName, "", nil, dev.Config["pool"], srcVolName, snapshots, op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -1808,7 +1810,9 @@ func (b *backend) RefreshInstance(inst instance.Instance, src instance.Instance,
|
||||
return fmt.Errorf("Failed loading storage pool: %w", err)
|
||||
}
|
||||
|
||||
err = diskPool.RefreshCustomVolume(inst.Project().Name, src.Project().Name, newDevices[dev.Name]["source"], "", nil, dev.Config["pool"], dev.Config["source"], snapshots, false, op)
|
||||
newVolName, _ := internalInstance.SplitVolumeSource(newDevices[dev.Name]["source"])
|
||||
srcVolName, _ := internalInstance.SplitVolumeSource(dev.Config["source"])
|
||||
err = diskPool.RefreshCustomVolume(inst.Project().Name, src.Project().Name, newVolName, "", nil, dev.Config["pool"], srcVolName, snapshots, false, op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -3020,7 +3024,8 @@ func (b *backend) BackupInstance(inst instance.Instance, tarWriter *instancewrit
|
||||
return fmt.Errorf("Failed loading storage pool: %w", err)
|
||||
}
|
||||
|
||||
err = diskPool.BackupCustomVolume(inst.Project().Name, dev.Config["source"], tarWriter, filepath.Join(backup.DefaultBackupPrefix, dev.Name), optimized, snapshots, op)
|
||||
volName, _ := internalInstance.SplitVolumeSource(dev.Config["source"])
|
||||
err = diskPool.BackupCustomVolume(inst.Project().Name, volName, tarWriter, filepath.Join(backup.DefaultBackupPrefix, dev.Name), optimized, snapshots, op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -3465,14 +3470,16 @@ func (b *backend) CreateInstanceSnapshot(inst instance.Instance, src instance.In
|
||||
return fmt.Errorf("Failed loading storage pool: %w", err)
|
||||
}
|
||||
|
||||
volName, _ := internalInstance.SplitVolumeSource(dev.Config["source"])
|
||||
|
||||
_, snapshotName, _ := api.GetParentAndSnapshotName(inst.Name())
|
||||
err = diskPool.CreateCustomVolumeSnapshot(inst.Project().Name, dev.Config["source"], snapshotName, time.Time{}, inst.IsStateful(), op)
|
||||
err = diskPool.CreateCustomVolumeSnapshot(inst.Project().Name, volName, snapshotName, time.Time{}, inst.IsStateful(), op)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create device snapshot for volume %q: %w", dev.Config["source"], err)
|
||||
return fmt.Errorf("Failed to create device snapshot for volume %q: %w", volName, err)
|
||||
}
|
||||
|
||||
reverter.Add(func() {
|
||||
_ = diskPool.DeleteCustomVolumeSnapshot(inst.Project().Name, fmt.Sprintf("%s/%s", dev.Config["source"], snapshotName), nil)
|
||||
_ = diskPool.DeleteCustomVolumeSnapshot(inst.Project().Name, fmt.Sprintf("%s/%s", volName, snapshotName), nil)
|
||||
})
|
||||
|
||||
return nil
|
||||
@ -3682,9 +3689,10 @@ func (b *backend) DeleteInstanceSnapshot(inst instance.Instance, op *operations.
|
||||
return fmt.Errorf("Failed loading storage pool: %w", err)
|
||||
}
|
||||
|
||||
err = diskPool.DeleteCustomVolumeSnapshot(inst.Project().Name, fmt.Sprintf("%s/%s", dev.Config["source"], snapName), op)
|
||||
volName, _ := internalInstance.SplitVolumeSource(dev.Config["source"])
|
||||
err = diskPool.DeleteCustomVolumeSnapshot(inst.Project().Name, fmt.Sprintf("%s/%s", volName, snapName), op)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to delete snapshot for volume %q: %w", dev.Config["source"], err)
|
||||
return fmt.Errorf("Failed to delete snapshot for volume %q: %w", volName, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -3886,7 +3894,8 @@ func (b *backend) RestoreInstanceSnapshot(inst instance.Instance, src instance.I
|
||||
return fmt.Errorf("Failed loading storage pool: %w", err)
|
||||
}
|
||||
|
||||
err = diskPool.RestoreCustomVolume(inst.Project().Name, dev.Config["source"], snapshotName, op)
|
||||
volName, _ := internalInstance.SplitVolumeSource(dev.Config["source"])
|
||||
err = diskPool.RestoreCustomVolume(inst.Project().Name, volName, snapshotName, op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -7084,7 +7093,8 @@ func (b *backend) GenerateInstanceBackupConfig(inst instance.Instance, snapshots
|
||||
return fmt.Errorf("Failed loading storage pool: %w", err)
|
||||
}
|
||||
|
||||
diskConfig, err := diskPool.GenerateCustomVolumeBackupConfig(inst.Project().Name, dev.Config["source"], snapshots, op)
|
||||
volName, _ := internalInstance.SplitVolumeSource(dev.Config["source"])
|
||||
diskConfig, err := diskPool.GenerateCustomVolumeBackupConfig(inst.Project().Name, volName, snapshots, op)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -8975,7 +8985,7 @@ func (b *backend) qcow2MigrateVolume(s *state.State, vol drivers.Volume, project
|
||||
_, volName := project.StorageVolumeParts(vol.Name())
|
||||
|
||||
inst, diskName, err := InstanceByVolumeName(b.state, vol.Pool(), projectName, volName, volumeDbType)
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, ErrVolumeNotAttachedToRunningInstance) {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -9165,7 +9175,7 @@ func (b *backend) qcow2CreateVolumeFromMigration(vol drivers.Volume, projectName
|
||||
_, volName := project.StorageVolumeParts(vol.Name())
|
||||
|
||||
inst, _, err := InstanceByVolumeName(b.state, vol.Pool(), projectName, volName, volumeDbType)
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, ErrVolumeNotAttachedToRunningInstance) {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -9735,7 +9745,8 @@ func (b *backend) createDependentVolumesFromBackup(srcBackup backup.Info, srcDat
|
||||
continue
|
||||
}
|
||||
|
||||
devKey := fmt.Sprintf("%s/%s", dev["pool"], dev["source"])
|
||||
volName, _ := internalInstance.SplitVolumeSource(dev["source"])
|
||||
devKey := fmt.Sprintf("%s/%s", dev["pool"], volName)
|
||||
devicesMap[devKey] = devName
|
||||
}
|
||||
|
||||
|
||||
@ -1468,8 +1468,11 @@ func DependentVolumesMatchMigrationType(s *state.State, migrationDependentVolume
|
||||
// needs to be migrated for this instance. Returns false if migration
|
||||
// can be skipped (e.g., on shared storage within the same cluster).
|
||||
func ShouldMigrateDependentVolume(s *state.State, poolName string, volumeName string, overrides map[string]string, clusterMove bool) (bool, error) {
|
||||
if overrides != nil && ((overrides["source"] != "" && volumeName != overrides["source"]) || (overrides["pool"] != "" && poolName != overrides["pool"])) {
|
||||
return true, nil
|
||||
if overrides != nil {
|
||||
overrideVolName, _ := internalInstance.SplitVolumeSource(overrides["source"])
|
||||
if (overrides["source"] != "" && volumeName != overrideVolName) || (overrides["pool"] != "" && poolName != overrides["pool"]) {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
diskPool, err := LoadByName(s, poolName)
|
||||
@ -1575,7 +1578,8 @@ func DevicesMapFromBackupConfig(config *backupConfig.Config) map[string]map[stri
|
||||
devicesMap[dev["pool"]] = map[string]string{}
|
||||
}
|
||||
|
||||
devicesMap[dev["pool"]][dev["source"]] = devName
|
||||
volName, _ := internalInstance.SplitVolumeSource(dev["source"])
|
||||
devicesMap[dev["pool"]][volName] = devName
|
||||
}
|
||||
|
||||
return devicesMap
|
||||
|
||||
Loading…
Reference in New Issue
Block a user