Skip to content

Commit c5a3a62

Browse files
committed
specs-go,cdi: implement NetDevice injection.
Implement OCI Spec (Linux) NetDevice injection. Signed-off-by: Krisztian Litkey <[email protected]>
1 parent 5e3870c commit c5a3a62

File tree

4 files changed

+195
-6
lines changed

4 files changed

+195
-6
lines changed

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+
}

specs-go/config.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,13 @@ type Device struct {
2424

2525
// ContainerEdits are edits a container runtime must make to the OCI spec to expose the device.
2626
type ContainerEdits struct {
27-
Env []string `json:"env,omitempty" yaml:"env,omitempty"`
28-
DeviceNodes []*DeviceNode `json:"deviceNodes,omitempty" yaml:"deviceNodes,omitempty"`
29-
Hooks []*Hook `json:"hooks,omitempty" yaml:"hooks,omitempty"`
30-
Mounts []*Mount `json:"mounts,omitempty" yaml:"mounts,omitempty"`
31-
IntelRdt *IntelRdt `json:"intelRdt,omitempty" yaml:"intelRdt,omitempty"` // Added in v0.7.0
32-
AdditionalGIDs []uint32 `json:"additionalGids,omitempty" yaml:"additionalGids,omitempty"` // Added in v0.7.0
27+
Env []string `json:"env,omitempty" yaml:"env,omitempty"`
28+
DeviceNodes []*DeviceNode `json:"deviceNodes,omitempty" yaml:"deviceNodes,omitempty"`
29+
NetDevices []*LinuxNetDevice `json:"netDevices,omitempty" yaml:"netDevices,omitempty"` // Added in v1.1.0
30+
Hooks []*Hook `json:"hooks,omitempty" yaml:"hooks,omitempty"`
31+
Mounts []*Mount `json:"mounts,omitempty" yaml:"mounts,omitempty"`
32+
IntelRdt *IntelRdt `json:"intelRdt,omitempty" yaml:"intelRdt,omitempty"` // Added in v0.7.0
33+
AdditionalGIDs []uint32 `json:"additionalGids,omitempty" yaml:"additionalGids,omitempty"` // Added in v0.7.0
3334
}
3435

3536
// DeviceNode represents a device node that needs to be added to the OCI spec.
@@ -70,3 +71,9 @@ type IntelRdt struct {
7071
Schemata []string `json:"schemata,omitempty" yaml:"schemata,omitempty"`
7172
EnableMonitoring bool `json:"enableMonitoring,omitempty" yaml:"enableMonitoring,omitempty"`
7273
}
74+
75+
// LinuxNetDevice represents an OCI LinuxNetDevice to be added to the OCI Spec.
76+
type LinuxNetDevice struct {
77+
HostInterfaceName string `json:"hostInterfaceName" yaml:"hostInterfaceName"`
78+
Name string `json:"name" yaml:"name"`
79+
}

0 commit comments

Comments
 (0)