Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -316,3 +316,54 @@ func (s *BlockDevicesState) Reload(ctx context.Context) error {
s.VMBDAByBlockDeviceRef = vmbdaByRef
return nil
}

// ResolveVMBDAAttachedDevices fills VIByName/CVIByName/VDByName with objects referenced
// by VMBDAs that are not yet reflected in the VM block device refs.
//
// During a hotplug attach KubeVirt adds the volume to KVVM before the device appears in
// .status.blockDeviceRefs. Reload builds the maps from those refs, so the freshly attached
// image is missing from them while the VMBDA-driven volume sync already tries to set its disk.
// Without this the KVVM builder fails to resolve the image and aborts the whole reconcile.
//
// Scoped to the KVVM builder on purpose: it must not be called from the block device status
// handler, whose readiness and finalizer logic operates on .spec/.status refs only.
func (s *BlockDevicesState) ResolveVMBDAAttachedDevices(ctx context.Context) error {
for ref := range s.VMBDAByBlockDeviceRef {
switch ref.Kind {
case v1alpha2.VMBDAObjectRefKindVirtualImage:
if _, ok := s.VIByName[ref.Name]; ok {
continue
}
vi, err := s.s.VirtualImage(ctx, ref.Name)
if err != nil {
return err
}
if vi != nil {
s.VIByName[ref.Name] = vi
}
case v1alpha2.VMBDAObjectRefKindClusterVirtualImage:
if _, ok := s.CVIByName[ref.Name]; ok {
continue
}
cvi, err := s.s.ClusterVirtualImage(ctx, ref.Name)
if err != nil {
return err
}
if cvi != nil {
s.CVIByName[ref.Name] = cvi
}
case v1alpha2.VMBDAObjectRefKindVirtualDisk:
if _, ok := s.VDByName[ref.Name]; ok {
continue
}
vd, err := s.s.VirtualDisk(ctx, ref.Name)
if err != nil {
return err
}
if vd != nil {
s.VDByName[ref.Name] = vd
}
}
}
return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -1582,6 +1582,69 @@ var _ = Describe("Capacity check", func() {
})
})

var _ = Describe("ResolveVMBDAAttachedDevices", func() {
var ctx context.Context

BeforeEach(func() {
ctx = logger.ToContext(context.TODO(), slog.Default())
})

It("fills the builder maps with VMBDA-attached devices missing from block device refs", func() {
const ns = "hotplug-race"

vi := &v1alpha2.VirtualImage{
ObjectMeta: metav1.ObjectMeta{Name: "vi-hotplug", Namespace: ns},
Status: v1alpha2.VirtualImageStatus{Phase: v1alpha2.ImageReady},
}
cvi := &v1alpha2.ClusterVirtualImage{
ObjectMeta: metav1.ObjectMeta{Name: "cvi-hotplug"},
Status: v1alpha2.ClusterVirtualImageStatus{Phase: v1alpha2.ImageReady},
}
vd := &v1alpha2.VirtualDisk{
ObjectMeta: metav1.ObjectMeta{Name: "vd-hotplug", Namespace: ns},
}

// Spec and Status BlockDeviceRefs are intentionally empty: this models the hotplug
// attach window where the volume is already added to KVVM but the device is not yet
// reflected in the VM block device refs.
vm := &v1alpha2.VirtualMachine{
ObjectMeta: metav1.ObjectMeta{Name: "vm-hotplug-race", Namespace: ns},
}

newVMBDA := func(name string, kind v1alpha2.VMBDAObjectRefKind, refName string) *v1alpha2.VirtualMachineBlockDeviceAttachment {
return &v1alpha2.VirtualMachineBlockDeviceAttachment{
ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: ns},
Spec: v1alpha2.VirtualMachineBlockDeviceAttachmentSpec{
VirtualMachineName: vm.Name,
BlockDeviceRef: v1alpha2.VMBDAObjectRef{Kind: kind, Name: refName},
},
}
}
vmbdaVi := newVMBDA("vmbda-vi", v1alpha2.VMBDAObjectRefKindVirtualImage, vi.Name)
vmbdaCvi := newVMBDA("vmbda-cvi", v1alpha2.VMBDAObjectRefKindClusterVirtualImage, cvi.Name)
vmbdaVd := newVMBDA("vmbda-vd", v1alpha2.VMBDAObjectRefKindVirtualDisk, vd.Name)
// A VMBDA whose image does not exist: must be tolerated, not cause an error.
vmbdaMissing := newVMBDA("vmbda-missing", v1alpha2.VMBDAObjectRefKindClusterVirtualImage, "cvi-gone")

_, _, vmState := setupEnvironment(vm, vi, cvi, vd, vmbdaVi, vmbdaCvi, vmbdaVd, vmbdaMissing)

bdState := NewBlockDeviceState(vmState)
Expect(bdState.Reload(ctx)).To(Succeed())

// Reload builds the maps from block device refs only, which are empty here.
Expect(bdState.VIByName).To(BeEmpty())
Expect(bdState.CVIByName).To(BeEmpty())
Expect(bdState.VDByName).To(BeEmpty())

Expect(bdState.ResolveVMBDAAttachedDevices(ctx)).To(Succeed())

Expect(bdState.VIByName).To(HaveKey(vi.Name))
Expect(bdState.CVIByName).To(HaveKey(cvi.Name))
Expect(bdState.VDByName).To(HaveKey(vd.Name))
Expect(bdState.CVIByName).NotTo(HaveKey("cvi-gone"))
})
})

func vmFactoryByVM(vm *v1alpha2.VirtualMachine) func() *v1alpha2.VirtualMachine {
return func() *v1alpha2.VirtualMachine {
return vm
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -525,6 +525,12 @@ func MakeKVVMFromVMSpec(ctx context.Context, s state.VirtualMachineState) (*virt
if err != nil {
return nil, fmt.Errorf("failed to reload blockdevice state for the virtual machine: %w", err)
}
// Resolve images/disks attached via VMBDA but not yet reflected in block device refs,
// so the builder can set their disks during the hotplug attach window instead of aborting.
err = bdState.ResolveVMBDAAttachedDevices(ctx)
if err != nil {
return nil, fmt.Errorf("failed to resolve VMBDA-attached block devices for the virtual machine: %w", err)
}
class, err := s.Class(ctx)
if err != nil {
return nil, err
Expand Down
Loading