1
2 package v1
3
4 import (
5 "context"
6 _ "embed"
7 "encoding/json"
8 "io/fs"
9 "log/slog"
10 "os"
11 "path/filepath"
12 "testing"
13 "time"
14
15 testfs "gotest.tools/v3/fs"
16
17 . "github.com/onsi/ginkgo/v2"
18 . "github.com/onsi/gomega"
19 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20 "k8s.io/apimachinery/pkg/runtime"
21
22 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
23 clientgoscheme "k8s.io/client-go/kubernetes/scheme"
24 "sigs.k8s.io/controller-runtime/pkg/client/fake"
25
26 "edge-infra.dev/pkg/lib/kernel/devices"
27 "edge-infra.dev/pkg/sds/devices/logger"
28 v1 "edge-infra.dev/pkg/sds/ien/k8s/apis/v1"
29 )
30
31 var (
32 subSystemUSB = "usb"
33 timeout = 5 * time.Second
34 testPath = etcPath
35 etcPath = "/etc"
36 soundName = "sound"
37 )
38
39
40 var deviceClassValidBytes []byte
41
42 func TestDeviceRead(t *testing.T) {
43 dir := testfs.NewDir(t, etcPath)
44 defer dir.Remove()
45 testPath = dir.Path()
46
47 RegisterFailHandler(Fail)
48 RunSpecs(t, "Device Class Validation Tests")
49 }
50
51 var _ = BeforeEach(func() {
52 _ = os.Remove(filepath.Join(testPath, "deviceclasses.json"))
53 })
54
55 var _ = Describe("Validation Tests", func() {
56 It("Valid name and includes subsystem", func() {
57 validName := "display"
58 cr := generateDeviceClassTestCR(validName, 1)
59 Expect(cr.Validate()).To(BeNil())
60 })
61
62 It("Valid name but device set is missing subsystem", func() {
63 validName := "display"
64 classCR := generateDeviceClassTestCR(validName, 1)
65 setCR := generateDeviceSetsTestCR(validName, false, map[string]string{}, map[string]devices.Device{})
66 Expect(classCR.Validate()).To(BeNil())
67 Expect(setCR.Validate()).To(MatchError(ErrInvalidMissingSubsystem))
68 })
69
70 It("Invalid name", func() {
71 invalidName := "displaydevices.edge.ncr.com/display"
72 cr := generateDeviceClassTestCR(invalidName, 1)
73 Expect(cr.Validate()).To(MatchError(ErrInvalidName))
74 })
75 })
76
77 var _ = Describe("Should Block Tests", func() {
78 It("Should block", func() {
79 cr := generateDeviceSetsTestCR(soundName, true, map[string]string{"SUBSYSTEM": subSystemUSB}, map[string]devices.Device{})
80 shouldBlock, _ := cr.WillBlock()
81 Expect(shouldBlock).To(BeTrue())
82 })
83
84 It("Should not block", func() {
85 cr := generateDeviceSetsTestCR(soundName, true, map[string]string{"SUBSYSTEM": subSystemUSB}, map[string]devices.Device{"dev1": &dummyDevice{subsystem: subSystemUSB}})
86 shouldBlock, _ := cr.WillBlock()
87 Expect(shouldBlock).To(BeFalse())
88 })
89 })
90
91 var _ = Describe("Caching Tests", func() {
92 It("Should not cache", func() {
93 classCR := generateDeviceClassTestCR(soundName, 1)
94 setCR := generateDeviceSetsTestCR(soundName, true, map[string]string{"SUBSYSTEM": subSystemUSB}, nil)
95 c := fake.NewClientBuilder().WithScheme(createScheme()).WithObjects(&classCR, &setCR).Build()
96
97 ctx := context.Background()
98 log := logger.New(logger.WithLevel(slog.LevelDebug))
99 ctx = logger.IntoContext(ctx, log)
100 ctx, cancelFn := context.WithTimeout(ctx, timeout)
101 defer cancelFn()
102
103 opts := []ListOption{
104 WithPersistence(false),
105 WithPersistencePath(testPath),
106 }
107
108 deviceClassMap, err := ListFromClient(ctx, c, opts...)
109 Expect(err).To(BeNil())
110 Expect(deviceClassMap).To(HaveLen(1))
111
112 filePath := filepath.Join(testPath, "deviceclasses.json")
113 _, err = os.Stat(filePath)
114 Expect(err).To(MatchError(fs.ErrNotExist))
115 })
116
117 It("Should cache", func() {
118 classCR := generateDeviceClassTestCR(soundName, 1)
119 setCR := generateDeviceSetsTestCR(soundName, true, map[string]string{"SUBSYSTEM": subSystemUSB}, nil)
120 c := fake.NewClientBuilder().WithScheme(createScheme()).WithObjects(&classCR, &setCR).Build()
121
122 ctx := context.Background()
123 log := logger.New(logger.WithLevel(slog.LevelDebug))
124 ctx = logger.IntoContext(ctx, log)
125 ctx, cancelFn := context.WithTimeout(ctx, timeout)
126 defer cancelFn()
127
128 opts := []ListOption{
129 WithPersistence(true),
130 WithPersistencePath(testPath),
131 }
132
133 deviceClassMap, err := ListFromClient(ctx, c, opts...)
134 Expect(err).To(BeNil())
135 Expect(deviceClassMap).To(HaveLen(1))
136
137 filePath := filepath.Join(testPath, deviceClassCacheName)
138 cachedClasses, err := os.ReadFile(filePath)
139 Expect(err).To(BeNil())
140
141 deviceClasses := DeviceClassList{}
142 Expect(json.Unmarshal(cachedClasses, &deviceClasses)).To(BeNil())
143 Expect(deviceClasses.Items).To(HaveLen(1))
144 Expect(deviceClasses.Items[0].ObjectMeta.Name).To(Equal(classCR.ObjectMeta.Name))
145 Expect(deviceClasses.Items[0].ObjectMeta.Generation).To(Equal(classCR.ObjectMeta.Generation))
146
147 filePath = filepath.Join(testPath, deviceCacheName)
148 cachedSets, err := os.ReadFile(filePath)
149 Expect(err).To(BeNil())
150
151 deviceSets := DeviceSetList{}
152 Expect(json.Unmarshal(cachedSets, &deviceSets)).To(BeNil())
153 Expect(deviceSets.Items).To(HaveLen(1))
154 Expect(deviceSets.Items[0].ObjectMeta.Name).To(Equal(setCR.ObjectMeta.Name))
155 Expect(deviceSets.Items[0].ObjectMeta.Generation).To(Equal(setCR.ObjectMeta.Generation))
156 })
157
158 It("Generation has changed", func() {
159 setCR := generateDeviceSetsTestCR(soundName, true, map[string]string{"SUBSYSTEM": subSystemUSB}, nil)
160 oldCR := generateDeviceClassTestCR("sound", 1)
161 newCR := generateDeviceClassTestCR("sound", 2)
162
163 ctx := context.Background()
164 log := logger.New(logger.WithLevel(slog.LevelDebug))
165 ctx = logger.IntoContext(ctx, log)
166 ctx, cancelFn := context.WithTimeout(ctx, timeout)
167 defer cancelFn()
168
169 Expect(writeDeviceClassCache(ctx, testPath, []DeviceClass{oldCR})).To(BeNil())
170
171 c := fake.NewClientBuilder().WithScheme(createScheme()).WithObjects(&newCR, &setCR).Build()
172
173 opts := []ListOption{
174 WithPersistence(true),
175 WithPersistencePath(testPath),
176 }
177
178 deviceClassMap, err := ListFromClient(ctx, c, opts...)
179 Expect(err).To(BeNil())
180 Expect(deviceClassMap).To(HaveLen(1))
181
182 Expect(deviceClassMap[newCR.ClassName()]).ToNot(BeNil())
183 Expect(deviceClassMap[newCR.ClassName()].ObjectMeta.Generation).To(Equal(newCR.ObjectMeta.Generation))
184
185 filePath := filepath.Join(testPath, deviceClassCacheName)
186 cachedClasses, err := os.ReadFile(filePath)
187 Expect(err).To(BeNil())
188
189 deviceClasses := DeviceClassList{}
190 Expect(json.Unmarshal(cachedClasses, &deviceClasses)).To(BeNil())
191 Expect(deviceClasses.Items).To(HaveLen(1))
192 Expect(deviceClasses.Items[0].ObjectMeta.Name).To(Equal(newCR.ObjectMeta.Name))
193 })
194 })
195
196 var _ = Describe("Device System CR Type Conversion", func() {
197 It("Should convert successfully to dsv1.DeviceClass", func() {
198 deviceClass := &DeviceClass{}
199 Expect(json.Unmarshal(deviceClassValidBytes, deviceClass)).To(BeNil())
200 })
201 })
202
203 func generateDeviceClassTestCR(name string, generation int64) DeviceClass {
204 return DeviceClass{
205 ObjectMeta: metav1.ObjectMeta{
206 Name: name,
207 Generation: generation,
208 },
209 Spec: DeviceClassSpec{
210 Devices: []DeviceRef{
211 {
212 Name: name,
213 },
214 },
215 Logging: Logging{
216 Level: "info",
217 },
218 },
219 }
220 }
221
222 func generateDeviceSetsTestCR(name string, shouldBlock bool, deviceSetProperties map[string]string, devices map[string]devices.Device) DeviceSet {
223 deviceSet := DeviceSetReference{
224 Name: name,
225 Properties: []Rule{},
226 }
227
228 if shouldBlock {
229 deviceSet.Blocking = &Blocking{}
230 }
231
232 for key, value := range deviceSetProperties {
233 deviceSet.Properties = append(deviceSet.Properties, Rule{
234 Name: key,
235 RegexValue: value,
236 })
237 }
238
239 return DeviceSet{
240 ObjectMeta: metav1.ObjectMeta{
241 Name: name,
242 Generation: 1,
243 },
244 Spec: DeviceSpec{
245 DeviceSets: []DeviceSetReference{
246 deviceSet,
247 },
248 },
249 DeviceStatus: deviceStatus{
250 devices: devices,
251 },
252 }
253 }
254
255 func createScheme() *runtime.Scheme {
256 scheme := runtime.NewScheme()
257 utilruntime.Must(clientgoscheme.AddToScheme(scheme))
258 utilruntime.Must(v1.AddToScheme(scheme))
259 utilruntime.Must(AddToScheme(scheme))
260 return scheme
261 }
262
263
264 type dummyDevice struct{ subsystem string }
265 type dummyNode struct{}
266
267 var devType = "char"
268
269
270 func (dd *dummyDevice) Path() string { return "" }
271 func (dd *dummyDevice) Node() (devices.Node, error) { return &dummyNode{}, nil }
272 func (dd *dummyDevice) Attribute(_ string) (string, bool, error) { return "", true, nil }
273 func (dd *dummyDevice) Property(_ string) (string, bool, error) {
274 return dd.subsystem, true, nil
275 }
276
277
278 func (dn *dummyNode) Path() string { return "" }
279 func (dn *dummyNode) Type() (string, error) { return devType, nil }
280 func (dn *dummyNode) GroupID() (int64, error) { return 1, nil }
281 func (dn *dummyNode) UserID() (int64, error) { return 1, nil }
282 func (dn *dummyNode) FileMode() (os.FileMode, error) { return 0, nil }
283
View as plain text