Skip to content

Commit 4984fec

Browse files
committed
fix(vm/validators): resolve compilation errors in USB devices validator
- Remove call to undefined validateAvailableUSBIPPortsDefault - Replace getNodeTotalPorts with direct client.Get for Node - Use usb.CheckFreePortForRequest from common/usb package - Remove unused imports Signed-off-by: Daniil Antoshin <daniil.antoshin@flant.com>
1 parent 8b2b353 commit 4984fec

6 files changed

Lines changed: 445 additions & 103 deletions

File tree

api/core/v1alpha2/usbdevicecondition/condition.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ const (
5151
Available AttachedReason = "Available"
5252
// DetachedForMigration signifies that device was detached for migration (e.g. live migration).
5353
DetachedForMigration AttachedReason = "DetachedForMigration"
54+
// NoFreeUSBIPPort signifies that device cannot be attached because there are no free USBIP ports on the target node.
55+
NoFreeUSBIPPort AttachedReason = "NoFreeUSBIPPort"
5456
)
5557

5658
func (r ReadyReason) String() string {
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
Copyright 2026 Flant JSC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package usb
18+
19+
import (
20+
"fmt"
21+
"strconv"
22+
23+
"github.com/deckhouse/virtualization-controller/pkg/common/annotations"
24+
)
25+
26+
// ResolveSpeed determines USB hub type from speed in Mbps.
27+
// https://mjmwired.net/kernel/Documentation/ABI/testing/sysfs-bus-usb#502
28+
func ResolveSpeed(speed int) (isHS, isSS bool) {
29+
return speed == 480, speed >= 5000
30+
}
31+
32+
// GetTotalPortsPerHub returns the number of ports per hub (total / 2).
33+
func GetTotalPortsPerHub(nodeAnnotations map[string]string) (int, error) {
34+
totalPortsStr, exists := nodeAnnotations[annotations.AnnUSBIPTotalPorts]
35+
if !exists {
36+
return 0, fmt.Errorf("node does not have %s annotation", annotations.AnnUSBIPTotalPorts)
37+
}
38+
totalPorts, err := strconv.Atoi(totalPortsStr)
39+
if err != nil {
40+
return 0, fmt.Errorf("failed to parse %s annotation: %w", annotations.AnnUSBIPTotalPorts, err)
41+
}
42+
return totalPorts / 2, nil
43+
}
44+
45+
// GetUsedPorts returns the number of used ports for the given hub type.
46+
func GetUsedPorts(nodeAnnotations map[string]string, hubAnnotation string) (int, error) {
47+
usedPortsStr, exists := nodeAnnotations[hubAnnotation]
48+
if !exists {
49+
return 0, fmt.Errorf("node does not have %s annotation", hubAnnotation)
50+
}
51+
usedPorts, err := strconv.Atoi(usedPortsStr)
52+
if err != nil {
53+
return 0, fmt.Errorf("failed to parse %s annotation: %w", hubAnnotation, err)
54+
}
55+
return usedPorts, nil
56+
}
57+
58+
// CheckFreePort checks if a node has free USBIP ports for the given speed.
59+
// Returns true if there is at least one free port, false otherwise.
60+
func CheckFreePort(nodeAnnotations map[string]string, speed int) (bool, error) {
61+
isHS, isSS := ResolveSpeed(speed)
62+
63+
var hubAnnotation string
64+
switch {
65+
case isHS:
66+
hubAnnotation = annotations.AnnUSBIPHighSpeedHubUsedPorts
67+
case isSS:
68+
hubAnnotation = annotations.AnnUSBIPSuperSpeedHubUsedPorts
69+
default:
70+
return false, fmt.Errorf("unsupported USB speed: %d", speed)
71+
}
72+
73+
totalPortsPerHub, err := GetTotalPortsPerHub(nodeAnnotations)
74+
if err != nil {
75+
return false, err
76+
}
77+
78+
usedPorts, err := GetUsedPorts(nodeAnnotations, hubAnnotation)
79+
if err != nil {
80+
return false, err
81+
}
82+
83+
return usedPorts < totalPortsPerHub, nil
84+
}
85+
86+
// CheckFreePortForRequest checks if there are enough free ports for a specific request.
87+
// It adds the requested count to the currently used ports and compares with total.
88+
func CheckFreePortForRequest(nodeAnnotations map[string]string, speed, requestedCount int) (bool, error) {
89+
isHS, isSS := ResolveSpeed(speed)
90+
91+
var hubAnnotation string
92+
switch {
93+
case isHS:
94+
hubAnnotation = annotations.AnnUSBIPHighSpeedHubUsedPorts
95+
case isSS:
96+
hubAnnotation = annotations.AnnUSBIPSuperSpeedHubUsedPorts
97+
default:
98+
return false, fmt.Errorf("unsupported USB speed: %d", speed)
99+
}
100+
101+
totalPortsPerHub, err := GetTotalPortsPerHub(nodeAnnotations)
102+
if err != nil {
103+
return false, err
104+
}
105+
106+
usedPorts, err := GetUsedPorts(nodeAnnotations, hubAnnotation)
107+
if err != nil {
108+
return false, err
109+
}
110+
111+
return (usedPorts + requestedCount) <= totalPortsPerHub, nil
112+
}
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
/*
2+
Copyright 2026 Flant JSC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package usb
18+
19+
import (
20+
"testing"
21+
22+
. "github.com/onsi/ginkgo/v2"
23+
. "github.com/onsi/gomega"
24+
25+
"github.com/deckhouse/virtualization-controller/pkg/common/annotations"
26+
)
27+
28+
func TestSpeed(t *testing.T) {
29+
RegisterFailHandler(Fail)
30+
RunSpecs(t, "USB speed package tests")
31+
}
32+
33+
var _ = Describe("ResolveSpeed", func() {
34+
DescribeTable("returns correct hub type for speed",
35+
func(speed int, expectedHS, expectedSS bool) {
36+
isHS, isSS := ResolveSpeed(speed)
37+
Expect(isHS).To(Equal(expectedHS))
38+
Expect(isSS).To(Equal(expectedSS))
39+
},
40+
Entry("low speed 1.0", 1, false, false),
41+
Entry("full speed 1.1", 12, false, false),
42+
Entry("high speed 2.0", 480, true, false),
43+
Entry("wireless 2.5", 2500, false, false),
44+
Entry("super speed 3.0", 5000, false, true),
45+
Entry("super speed 3.1", 10000, false, true),
46+
Entry("super speed 3.2", 20000, false, true),
47+
Entry("super speed plus 3.1", 10000, false, true),
48+
)
49+
})
50+
51+
var _ = Describe("CheckFreePort", func() {
52+
DescribeTable("returns correct availability",
53+
func(speed int, nodeAnnotations map[string]string, expectedResult, expectError bool) {
54+
result, err := CheckFreePort(nodeAnnotations, speed)
55+
if expectError {
56+
Expect(err).To(HaveOccurred())
57+
} else {
58+
Expect(err).NotTo(HaveOccurred())
59+
Expect(result).To(Equal(expectedResult))
60+
}
61+
},
62+
Entry("HS speed, enough ports",
63+
480,
64+
map[string]string{
65+
annotations.AnnUSBIPTotalPorts: "8",
66+
annotations.AnnUSBIPHighSpeedHubUsedPorts: "3",
67+
annotations.AnnUSBIPSuperSpeedHubUsedPorts: "0",
68+
},
69+
true, false,
70+
),
71+
Entry("HS speed, no ports left",
72+
480,
73+
map[string]string{
74+
annotations.AnnUSBIPTotalPorts: "8",
75+
annotations.AnnUSBIPHighSpeedHubUsedPorts: "4",
76+
annotations.AnnUSBIPSuperSpeedHubUsedPorts: "0",
77+
},
78+
false, false,
79+
),
80+
Entry("SS speed, enough ports",
81+
5000,
82+
map[string]string{
83+
annotations.AnnUSBIPTotalPorts: "8",
84+
annotations.AnnUSBIPHighSpeedHubUsedPorts: "4",
85+
annotations.AnnUSBIPSuperSpeedHubUsedPorts: "2",
86+
},
87+
true, false,
88+
),
89+
Entry("SS speed, no ports left",
90+
10000,
91+
map[string]string{
92+
annotations.AnnUSBIPTotalPorts: "8",
93+
annotations.AnnUSBIPHighSpeedHubUsedPorts: "4",
94+
annotations.AnnUSBIPSuperSpeedHubUsedPorts: "4",
95+
},
96+
false, false,
97+
),
98+
Entry("unsupported speed",
99+
12,
100+
map[string]string{
101+
annotations.AnnUSBIPTotalPorts: "8",
102+
annotations.AnnUSBIPHighSpeedHubUsedPorts: "0",
103+
annotations.AnnUSBIPSuperSpeedHubUsedPorts: "0",
104+
},
105+
false, true,
106+
),
107+
Entry("missing total ports annotation",
108+
480,
109+
map[string]string{
110+
annotations.AnnUSBIPHighSpeedHubUsedPorts: "0",
111+
annotations.AnnUSBIPSuperSpeedHubUsedPorts: "0",
112+
},
113+
false, true,
114+
),
115+
Entry("missing HS hub annotation",
116+
480,
117+
map[string]string{
118+
annotations.AnnUSBIPTotalPorts: "8",
119+
annotations.AnnUSBIPSuperSpeedHubUsedPorts: "0",
120+
},
121+
false, true,
122+
),
123+
Entry("missing SS hub annotation",
124+
5000,
125+
map[string]string{
126+
annotations.AnnUSBIPTotalPorts: "8",
127+
annotations.AnnUSBIPHighSpeedHubUsedPorts: "0",
128+
},
129+
false, true,
130+
),
131+
)
132+
})
133+
134+
var _ = Describe("CheckFreePortForRequest", func() {
135+
DescribeTable("returns correct availability for request",
136+
func(speed int, nodeAnnotations map[string]string, requestedCount int, expectedResult, expectError bool) {
137+
result, err := CheckFreePortForRequest(nodeAnnotations, speed, requestedCount)
138+
if expectError {
139+
Expect(err).To(HaveOccurred())
140+
} else {
141+
Expect(err).NotTo(HaveOccurred())
142+
Expect(result).To(Equal(expectedResult))
143+
}
144+
},
145+
Entry("HS speed, request 1, enough ports",
146+
480,
147+
map[string]string{
148+
annotations.AnnUSBIPTotalPorts: "8",
149+
annotations.AnnUSBIPHighSpeedHubUsedPorts: "3",
150+
annotations.AnnUSBIPSuperSpeedHubUsedPorts: "0",
151+
},
152+
1, true, false,
153+
),
154+
Entry("HS speed, request 2, no ports",
155+
480,
156+
map[string]string{
157+
annotations.AnnUSBIPTotalPorts: "8",
158+
annotations.AnnUSBIPHighSpeedHubUsedPorts: "3",
159+
annotations.AnnUSBIPSuperSpeedHubUsedPorts: "0",
160+
},
161+
2, false, false,
162+
),
163+
Entry("HS speed, request 2, exactly at limit",
164+
480,
165+
map[string]string{
166+
annotations.AnnUSBIPTotalPorts: "8",
167+
annotations.AnnUSBIPHighSpeedHubUsedPorts: "2",
168+
annotations.AnnUSBIPSuperSpeedHubUsedPorts: "0",
169+
},
170+
2, true, false,
171+
),
172+
)
173+
})
174+
175+
var _ = Describe("GetTotalPortsPerHub", func() {
176+
DescribeTable("returns correct total ports per hub",
177+
func(nodeAnnotations map[string]string, expectedResult int, expectError bool) {
178+
result, err := GetTotalPortsPerHub(nodeAnnotations)
179+
if expectError {
180+
Expect(err).To(HaveOccurred())
181+
} else {
182+
Expect(err).NotTo(HaveOccurred())
183+
Expect(result).To(Equal(expectedResult))
184+
}
185+
},
186+
Entry("total 8 ports",
187+
map[string]string{annotations.AnnUSBIPTotalPorts: "8"},
188+
4, false,
189+
),
190+
Entry("total 4 ports",
191+
map[string]string{annotations.AnnUSBIPTotalPorts: "4"},
192+
2, false,
193+
),
194+
Entry("missing annotation",
195+
map[string]string{},
196+
0, true,
197+
),
198+
Entry("invalid value",
199+
map[string]string{annotations.AnnUSBIPTotalPorts: "invalid"},
200+
0, true,
201+
),
202+
)
203+
})
204+
205+
var _ = Describe("GetUsedPorts", func() {
206+
DescribeTable("returns correct used ports",
207+
func(nodeAnnotations map[string]string, hubAnnotation string, expectedResult int, expectError bool) {
208+
result, err := GetUsedPorts(nodeAnnotations, hubAnnotation)
209+
if expectError {
210+
Expect(err).To(HaveOccurred())
211+
} else {
212+
Expect(err).NotTo(HaveOccurred())
213+
Expect(result).To(Equal(expectedResult))
214+
}
215+
},
216+
Entry("HS hub, 3 used",
217+
map[string]string{annotations.AnnUSBIPHighSpeedHubUsedPorts: "3"},
218+
annotations.AnnUSBIPHighSpeedHubUsedPorts,
219+
3, false,
220+
),
221+
Entry("SS hub, 0 used",
222+
map[string]string{annotations.AnnUSBIPSuperSpeedHubUsedPorts: "0"},
223+
annotations.AnnUSBIPSuperSpeedHubUsedPorts,
224+
0, false,
225+
),
226+
Entry("missing annotation",
227+
map[string]string{},
228+
annotations.AnnUSBIPHighSpeedHubUsedPorts,
229+
0, true,
230+
),
231+
Entry("invalid value",
232+
map[string]string{annotations.AnnUSBIPHighSpeedHubUsedPorts: "invalid"},
233+
annotations.AnnUSBIPHighSpeedHubUsedPorts,
234+
0, true,
235+
),
236+
)
237+
})

0 commit comments

Comments
 (0)