@@ -31,6 +31,7 @@ import (
3131 "github.com/deckhouse/virtualization-controller/pkg/common/annotations"
3232 "github.com/deckhouse/virtualization-controller/pkg/controller/indexer"
3333 "github.com/deckhouse/virtualization-controller/pkg/featuregates"
34+ "github.com/deckhouse/virtualization-controller/pkg/kubeapi"
3435 "github.com/deckhouse/virtualization/api/core/v1alpha2"
3536)
3637
@@ -144,14 +145,22 @@ func getUSBDeviceNames(refs []v1alpha2.USBDeviceSpecRef) map[string]struct{} {
144145}
145146
146147func (v * USBDevicesValidator ) validateAvailableUSBIPPorts (ctx context.Context , vm * v1alpha2.VirtualMachine , oldUSBDevices map [string ]struct {}) (admission.Warnings , error ) {
148+ if kubeapi .HasDRAPartitionableDevices () {
149+ return v .validateAvailableUSBIPPortsWithPartitionableDevices (ctx , vm , oldUSBDevices )
150+ }
151+ return v .validateAvailableUSBIPPortsDefault (ctx , vm , oldUSBDevices )
152+ }
153+
154+ func (v * USBDevicesValidator ) validateAvailableUSBIPPortsWithPartitionableDevices (ctx context.Context , vm * v1alpha2.VirtualMachine , oldUSBDevices map [string ]struct {}) (admission.Warnings , error ) {
147155 if vm .Status .Node == "" {
148156 return admission.Warnings {}, nil
149157 }
150158 if vm .Spec .USBDevices == nil {
151159 return admission.Warnings {}, nil
152160 }
153161
154- var usbFromOtherNodes []string
162+ var hsUSBFromOtherNodes []string
163+ var ssUSBFromOtherNodes []string
155164
156165 for _ , ref := range vm .Spec .USBDevices {
157166 if _ , exists := oldUSBDevices [ref .Name ]; exists {
@@ -164,47 +173,126 @@ func (v *USBDevicesValidator) validateAvailableUSBIPPorts(ctx context.Context, v
164173 return admission.Warnings {}, fmt .Errorf ("failed to get USB device %s: %w" , ref .Name , err )
165174 }
166175
167- if usbDevice .Status .NodeName != vm .Status .Node {
168- usbFromOtherNodes = append (usbFromOtherNodes , ref .Name )
176+ if usbDevice .Status .NodeName == vm .Status .Node {
177+ continue
178+ }
179+
180+ isHs , isSS := resolveSpeed (usbDevice .Status .Attributes .Speed )
181+ switch {
182+ case isHs :
183+ hsUSBFromOtherNodes = append (hsUSBFromOtherNodes , ref .Name )
184+ case isSS :
185+ ssUSBFromOtherNodes = append (ssUSBFromOtherNodes , ref .Name )
186+ default :
187+ return admission.Warnings {}, fmt .Errorf ("USB device %s has unsupported speed %d" , ref .Name , usbDevice .Status .Attributes .Speed )
169188 }
170189 }
171190
172- if len (usbFromOtherNodes ) == 0 {
191+ if len (hsUSBFromOtherNodes ) == 0 && len ( ssUSBFromOtherNodes ) == 0 {
173192 return admission.Warnings {}, nil
174193 }
175194
195+ node , totalPorts , err := v .getNodeTotalPorts (ctx , vm .Status .Node )
196+ if err != nil {
197+ return admission.Warnings {}, err
198+ }
199+
200+ totalPortsPerHub := totalPorts / 2
201+
202+ if len (hsUSBFromOtherNodes ) > 0 {
203+ if err = validateUsedPortsByAnnotation (node , annotations .AnnUSBIPHighSpeedHubUsedPorts , hsUSBFromOtherNodes , totalPortsPerHub ); err != nil {
204+ return admission.Warnings {}, err
205+ }
206+ }
207+
208+ if len (ssUSBFromOtherNodes ) > 0 {
209+ if err = validateUsedPortsByAnnotation (node , annotations .AnnUSBIPSuperSpeedHubUsedPorts , ssUSBFromOtherNodes , totalPortsPerHub ); err != nil {
210+ return admission.Warnings {}, err
211+ }
212+ }
213+
214+ return admission.Warnings {}, nil
215+ }
216+
217+ func (v * USBDevicesValidator ) getNodeTotalPorts (ctx context.Context , nodeName string ) (* corev1.Node , int , error ) {
176218 node := & corev1.Node {}
177- err := v .client .Get (ctx , client.ObjectKey {Name : vm . Status . Node }, node )
219+ err := v .client .Get (ctx , client.ObjectKey {Name : nodeName }, node )
178220 if err != nil {
179- return admission. Warnings {}, fmt .Errorf ("failed to get node %s: %w" , vm . Status . Node , err )
221+ return nil , - 1 , fmt .Errorf ("failed to get node %s: %w" , nodeName , err )
180222 }
181223
182224 totalPorts , exists := node .Annotations [annotations .AnnUSBIPTotalPorts ]
183225 if ! exists {
184- return admission. Warnings {}, fmt .Errorf ("node %s does not have %s annotation" , vm . Status . Node , annotations .AnnUSBIPTotalPorts )
226+ return nil , - 1 , fmt .Errorf ("node %s does not have %s annotation" , nodeName , annotations .AnnUSBIPTotalPorts )
185227 }
186228 totalPortsInt , err := strconv .Atoi (totalPorts )
187229 if err != nil {
188- return admission. Warnings {} , fmt .Errorf ("failed to parse %s annotation: %w" , annotations .AnnUSBIPTotalPorts , err )
230+ return nil , - 1 , fmt .Errorf ("failed to parse %s annotation: %w" , annotations .AnnUSBIPTotalPorts , err )
189231 }
190232
191- // total for 2 usb hubs (2.0 and 3.0)
192- totalPortsInt /= 2
233+ return node , totalPortsInt , nil
234+ }
193235
194- usedPorts , exists := node .Annotations [annotations .AnnUSBIPUsedPorts ]
236+ func validateUsedPortsByAnnotation (node * corev1.Node , anno string , usbFromOtherNodes []string , totalPortsPerHub int ) error {
237+ usedPorts , exists := node .Annotations [anno ]
195238 if ! exists {
196- return admission. Warnings {}, fmt .Errorf ("node %s does not have %s annotation" , vm . Status . Node , annotations . AnnUSBIPUsedPorts )
239+ return fmt .Errorf ("node %s does not have %s annotation" , node . Name , anno )
197240 }
198241 usedPortsInt , err := strconv .Atoi (usedPorts )
199242 if err != nil {
200- return admission. Warnings {}, fmt .Errorf ("failed to parse %s annotation: %w" , annotations . AnnUSBIPUsedPorts , err )
243+ return fmt .Errorf ("failed to parse %s annotation: %w" , anno , err )
201244 }
202245
203246 wantedPorts := usedPortsInt + len (usbFromOtherNodes )
247+ if wantedPorts > totalPortsPerHub {
248+ return fmt .Errorf ("node %s not available ports for sharing USB devices %s. total: %d, used: %d, wanted: %d" , node .Name , strings .Join (usbFromOtherNodes , ", " ), totalPortsPerHub , usedPortsInt , wantedPorts )
249+ }
250+
251+ return nil
252+ }
204253
205- if wantedPorts > totalPortsInt {
206- return admission.Warnings {}, fmt .Errorf ("node %s not available ports for sharing USB devices %s. total: %d, used: %d, wanted: %d" , vm .Status .Node , strings .Join (usbFromOtherNodes , ", " ), totalPortsInt , usedPortsInt , wantedPorts )
254+ // https://mjmwired.net/kernel/Documentation/ABI/testing/sysfs-bus-usb#502
255+ func resolveSpeed (speed int ) (isHs , isSS bool ) {
256+ return speed == 480 , speed >= 5000
257+ }
258+
259+ func (v * USBDevicesValidator ) validateAvailableUSBIPPortsDefault (ctx context.Context , vm * v1alpha2.VirtualMachine , oldUSBDevices map [string ]struct {}) (admission.Warnings , error ) {
260+ if vm .Status .Node == "" {
261+ return admission.Warnings {}, nil
262+ }
263+ if vm .Spec .USBDevices == nil {
264+ return admission.Warnings {}, nil
207265 }
208266
209- return admission.Warnings {}, nil
267+ var usbFromOtherNodes []string
268+
269+ for _ , ref := range vm .Spec .USBDevices {
270+ if _ , exists := oldUSBDevices [ref .Name ]; exists {
271+ continue
272+ }
273+
274+ usbDevice := & v1alpha2.USBDevice {}
275+ err := v .client .Get (ctx , client.ObjectKey {Name : ref .Name , Namespace : vm .Namespace }, usbDevice )
276+ if err != nil {
277+ return admission.Warnings {}, fmt .Errorf ("failed to get USB device %s: %w" , ref .Name , err )
278+ }
279+
280+ if usbDevice .Status .NodeName != vm .Status .Node {
281+ usbFromOtherNodes = append (usbFromOtherNodes , ref .Name )
282+ }
283+ }
284+
285+ if len (usbFromOtherNodes ) == 0 {
286+ return admission.Warnings {}, nil
287+ }
288+
289+ node , totalPorts , err := v .getNodeTotalPorts (ctx , vm .Status .Node )
290+ if err != nil {
291+ return admission.Warnings {}, err
292+ }
293+
294+ // total for 2 usb hubs (2.0 and 3.0)
295+ totalPorts /= 2
296+
297+ return admission.Warnings {}, validateUsedPortsByAnnotation (node , annotations .AnnUSBIPUsedPorts , usbFromOtherNodes , totalPorts )
210298}
0 commit comments