From 49e4bd137bd3c69d0cf81c0c4ab51ab0d5674f87 Mon Sep 17 00:00:00 2001 From: Rob Scott Date: Mon, 9 Dec 2019 14:20:48 -0800 Subject: [PATCH] Ensuring kube-proxy does not mutate shared EndpointSlices --- pkg/proxy/BUILD | 1 + pkg/proxy/endpointslicecache.go | 4 ++- pkg/proxy/endpointslicecache_test.go | 49 ++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/pkg/proxy/BUILD b/pkg/proxy/BUILD index 0057cdf8fc1..7147af83dfc 100644 --- a/pkg/proxy/BUILD +++ b/pkg/proxy/BUILD @@ -70,6 +70,7 @@ go_test( "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/discovery/v1beta1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/types:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/intstr:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", diff --git a/pkg/proxy/endpointslicecache.go b/pkg/proxy/endpointslicecache.go index e9c6165353b..e75b6848527 100644 --- a/pkg/proxy/endpointslicecache.go +++ b/pkg/proxy/endpointslicecache.go @@ -108,11 +108,13 @@ func newEndpointSliceTracker() *endpointSliceTracker { // newEndpointSliceInfo generates endpointSliceInfo from an EndpointSlice. func newEndpointSliceInfo(endpointSlice *discovery.EndpointSlice, remove bool) *endpointSliceInfo { esInfo := &endpointSliceInfo{ - Ports: endpointSlice.Ports, + Ports: make([]discovery.EndpointPort, len(endpointSlice.Ports)), Endpoints: []*endpointInfo{}, Remove: remove, } + // copy here to avoid mutating shared EndpointSlice object. + copy(esInfo.Ports, endpointSlice.Ports) sort.Sort(byPort(esInfo.Ports)) if !remove { diff --git a/pkg/proxy/endpointslicecache_test.go b/pkg/proxy/endpointslicecache_test.go index 6651fa8daf0..1e45c7a7bb1 100644 --- a/pkg/proxy/endpointslicecache_test.go +++ b/pkg/proxy/endpointslicecache_test.go @@ -24,6 +24,7 @@ import ( "k8s.io/api/core/v1" discovery "k8s.io/api/discovery/v1beta1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" utilpointer "k8s.io/utils/pointer" ) @@ -152,11 +153,13 @@ func TestEndpointsMapFromESC(t *testing.T) { t.Run(name, func(t *testing.T) { esCache := NewEndpointSliceCache(tc.hostname, nil, nil, nil) + cmc := newCacheMutationCheck(tc.endpointSlices) for _, endpointSlice := range tc.endpointSlices { esCache.updatePending(endpointSlice, false) } compareEndpointsMapsStr(t, esCache.getEndpointsMap(tc.namespacedName, esCache.trackerByServiceMap[tc.namespacedName].pending), tc.expectedMap) + cmc.Check(t) }) } } @@ -315,6 +318,8 @@ func TestEsInfoChanged(t *testing.T) { for name, tc := range testCases { t.Run(name, func(t *testing.T) { + cmc := newCacheMutationCheck([]*discovery.EndpointSlice{tc.initialSlice}) + if tc.initialSlice != nil { tc.cache.updatePending(tc.initialSlice, false) tc.cache.checkoutChanges() @@ -331,6 +336,8 @@ func TestEsInfoChanged(t *testing.T) { if tc.expectChanged != changed { t.Errorf("Expected esInfoChanged() to return %t, got %t", tc.expectChanged, changed) } + + cmc.Check(t) }) } } @@ -378,3 +385,45 @@ func generateEndpointSliceWithOffset(serviceName, namespace string, sliceNum, of func generateEndpointSlice(serviceName, namespace string, sliceNum, numEndpoints, unreadyMod int, hosts []string, portNums []*int32) *discovery.EndpointSlice { return generateEndpointSliceWithOffset(serviceName, namespace, sliceNum, sliceNum, numEndpoints, unreadyMod, hosts, portNums) } + +// cacheMutationCheck helps ensure that cached objects have not been changed +// in any way throughout a test run. +type cacheMutationCheck struct { + objects []cacheObject +} + +// cacheObject stores a reference to an original object as well as a deep copy +// of that object to track any mutations in the original object. +type cacheObject struct { + original runtime.Object + deepCopy runtime.Object +} + +// newCacheMutationCheck initializes a cacheMutationCheck with EndpointSlices. +func newCacheMutationCheck(endpointSlices []*discovery.EndpointSlice) cacheMutationCheck { + cmc := cacheMutationCheck{} + for _, endpointSlice := range endpointSlices { + cmc.Add(endpointSlice) + } + return cmc +} + +// Add appends a runtime.Object and a deep copy of that object into the +// cacheMutationCheck. +func (cmc *cacheMutationCheck) Add(o runtime.Object) { + cmc.objects = append(cmc.objects, cacheObject{ + original: o, + deepCopy: o.DeepCopyObject(), + }) +} + +// Check verifies that no objects in the cacheMutationCheck have been mutated. +func (cmc *cacheMutationCheck) Check(t *testing.T) { + for _, o := range cmc.objects { + if !reflect.DeepEqual(o.original, o.deepCopy) { + // Cached objects can't be safely mutated and instead should be deep + // copied before changed in any way. + t.Errorf("Cached object was unexpectedly mutated. Original: %+v, Mutated: %+v", o.deepCopy, o.original) + } + } +}