Skip to content

Commit 2e7db95

Browse files
authored
Merge pull request #3765 from chethanv28/topic/chethanv28/syncer-unit-tests
Add unit tests for syncer, driver & node packages
2 parents aaf4693 + ba1dddb commit 2e7db95

File tree

4 files changed

+948
-5
lines changed

4 files changed

+948
-5
lines changed

pkg/csi/service/driver_test.go

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
/*
2+
Copyright 2025 The Kubernetes Authors.
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 service
18+
19+
import (
20+
"context"
21+
"os"
22+
"strings"
23+
"testing"
24+
25+
"github.com/container-storage-interface/spec/lib/go/csi"
26+
"github.com/stretchr/testify/assert"
27+
cnstypes "github.com/vmware/govmomi/cns/types"
28+
29+
cnsconfig "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/config"
30+
csitypes "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/types"
31+
)
32+
33+
func TestNewDriver(t *testing.T) {
34+
driver := NewDriver()
35+
assert.NotNil(t, driver)
36+
37+
// Verify that the driver implements the required interfaces
38+
_, ok := driver.(csi.IdentityServer)
39+
assert.True(t, ok, "Driver should implement IdentityServer interface")
40+
41+
_, ok = driver.(csi.NodeServer)
42+
assert.True(t, ok, "Driver should implement NodeServer interface")
43+
44+
// Verify that GetController method exists
45+
assert.NotNil(t, driver.GetController)
46+
}
47+
48+
func TestVsphereCSIDriver_GetController(t *testing.T) {
49+
tests := []struct {
50+
name string
51+
clusterFlavor string
52+
expectedType string
53+
}{
54+
{
55+
name: "Vanilla cluster flavor",
56+
clusterFlavor: string(cnstypes.CnsClusterFlavorVanilla),
57+
expectedType: "*vanilla.controller",
58+
},
59+
{
60+
name: "Workload cluster flavor",
61+
clusterFlavor: string(cnstypes.CnsClusterFlavorWorkload),
62+
expectedType: "*wcp.controller",
63+
},
64+
{
65+
name: "Guest cluster flavor",
66+
clusterFlavor: string(cnstypes.CnsClusterFlavorGuest),
67+
expectedType: "*wcpguest.controller",
68+
},
69+
{
70+
name: "Default cluster flavor (empty)",
71+
clusterFlavor: "",
72+
expectedType: "*vanilla.controller",
73+
},
74+
{
75+
name: "Invalid cluster flavor",
76+
clusterFlavor: "invalid",
77+
expectedType: "*vanilla.controller",
78+
},
79+
}
80+
81+
for _, tt := range tests {
82+
t.Run(tt.name, func(t *testing.T) {
83+
// Set environment variable
84+
if tt.clusterFlavor != "" {
85+
os.Setenv(cnsconfig.EnvClusterFlavor, tt.clusterFlavor)
86+
} else {
87+
os.Unsetenv(cnsconfig.EnvClusterFlavor)
88+
}
89+
defer os.Unsetenv(cnsconfig.EnvClusterFlavor)
90+
91+
driver := NewDriver()
92+
controller := driver.GetController()
93+
94+
assert.NotNil(t, controller)
95+
96+
// Verify the controller is not nil - we can't easily test the exact type
97+
// without importing the controller packages which would create circular dependencies
98+
assert.NotNil(t, controller, "Controller should not be nil")
99+
})
100+
}
101+
}
102+
103+
func TestGetNewUUID(t *testing.T) {
104+
uuid1 := getNewUUID()
105+
uuid2 := getNewUUID()
106+
107+
// Verify UUIDs are not empty
108+
assert.NotEmpty(t, uuid1)
109+
assert.NotEmpty(t, uuid2)
110+
111+
// Verify UUIDs are different
112+
assert.NotEqual(t, uuid1, uuid2)
113+
114+
// Verify UUID format (basic check for length and hyphens)
115+
assert.Len(t, uuid1, 36, "UUID should be 36 characters long")
116+
assert.True(t, strings.Contains(uuid1, "-"), "UUID should contain hyphens")
117+
}
118+
119+
func TestVsphereCSIDriver_BeforeServe_NodeMode(t *testing.T) {
120+
ctx := context.Background()
121+
driver := NewDriver().(*vsphereCSIDriver)
122+
123+
// Set mode to node
124+
os.Setenv(csitypes.EnvVarMode, "node")
125+
defer os.Unsetenv(csitypes.EnvVarMode)
126+
127+
// Mock CO init params
128+
COInitParams = map[string]interface{}{
129+
"test": "value",
130+
}
131+
132+
err := driver.BeforeServe(ctx)
133+
134+
// In node mode without Kubernetes cluster, BeforeServe will fail during CO initialization
135+
// This is expected behavior in a test environment
136+
assert.Error(t, err)
137+
// The error can be either missing env vars or missing service account token file
138+
assert.True(t,
139+
strings.Contains(err.Error(), "KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined") ||
140+
strings.Contains(err.Error(), "open /var/run/secrets/kubernetes.io/serviceaccount/token: no such file or directory"),
141+
"Expected Kubernetes connection error, got: %v", err)
142+
}
143+
144+
func TestVsphereCSIDriver_BeforeServe_ControllerMode(t *testing.T) {
145+
ctx := context.Background()
146+
driver := NewDriver().(*vsphereCSIDriver)
147+
148+
// Set mode to controller
149+
os.Setenv(csitypes.EnvVarMode, "controller")
150+
defer os.Unsetenv(csitypes.EnvVarMode)
151+
152+
// Set cluster flavor to vanilla for predictable behavior
153+
os.Setenv(cnsconfig.EnvClusterFlavor, string(cnstypes.CnsClusterFlavorVanilla))
154+
defer os.Unsetenv(cnsconfig.EnvClusterFlavor)
155+
156+
// Mock CO init params
157+
COInitParams = map[string]interface{}{
158+
"test": "value",
159+
}
160+
161+
err := driver.BeforeServe(ctx)
162+
163+
// In controller mode without Kubernetes cluster, it should fail during CO initialization
164+
// This is expected behavior in a test environment
165+
assert.Error(t, err)
166+
// The error can be either missing env vars or missing service account token file
167+
assert.True(t,
168+
strings.Contains(err.Error(), "KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT must be defined") ||
169+
strings.Contains(err.Error(), "open /var/run/secrets/kubernetes.io/serviceaccount/token: no such file or directory"),
170+
"Expected Kubernetes connection error, got: %v", err)
171+
}
172+
173+
func TestInit_SocketCleanup(t *testing.T) {
174+
// Test the init function's socket cleanup behavior
175+
tempFile := "/tmp/test-csi-socket"
176+
177+
// Create a temporary file to simulate leftover socket
178+
file, err := os.Create(tempFile)
179+
assert.NoError(t, err)
180+
file.Close()
181+
182+
// Verify file exists
183+
_, err = os.Stat(tempFile)
184+
assert.NoError(t, err)
185+
186+
// Set environment variable and call init
187+
os.Setenv(csitypes.EnvVarEndpoint, UnixSocketPrefix+tempFile)
188+
defer os.Unsetenv(csitypes.EnvVarEndpoint)
189+
190+
// Simulate init function behavior
191+
sockPath := os.Getenv(csitypes.EnvVarEndpoint)
192+
sockPath = strings.TrimPrefix(sockPath, UnixSocketPrefix)
193+
if len(sockPath) > 1 {
194+
os.Remove(sockPath)
195+
}
196+
197+
// Verify file is removed
198+
_, err = os.Stat(tempFile)
199+
assert.True(t, os.IsNotExist(err))
200+
}
201+
202+
func TestVsphereCSIDriver_Run(t *testing.T) {
203+
// This test verifies the Run method structure without actually starting the server
204+
driver := NewDriver().(*vsphereCSIDriver)
205+
206+
// We can't easily test the full Run method without mocking the GRPC server
207+
// But we can test that GetController returns a valid controller
208+
controller := driver.GetController()
209+
assert.NotNil(t, controller)
210+
211+
// Note: We skip testing BeforeServe here as it requires Kubernetes cluster connectivity
212+
// which is not available in unit test environment
213+
}
214+
215+
func TestDriverInterface(t *testing.T) {
216+
driver := NewDriver()
217+
218+
// Test that driver implements all required interfaces
219+
assert.Implements(t, (*Driver)(nil), driver, "Should implement Driver interface")
220+
assert.Implements(t, (*csi.IdentityServer)(nil), driver, "Should implement IdentityServer interface")
221+
assert.Implements(t, (*csi.NodeServer)(nil), driver, "Should implement NodeServer interface")
222+
223+
// Test that GetController returns a ControllerServer
224+
controller := driver.GetController()
225+
assert.Implements(t, (*csi.ControllerServer)(nil), controller,
226+
"GetController should return ControllerServer interface")
227+
}
228+
229+
func TestDriverConstants(t *testing.T) {
230+
// Test that constants are properly defined
231+
assert.Equal(t, cnstypes.CnsClusterFlavorVanilla, defaultClusterFlavor)
232+
assert.Equal(t, "unix://", UnixSocketPrefix)
233+
}
234+
235+
func TestGlobalVariables(t *testing.T) {
236+
// Test that global variables are properly initialized
237+
assert.Equal(t, defaultClusterFlavor, clusterFlavor)
238+
239+
// COInitParams should be settable
240+
testParams := map[string]interface{}{"test": "value"}
241+
COInitParams = testParams
242+
assert.Equal(t, testParams, COInitParams)
243+
}

pkg/csi/service/node.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,18 @@ func (driver *vsphereCSIDriver) NodeStageVolume(
5555

5656
volumeID := req.GetVolumeId()
5757
volCap := req.GetVolumeCapability()
58+
59+
if volCap == nil {
60+
return nil, logger.LogNewErrorCode(log, codes.InvalidArgument,
61+
"NodeStageVolume failed: volume capability not provided")
62+
}
63+
5864
// Check for block volume or file share.
5965
if common.IsFileVolumeRequest(ctx, []*csi.VolumeCapability{volCap}) {
6066
log.Infof("NodeStageVolume: Volume %q detected as a file share volume. Ignoring staging for file volumes.",
6167
volumeID)
6268
return &csi.NodeStageVolumeResponse{}, nil
6369
}
64-
65-
if volCap == nil {
66-
return nil, logger.LogNewErrorCode(log, codes.InvalidArgument,
67-
"NodeStageVolume failed: volume capability not provided")
68-
}
6970
if acquired := driver.volumeLocks.TryAcquire(volumeID); !acquired {
7071
return nil, logger.LogNewErrorCodef(log, codes.Aborted,
7172
"NodeStageVolume failed: An operation with the given Volume ID %s already exists", volumeID)
@@ -250,6 +251,11 @@ func (driver *vsphereCSIDriver) NodeUnpublishVolume(
250251
volID := req.GetVolumeId()
251252
target := req.GetTargetPath()
252253

254+
if len(volID) == 0 {
255+
return nil, logger.LogNewErrorCode(log, codes.InvalidArgument,
256+
"NodeUnpublishVolume: Volume ID must be provided")
257+
}
258+
253259
if target == "" {
254260
return nil, logger.LogNewErrorCodef(log, codes.FailedPrecondition,
255261
"NodeUnpublishVolume failed: target path %q not set", target)
@@ -278,7 +284,14 @@ func (driver *vsphereCSIDriver) NodeGetVolumeStats(
278284
log.Infof("NodeGetVolumeStats: called with args %+v", req)
279285

280286
var err error
287+
volumeID := req.GetVolumeId()
281288
targetPath := req.GetVolumePath()
289+
290+
if len(volumeID) == 0 {
291+
return nil, logger.LogNewErrorCode(log, codes.InvalidArgument,
292+
"NodeGetVolumeStats: Volume ID must be provided")
293+
}
294+
282295
if targetPath == "" {
283296
return nil, logger.LogNewErrorCodef(log, codes.InvalidArgument,
284297
"received empty targetpath %q", targetPath)

0 commit comments

Comments
 (0)