diff --git a/SPEC.md b/SPEC.md index fc918d7..a080a44 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 diff --git a/cmd/cdi/cmd/validate.go b/cmd/cdi/cmd/validate.go index 9dc8faf..5a08843 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 812706d..4a1a59e 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 ( diff --git a/pkg/cdi/container-edits.go b/pkg/cdi/container-edits.go index 450a84f..f498049 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 e6c19df..2c553d9 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 d8fa14a..f37499f 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/schema/defs.json b/schema/defs.json index f549423..e942c5c 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": { @@ -150,10 +174,10 @@ "memBwSchema": { "type": "string" }, - "enableCMT": { - "type": "boolean" + "schemata": { + "$ref": "#/definitions/ArrayOfStrings" }, - "enableMBM": { + "enableMonitoring": { "type": "boolean" } } diff --git a/specs-go/config.go b/specs-go/config.go index 577331f..ccda9d8 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. @@ -67,6 +68,12 @@ 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. +type LinuxNetDevice struct { + HostInterfaceName string `json:"hostInterfaceName" yaml:"hostInterfaceName"` + Name string `json:"name" yaml:"name"` } diff --git a/specs-go/version.go b/specs-go/version.go index 4e5d736..62be6b7 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