@@ -17,11 +17,18 @@ limitations under the License.
17
17
package syncer
18
18
19
19
import (
20
+ "context"
20
21
"testing"
22
+ "time"
21
23
22
24
"github.com/container-storage-interface/spec/lib/go/csi"
23
25
"github.com/stretchr/testify/assert"
24
26
v1 "k8s.io/api/core/v1"
27
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28
+ clientset "k8s.io/client-go/kubernetes"
29
+ k8sfake "k8s.io/client-go/kubernetes/fake"
30
+ cnsconfig "sigs.k8s.io/vsphere-csi-driver/v3/pkg/common/config"
31
+ "sigs.k8s.io/vsphere-csi-driver/v3/pkg/csi/service/common"
25
32
)
26
33
27
34
func TestGenerateVolumeNodeAffinity (t * testing.T ) {
@@ -107,3 +114,178 @@ func TestGenerateVolumeNodeAffinity(t *testing.T) {
107
114
})
108
115
}
109
116
}
117
+
118
+ func TestAddNodeAffinityRulesOnPVTopologyAnnotationPresent (t * testing.T ) {
119
+ ctx := context .Background ()
120
+
121
+ // Create supervisor PVC with topology annotation
122
+ supPVC := & v1.PersistentVolumeClaim {
123
+ ObjectMeta : metav1.ObjectMeta {
124
+ Name : "volume-1" ,
125
+ Namespace : "sv-namespace" ,
126
+ Annotations : map [string ]string {
127
+ common .AnnVolumeAccessibleTopology : `[{"zone":"zone-a"}]` ,
128
+ },
129
+ },
130
+ }
131
+ // Create guest PV without node affinity
132
+ guestPV := & v1.PersistentVolume {
133
+ ObjectMeta : metav1.ObjectMeta {
134
+ Name : "pv-1" ,
135
+ },
136
+ Spec : v1.PersistentVolumeSpec {
137
+ PersistentVolumeSource : v1.PersistentVolumeSource {
138
+ CSI : & v1.CSIPersistentVolumeSource {
139
+ VolumeHandle : "volume-1" ,
140
+ },
141
+ },
142
+ },
143
+ }
144
+
145
+ // Setup supervisor client with PVC
146
+ supervisorClient := k8sfake .NewSimpleClientset (supPVC )
147
+ // Setup guest client with PV
148
+ guestClient := k8sfake .NewSimpleClientset (guestPV )
149
+
150
+ // Setup metadataSyncer
151
+ metadataSyncer := & metadataSyncInformer {
152
+ supervisorClient : supervisorClient ,
153
+ }
154
+ metadataSyncer .configInfo = & cnsconfig.ConfigurationInfo {
155
+ Cfg : & cnsconfig.Config {
156
+ GC : cnsconfig.GCConfig {
157
+ Endpoint : "endpoint" ,
158
+ Port : "443" ,
159
+ },
160
+ },
161
+ }
162
+
163
+ // Patch k8sNewClient to return our guestClient
164
+ origK8sClient := k8sNewClient
165
+ defer func () {
166
+ k8sNewClient = origK8sClient
167
+ }()
168
+ k8sNewClient = func (ctx context.Context ) (clientset.Interface , error ) {
169
+ return guestClient , nil
170
+ }
171
+
172
+ // Patch getPVsInBoundAvailableOrReleased to return our PV
173
+ origGetPVs := getPVsInBoundAvailableOrReleased
174
+ defer func () {
175
+ getPVsInBoundAvailableOrReleased = origGetPVs
176
+ }()
177
+ getPVsInBoundAvailableOrReleased = func (ctx context.Context ,
178
+ syncer * metadataSyncInformer ) ([]* v1.PersistentVolume , error ) {
179
+ return []* v1.PersistentVolume {guestPV }, nil
180
+ }
181
+
182
+ // Patch cnsconfig.GetSupervisorNamespace to return our namespace
183
+ origGetSuperNS := cnsconfigGetSupervisorNamespace
184
+ defer func () {
185
+ cnsconfigGetSupervisorNamespace = origGetSuperNS
186
+ }()
187
+ cnsconfigGetSupervisorNamespace = func (ctx context.Context ) (string , error ) {
188
+ return "sv-namespace" , nil
189
+ }
190
+
191
+ // Run function
192
+ AddNodeAffinityRulesOnPV (ctx , metadataSyncer )
193
+
194
+ // Verify node affinity added
195
+ gotPV , err := guestClient .CoreV1 ().PersistentVolumes ().Get (ctx , "pv-1" , metav1.GetOptions {})
196
+ if err != nil {
197
+ t .Fatalf ("PV not found: %v" , err )
198
+ }
199
+ if gotPV .Spec .NodeAffinity == nil || len (gotPV .Spec .NodeAffinity .Required .NodeSelectorTerms ) == 0 {
200
+ t .Errorf ("Expected node affinity to be set on PV when supervisor PVC has topology annotation" )
201
+ }
202
+ }
203
+
204
+ func TestAddNodeAffinityRulesOnPVTopologyAnnotationAbsent (t * testing.T ) {
205
+ ctx := context .Background ()
206
+
207
+ // Create supervisor PVC without annotation
208
+ supPVC := & v1.PersistentVolumeClaim {
209
+ ObjectMeta : metav1.ObjectMeta {
210
+ Name : "volume-2" ,
211
+ Namespace : "sv-namespace" ,
212
+ Annotations : map [string ]string {},
213
+ },
214
+ }
215
+ // Create guest PV without node affinity
216
+ guestPV := & v1.PersistentVolume {
217
+ ObjectMeta : metav1.ObjectMeta {
218
+ Name : "pv-2" ,
219
+ },
220
+ Spec : v1.PersistentVolumeSpec {
221
+ PersistentVolumeSource : v1.PersistentVolumeSource {
222
+ CSI : & v1.CSIPersistentVolumeSource {
223
+ VolumeHandle : "volume-2" ,
224
+ },
225
+ },
226
+ },
227
+ }
228
+
229
+ supervisorClient := k8sfake .NewSimpleClientset (supPVC )
230
+ guestClient := k8sfake .NewSimpleClientset (guestPV )
231
+
232
+ // Setup metadataSyncer
233
+ metadataSyncer := & metadataSyncInformer {
234
+ supervisorClient : supervisorClient ,
235
+ }
236
+ metadataSyncer .configInfo = & cnsconfig.ConfigurationInfo {
237
+ Cfg : & cnsconfig.Config {
238
+ GC : cnsconfig.GCConfig {
239
+ Endpoint : "endpoint" ,
240
+ Port : "443" ,
241
+ },
242
+ },
243
+ }
244
+
245
+ // Patch k8sNewClient to return our guestClient
246
+ origK8sClient := k8sNewClient
247
+ defer func () {
248
+ k8sNewClient = origK8sClient
249
+ }()
250
+ k8sNewClient = func (ctx context.Context ) (clientset.Interface , error ) {
251
+ return guestClient , nil
252
+ }
253
+
254
+ // Patch getPVsInBoundAvailableOrReleased to return our PV
255
+ origGetPVs := getPVsInBoundAvailableOrReleased
256
+ defer func () {
257
+ getPVsInBoundAvailableOrReleased = origGetPVs
258
+ }()
259
+ getPVsInBoundAvailableOrReleased = func (ctx context.Context ,
260
+ syncer * metadataSyncInformer ) ([]* v1.PersistentVolume , error ) {
261
+ return []* v1.PersistentVolume {guestPV }, nil
262
+ }
263
+
264
+ // Patch cnsconfig.GetSupervisorNamespace to return our namespace
265
+ origGetSuperNS := cnsconfigGetSupervisorNamespace
266
+ defer func () {
267
+ cnsconfigGetSupervisorNamespace = origGetSuperNS
268
+ }()
269
+ cnsconfigGetSupervisorNamespace = func (ctx context.Context ) (string , error ) {
270
+ return "sv-namespace" , nil
271
+ }
272
+
273
+ // Reduce timeout value used in code for testing
274
+ origTimeout := timeoutAddNodeAffinityOnPVs
275
+ defer func () {
276
+ timeoutAddNodeAffinityOnPVs = origTimeout
277
+ }()
278
+ timeoutAddNodeAffinityOnPVs = 15 * time .Second
279
+
280
+ // Run function
281
+ AddNodeAffinityRulesOnPV (ctx , metadataSyncer )
282
+
283
+ // Verify node affinity NOT added as supervisor PVC doesn't have topology annotation
284
+ gotPV , err := guestClient .CoreV1 ().PersistentVolumes ().Get (ctx , "pv-2" , metav1.GetOptions {})
285
+ if err != nil {
286
+ t .Fatalf ("PV not found: %v" , err )
287
+ }
288
+ if gotPV .Spec .NodeAffinity != nil {
289
+ t .Errorf ("Expected node affinity NOT to be set on PV when supervisor PVC has no topology annotation" )
290
+ }
291
+ }
0 commit comments