Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG/CHANGELOG-v1.9.3.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module:
fixes:
- summary: >-
Pre-create missing container mount points so virt-handler and virtualization-dra pods start
under containerd strict mode.
pull_request: https://github.com/deckhouse/virtualization/pull/2584
- summary: >-
Fixed slow downloading of images from DVCR to the node when attaching them to a virtual
machine.
pull_request: https://github.com/deckhouse/virtualization/pull/2576
vm:
fixes:
- summary: >-
Fixed a volume mount leak that could leave a VM with hotplugged images stuck in the
Terminating state during deletion.
pull_request: https://github.com/deckhouse/virtualization/pull/2581

2 changes: 2 additions & 0 deletions CHANGELOG/CHANGELOG-v1.9.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@
- **[core]** Fixed reduced throughput during live migration of running VMs compared to v1.8.3. [#2541](https://github.com/deckhouse/virtualization/pull/2541)
- **[core]** Remove excess empty labels with unused tsc frequencies on nodes. [#2351](https://github.com/deckhouse/virtualization/pull/2351)
- **[core]** Better handling Windows guests: start and migration should work in clusters with frequent CPU frequencies drifts [#2345](https://github.com/deckhouse/virtualization/pull/2345)
- **[module]** Fixed slow downloading of images from DVCR to the node when attaching them to a virtual machine. [#2576](https://github.com/deckhouse/virtualization/pull/2576)
- **[module]** Fixed slow import and upload of images to DVCR when network bandwidth was not the bottleneck. [#2552](https://github.com/deckhouse/virtualization/pull/2552)
- **[module]** Fixed an issue where invalid `virtualization` module ModuleConfig settings could block the Deckhouse queue. [#2246](https://github.com/deckhouse/virtualization/pull/2246)
- **[module]** Fixed duplicate series on the `Virtualization / Overview` dashboard. [#2189](https://github.com/deckhouse/virtualization/pull/2189)
- **[observability]** Fixed loss of audit events and false `D8LogShipperDestinationErrors` alerts during certificate rotation of the `virtualization-audit` pod. [#2551](https://github.com/deckhouse/virtualization/pull/2551)
- **[vd]** Fixed cancellation of virtual disk storage class changes and cancellation of local disk migration. [#2502](https://github.com/deckhouse/virtualization/pull/2502)
- **[vd]** Time spent in the `WaitForFirstConsumer` phase is no longer included in `.status.stats.creationDuration.totalProvisioning` of virtual disks. [#2379](https://github.com/deckhouse/virtualization/pull/2379)
- **[vd]** Allow ingress from virtualization namespace to importer pods [#2356](https://github.com/deckhouse/virtualization/pull/2356)
- **[vm]** Fixed a volume mount leak that could leave a VM with hotplugged images stuck in the Terminating state during deletion. [#2581](https://github.com/deckhouse/virtualization/pull/2581)
- **[vm]** Fixed an issue that prevented a VM from starting after a failed migration of a disk on local storage. [#2509](https://github.com/deckhouse/virtualization/pull/2509)
- **[vm]** Fixed live migration of VMs with disks on local storage attached via VirtualMachineBlockDeviceAttachment (hotplug). The target node no longer matches the source node. [#2508](https://github.com/deckhouse/virtualization/pull/2508)
- **[vm]** Fixed a false reboot requirement for VMs with only the Main network after upgrading to v1.9.1. Such VMs now do not receive the RestartRequired status if their configuration has not actually changed. [#2475](https://github.com/deckhouse/virtualization/pull/2475)
Expand Down
13 changes: 13 additions & 0 deletions api/core/v1alpha2/cvicondition/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,17 @@ const (
DatasourceReadyType Type = "DatasourceReady"
// ReadyType indicates whether the import process succeeded and the `ClusterVirtualImage` is ready for use.
ReadyType Type = "Ready"
// DeletingType indicates whether the ClusterVirtualImage deletion can be completed.
DeletingType Type = "Deleting"
)

type (
// DatasourceReadyReason represents the various reasons for the DatasourceReady condition type.
DatasourceReadyReason string
// ReadyReason represents the various reasons for the Ready condition type.
ReadyReason string
// DeletingReason represents the various reasons for the Deleting condition type.
DeletingReason string
)

func (s DatasourceReadyReason) String() string {
Expand All @@ -45,6 +49,10 @@ func (s ReadyReason) String() string {
return string(s)
}

func (s DeletingReason) String() string {
return string(s)
}

const (
// DatasourceReady indicates that the datasource is ready for use, allowing the import process to start.
DatasourceReady DatasourceReadyReason = "DatasourceReady"
Expand Down Expand Up @@ -75,4 +83,9 @@ const (
Ready ReadyReason = "Ready"
// ImageLost indicates that the image in DVCR has been lost and the `ClusterVirtualImage` can no longer be used.
ImageLost ReadyReason = "ImageLost"

// DeletionBlockedByProtection indicates that the ClusterVirtualImage cannot be deleted while it is protected.
DeletionBlockedByProtection DeletingReason = "DeletionBlockedByProtection"
// DeletionCleanupPending indicates that the ClusterVirtualImage cleanup is still in progress.
DeletionCleanupPending DeletingReason = "CleanupPending"
)
15 changes: 15 additions & 0 deletions api/core/v1alpha2/vdcondition/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ const (
InUseType Type = "InUse"
// MigratingType indicates that the virtual disk is in the process of migrating data from one volume to another (during the migration of a local disk or migration to another storage class).
MigratingType Type = "Migrating"
// DeletingType indicates whether the VirtualDisk deletion can be completed.
DeletingType Type = "Deleting"
)

type (
Expand All @@ -55,6 +57,8 @@ type (
InUseReason string
// MigratingReason represents the various reasons for the Migration condition type.
MigratingReason string
// DeletingReason represents the various reasons for the Deleting condition type.
DeletingReason string
)

func (s DatasourceReadyReason) String() string {
Expand Down Expand Up @@ -85,6 +89,10 @@ func (s MigratingReason) String() string {
return string(s)
}

func (s DeletingReason) String() string {
return string(s)
}

const (
// DatasourceReady indicates that the datasource is ready for use, allowing the import process to start.
DatasourceReady DatasourceReadyReason = "DatasourceReady"
Expand Down Expand Up @@ -189,3 +197,10 @@ const (
SnapshottingInProgressReason MigratingReason = "SnapshottingInProgress"
StorageClassNotFoundReason MigratingReason = "StorageClassNotFound"
)

const (
// DeletionBlockedByProtection indicates that the VirtualDisk cannot be deleted while it is protected.
DeletionBlockedByProtection DeletingReason = "DeletionBlockedByProtection"
// DeletionCleanupPending indicates that the VirtualDisk cleanup is still in progress.
DeletionCleanupPending DeletingReason = "CleanupPending"
)
13 changes: 13 additions & 0 deletions api/core/v1alpha2/vicondition/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ const (
ReadyType Type = "Ready"
// StorageClassReadyType indicates whether the storageClass ready.
StorageClassReadyType Type = "StorageClassReady"
// DeletingType indicates whether the VirtualImage deletion can be completed.
DeletingType Type = "Deleting"
)

type (
Expand All @@ -39,6 +41,8 @@ type (
ReadyReason string
// StorageClassReadyReason represents the various reasons for the StorageClassReady condition type.
StorageClassReadyReason string
// DeletingReason represents the various reasons for the Deleting condition type.
DeletingReason string
)

func (s DatasourceReadyReason) String() string {
Expand All @@ -53,6 +57,10 @@ func (s StorageClassReadyReason) String() string {
return string(s)
}

func (s DeletingReason) String() string {
return string(s)
}

const (
// DatasourceReady indicates that the datasource is ready for use, allowing the import process to start.
DatasourceReady DatasourceReadyReason = "DatasourceReady"
Expand Down Expand Up @@ -103,4 +111,9 @@ const (
StorageClassNotFound StorageClassReadyReason = "StorageClassNotFound"
// DVCRTypeUsed indicates that the DVCR provisioning chosen.
DVCRTypeUsed StorageClassReadyReason = "DVCRTypeUsed"

// DeletionBlockedByProtection indicates that the VirtualImage cannot be deleted while it is protected.
DeletionBlockedByProtection DeletingReason = "DeletionBlockedByProtection"
// DeletionCleanupPending indicates that the VirtualImage cleanup is still in progress.
DeletionCleanupPending DeletingReason = "CleanupPending"
)
11 changes: 11 additions & 0 deletions api/core/v1alpha2/vmbdacondition/condition.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ const (
AttachedType Type = "Attached"
// DiskAttachmentCapacityAvailableType indicates that the entity has not yet reached its predefined limit for block device attachments.
DiskAttachmentCapacityAvailableType Type = "DiskAttachmentCapacityAvailableType"
// DeletingType indicates whether the VirtualMachineBlockDeviceAttachment deletion can be completed.
DeletingType Type = "Deleting"
)

type (
Expand All @@ -39,6 +41,8 @@ type (
AttachedReason string
// DiskAttachmentCapacityAvailableReason represent the various reasons for the `DiskAttachmentCapacityAvailableType` condition type.
DiskAttachmentCapacityAvailableReason string
// DeletingReason represents the various reasons for the `Deleting` condition type.
DeletingReason string
)

const (
Expand Down Expand Up @@ -72,6 +76,9 @@ const (
CapacityReached DiskAttachmentCapacityAvailableReason = "CapacityReached"
// CapacityUnknown represents unknown condition state
CapacityUnknown DiskAttachmentCapacityAvailableReason = "CapacityUnknown"

// DeletionCleanupPending indicates that the VirtualMachineBlockDeviceAttachment cleanup is still in progress.
DeletionCleanupPending DeletingReason = "CleanupPending"
)

func (t Type) String() string {
Expand All @@ -93,3 +100,7 @@ func (t AttachedReason) String() string {
func (t DiskAttachmentCapacityAvailableReason) String() string {
return string(t)
}

func (t DeletingReason) String() string {
return string(t)
}
10 changes: 10 additions & 0 deletions docs/RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ title: "Release Notes"
weight: 70
---

## v1.9.3
<span style="opacity:0.6; font-style:italic; font-size:0.9em;">
Release date: July 7, 2026.
</span>

### Fixes

- [module] Fixed slow downloading of images from DVCR to the node when attaching them to a virtual machine.
- [vm] Fixed a volume mount leak that could leave a VM with hotplugged images stuck in the Terminating state during deletion.

## v1.9.2
<span style="opacity:0.6; font-style:italic; font-size:0.9em;">
Release date: July 1, 2026.
Expand Down
10 changes: 10 additions & 0 deletions docs/RELEASE_NOTES.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,16 @@ title: "Релизы"
weight: 70
---

## v1.9.3
<span style="opacity:0.6; font-style:italic; font-size:0.9em;">
Дата релиза: 7 июля 2026.
</span>

### Исправления

- [module] Исправлено медленное скачивание образов из DVCR на узел для подключения к виртуальной машине.
- [vm] Исправлена утечка монтирований томов, из-за которой ВМ при наличии подключённых на лету образов (hotplug) могла зависнуть при удалении в состоянии Terminating.

## v1.9.2
<span style="opacity:0.6; font-style:italic; font-size:0.9em;">
Дата релиза: 1 июля 2026.
Expand Down
70 changes: 70 additions & 0 deletions images/virtualization-artifact/pkg/common/vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package vm
import (
"context"
"fmt"
"sort"
"strings"

corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -116,6 +117,75 @@ func IsVMActive(ctx context.Context, cli client.Client, vm v1alpha2.VirtualMachi
return false, nil
}

// BlockDeviceUsage reports whether a VM status references a block device and whether that reference is actively mounted.
func BlockDeviceUsage(
ctx context.Context,
cli client.Client,
vm v1alpha2.VirtualMachine,
kind v1alpha2.BlockDeviceKind,
name string,
) (referenced, mounted bool, err error) {
if !HasBlockDeviceStatusRef(vm, kind, name) {
return false, false, nil
}

switch vm.Status.Phase {
case "":
return true, false, nil
case v1alpha2.MachineStopped:
vmIsActive, err := IsVMActive(ctx, cli, vm)
if err != nil {
return true, false, err
}

return true, vmIsActive, nil
default:
return true, true, nil
}
}

// MountedVirtualMachineNames returns the sorted names of VirtualMachines that currently
// mount the block device of the given kind and name. When namespace is empty, all namespaces
// are scanned. When withNamespace is true, each name is formatted as "<namespace>/<name>".
func MountedVirtualMachineNames(ctx context.Context, cli client.Client, kind v1alpha2.BlockDeviceKind, name, namespace string, withNamespace bool) ([]string, error) {
var vms v1alpha2.VirtualMachineList
if err := cli.List(ctx, &vms, &client.ListOptions{Namespace: namespace}); err != nil {
return nil, err
}

var names []string
for _, vm := range vms.Items {
_, mounted, err := BlockDeviceUsage(ctx, cli, vm, kind, name)
if err != nil {
return nil, err
}

if !mounted {
continue
}

if withNamespace {
names = append(names, vm.Namespace+"/"+vm.Name)
} else {
names = append(names, vm.Name)
}
}

sort.Strings(names)
return names, nil
}

// HasBlockDeviceStatusRef reports whether VM status contains a block device reference with the provided kind and name.
func HasBlockDeviceStatusRef(vm v1alpha2.VirtualMachine, kind v1alpha2.BlockDeviceKind, name string) bool {
for _, bd := range vm.Status.BlockDeviceRefs {
if bd.Kind == kind && bd.Name == name {
return true
}
}

return false
}

func GetActivePodName(vm *v1alpha2.VirtualMachine) (string, bool) {
for _, pod := range vm.Status.VirtualMachinePods {
if pod.Active {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ func NewController(
internal.NewDatasourceReadyHandler(sources),
internal.NewLifeCycleHandler(sources, mgr.GetClient()),
internal.NewImagePresenceHandler(recorder, dvcr.NewImageChecker(mgr.GetClient(), dvcrSettings)),
internal.NewDeletionHandler(sources),
internal.NewAttacheeHandler(mgr.GetClient()),
internal.NewDeletionHandler(sources, mgr.GetClient()),
)

cviController, err := controller.New(ControllerName, mgr, controller.Options{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,35 +69,15 @@ func (h AttacheeHandler) hasAttachedVM(ctx context.Context, cvi client.Object) (
}

for _, vm := range vms.Items {
if vm.Status.Phase == "" {
continue
_, mounted, err := commonvm.BlockDeviceUsage(ctx, h.client, vm, v1alpha2.ClusterImageDevice, cvi.GetName())
if err != nil {
return false, err
}

if vm.Status.Phase == v1alpha2.MachineStopped {
vmIsActive, err := commonvm.IsVMActive(ctx, h.client, vm)
if err != nil {
return false, err
}

if !vmIsActive {
continue
}
}

if h.isCVIAttachedToVM(cvi.GetName(), vm) {
if mounted {
return true, nil
}
}

return false, nil
}

func (h AttacheeHandler) isCVIAttachedToVM(cviName string, vm v1alpha2.VirtualMachine) bool {
for _, bda := range vm.Status.BlockDeviceRefs {
if bda.Kind == v1alpha2.ClusterImageDevice && bda.Name == cviName {
return true
}
}

return false
}
Loading
Loading