Skip to content

Commit dd1ae1b

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

File tree

4 files changed

+198
-6
lines changed

4 files changed

+198
-6
lines changed

pkg/cdi/container-edits.go

Lines changed: 79 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 e.NetDevices != nil {
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.HostIf, (&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 e.NetDevices != nil {
278+
return false
279+
}
247280
return true
248281
}
249282

@@ -257,6 +290,52 @@ 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 dev.HostIf == "" {
302+
return fmt.Errorf("invalid linux net device, empty HostIf for %q", dev.Name)
303+
}
304+
if dev.Name == "" {
305+
return fmt.Errorf("invalid linux net device, empty Name for %q", dev.HostIf)
306+
}
307+
if other, ok := hostSeen[dev.HostIf]; ok {
308+
return fmt.Errorf("invalid linux net device, duplicate HostIf %q with names %q and %q",
309+
dev.HostIf, dev.Name, other)
310+
}
311+
hostSeen[dev.HostIf] = dev.Name
312+
313+
if other, ok := nameSeen[dev.Name]; ok {
314+
return fmt.Errorf("invalid linux net device, duplicate Name %q with HostIf %q and %q",
315+
dev.Name, dev.HostIf, other)
316+
}
317+
nameSeen[dev.Name] = dev.HostIf
318+
}
319+
320+
return nil
321+
}
322+
323+
// LinuxNetDevice is a CDI Spec LinuxNetDevice wrapper, used for OCI conversion and validating.
324+
type LinuxNetDevice struct {
325+
*cdi.LinuxNetDevice
326+
}
327+
328+
// Validate LinuxNetDevice.
329+
func (d *LinuxNetDevice) Validate() error {
330+
if d.HostIf == "" {
331+
return errors.New("invalid linux net device, empty HostIf")
332+
}
333+
if d.Name == "" {
334+
return errors.New("invalid linux net device, empty Name")
335+
}
336+
return nil
337+
}
338+
260339
// DeviceNode is a CDI Spec DeviceNode wrapper, used for validating DeviceNodes.
261340
type DeviceNode struct {
262341
*cdi.DeviceNode

pkg/cdi/container-edits_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,41 @@ func TestValidateContainerEdits(t *testing.T) {
298298
},
299299
invalid: true,
300300
},
301+
{
302+
name: "valid Linux net device",
303+
edits: &cdi.ContainerEdits{
304+
NetDevices: []*cdi.LinuxNetDevice{
305+
{
306+
HostIf: "eno1",
307+
Name: "netdev0",
308+
},
309+
},
310+
},
311+
},
312+
{
313+
name: "invalid Linux net device, empty host interface name",
314+
edits: &cdi.ContainerEdits{
315+
NetDevices: []*cdi.LinuxNetDevice{
316+
{
317+
HostIf: "",
318+
Name: "netdev0",
319+
},
320+
},
321+
},
322+
invalid: true,
323+
},
324+
{
325+
name: "invalid Linux net device, empty container interface name",
326+
edits: &cdi.ContainerEdits{
327+
NetDevices: []*cdi.LinuxNetDevice{
328+
{
329+
HostIf: "eno1",
330+
Name: "",
331+
},
332+
},
333+
},
334+
invalid: true,
335+
},
301336
} {
302337
t.Run(tc.name, func(t *testing.T) {
303338
edits := ContainerEdits{tc.edits}
@@ -581,6 +616,70 @@ func TestApplyContainerEdits(t *testing.T) {
581616
},
582617
},
583618
},
619+
{
620+
name: "empty spec, Linux net devices",
621+
spec: &oci.Spec{},
622+
edits: &cdi.ContainerEdits{
623+
NetDevices: []*cdi.LinuxNetDevice{
624+
{
625+
HostIf: "eno1",
626+
Name: "netdev0",
627+
},
628+
{
629+
HostIf: "eno2",
630+
Name: "netdev1",
631+
},
632+
},
633+
},
634+
result: &oci.Spec{
635+
Linux: &oci.Linux{
636+
NetDevices: map[string]oci.LinuxNetDevice{
637+
"eno1": {
638+
Name: "netdev0",
639+
},
640+
"eno2": {
641+
Name: "netdev1",
642+
},
643+
},
644+
},
645+
},
646+
},
647+
{
648+
name: "non-empty spec, overriding Linux net devices",
649+
spec: &oci.Spec{
650+
Linux: &oci.Linux{
651+
NetDevices: map[string]oci.LinuxNetDevice{
652+
"eno1": {
653+
Name: "netdev1",
654+
},
655+
},
656+
},
657+
},
658+
edits: &cdi.ContainerEdits{
659+
NetDevices: []*cdi.LinuxNetDevice{
660+
{
661+
HostIf: "eno1",
662+
Name: "netdev2",
663+
},
664+
{
665+
HostIf: "eno2",
666+
Name: "netdev1",
667+
},
668+
},
669+
},
670+
result: &oci.Spec{
671+
Linux: &oci.Linux{
672+
NetDevices: map[string]oci.LinuxNetDevice{
673+
"eno1": {
674+
Name: "netdev2",
675+
},
676+
"eno2": {
677+
Name: "netdev1",
678+
},
679+
},
680+
},
681+
},
682+
},
584683
{
585684
name: "additional GIDs are applied",
586685
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+
HostIf string `json:"hostIf" yaml:"hostIf"`
78+
Name string `json:"name" yaml:"name"`
79+
}

0 commit comments

Comments
 (0)