Skip to content

Commit 9bff285

Browse files
committed
refactor: update mergeMap to initialize nil destination maps and return the merged map, adjusting its callers and tests.
1 parent e852484 commit 9bff285

File tree

3 files changed

+213
-11
lines changed

3 files changed

+213
-11
lines changed

providers/gce/gce_annotations.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -197,15 +197,26 @@ func GetLoadBalancerAnnotationSubnet(service *v1.Service) string {
197197
return ""
198198
}
199199

200-
// mergeMap merges the update map into the dst map.
201-
// Keys in dst are overwritten by values from update.
202-
// If a value in the update map is an empty string, the key is removed from dst.
203-
func mergeMap(dst, update map[string]string) {
200+
// mergeMap returns a new map containing the merged content of existing and update.
201+
// Keys in existing are overwritten by values from update.
202+
// If a value in the update map is an empty string, the key is removed from the returned map.
203+
// The existing map is not modified.
204+
func mergeMap(existing, update map[string]string) map[string]string {
205+
if existing == nil && len(update) == 0 {
206+
return nil
207+
}
208+
res := make(map[string]string)
209+
for k, v := range existing {
210+
if _, exists := update[k]; exists && update[k] != "" {
211+
res[k] = update[k]
212+
} else if !exists {
213+
res[k] = v
214+
}
215+
}
204216
for k, v := range update {
205-
if v == "" {
206-
delete(dst, k)
207-
} else {
208-
dst[k] = v
217+
if _, exists := existing[k]; !exists && v != "" {
218+
res[k] = v
209219
}
210220
}
221+
return res
211222
}

providers/gce/gce_annotations_test.go

Lines changed: 193 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ limitations under the License.
2020
package gce
2121

2222
import (
23+
"maps"
24+
"reflect"
2325
"testing"
2426

2527
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud"
@@ -119,10 +121,199 @@ func TestMergeMap(t *testing.T) {
119121
updates: map[string]string{},
120122
expectedResult: map[string]string{"key1": "val1"},
121123
},
124+
{
125+
desc: "empty updates should result in no change",
126+
existing: nil,
127+
updates: map[string]string{},
128+
expectedResult: nil,
129+
},
130+
{
131+
desc: "nil existing map should be initialized and updated",
132+
existing: nil,
133+
updates: map[string]string{"key": "val"},
134+
expectedResult: map[string]string{
135+
"key": "val",
136+
},
137+
},
122138
} {
123139
t.Run(tc.desc, func(t *testing.T) {
124-
mergeMap(tc.existing, tc.updates)
125-
assert.Equal(t, tc.expectedResult, tc.existing)
140+
originalExisting := maps.Clone(tc.existing)
141+
142+
result := mergeMap(tc.existing, tc.updates)
143+
assert.Equal(t, tc.expectedResult, result)
144+
145+
if tc.existing != nil {
146+
assert.Equal(t, originalExisting, tc.existing, "Input map should not be modified")
147+
}
148+
})
149+
}
150+
}
151+
152+
func TestComputeNewAnnotationsIfNeeded(t *testing.T) {
153+
testCases := []struct {
154+
desc string
155+
svc *v1.Service
156+
newAnnotations map[string]string
157+
expectUpdate bool
158+
expectedAnns map[string]string
159+
}{
160+
{
161+
desc: "nil annotations in svc, new annotations added",
162+
svc: &v1.Service{
163+
ObjectMeta: metav1.ObjectMeta{
164+
Name: "test-svc",
165+
Namespace: "test-ns",
166+
Annotations: nil,
167+
},
168+
},
169+
newAnnotations: map[string]string{"key1": "val1"},
170+
expectUpdate: true,
171+
expectedAnns: map[string]string{"key1": "val1"},
172+
},
173+
{
174+
desc: "empty annotations in svc, new annotations added",
175+
svc: &v1.Service{
176+
ObjectMeta: metav1.ObjectMeta{
177+
Name: "test-svc",
178+
Namespace: "test-ns",
179+
Annotations: map[string]string{},
180+
},
181+
},
182+
newAnnotations: map[string]string{"key1": "val1"},
183+
expectUpdate: true,
184+
expectedAnns: map[string]string{"key1": "val1"},
185+
},
186+
{
187+
desc: "existing annotations, new annotations merged",
188+
svc: &v1.Service{
189+
ObjectMeta: metav1.ObjectMeta{
190+
Name: "test-svc",
191+
Namespace: "test-ns",
192+
Annotations: map[string]string{
193+
"existing1": "old1",
194+
"key1": "oldVal1",
195+
},
196+
},
197+
},
198+
newAnnotations: map[string]string{"key1": "val1", "key2": "val2"},
199+
expectUpdate: true,
200+
expectedAnns: map[string]string{
201+
"existing1": "old1",
202+
"key1": "val1",
203+
"key2": "val2",
204+
},
205+
},
206+
{
207+
desc: "existing annotations, key removed",
208+
svc: &v1.Service{
209+
ObjectMeta: metav1.ObjectMeta{
210+
Name: "test-svc",
211+
Namespace: "test-ns",
212+
Annotations: map[string]string{
213+
"key1": "val1",
214+
"key2": "val2",
215+
},
216+
},
217+
},
218+
newAnnotations: map[string]string{"key1": ""},
219+
expectUpdate: true,
220+
expectedAnns: map[string]string{"key2": "val2"},
221+
},
222+
{
223+
desc: "no changes to annotations",
224+
svc: &v1.Service{
225+
ObjectMeta: metav1.ObjectMeta{
226+
Name: "test-svc",
227+
Namespace: "test-ns",
228+
Annotations: map[string]string{
229+
"key1": "val1",
230+
},
231+
},
232+
},
233+
newAnnotations: map[string]string{"key1": "val1"},
234+
expectUpdate: false,
235+
expectedAnns: nil,
236+
},
237+
{
238+
desc: "nil newAnnotations",
239+
svc: &v1.Service{
240+
ObjectMeta: metav1.ObjectMeta{
241+
Name: "test-svc",
242+
Namespace: "test-ns",
243+
Annotations: map[string]string{
244+
"key1": "val1",
245+
},
246+
},
247+
},
248+
newAnnotations: nil,
249+
expectUpdate: false,
250+
expectedAnns: nil,
251+
},
252+
{
253+
desc: "empty newAnnotations",
254+
svc: &v1.Service{
255+
ObjectMeta: metav1.ObjectMeta{
256+
Name: "test-svc",
257+
Namespace: "test-ns",
258+
Annotations: map[string]string{
259+
"key1": "val1",
260+
},
261+
},
262+
},
263+
newAnnotations: map[string]string{},
264+
expectUpdate: false,
265+
expectedAnns: nil,
266+
},
267+
{
268+
desc: "nil annotations in svc, empty new annotations",
269+
svc: &v1.Service{
270+
ObjectMeta: metav1.ObjectMeta{
271+
Name: "test-svc",
272+
Namespace: "test-ns",
273+
Annotations: nil,
274+
},
275+
},
276+
newAnnotations: map[string]string{},
277+
expectUpdate: false,
278+
expectedAnns: nil,
279+
},
280+
}
281+
282+
for _, tc := range testCases {
283+
t.Run(tc.desc, func(t *testing.T) {
284+
// Capture original annotations to check for unintended modifications
285+
originalAnnotations := make(map[string]string)
286+
if tc.svc.Annotations != nil {
287+
for k, v := range tc.svc.Annotations {
288+
originalAnnotations[k] = v
289+
}
290+
}
291+
292+
newMeta, needsUpdate := computeNewAnnotationsIfNeeded(tc.svc, tc.newAnnotations)
293+
294+
assert.Equal(t, tc.expectUpdate, needsUpdate)
295+
296+
if tc.expectUpdate {
297+
assert.NotNil(t, newMeta)
298+
assert.True(t, reflect.DeepEqual(tc.expectedAnns, newMeta.Annotations), "Expected annotations: %v, got: %v", tc.expectedAnns, newMeta.Annotations)
299+
// Ensure the map was actually changed from the original
300+
assert.False(t, reflect.DeepEqual(originalAnnotations, newMeta.Annotations), "Annotations should have changed, but match original")
301+
} else {
302+
assert.Nil(t, newMeta)
303+
// Ensure original service annotations are not modified
304+
if len(originalAnnotations) == 0 && tc.svc.Annotations == nil {
305+
// Special case: nil to nil is fine
306+
} else {
307+
assert.True(t, reflect.DeepEqual(originalAnnotations, tc.svc.Annotations), "Original svc.Annotations should not be modified on no update")
308+
}
309+
}
310+
311+
// Always check that the original service object's annotations map instance is not a different instance if no update was needed.
312+
if !needsUpdate {
313+
if len(originalAnnotations) > 0 || (len(originalAnnotations) == 0 && tc.svc.Annotations != nil) {
314+
assert.True(t, reflect.DeepEqual(originalAnnotations, tc.svc.Annotations), "Original svc.Annotations instance should not change when no update is needed")
315+
}
316+
}
126317
})
127318
}
128319
}

providers/gce/gce_loadbalancer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,7 @@ func hasLoadBalancerPortsError(service *v1.Service) bool {
405405
// This function is used by L4 LB controllers.
406406
func computeNewAnnotationsIfNeeded(svc *v1.Service, newAnnotations map[string]string) (*metav1.ObjectMeta, bool) {
407407
newObjectMeta := svc.ObjectMeta.DeepCopy()
408-
mergeMap(newObjectMeta.Annotations, newAnnotations)
408+
newObjectMeta.Annotations = mergeMap(newObjectMeta.Annotations, newAnnotations)
409409
if reflect.DeepEqual(svc.Annotations, newObjectMeta.Annotations) {
410410
return nil, false
411411
}

0 commit comments

Comments
 (0)