Skip to content

Commit 0cceece

Browse files
committed
feat: Go implement UDEV
Signed-off-by: Fred Rolland <[email protected]>
1 parent 1bde796 commit 0cceece

File tree

4 files changed

+316
-4
lines changed

4 files changed

+316
-4
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
ACTION!="add", GOTO="mlnx_ofed_name_end"
2+
SUBSYSTEM!="net", GOTO="mlnx_ofed_name_end"
3+
4+
# Rename physical interfaces (first case) of virtual functions (second case).
5+
# Example names:
6+
# enp8s0f0np0 -> enp8s0f0
7+
# enp8s0f0np1v12 -> enp8s0f0v12
8+
9+
DRIVERS=="mlx5_core", ENV{ID_NET_NAME_PATH}!="", \
10+
PROGRAM="/bin/sh -c 'echo $env{ID_NET_NAME_PATH} | sed -r -e s/np[01]$// -e s/np[01]v/v/'", \
11+
ENV{ID_NET_NAME_PATH}="$result"
12+
13+
DRIVERS=="mlx5_core", ENV{ID_NET_NAME_SLOT}!="", \
14+
PROGRAM="/bin/sh -c 'echo $env{ID_NET_NAME_SLOT} | sed -r -e s/np[01]$// -e s/np[01]v/v/'", \
15+
ENV{ID_NET_NAME_SLOT}="$result"
16+
17+
LABEL="mlnx_ofed_name_end"

entrypoint/internal/utils/udev/udev.go

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,17 @@ package udev
1818

1919
import (
2020
"context"
21+
_ "embed"
22+
"os"
2123

2224
"github.com/go-logr/logr"
2325

2426
"github.com/Mellanox/doca-driver-build/entrypoint/internal/wrappers"
2527
)
2628

29+
//go:embed 70-mlnx-ofed-naming.rules
30+
var udevRulesContent string
31+
2732
// New initialize default implementation of the udev.Interface.
2833
func New(path string, osWrapper wrappers.OSWrapper) Interface {
2934
return &udev{
@@ -50,15 +55,50 @@ type udev struct {
5055

5156
// CreateRules is the default implementation of the udev.Interface.
5257
func (u *udev) CreateRules(ctx context.Context) error {
53-
_ = logr.FromContextOrDiscard(ctx)
54-
// TODO add implementation
58+
log := logr.FromContextOrDiscard(ctx)
59+
log.Info("create udev rules")
60+
61+
// Write the udev rules file
62+
if err := u.os.WriteFile(u.path, []byte(udevRulesContent), 0o644); err != nil {
63+
log.Error(err, "failed to create udev rules file", "path", u.path)
64+
return err
65+
}
66+
67+
log.Info("udev rules file created successfully", "path", u.path)
68+
69+
// Log the file content on debug level (equivalent to bash: debug_print `cat ${MLX_UDEV_RULES_FILE}`)
70+
log.V(1).Info("udev rules file content", "path", u.path, "content", udevRulesContent)
71+
5572
return nil
5673
}
5774

5875
// RemoveRules is the default implementation of the udev.Interface.
5976
func (u *udev) RemoveRules(ctx context.Context) error {
60-
_ = logr.FromContextOrDiscard(ctx)
61-
// TODO add implementation
77+
log := logr.FromContextOrDiscard(ctx)
78+
log.Info("remove udev rules")
79+
80+
// Check if the udev rules file exists
81+
_, err := u.os.Stat(u.path)
82+
if err != nil {
83+
// Check if it's a "file not found" error
84+
if os.IsNotExist(err) {
85+
// File doesn't exist, log and skip
86+
log.Info("udev rules file was not previously created, skipping", "path", u.path)
87+
return nil
88+
}
89+
// Other errors (permission denied, etc.) should be returned
90+
log.Error(err, "failed to check if udev rules file exists", "path", u.path)
91+
return err
92+
}
93+
94+
// File exists, delete it
95+
log.Info("deleting udev rules", "path", u.path)
96+
if err := u.os.RemoveAll(u.path); err != nil {
97+
log.Error(err, "failed to remove udev rules file", "path", u.path)
98+
return err
99+
}
100+
101+
log.Info("udev rules file deleted successfully", "path", u.path)
62102
return nil
63103
}
64104

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
Copyright 2025, NVIDIA CORPORATION & AFFILIATES
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package udev
18+
19+
import (
20+
"testing"
21+
22+
. "github.com/onsi/ginkgo/v2"
23+
. "github.com/onsi/gomega"
24+
)
25+
26+
func TestUdev(t *testing.T) {
27+
RegisterFailHandler(Fail)
28+
RunSpecs(t, "Udev Suite")
29+
}
Lines changed: 226 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
/*
2+
Copyright 2025, NVIDIA CORPORATION & AFFILIATES
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package udev
18+
19+
import (
20+
"context"
21+
"os"
22+
"time"
23+
24+
. "github.com/onsi/ginkgo/v2"
25+
. "github.com/onsi/gomega"
26+
"github.com/stretchr/testify/assert"
27+
"github.com/stretchr/testify/mock"
28+
29+
osMockPkg "github.com/Mellanox/doca-driver-build/entrypoint/internal/wrappers/mocks"
30+
)
31+
32+
// mockFileInfo is a simple mock implementation of os.FileInfo
33+
type mockFileInfo struct{}
34+
35+
func (m mockFileInfo) Name() string { return "mock" }
36+
func (m mockFileInfo) Size() int64 { return 0 }
37+
func (m mockFileInfo) Mode() os.FileMode { return 0 }
38+
func (m mockFileInfo) ModTime() time.Time { return time.Now() }
39+
func (m mockFileInfo) IsDir() bool { return false }
40+
func (m mockFileInfo) Sys() interface{} { return nil }
41+
42+
var _ = Describe("Udev", func() {
43+
Context("CreateRules", func() {
44+
var (
45+
u Interface
46+
osMock *osMockPkg.OSWrapper
47+
testPath string
48+
)
49+
50+
BeforeEach(func() {
51+
osMock = osMockPkg.NewOSWrapper(GinkgoT())
52+
testPath = "/host/etc/udev/rules.d/77-mlnx-net-names.rules"
53+
u = New(testPath, osMock)
54+
})
55+
56+
It("should create udev rules file successfully", func() {
57+
expectedContent := `ACTION!="add", GOTO="mlnx_ofed_name_end"
58+
SUBSYSTEM!="net", GOTO="mlnx_ofed_name_end"
59+
60+
# Rename physical interfaces (first case) of virtual functions (second case).
61+
# Example names:
62+
# enp8s0f0np0 -> enp8s0f0
63+
# enp8s0f0np1v12 -> enp8s0f0v12
64+
65+
DRIVERS=="mlx5_core", ENV{ID_NET_NAME_PATH}!="", \
66+
PROGRAM="/bin/sh -c 'echo $env{ID_NET_NAME_PATH} | sed -r -e s/np[01]$// -e s/np[01]v/v/'", \
67+
ENV{ID_NET_NAME_PATH}="$result"
68+
69+
DRIVERS=="mlx5_core", ENV{ID_NET_NAME_SLOT}!="", \
70+
PROGRAM="/bin/sh -c 'echo $env{ID_NET_NAME_SLOT} | sed -r -e s/np[01]$// -e s/np[01]v/v/'", \
71+
ENV{ID_NET_NAME_SLOT}="$result"
72+
73+
LABEL="mlnx_ofed_name_end"
74+
`
75+
76+
osMock.EXPECT().WriteFile(testPath, []byte(expectedContent), os.FileMode(0o644)).Return(nil)
77+
78+
err := u.CreateRules(context.Background())
79+
Expect(err).ToNot(HaveOccurred())
80+
})
81+
82+
It("should handle file creation failure", func() {
83+
osMock.EXPECT().WriteFile(testPath, mock.AnythingOfType("[]uint8"), os.FileMode(0o644)).Return(assert.AnError)
84+
85+
err := u.CreateRules(context.Background())
86+
Expect(err).To(HaveOccurred())
87+
Expect(err).To(Equal(assert.AnError))
88+
})
89+
90+
It("should handle different file paths", func() {
91+
customPath := "/custom/path/to/udev/rules.d/99-custom.rules"
92+
u = New(customPath, osMock)
93+
94+
osMock.EXPECT().WriteFile(customPath, mock.AnythingOfType("[]uint8"), os.FileMode(0o644)).Return(nil)
95+
96+
err := u.CreateRules(context.Background())
97+
Expect(err).ToNot(HaveOccurred())
98+
})
99+
100+
It("should handle nested directory paths", func() {
101+
nestedPath := "/host/etc/udev/rules.d/nested/deep/path/77-mlnx-net-names.rules"
102+
u = New(nestedPath, osMock)
103+
104+
osMock.EXPECT().WriteFile(nestedPath, mock.AnythingOfType("[]uint8"), os.FileMode(0o644)).Return(nil)
105+
106+
err := u.CreateRules(context.Background())
107+
Expect(err).ToNot(HaveOccurred())
108+
})
109+
110+
It("should create rules with correct content structure", func() {
111+
var capturedContent []byte
112+
osMock.EXPECT().WriteFile(testPath, mock.AnythingOfType("[]uint8"), os.FileMode(0o644)).Run(func(name string, data []byte, perm os.FileMode) {
113+
capturedContent = data
114+
}).Return(nil)
115+
116+
err := u.CreateRules(context.Background())
117+
Expect(err).ToNot(HaveOccurred())
118+
119+
content := string(capturedContent)
120+
Expect(content).To(ContainSubstring(`ACTION!="add", GOTO="mlnx_ofed_name_end"`))
121+
Expect(content).To(ContainSubstring(`SUBSYSTEM!="net", GOTO="mlnx_ofed_name_end"`))
122+
Expect(content).To(ContainSubstring(`DRIVERS=="mlx5_core", ENV{ID_NET_NAME_PATH}!=""`))
123+
Expect(content).To(ContainSubstring(`DRIVERS=="mlx5_core", ENV{ID_NET_NAME_SLOT}!=""`))
124+
Expect(content).To(ContainSubstring(`LABEL="mlnx_ofed_name_end"`))
125+
Expect(content).To(ContainSubstring(`sed -r -e s/np[01]$// -e s/np[01]v/v/`))
126+
})
127+
128+
It("should handle multiple calls to CreateRules", func() {
129+
osMock.EXPECT().WriteFile(testPath, mock.AnythingOfType("[]uint8"), os.FileMode(0o644)).Return(nil).Times(2)
130+
131+
err := u.CreateRules(context.Background())
132+
Expect(err).ToNot(HaveOccurred())
133+
134+
err = u.CreateRules(context.Background())
135+
Expect(err).ToNot(HaveOccurred())
136+
})
137+
138+
})
139+
140+
Context("RemoveRules", func() {
141+
var (
142+
u Interface
143+
osMock *osMockPkg.OSWrapper
144+
testPath string
145+
)
146+
147+
BeforeEach(func() {
148+
osMock = osMockPkg.NewOSWrapper(GinkgoT())
149+
testPath = "/host/etc/udev/rules.d/77-mlnx-net-names.rules"
150+
u = New(testPath, osMock)
151+
})
152+
153+
It("should remove udev rules file when it exists", func() {
154+
// Mock file exists check
155+
osMock.EXPECT().Stat(testPath).Return(mockFileInfo{}, nil)
156+
osMock.EXPECT().RemoveAll(testPath).Return(nil)
157+
158+
err := u.RemoveRules(context.Background())
159+
Expect(err).ToNot(HaveOccurred())
160+
})
161+
162+
It("should handle file not existing gracefully", func() {
163+
// Mock file doesn't exist (Stat returns os.ErrNotExist)
164+
osMock.EXPECT().Stat(testPath).Return(nil, os.ErrNotExist)
165+
166+
err := u.RemoveRules(context.Background())
167+
Expect(err).ToNot(HaveOccurred())
168+
})
169+
170+
It("should handle file removal failure", func() {
171+
// Mock file exists but removal fails
172+
osMock.EXPECT().Stat(testPath).Return(mockFileInfo{}, nil)
173+
osMock.EXPECT().RemoveAll(testPath).Return(assert.AnError)
174+
175+
err := u.RemoveRules(context.Background())
176+
Expect(err).To(HaveOccurred())
177+
Expect(err).To(Equal(assert.AnError))
178+
})
179+
180+
It("should handle different file paths", func() {
181+
customPath := "/custom/path/to/udev/rules.d/99-custom.rules"
182+
u = New(customPath, osMock)
183+
184+
osMock.EXPECT().Stat(customPath).Return(mockFileInfo{}, nil)
185+
osMock.EXPECT().RemoveAll(customPath).Return(nil)
186+
187+
err := u.RemoveRules(context.Background())
188+
Expect(err).ToNot(HaveOccurred())
189+
})
190+
191+
It("should handle nested directory paths", func() {
192+
nestedPath := "/host/etc/udev/rules.d/nested/deep/path/77-mlnx-net-names.rules"
193+
u = New(nestedPath, osMock)
194+
195+
osMock.EXPECT().Stat(nestedPath).Return(mockFileInfo{}, nil)
196+
osMock.EXPECT().RemoveAll(nestedPath).Return(nil)
197+
198+
err := u.RemoveRules(context.Background())
199+
Expect(err).ToNot(HaveOccurred())
200+
})
201+
202+
It("should handle multiple calls to RemoveRules", func() {
203+
// First call - file exists
204+
osMock.EXPECT().Stat(testPath).Return(mockFileInfo{}, nil)
205+
osMock.EXPECT().RemoveAll(testPath).Return(nil)
206+
207+
err := u.RemoveRules(context.Background())
208+
Expect(err).ToNot(HaveOccurred())
209+
210+
// Second call - file no longer exists
211+
osMock.EXPECT().Stat(testPath).Return(nil, os.ErrNotExist)
212+
213+
err = u.RemoveRules(context.Background())
214+
Expect(err).ToNot(HaveOccurred())
215+
})
216+
217+
It("should handle Stat error other than file not found", func() {
218+
// Mock Stat returning a different error (e.g., permission denied)
219+
osMock.EXPECT().Stat(testPath).Return(nil, assert.AnError)
220+
221+
err := u.RemoveRules(context.Background())
222+
Expect(err).To(HaveOccurred())
223+
Expect(err).To(Equal(assert.AnError))
224+
})
225+
})
226+
})

0 commit comments

Comments
 (0)