Skip to content

Commit 922fc93

Browse files
authored
Merge pull request #269 from klihub/devel/linux-net-device
Add support for (Linux) net device injection.
2 parents 16a1328 + 1badadd commit 922fc93

File tree

9 files changed

+259
-19
lines changed

9 files changed

+259
-19
lines changed

SPEC.md

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Released versions of the spec are available as Git tags.
3333
| | | Add `AdditionalGIDs` to `ContainerEdits` |
3434
| v0.8.0 | | Remove .ToOCI() functions from specs-go package. |
3535
| v1.0.0 | | Move minimum version logic to specs-go package. |
36+
| v1.1.0 | | Add `NetDevices` to `ContainerEdits`, `Schemata` and `EnableMonitoring` to `IntelRdt`. Dropped `EnableCMT` and `EnableMBM` fields from `IntelRdt`. |
3637

3738
*Note*: spec loading fails on unknown fields and when the minimum required version is higher than the version specified in the spec. The minimum required version is determined based on the usage of fields mentioned in the table above. For example the minimum required version is v0.6.0 if the `Annotations` field is used in the spec, but `IntelRdt` is not.
3839
`MinimumRequiredVersion` API can be used to get the minimum required version.
@@ -164,14 +165,22 @@ The keywords "must", "must not", "required", "shall", "shall not", "should", "sh
164165
// Note that a value of 0 is ignored.
165166
additionalGIDs: [ (optional)
166167
<uint32>
167-
]
168+
],
168169
"intelRdt": { (optional)
169170
"closID": "<name>", (optional)
170171
"l3CacheSchema": "string" (optional)
171172
"memBwSchema": "string" (optional)
172-
"enableCMT": "<boolean>" (optional)
173-
"enableMBM": "<boolean>" (optional)
174-
}
173+
"schema": [ "string" ] (optional)
174+
"enableMonitoring": <boolean> (optional)
175+
},
176+
// This field contains network interfaces that should be moved
177+
// from the host to the container.
178+
"netDevices": [ (optional)
179+
{
180+
"hostInterfaceName": "<interface name on the host>",
181+
"name": "<interface name in the container>"
182+
}
183+
]
175184
}
176185
]
177186
}
@@ -247,8 +256,8 @@ The `containerEdits` field has the following definition:
247256
* `closID` (string, OPTIONAL) name of the `CLOS` (Class of Service).
248257
* `l3CacheSchema` (string, OPTIONAL) L3 cache allocation schema for the `CLOS`.
249258
* `memBwSchema` (string, OPTIONAL) memory bandwidth allocation schema for the `CLOS`.
250-
* `enableCMT` (boolean, OPTIONAL) whether to enable cache monitoring
251-
* `enableMBM` (boolean, OPTIONAL) whether to enable memory bandwidth monitoring
259+
* `schemata` (array of strings, OPTIONAL) RDT schema for the CLOS.
260+
* `enableMonitoring` (boolean, OPTIONAL) whether to enable memory bandwidth monitoring for the CLOS.
252261
* `additionalGids` (array of uint32s, OPTIONAL) A list of additional group IDs to add with the container process. These values are added to the `user.additionalGids` field in the OCI runtime specification. Values of 0 are ignored. Added in v0.7.0.
253262

254263
## Error Handling

cmd/cdi/cmd/validate.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/spf13/cobra"
2525

2626
"tags.cncf.io/container-device-interface/pkg/cdi"
27+
"tags.cncf.io/container-device-interface/specs-go"
2728
)
2829

2930
// validateCmd is our CDI command for validating CDI Spec files in the cache.
@@ -49,6 +50,15 @@ were reported by the cache.`,
4950
fmt.Printf(" %2d: %v\n", idx, strings.TrimSpace(err.Error()))
5051
}
5152
}
53+
54+
for _, v := range cache.ListVendors() {
55+
for _, s := range cache.GetVendorSpecs(v) {
56+
if err := specs.ValidateVersion(s.Spec); err != nil {
57+
fmt.Printf("Spec file %s failed version validation: %v\n", s.GetPath(), err)
58+
}
59+
}
60+
}
61+
5262
os.Exit(1)
5363
},
5464
}

cmd/cdi/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
sigs.k8s.io/yaml v1.4.0
1212
tags.cncf.io/container-device-interface v1.0.1
1313
tags.cncf.io/container-device-interface/schema v0.0.0
14+
tags.cncf.io/container-device-interface/specs-go v1.0.0
1415
)
1516

1617
require (
@@ -22,7 +23,6 @@ require (
2223
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
2324
golang.org/x/mod v0.19.0 // indirect
2425
golang.org/x/sys v0.19.0 // indirect
25-
tags.cncf.io/container-device-interface/specs-go v1.0.0 // indirect
2626
)
2727

2828
replace (

pkg/cdi/container-edits.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@ func (e *ContainerEdits) Apply(spec *oci.Spec) error {
113113
}
114114
}
115115

116+
if len(e.NetDevices) > 0 {
117+
// specgen is currently missing functionality to set Linux NetDevices,
118+
// so we use a locally rolled function for now.
119+
for _, dev := range e.NetDevices {
120+
specgenAddLinuxNetDevice(&specgen, dev.HostInterfaceName, (&LinuxNetDevice{dev}).toOCI())
121+
}
122+
}
123+
116124
if len(e.Mounts) > 0 {
117125
for _, m := range e.Mounts {
118126
specgen.RemoveMount(m.ContainerPath)
@@ -162,6 +170,24 @@ func (e *ContainerEdits) Apply(spec *oci.Spec) error {
162170
return nil
163171
}
164172

173+
func specgenAddLinuxNetDevice(specgen *ocigen.Generator, hostIf string, netDev *oci.LinuxNetDevice) {
174+
if specgen == nil || netDev == nil {
175+
return
176+
}
177+
ensureLinuxNetDevices(specgen.Config)
178+
specgen.Config.Linux.NetDevices[hostIf] = *netDev
179+
}
180+
181+
// Ensure OCI Spec Linux NetDevices map is not nil.
182+
func ensureLinuxNetDevices(spec *oci.Spec) {
183+
if spec.Linux == nil {
184+
spec.Linux = &oci.Linux{}
185+
}
186+
if spec.Linux.NetDevices == nil {
187+
spec.Linux.NetDevices = map[string]oci.LinuxNetDevice{}
188+
}
189+
}
190+
165191
// Validate container edits.
166192
func (e *ContainerEdits) Validate() error {
167193
if e == nil || e.ContainerEdits == nil {
@@ -191,6 +217,9 @@ func (e *ContainerEdits) Validate() error {
191217
return err
192218
}
193219
}
220+
if err := ValidateNetDevices(e.NetDevices); err != nil {
221+
return err
222+
}
194223

195224
return nil
196225
}
@@ -210,6 +239,7 @@ func (e *ContainerEdits) Append(o *ContainerEdits) *ContainerEdits {
210239

211240
e.Env = append(e.Env, o.Env...)
212241
e.DeviceNodes = append(e.DeviceNodes, o.DeviceNodes...)
242+
e.NetDevices = append(e.NetDevices, o.NetDevices...)
213243
e.Hooks = append(e.Hooks, o.Hooks...)
214244
e.Mounts = append(e.Mounts, o.Mounts...)
215245
if o.IntelRdt != nil {
@@ -244,6 +274,9 @@ func (e *ContainerEdits) isEmpty() bool {
244274
if e.IntelRdt != nil {
245275
return false
246276
}
277+
if len(e.NetDevices) > 0 {
278+
return false
279+
}
247280
return true
248281
}
249282

@@ -257,6 +290,49 @@ func ValidateEnv(env []string) error {
257290
return nil
258291
}
259292

293+
// ValidateNetDevices validates the given net devices.
294+
func ValidateNetDevices(devices []*cdi.LinuxNetDevice) error {
295+
var (
296+
hostSeen = map[string]string{}
297+
nameSeen = map[string]string{}
298+
)
299+
300+
for _, dev := range devices {
301+
if err := (&LinuxNetDevice{dev}).Validate(); err != nil {
302+
return err
303+
}
304+
if other, ok := hostSeen[dev.HostInterfaceName]; ok {
305+
return fmt.Errorf("invalid linux net device, duplicate HostInterfaceName %q with names %q and %q",
306+
dev.HostInterfaceName, dev.Name, other)
307+
}
308+
hostSeen[dev.HostInterfaceName] = dev.Name
309+
310+
if other, ok := nameSeen[dev.Name]; ok {
311+
return fmt.Errorf("invalid linux net device, duplicate Name %q with HostInterfaceName %q and %q",
312+
dev.Name, dev.HostInterfaceName, other)
313+
}
314+
nameSeen[dev.Name] = dev.HostInterfaceName
315+
}
316+
317+
return nil
318+
}
319+
320+
// LinuxNetDevice is a CDI Spec LinuxNetDevice wrapper, used for OCI conversion and validating.
321+
type LinuxNetDevice struct {
322+
*cdi.LinuxNetDevice
323+
}
324+
325+
// Validate LinuxNetDevice.
326+
func (d *LinuxNetDevice) Validate() error {
327+
if d.HostInterfaceName == "" {
328+
return errors.New("invalid linux net device, empty HostInterfaceName")
329+
}
330+
if d.Name == "" {
331+
return errors.New("invalid linux net device, empty Name")
332+
}
333+
return nil
334+
}
335+
260336
// DeviceNode is a CDI Spec DeviceNode wrapper, used for validating DeviceNodes.
261337
type DeviceNode struct {
262338
*cdi.DeviceNode

pkg/cdi/container-edits_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,41 @@ func TestValidateContainerEdits(t *testing.T) {
299299
},
300300
invalid: true,
301301
},
302+
{
303+
name: "valid Linux net device",
304+
edits: &cdi.ContainerEdits{
305+
NetDevices: []*cdi.LinuxNetDevice{
306+
{
307+
HostInterfaceName: "eno1",
308+
Name: "netdev0",
309+
},
310+
},
311+
},
312+
},
313+
{
314+
name: "invalid Linux net device, empty host interface name",
315+
edits: &cdi.ContainerEdits{
316+
NetDevices: []*cdi.LinuxNetDevice{
317+
{
318+
HostInterfaceName: "",
319+
Name: "netdev0",
320+
},
321+
},
322+
},
323+
invalid: true,
324+
},
325+
{
326+
name: "invalid Linux net device, empty container interface name",
327+
edits: &cdi.ContainerEdits{
328+
NetDevices: []*cdi.LinuxNetDevice{
329+
{
330+
HostInterfaceName: "eno1",
331+
Name: "",
332+
},
333+
},
334+
},
335+
invalid: true,
336+
},
302337
} {
303338
t.Run(tc.name, func(t *testing.T) {
304339
edits := ContainerEdits{tc.edits}
@@ -587,6 +622,70 @@ func TestApplyContainerEdits(t *testing.T) {
587622
},
588623
},
589624
},
625+
{
626+
name: "empty spec, Linux net devices",
627+
spec: &oci.Spec{},
628+
edits: &cdi.ContainerEdits{
629+
NetDevices: []*cdi.LinuxNetDevice{
630+
{
631+
HostInterfaceName: "eno1",
632+
Name: "netdev0",
633+
},
634+
{
635+
HostInterfaceName: "eno2",
636+
Name: "netdev1",
637+
},
638+
},
639+
},
640+
result: &oci.Spec{
641+
Linux: &oci.Linux{
642+
NetDevices: map[string]oci.LinuxNetDevice{
643+
"eno1": {
644+
Name: "netdev0",
645+
},
646+
"eno2": {
647+
Name: "netdev1",
648+
},
649+
},
650+
},
651+
},
652+
},
653+
{
654+
name: "non-empty spec, overriding Linux net devices",
655+
spec: &oci.Spec{
656+
Linux: &oci.Linux{
657+
NetDevices: map[string]oci.LinuxNetDevice{
658+
"eno1": {
659+
Name: "netdev1",
660+
},
661+
},
662+
},
663+
},
664+
edits: &cdi.ContainerEdits{
665+
NetDevices: []*cdi.LinuxNetDevice{
666+
{
667+
HostInterfaceName: "eno1",
668+
Name: "netdev2",
669+
},
670+
{
671+
HostInterfaceName: "eno2",
672+
Name: "netdev1",
673+
},
674+
},
675+
},
676+
result: &oci.Spec{
677+
Linux: &oci.Linux{
678+
NetDevices: map[string]oci.LinuxNetDevice{
679+
"eno1": {
680+
Name: "netdev2",
681+
},
682+
"eno2": {
683+
Name: "netdev1",
684+
},
685+
},
686+
},
687+
},
688+
},
590689
{
591690
name: "additional GIDs are applied",
592691
spec: &oci.Spec{},

pkg/cdi/oci.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,10 @@ func (i *IntelRdt) toOCI() *spec.LinuxIntelRdt {
6363
EnableMonitoring: i.EnableMonitoring,
6464
}
6565
}
66+
67+
// toOCI returns the opencontainers runtime Spec LinuxNetDevice for this LinuxNetDevice.
68+
func (d *LinuxNetDevice) toOCI() *spec.LinuxNetDevice {
69+
return &spec.LinuxNetDevice{
70+
Name: d.Name,
71+
}
72+
}

schema/defs.json

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
"Env": {
2727
"$ref": "#/definitions/ArrayOfStrings"
2828
},
29+
"InterfaceName": {
30+
"type": "string"
31+
},
2932
"mapStringString": {
3033
"type": "object",
3134
"patternProperties": {
@@ -111,6 +114,21 @@
111114
"path"
112115
]
113116
},
117+
"LinuxNetDevice": {
118+
"type": "object",
119+
"properties": {
120+
"hostInterfaceName": {
121+
"$ref": "#/definitions/InterfaceName"
122+
},
123+
"name": {
124+
"$ref": "#/definitions/InterfaceName"
125+
}
126+
},
127+
"required": [
128+
"hostInterfaceName",
129+
"name"
130+
]
131+
},
114132
"containerEdits": {
115133
"type": "object",
116134
"properties": {
@@ -126,6 +144,12 @@
126144
"$ref": "#/definitions/DeviceNode"
127145
}
128146
},
147+
"netDevices": {
148+
"type": "array",
149+
"items": {
150+
"$ref": "#/definitions/LinuxNetDevice"
151+
}
152+
},
129153
"mounts": {
130154
"type": "array",
131155
"items": {
@@ -150,10 +174,10 @@
150174
"memBwSchema": {
151175
"type": "string"
152176
},
153-
"enableCMT": {
154-
"type": "boolean"
177+
"schemata": {
178+
"$ref": "#/definitions/ArrayOfStrings"
155179
},
156-
"enableMBM": {
180+
"enableMonitoring": {
157181
"type": "boolean"
158182
}
159183
}

0 commit comments

Comments
 (0)