Skip to content

Commit 42bafe1

Browse files
authored
chore: create file system fake (#2610)
Create a file system abstraction and pass it into our resources library so that we can test the resources logic. Greatly simplify the CPU count logic. Update some unit tests to use the new file system abstraction.
1 parent 3bbb3cd commit 42bafe1

File tree

5 files changed

+396
-199
lines changed

5 files changed

+396
-199
lines changed

nodeadm/cmd/nodeadm/init/init.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,10 @@ func (c *initCmd) Run(log *zap.Logger, opts *cli.GlobalOptions) error {
100100
}
101101
defer daemonManager.Close()
102102

103+
resources := system.NewResources(system.RealFileSystem{})
103104
daemons := []daemon.Daemon{
104-
containerd.NewContainerdDaemon(daemonManager, system.SysfsResources{}),
105-
kubelet.NewKubeletDaemon(daemonManager, system.SysfsResources{}),
105+
containerd.NewContainerdDaemon(daemonManager, resources),
106+
kubelet.NewKubeletDaemon(daemonManager, resources),
106107
}
107108

108109
// to handle edge cases where the cached config is stale (because the user

nodeadm/internal/containerd/containerd_config_test.go

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package containerd
22

33
import (
44
"fmt"
5+
"strconv"
56
"testing"
67

78
"github.com/awslabs/amazon-eks-ami/nodeadm/internal/api"
@@ -10,6 +11,7 @@ import (
1011
"github.com/stretchr/testify/assert"
1112
)
1213

14+
const blockSize int64 = 0x8000000 // 128MB
1315
func TestContainerdConfigWithUserConfigAndFastImagePullFeature(t *testing.T) {
1416
cfg := &api.NodeConfig{
1517
Spec: api.NodeConfigSpec{
@@ -24,7 +26,7 @@ func TestContainerdConfigWithUserConfigAndFastImagePullFeature(t *testing.T) {
2426
},
2527
},
2628
}
27-
resources := system.FakeResources{Cpu: 8, Memory: 16 * 1024 * 1024 * 1024}
29+
resources := fakeResources(8, 16*1024*1024*1024)
2830
template, err := getConfigTemplateVersion(cfg, false)
2931
assert.NoError(t, err)
3032
containerdConfig, err := generateContainerdConfig(cfg, resources, template)
@@ -68,7 +70,7 @@ func TestContainerdConfig(t *testing.T) {
6870
}{
6971
{
7072
cfg: &api.NodeConfig{},
71-
resources: system.FakeResources{Cpu: 4, Memory: 7 * 1024 * 1024 * 1024},
73+
resources: fakeResources(4, 7*1024*1024*1024),
7274
isContainerdV2: false,
7375
// Flag not enabled
7476
expectSOCIEnabled: false,
@@ -81,7 +83,7 @@ func TestContainerdConfig(t *testing.T) {
8183
},
8284
},
8385
},
84-
resources: system.FakeResources{Cpu: 2, Memory: 4 * 1024 * 1024 * 1024},
86+
resources: fakeResources(2, 4*1024*1024*1024),
8587
isContainerdV2: false,
8688
// Cpu too low
8789
expectSOCIEnabled: false,
@@ -94,7 +96,7 @@ func TestContainerdConfig(t *testing.T) {
9496
},
9597
},
9698
},
97-
resources: system.FakeResources{Cpu: 4, Memory: 6 * 1024 * 1024 * 1024},
99+
resources: fakeResources(4, 6*1024*1024*1024),
98100
isContainerdV2: false,
99101
// Memory too low
100102
expectSOCIEnabled: false,
@@ -107,13 +109,13 @@ func TestContainerdConfig(t *testing.T) {
107109
},
108110
},
109111
},
110-
resources: system.FakeResources{Cpu: 4, Memory: 7.5 * 1024 * 1024 * 1024},
112+
resources: fakeResources(4, 8*1024*1024*1024),
111113
isContainerdV2: false,
112114
expectSOCIEnabled: true,
113115
},
114116
{
115117
cfg: &api.NodeConfig{},
116-
resources: system.FakeResources{Cpu: 4, Memory: 7 * 1024 * 1024 * 1024},
118+
resources: fakeResources(4, 7*1024*1024*1024),
117119
isContainerdV2: true,
118120
// Flag not enabled
119121
expectSOCIEnabled: false,
@@ -126,7 +128,7 @@ func TestContainerdConfig(t *testing.T) {
126128
},
127129
},
128130
},
129-
resources: system.FakeResources{Cpu: 2, Memory: 4 * 1024 * 1024 * 1024},
131+
resources: fakeResources(2, 4*1024*1024*1024),
130132
isContainerdV2: true,
131133
// Cpu too low
132134
expectSOCIEnabled: false,
@@ -139,7 +141,7 @@ func TestContainerdConfig(t *testing.T) {
139141
},
140142
},
141143
},
142-
resources: system.FakeResources{Cpu: 4, Memory: 6 * 1024 * 1024 * 1024},
144+
resources: fakeResources(4, 6*1024*1024*1024),
143145
isContainerdV2: true,
144146
// Memory too low
145147
expectSOCIEnabled: false,
@@ -152,7 +154,7 @@ func TestContainerdConfig(t *testing.T) {
152154
},
153155
},
154156
},
155-
resources: system.FakeResources{Cpu: 4, Memory: 7.5 * 1024 * 1024 * 1024},
157+
resources: fakeResources(4, 8*1024*1024*1024),
156158
isContainerdV2: true,
157159
expectSOCIEnabled: true,
158160
},
@@ -202,3 +204,18 @@ func TestContainerdConfig(t *testing.T) {
202204
})
203205
}
204206
}
207+
208+
func fakeResources(cpuCount int, memoryBytes int64) system.Resources {
209+
files := map[string]string{
210+
"/sys/devices/system/memory/block_size_bytes": fmt.Sprintf("%x", blockSize),
211+
}
212+
memoryBlocks := memoryBytes / blockSize
213+
for i := range memoryBlocks {
214+
files[fmt.Sprintf("/sys/devices/system/memory/memory%d/online", i)] = "1"
215+
}
216+
for i := range cpuCount {
217+
files[fmt.Sprintf("/sys/devices/system/cpu/cpu%d/topology/core_id", i)] = strconv.Itoa(i)
218+
files[fmt.Sprintf("/sys/devices/system/cpu/cpu%d/topology/physical_package_id", i)] = "0"
219+
}
220+
return system.NewResources(system.FakeFileSystem{Files: files})
221+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package system
2+
3+
import (
4+
"io/fs"
5+
"os"
6+
"path/filepath"
7+
"strings"
8+
"time"
9+
)
10+
11+
type FileSystem interface {
12+
Glob(pattern string) ([]string, error)
13+
ReadFile(name string) ([]byte, error)
14+
ReadDir(name string) ([]fs.DirEntry, error)
15+
Stat(name string) (fs.FileInfo, error)
16+
}
17+
18+
type RealFileSystem struct{}
19+
20+
func (RealFileSystem) Glob(pattern string) ([]string, error) {
21+
return filepath.Glob(pattern)
22+
}
23+
24+
func (RealFileSystem) ReadFile(name string) ([]byte, error) {
25+
// This has a slight issue since gosec will not flag lines that call RealFileSystem.ReadFile.
26+
// #nosec G304 intended usage.
27+
return os.ReadFile(name)
28+
}
29+
30+
func (RealFileSystem) ReadDir(name string) ([]fs.DirEntry, error) {
31+
return os.ReadDir(name)
32+
}
33+
34+
func (RealFileSystem) Stat(name string) (fs.FileInfo, error) {
35+
return os.Stat(name)
36+
}
37+
38+
const EmptyDirectoryMarker = "[empty directory]"
39+
40+
type FakeFileSystem struct {
41+
Files map[string]string
42+
}
43+
44+
func (f FakeFileSystem) allPaths() map[string]bool {
45+
paths := make(map[string]bool)
46+
for path := range f.Files {
47+
paths[path] = true
48+
for dir := filepath.Dir(path); dir != "/" && dir != "."; dir = filepath.Dir(dir) {
49+
paths[dir] = true
50+
}
51+
}
52+
return paths
53+
}
54+
55+
func (f FakeFileSystem) Glob(pattern string) ([]string, error) {
56+
var matches []string
57+
for path := range f.allPaths() {
58+
matched, err := filepath.Match(pattern, path)
59+
if err != nil {
60+
return nil, err
61+
}
62+
if matched {
63+
matches = append(matches, path)
64+
}
65+
}
66+
return matches, nil
67+
}
68+
69+
func (f FakeFileSystem) ReadFile(name string) ([]byte, error) {
70+
content, ok := f.Files[name]
71+
if !ok {
72+
return nil, os.ErrNotExist
73+
}
74+
if content == EmptyDirectoryMarker {
75+
return nil, &os.PathError{Op: "read", Path: name, Err: os.ErrInvalid}
76+
}
77+
return []byte(content), nil
78+
}
79+
80+
func (f FakeFileSystem) ReadDir(name string) ([]fs.DirEntry, error) {
81+
name = strings.TrimSuffix(name, "/")
82+
if content, ok := f.Files[name]; ok && content != EmptyDirectoryMarker {
83+
return nil, &os.PathError{Op: "readdir", Path: name, Err: os.ErrInvalid}
84+
}
85+
86+
var entries []fs.DirEntry
87+
seen := make(map[string]bool)
88+
prefix := name + "/"
89+
90+
for path := range f.Files {
91+
if !strings.HasPrefix(path, prefix) {
92+
continue
93+
}
94+
rest := strings.TrimPrefix(path, prefix)
95+
parts := strings.SplitN(rest, "/", 2)
96+
entryName := parts[0]
97+
if seen[entryName] {
98+
continue
99+
}
100+
seen[entryName] = true
101+
102+
entryPath := name + "/" + entryName
103+
entries = append(entries, &fakeEntry{name: entryName, isDir: f.isDir(entryPath)})
104+
}
105+
106+
if len(entries) == 0 {
107+
if _, ok := f.Files[name]; !ok {
108+
return nil, os.ErrNotExist
109+
}
110+
}
111+
return entries, nil
112+
}
113+
114+
func (f FakeFileSystem) isDir(name string) bool {
115+
if content, ok := f.Files[name]; ok && content == EmptyDirectoryMarker {
116+
return true
117+
}
118+
prefix := name + "/"
119+
for path := range f.Files {
120+
if strings.HasPrefix(path, prefix) {
121+
return true
122+
}
123+
}
124+
return false
125+
}
126+
127+
func (f FakeFileSystem) Stat(name string) (fs.FileInfo, error) {
128+
name = strings.TrimSuffix(name, "/")
129+
if content, ok := f.Files[name]; ok {
130+
return &fakeFileInfo{name: filepath.Base(name), isDir: content == EmptyDirectoryMarker, size: int64(len(content))}, nil
131+
}
132+
if f.isDir(name) {
133+
return &fakeFileInfo{name: filepath.Base(name), isDir: true}, nil
134+
}
135+
return nil, os.ErrNotExist
136+
}
137+
138+
type fakeEntry struct {
139+
name string
140+
isDir bool
141+
}
142+
143+
func (e *fakeEntry) Name() string { return e.name }
144+
func (e *fakeEntry) IsDir() bool { return e.isDir }
145+
func (e *fakeEntry) Type() fs.FileMode {
146+
if e.isDir {
147+
return fs.ModeDir
148+
}
149+
return 0
150+
}
151+
func (e *fakeEntry) Info() (fs.FileInfo, error) {
152+
return &fakeFileInfo{name: e.name, isDir: e.isDir}, nil
153+
}
154+
155+
type fakeFileInfo struct {
156+
name string
157+
isDir bool
158+
size int64
159+
}
160+
161+
func (fi *fakeFileInfo) Name() string { return fi.name }
162+
func (fi *fakeFileInfo) Size() int64 { return fi.size }
163+
func (fi *fakeFileInfo) Mode() fs.FileMode {
164+
if fi.isDir {
165+
return fs.ModeDir | 0755
166+
}
167+
return 0644
168+
}
169+
func (fi *fakeFileInfo) ModTime() time.Time { return time.Time{} }
170+
func (fi *fakeFileInfo) IsDir() bool { return fi.isDir }
171+
func (fi *fakeFileInfo) Sys() any { return nil }

0 commit comments

Comments
 (0)