From 5e3870c3fcbc5ab696e78a1e548a5005afd2285e Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Wed, 9 Apr 2025 14:54:47 +0300 Subject: [PATCH 1/7] schema: add ContainerEdits.NetDevices. Signed-off-by: Krisztian Litkey --- schema/defs.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/schema/defs.json b/schema/defs.json index f5494234..0ff135ed 100644 --- a/schema/defs.json +++ b/schema/defs.json @@ -26,6 +26,9 @@ "Env": { "$ref": "#/definitions/ArrayOfStrings" }, + "InterfaceName": { + "type": "string" + }, "mapStringString": { "type": "object", "patternProperties": { @@ -111,6 +114,21 @@ "path" ] }, + "LinuxNetDevice": { + "type": "object", + "properties": { + "hostInterfaceName": { + "$ref": "#/definitions/InterfaceName" + }, + "name": { + "$ref": "#/definitions/InterfaceName" + } + }, + "required": [ + "hostInterfaceName", + "name" + ] + }, "containerEdits": { "type": "object", "properties": { @@ -126,6 +144,12 @@ "$ref": "#/definitions/DeviceNode" } }, + "netDevices": { + "type": "array", + "items": { + "$ref": "#/definitions/LinuxNetDevice" + } + }, "mounts": { "type": "array", "items": { From c5a3a6225060f4d48d7b8640f3f6efa9837ce517 Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Wed, 9 Apr 2025 14:55:25 +0300 Subject: [PATCH 2/7] specs-go,cdi: implement NetDevice injection. Implement OCI Spec (Linux) NetDevice injection. Signed-off-by: Krisztian Litkey --- pkg/cdi/container-edits.go | 76 +++++++++++++++++++++++++ pkg/cdi/container-edits_test.go | 99 +++++++++++++++++++++++++++++++++ pkg/cdi/oci.go | 7 +++ specs-go/config.go | 19 +++++-- 4 files changed, 195 insertions(+), 6 deletions(-) diff --git a/pkg/cdi/container-edits.go b/pkg/cdi/container-edits.go index 450a84f6..f498049c 100644 --- a/pkg/cdi/container-edits.go +++ b/pkg/cdi/container-edits.go @@ -113,6 +113,14 @@ func (e *ContainerEdits) Apply(spec *oci.Spec) error { } } + if len(e.NetDevices) > 0 { + // specgen is currently missing functionality to set Linux NetDevices, + // so we use a locally rolled function for now. + for _, dev := range e.NetDevices { + specgenAddLinuxNetDevice(&specgen, dev.HostInterfaceName, (&LinuxNetDevice{dev}).toOCI()) + } + } + if len(e.Mounts) > 0 { for _, m := range e.Mounts { specgen.RemoveMount(m.ContainerPath) @@ -162,6 +170,24 @@ func (e *ContainerEdits) Apply(spec *oci.Spec) error { return nil } +func specgenAddLinuxNetDevice(specgen *ocigen.Generator, hostIf string, netDev *oci.LinuxNetDevice) { + if specgen == nil || netDev == nil { + return + } + ensureLinuxNetDevices(specgen.Config) + specgen.Config.Linux.NetDevices[hostIf] = *netDev +} + +// Ensure OCI Spec Linux NetDevices map is not nil. +func ensureLinuxNetDevices(spec *oci.Spec) { + if spec.Linux == nil { + spec.Linux = &oci.Linux{} + } + if spec.Linux.NetDevices == nil { + spec.Linux.NetDevices = map[string]oci.LinuxNetDevice{} + } +} + // Validate container edits. func (e *ContainerEdits) Validate() error { if e == nil || e.ContainerEdits == nil { @@ -191,6 +217,9 @@ func (e *ContainerEdits) Validate() error { return err } } + if err := ValidateNetDevices(e.NetDevices); err != nil { + return err + } return nil } @@ -210,6 +239,7 @@ func (e *ContainerEdits) Append(o *ContainerEdits) *ContainerEdits { e.Env = append(e.Env, o.Env...) e.DeviceNodes = append(e.DeviceNodes, o.DeviceNodes...) + e.NetDevices = append(e.NetDevices, o.NetDevices...) e.Hooks = append(e.Hooks, o.Hooks...) e.Mounts = append(e.Mounts, o.Mounts...) if o.IntelRdt != nil { @@ -244,6 +274,9 @@ func (e *ContainerEdits) isEmpty() bool { if e.IntelRdt != nil { return false } + if len(e.NetDevices) > 0 { + return false + } return true } @@ -257,6 +290,49 @@ func ValidateEnv(env []string) error { return nil } +// ValidateNetDevices validates the given net devices. +func ValidateNetDevices(devices []*cdi.LinuxNetDevice) error { + var ( + hostSeen = map[string]string{} + nameSeen = map[string]string{} + ) + + for _, dev := range devices { + if err := (&LinuxNetDevice{dev}).Validate(); err != nil { + return err + } + if other, ok := hostSeen[dev.HostInterfaceName]; ok { + return fmt.Errorf("invalid linux net device, duplicate HostInterfaceName %q with names %q and %q", + dev.HostInterfaceName, dev.Name, other) + } + hostSeen[dev.HostInterfaceName] = dev.Name + + if other, ok := nameSeen[dev.Name]; ok { + return fmt.Errorf("invalid linux net device, duplicate Name %q with HostInterfaceName %q and %q", + dev.Name, dev.HostInterfaceName, other) + } + nameSeen[dev.Name] = dev.HostInterfaceName + } + + return nil +} + +// LinuxNetDevice is a CDI Spec LinuxNetDevice wrapper, used for OCI conversion and validating. +type LinuxNetDevice struct { + *cdi.LinuxNetDevice +} + +// Validate LinuxNetDevice. +func (d *LinuxNetDevice) Validate() error { + if d.HostInterfaceName == "" { + return errors.New("invalid linux net device, empty HostInterfaceName") + } + if d.Name == "" { + return errors.New("invalid linux net device, empty Name") + } + return nil +} + // DeviceNode is a CDI Spec DeviceNode wrapper, used for validating DeviceNodes. type DeviceNode struct { *cdi.DeviceNode diff --git a/pkg/cdi/container-edits_test.go b/pkg/cdi/container-edits_test.go index e6c19df4..2c553d92 100644 --- a/pkg/cdi/container-edits_test.go +++ b/pkg/cdi/container-edits_test.go @@ -299,6 +299,41 @@ func TestValidateContainerEdits(t *testing.T) { }, invalid: true, }, + { + name: "valid Linux net device", + edits: &cdi.ContainerEdits{ + NetDevices: []*cdi.LinuxNetDevice{ + { + HostInterfaceName: "eno1", + Name: "netdev0", + }, + }, + }, + }, + { + name: "invalid Linux net device, empty host interface name", + edits: &cdi.ContainerEdits{ + NetDevices: []*cdi.LinuxNetDevice{ + { + HostInterfaceName: "", + Name: "netdev0", + }, + }, + }, + invalid: true, + }, + { + name: "invalid Linux net device, empty container interface name", + edits: &cdi.ContainerEdits{ + NetDevices: []*cdi.LinuxNetDevice{ + { + HostInterfaceName: "eno1", + Name: "", + }, + }, + }, + invalid: true, + }, } { t.Run(tc.name, func(t *testing.T) { edits := ContainerEdits{tc.edits} @@ -587,6 +622,70 @@ func TestApplyContainerEdits(t *testing.T) { }, }, }, + { + name: "empty spec, Linux net devices", + spec: &oci.Spec{}, + edits: &cdi.ContainerEdits{ + NetDevices: []*cdi.LinuxNetDevice{ + { + HostInterfaceName: "eno1", + Name: "netdev0", + }, + { + HostInterfaceName: "eno2", + Name: "netdev1", + }, + }, + }, + result: &oci.Spec{ + Linux: &oci.Linux{ + NetDevices: map[string]oci.LinuxNetDevice{ + "eno1": { + Name: "netdev0", + }, + "eno2": { + Name: "netdev1", + }, + }, + }, + }, + }, + { + name: "non-empty spec, overriding Linux net devices", + spec: &oci.Spec{ + Linux: &oci.Linux{ + NetDevices: map[string]oci.LinuxNetDevice{ + "eno1": { + Name: "netdev1", + }, + }, + }, + }, + edits: &cdi.ContainerEdits{ + NetDevices: []*cdi.LinuxNetDevice{ + { + HostInterfaceName: "eno1", + Name: "netdev2", + }, + { + HostInterfaceName: "eno2", + Name: "netdev1", + }, + }, + }, + result: &oci.Spec{ + Linux: &oci.Linux{ + NetDevices: map[string]oci.LinuxNetDevice{ + "eno1": { + Name: "netdev2", + }, + "eno2": { + Name: "netdev1", + }, + }, + }, + }, + }, { name: "additional GIDs are applied", spec: &oci.Spec{}, diff --git a/pkg/cdi/oci.go b/pkg/cdi/oci.go index d8fa14a2..f37499fc 100644 --- a/pkg/cdi/oci.go +++ b/pkg/cdi/oci.go @@ -63,3 +63,10 @@ func (i *IntelRdt) toOCI() *spec.LinuxIntelRdt { EnableMonitoring: i.EnableMonitoring, } } + +// toOCI returns the opencontainers runtime Spec LinuxNetDevice for this LinuxNetDevice. +func (d *LinuxNetDevice) toOCI() *spec.LinuxNetDevice { + return &spec.LinuxNetDevice{ + Name: d.Name, + } +} diff --git a/specs-go/config.go b/specs-go/config.go index 577331f0..da475172 100644 --- a/specs-go/config.go +++ b/specs-go/config.go @@ -24,12 +24,13 @@ type Device struct { // ContainerEdits are edits a container runtime must make to the OCI spec to expose the device. type ContainerEdits struct { - Env []string `json:"env,omitempty" yaml:"env,omitempty"` - DeviceNodes []*DeviceNode `json:"deviceNodes,omitempty" yaml:"deviceNodes,omitempty"` - Hooks []*Hook `json:"hooks,omitempty" yaml:"hooks,omitempty"` - Mounts []*Mount `json:"mounts,omitempty" yaml:"mounts,omitempty"` - IntelRdt *IntelRdt `json:"intelRdt,omitempty" yaml:"intelRdt,omitempty"` // Added in v0.7.0 - AdditionalGIDs []uint32 `json:"additionalGids,omitempty" yaml:"additionalGids,omitempty"` // Added in v0.7.0 + Env []string `json:"env,omitempty" yaml:"env,omitempty"` + DeviceNodes []*DeviceNode `json:"deviceNodes,omitempty" yaml:"deviceNodes,omitempty"` + NetDevices []*LinuxNetDevice `json:"netDevices,omitempty" yaml:"netDevices,omitempty"` // Added in v1.1.0 + Hooks []*Hook `json:"hooks,omitempty" yaml:"hooks,omitempty"` + Mounts []*Mount `json:"mounts,omitempty" yaml:"mounts,omitempty"` + IntelRdt *IntelRdt `json:"intelRdt,omitempty" yaml:"intelRdt,omitempty"` // Added in v0.7.0 + AdditionalGIDs []uint32 `json:"additionalGids,omitempty" yaml:"additionalGids,omitempty"` // Added in v0.7.0 } // DeviceNode represents a device node that needs to be added to the OCI spec. @@ -70,3 +71,9 @@ type IntelRdt struct { Schemata []string `json:"schemata,omitempty" yaml:"schemata,omitempty"` EnableMonitoring bool `json:"enableMonitoring,omitempty" yaml:"enableMonitoring,omitempty"` } + +// LinuxNetDevice represents an OCI LinuxNetDevice to be added to the OCI Spec. +type LinuxNetDevice struct { + HostInterfaceName string `json:"hostInterfaceName" yaml:"hostInterfaceName"` + Name string `json:"name" yaml:"name"` +} From 5cefa6b949b3a3428e31ab00f96aa3b4dba6807e Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Thu, 20 Nov 2025 10:26:44 +0200 Subject: [PATCH 3/7] specs-go: bump version to 1.1.0. Bump current version to 1.1.0, include net devices in minimum required version check. Signed-off-by: Krisztian Litkey --- specs-go/version.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/specs-go/version.go b/specs-go/version.go index 4e5d7366..62be6b7a 100644 --- a/specs-go/version.go +++ b/specs-go/version.go @@ -25,7 +25,7 @@ import ( const ( // CurrentVersion is the current version of the Spec. - CurrentVersion = "1.0.0" + CurrentVersion = "1.1.0" // vCurrent is the current version as a semver-comparable type vCurrent version = "v" + CurrentVersion @@ -150,12 +150,20 @@ func requiresV110(spec *Spec) bool { } } + if len(spec.ContainerEdits.NetDevices) != 0 { + return true + } + for _, dev := range spec.Devices { if i := dev.ContainerEdits.IntelRdt; i != nil { if i.Schemata != nil || i.EnableMonitoring { return true } } + + if len(dev.ContainerEdits.NetDevices) != 0 { + return true + } } return false From b2978348fef8725600edb73c00362f59a0125471 Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Wed, 9 Apr 2025 16:26:38 +0300 Subject: [PATCH 4/7] cmd/cdi: validate also Spec version. Signed-off-by: Krisztian Litkey --- cmd/cdi/cmd/validate.go | 10 ++++++++++ cmd/cdi/go.mod | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/cmd/cdi/cmd/validate.go b/cmd/cdi/cmd/validate.go index 9dc8faf9..5a088434 100644 --- a/cmd/cdi/cmd/validate.go +++ b/cmd/cdi/cmd/validate.go @@ -24,6 +24,7 @@ import ( "github.com/spf13/cobra" "tags.cncf.io/container-device-interface/pkg/cdi" + "tags.cncf.io/container-device-interface/specs-go" ) // validateCmd is our CDI command for validating CDI Spec files in the cache. @@ -49,6 +50,15 @@ were reported by the cache.`, fmt.Printf(" %2d: %v\n", idx, strings.TrimSpace(err.Error())) } } + + for _, v := range cache.ListVendors() { + for _, s := range cache.GetVendorSpecs(v) { + if err := specs.ValidateVersion(s.Spec); err != nil { + fmt.Printf("Spec file %s failed version validation: %v\n", s.GetPath(), err) + } + } + } + os.Exit(1) }, } diff --git a/cmd/cdi/go.mod b/cmd/cdi/go.mod index 812706dd..4a1a59ea 100644 --- a/cmd/cdi/go.mod +++ b/cmd/cdi/go.mod @@ -11,6 +11,7 @@ require ( sigs.k8s.io/yaml v1.4.0 tags.cncf.io/container-device-interface v1.0.1 tags.cncf.io/container-device-interface/schema v0.0.0 + tags.cncf.io/container-device-interface/specs-go v1.0.0 ) require ( @@ -22,7 +23,6 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect golang.org/x/mod v0.19.0 // indirect golang.org/x/sys v0.19.0 // indirect - tags.cncf.io/container-device-interface/specs-go v1.0.0 // indirect ) replace ( From c8531e899690870a293c51f64093e319082c11cc Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Wed, 10 Dec 2025 08:52:04 +0200 Subject: [PATCH 5/7] SPECS.md: add v1.1.0 with new NetDevices, updates for RDT. Update spec description, adding net devices and updating RDT to reflect recent changes. Signed-off-by: Krisztian Litkey --- SPEC.md | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/SPEC.md b/SPEC.md index fc918d74..a080a44c 100644 --- a/SPEC.md +++ b/SPEC.md @@ -33,6 +33,7 @@ Released versions of the spec are available as Git tags. | | | Add `AdditionalGIDs` to `ContainerEdits` | | v0.8.0 | | Remove .ToOCI() functions from specs-go package. | | v1.0.0 | | Move minimum version logic to specs-go package. | +| v1.1.0 | | Add `NetDevices` to `ContainerEdits`, `Schemata` and `EnableMonitoring` to `IntelRdt`. Dropped `EnableCMT` and `EnableMBM` fields from `IntelRdt`. | *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. `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 // Note that a value of 0 is ignored. additionalGIDs: [ (optional) - ] + ], "intelRdt": { (optional) "closID": "", (optional) "l3CacheSchema": "string" (optional) "memBwSchema": "string" (optional) - "enableCMT": "" (optional) - "enableMBM": "" (optional) - } + "schema": [ "string" ] (optional) + "enableMonitoring": (optional) + }, + // This field contains network interfaces that should be moved + // from the host to the container. + "netDevices": [ (optional) + { + "hostInterfaceName": "", + "name": "" + } + ] } ] } @@ -247,8 +256,8 @@ The `containerEdits` field has the following definition: * `closID` (string, OPTIONAL) name of the `CLOS` (Class of Service). * `l3CacheSchema` (string, OPTIONAL) L3 cache allocation schema for the `CLOS`. * `memBwSchema` (string, OPTIONAL) memory bandwidth allocation schema for the `CLOS`. - * `enableCMT` (boolean, OPTIONAL) whether to enable cache monitoring - * `enableMBM` (boolean, OPTIONAL) whether to enable memory bandwidth monitoring + * `schemata` (array of strings, OPTIONAL) RDT schema for the CLOS. + * `enableMonitoring` (boolean, OPTIONAL) whether to enable memory bandwidth monitoring for the CLOS. * `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. ## Error Handling From 18d950c6f63e50d4c268bf5dd1213d964b3c2c31 Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Wed, 10 Dec 2025 09:03:41 +0200 Subject: [PATCH 6/7] schema: update for recent RDT changes. Signed-off-by: Krisztian Litkey --- schema/defs.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/schema/defs.json b/schema/defs.json index 0ff135ed..e942c5c6 100644 --- a/schema/defs.json +++ b/schema/defs.json @@ -174,10 +174,10 @@ "memBwSchema": { "type": "string" }, - "enableCMT": { - "type": "boolean" + "schemata": { + "$ref": "#/definitions/ArrayOfStrings" }, - "enableMBM": { + "enableMonitoring": { "type": "boolean" } } From 1badadd64af74a7b0422d460ddfd0c64359db2f1 Mon Sep 17 00:00:00 2001 From: Krisztian Litkey Date: Wed, 10 Dec 2025 09:15:40 +0200 Subject: [PATCH 7/7] specs-go: add 1.1.0 version-clarification comments for recent RDT fields. Signed-off-by: Krisztian Litkey --- specs-go/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specs-go/config.go b/specs-go/config.go index da475172..ccda9d86 100644 --- a/specs-go/config.go +++ b/specs-go/config.go @@ -68,8 +68,8 @@ type IntelRdt struct { ClosID string `json:"closID,omitempty" yaml:"closID,omitempty"` L3CacheSchema string `json:"l3CacheSchema,omitempty" yaml:"l3CacheSchema,omitempty"` MemBwSchema string `json:"memBwSchema,omitempty" yaml:"memBwSchema,omitempty"` - Schemata []string `json:"schemata,omitempty" yaml:"schemata,omitempty"` - EnableMonitoring bool `json:"enableMonitoring,omitempty" yaml:"enableMonitoring,omitempty"` + Schemata []string `json:"schemata,omitempty" yaml:"schemata,omitempty"` // Added in v1.1.0. + EnableMonitoring bool `json:"enableMonitoring,omitempty" yaml:"enableMonitoring,omitempty"` // Added in v1.1.0. } // LinuxNetDevice represents an OCI LinuxNetDevice to be added to the OCI Spec.