@@ -95,10 +95,6 @@ func (h *USBDeviceAttachHandler) Handle(ctx context.Context, s state.VirtualMach
9595 var kvvmi * virtv1.VirtualMachineInstance
9696 var hostDeviceReadyByName map [string ]bool
9797
98- // Lazy-loaded node for USBIP port check
99- var nodeLoaded bool
100- var node * corev1.Node
101-
10298 var nextStatusRefs []v1alpha2.USBDeviceStatusRef
10399 for _ , usbDeviceRef := range vm .Spec .USBDevices {
104100 deviceName := usbDeviceRef .Name
@@ -136,37 +132,7 @@ func (h *USBDeviceAttachHandler) Handle(ctx context.Context, s state.VirtualMach
136132 continue
137133 }
138134
139- // 3) Check free USBIP ports for devices from other nodes (USBIP scenario)
140- if usbDevice .Status .NodeName != "" && usbDevice .Status .NodeName != vm .Status .Node {
141- if ! nodeLoaded && vm .Status .Node != "" {
142- node = & corev1.Node {}
143- if err := h .client .Get (ctx , client.ObjectKey {Name : vm .Status .Node }, node ); err != nil {
144- if ! apierrors .IsNotFound (err ) {
145- return reconcile.Result {}, fmt .Errorf ("failed to get node %s: %w" , vm .Status .Node , err )
146- }
147- node = nil
148- }
149- nodeLoaded = true
150- }
151-
152- if node != nil {
153- hasFreePort , err := usb .CheckFreePort (node .Annotations , usbDevice .Status .Attributes .Speed )
154- if err != nil {
155- log .Error ("failed to check free USBIP ports" , "error" , err , "device" , deviceName , "node" , vm .Status .Node )
156- nextStatusRefs = append (nextStatusRefs , h .buildDetachedStatus (existingStatus , deviceName , isReady ))
157- continue
158- }
159- if ! hasFreePort {
160- log .Info ("no free USBIP ports available" , "device" , deviceName , "speed" , usbDevice .Status .Attributes .Speed , "node" , vm .Status .Node )
161- // Return requeue to retry after 5 seconds
162- nextStatusRefs = append (nextStatusRefs , h .buildDetachedStatus (existingStatus , deviceName , isReady ))
163- changed .Status .USBDevices = nextStatusRefs
164- return reconcile.Result {RequeueAfter : 5 * time .Second }, nil
165- }
166- }
167- }
168-
169- // 4) Runtime evidence from KVVMI and attach action.
135+ // 3) Runtime evidence from KVVMI and attach action.
170136 if ! kvvmiLoaded {
171137 fetchedKVVMI , err := s .KVVMI (ctx )
172138 if err != nil {
@@ -180,6 +146,7 @@ func (h *USBDeviceAttachHandler) Handle(ctx context.Context, s state.VirtualMach
180146 hostDeviceReadyByName = h .hostDeviceReadyByName (kvvmi )
181147 }
182148
149+ // If device is already attached in KVVMI - preserve status, skip port check.
183150 if hostDeviceReadyByName [deviceName ] {
184151 address := h .getUSBAddressFromKVVMI (deviceName , kvvmi )
185152 isHotplugged := vm .Status .Phase == v1alpha2 .MachineRunning
@@ -196,6 +163,31 @@ func (h *USBDeviceAttachHandler) Handle(ctx context.Context, s state.VirtualMach
196163 continue
197164 }
198165
166+ // 4) Check free USBIP ports for new attachments from other nodes.
167+ if usbDevice .Status .NodeName != "" && usbDevice .Status .NodeName != vm .Status .Node {
168+ node := & corev1.Node {}
169+ if err := h .client .Get (ctx , client.ObjectKey {Name : vm .Status .Node }, node ); err != nil {
170+ if ! apierrors .IsNotFound (err ) {
171+ return reconcile.Result {}, fmt .Errorf ("failed to get node %s: %w" , vm .Status .Node , err )
172+ }
173+ nextStatusRefs = append (nextStatusRefs , h .buildDetachedStatus (existingStatus , deviceName , isReady ))
174+ continue
175+ }
176+
177+ hasFreePort , err := usb .CheckFreePort (node .Annotations , usbDevice .Status .Attributes .Speed )
178+ if err != nil {
179+ log .Error ("failed to check free USBIP ports" , "error" , err , "device" , deviceName , "node" , vm .Status .Node )
180+ nextStatusRefs = append (nextStatusRefs , h .buildDetachedStatus (existingStatus , deviceName , isReady ))
181+ continue
182+ }
183+ if ! hasFreePort {
184+ log .Info ("no free USBIP ports available" , "device" , deviceName , "speed" , usbDevice .Status .Attributes .Speed , "node" , vm .Status .Node )
185+ nextStatusRefs = append (nextStatusRefs , h .buildDetachedStatus (existingStatus , deviceName , isReady ))
186+ changed .Status .USBDevices = nextStatusRefs
187+ return reconcile.Result {RequeueAfter : 5 * time .Second }, nil
188+ }
189+ }
190+
199191 requestName := h .getResourceClaimRequestName (deviceName )
200192 err := h .attachUSBDevice (ctx , vm , deviceName , templateName , requestName )
201193 if err != nil && ! apierrors .IsAlreadyExists (err ) && ! strings .Contains (err .Error (), "already exists" ) {
0 commit comments