Skip to content

Commit a80a40e

Browse files
authored
Merge pull request #82 from klihub/fixes/configure-refresh-race
Fix a read/write data race in the cache.
2 parents 67e7ef7 + c63bf64 commit a80a40e

File tree

3 files changed

+187
-4
lines changed

3 files changed

+187
-4
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
GO_CMD := go
22
GO_BUILD := $(GO_CMD) build
3-
GO_TEST := $(GO_CMD) test -v -cover
3+
GO_TEST := $(GO_CMD) test -race -v -cover
44

55
GO_LINT := golint -set_exit_status
66
GO_FMT := gofmt

pkg/cdi/cache.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ func (w *watch) setup(dirs []string, dirErrors map[string]error) {
446446

447447
// Start watching Spec directories for relevant changes.
448448
func (w *watch) start(m *sync.Mutex, refresh func() error, dirErrors map[string]error) {
449-
go w.watch(m, refresh, dirErrors)
449+
go w.watch(w.watcher, m, refresh, dirErrors)
450450
}
451451

452452
// Stop watching directories.
@@ -460,8 +460,8 @@ func (w *watch) stop() {
460460
}
461461

462462
// Watch Spec directory changes, triggering a refresh if necessary.
463-
func (w *watch) watch(m *sync.Mutex, refresh func() error, dirErrors map[string]error) {
464-
watch := w.watcher
463+
func (w *watch) watch(fsw *fsnotify.Watcher, m *sync.Mutex, refresh func() error, dirErrors map[string]error) {
464+
watch := fsw
465465
if watch == nil {
466466
return
467467
}

pkg/cdi/regressions_test.go

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package cdi
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
oci "github.com/opencontainers/runtime-spec/specs-go"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestCDIInjectionRace(t *testing.T) {
16+
// This is a gutted version of a containerd test case which triggered
17+
// read/write data race in the Cache.
18+
19+
for _, test := range []struct {
20+
description string
21+
cdiSpecFiles []string
22+
annotations map[string]string
23+
expectError bool
24+
expectDevices []oci.LinuxDevice
25+
expectEnv []string
26+
}{
27+
{description: "expect no CDI error for nil annotations"},
28+
{description: "expect no CDI error for empty annotations",
29+
annotations: map[string]string{},
30+
},
31+
{description: "expect CDI error for invalid CDI device reference in annotations",
32+
annotations: map[string]string{
33+
AnnotationPrefix + "devices": "foobar",
34+
},
35+
expectError: true,
36+
},
37+
{description: "expect CDI error for unresolvable devices",
38+
annotations: map[string]string{
39+
AnnotationPrefix + "vendor1_devices": "vendor1.com/device=no-such-dev",
40+
},
41+
expectError: true,
42+
},
43+
{description: "expect properly injected resolvable CDI devices",
44+
cdiSpecFiles: []string{
45+
`
46+
cdiVersion: "0.2.0"
47+
kind: "vendor1.com/device"
48+
devices:
49+
- name: foo
50+
containerEdits:
51+
deviceNodes:
52+
- path: /dev/loop8
53+
type: b
54+
major: 7
55+
minor: 8
56+
env:
57+
- FOO=injected
58+
containerEdits:
59+
env:
60+
- "VENDOR1=present"
61+
`,
62+
`
63+
cdiVersion: "0.2.0"
64+
kind: "vendor2.com/device"
65+
devices:
66+
- name: bar
67+
containerEdits:
68+
deviceNodes:
69+
- path: /dev/loop9
70+
type: b
71+
major: 7
72+
minor: 9
73+
env:
74+
- BAR=injected
75+
containerEdits:
76+
env:
77+
- "VENDOR2=present"
78+
`,
79+
},
80+
annotations: map[string]string{
81+
AnnotationPrefix + "vendor1_devices": "vendor1.com/device=foo",
82+
AnnotationPrefix + "vendor2_devices": "vendor2.com/device=bar",
83+
},
84+
expectDevices: []oci.LinuxDevice{
85+
{
86+
Path: "/dev/loop8",
87+
Type: "b",
88+
Major: 7,
89+
Minor: 8,
90+
},
91+
{
92+
Path: "/dev/loop9",
93+
Type: "b",
94+
Major: 7,
95+
Minor: 9,
96+
},
97+
},
98+
expectEnv: []string{
99+
"FOO=injected",
100+
"VENDOR1=present",
101+
"BAR=injected",
102+
"VENDOR2=present",
103+
},
104+
},
105+
} {
106+
t.Run(test.description, func(t *testing.T) {
107+
var (
108+
err error
109+
spec = &oci.Spec{}
110+
)
111+
112+
cdiDir, err := writeFilesToTempDir("containerd-test-CDI-injections-", test.cdiSpecFiles)
113+
if cdiDir != "" {
114+
defer os.RemoveAll(cdiDir)
115+
}
116+
require.NoError(t, err)
117+
118+
injectFun := withCDI(t, test.annotations, []string{cdiDir})
119+
err = injectFun(spec)
120+
assert.Equal(t, test.expectError, err != nil)
121+
122+
if err != nil {
123+
if test.expectEnv != nil {
124+
for _, expectedEnv := range test.expectEnv {
125+
assert.Contains(t, spec.Process.Env, expectedEnv)
126+
}
127+
}
128+
if test.expectDevices != nil {
129+
for _, expectedDev := range test.expectDevices {
130+
assert.Contains(t, spec.Linux.Devices, expectedDev)
131+
}
132+
}
133+
}
134+
})
135+
}
136+
}
137+
138+
type specOpts func(*oci.Spec) error
139+
140+
// withCDI (WithCDI) SpecOpt adopted from containerd.
141+
func withCDI(t *testing.T, annotations map[string]string, cdiSpecDirs []string) specOpts {
142+
return func(s *oci.Spec) error {
143+
_, cdiDevices, err := ParseAnnotations(annotations)
144+
if err != nil {
145+
return fmt.Errorf("failed to parse CDI device annotations: %w", err)
146+
}
147+
if cdiDevices == nil {
148+
return nil
149+
}
150+
151+
registry := GetRegistry(WithSpecDirs(cdiSpecDirs...))
152+
if err = registry.Refresh(); err != nil {
153+
t.Logf("CDI registry refresh failed: %v", err)
154+
}
155+
156+
if _, err := registry.InjectDevices(s, cdiDevices...); err != nil {
157+
return fmt.Errorf("CDI device injection failed: %w", err)
158+
}
159+
160+
return nil
161+
}
162+
}
163+
164+
func writeFilesToTempDir(tmpDirPattern string, content []string) (string, error) {
165+
if len(content) == 0 {
166+
return "", nil
167+
}
168+
169+
dir, err := os.MkdirTemp("", tmpDirPattern)
170+
if err != nil {
171+
return "", err
172+
}
173+
174+
for idx, data := range content {
175+
file := filepath.Join(dir, fmt.Sprintf("spec-%d.yaml", idx))
176+
err := os.WriteFile(file, []byte(data), 0644)
177+
if err != nil {
178+
return "", err
179+
}
180+
}
181+
182+
return dir, nil
183+
}

0 commit comments

Comments
 (0)