Skip to content

Commit 2eb739c

Browse files
committed
Add MT NEG Controller unit tests
1 parent e638f2b commit 2eb739c

File tree

2 files changed

+183
-1
lines changed

2 files changed

+183
-1
lines changed

pkg/multiproject/neg/neg.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ import (
2323
"k8s.io/klog/v2"
2424
)
2525

26+
// newNEGController is a package-level indirection to allow tests to stub the
27+
// actual NEG controller constructor. In production it points to neg.NewController.
28+
var newNEGController = neg.NewController
29+
2630
// StartNEGController creates and runs a NEG controller for the specified ProviderConfig.
2731
// The returned channel is closed by StopControllersForProviderConfig to signal a shutdown
2832
// specific to this ProviderConfig's controller.
@@ -150,7 +154,7 @@ func createNEGController(
150154

151155
noDefaultBackendServicePort := utils.ServicePort{}
152156

153-
negController, err := neg.NewController(
157+
negController, err := newNEGController(
154158
kubeClient,
155159
svcNegClient,
156160
eventRecorderClient,

pkg/multiproject/neg/neg_test.go

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
package neg
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
networkclient "github.com/GoogleCloudPlatform/gke-networking-api/client/network/clientset/versioned"
8+
nodetopologyclient "github.com/GoogleCloudPlatform/gke-networking-api/client/nodetopology/clientset/versioned"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"k8s.io/apimachinery/pkg/types"
11+
"k8s.io/client-go/kubernetes"
12+
k8sfake "k8s.io/client-go/kubernetes/fake"
13+
"k8s.io/client-go/tools/cache"
14+
providerconfig "k8s.io/ingress-gce/pkg/apis/providerconfig/v1"
15+
multiprojectgce "k8s.io/ingress-gce/pkg/multiproject/gce"
16+
multiprojectinformers "k8s.io/ingress-gce/pkg/multiproject/informerset"
17+
"k8s.io/ingress-gce/pkg/neg"
18+
"k8s.io/ingress-gce/pkg/neg/syncers/labels"
19+
negtypes "k8s.io/ingress-gce/pkg/neg/types"
20+
svcnegclient "k8s.io/ingress-gce/pkg/svcneg/client/clientset/versioned"
21+
svcnegfake "k8s.io/ingress-gce/pkg/svcneg/client/clientset/versioned/fake"
22+
"k8s.io/ingress-gce/pkg/utils"
23+
"k8s.io/ingress-gce/pkg/utils/namer"
24+
"k8s.io/ingress-gce/pkg/utils/zonegetter"
25+
klog "k8s.io/klog/v2"
26+
ktesting "k8s.io/klog/v2/ktesting"
27+
)
28+
29+
// TestStartNEGController_StopJoin verifies that the stop channel passed to the controller
30+
// closes when either the global stop channel or the per-ProviderConfig stop channel closes.
31+
func TestStartNEGController_StopJoin(t *testing.T) {
32+
33+
logger, _ := ktesting.NewTestContext(t)
34+
kubeClient := k8sfake.NewSimpleClientset()
35+
informers := multiprojectinformers.NewInformerSet(kubeClient, svcnegfake.NewSimpleClientset(), networkclient.Interface(nil), nodetopologyclient.Interface(nil), metav1.Duration{})
36+
37+
// Start base informers; they are not strictly required by our stubbed controller,
38+
// but mirrors real startup flow and ensures CombinedHasSynced would be true if used.
39+
globalStop := make(chan struct{})
40+
t.Cleanup(func() { close(globalStop) })
41+
if err := informers.Start(globalStop, logger); err != nil {
42+
t.Fatalf("start informers: %v", err)
43+
}
44+
45+
// Provide required inputs for StartNEGController
46+
pc := &providerconfig.ProviderConfig{ObjectMeta: metav1.ObjectMeta{Name: "pc-1"}}
47+
kubeSystemUID := types.UID("uid")
48+
rootNamer := namer.NewNamer("clusteruid", "", logger)
49+
l4Namer := namer.NewL4Namer(string(kubeSystemUID), rootNamer)
50+
lpCfg := labels.PodLabelPropagationConfig{}
51+
// Create a fake cloud with a valid SubnetworkURL via multiproject helper.
52+
gceCreator := multiprojectgce.NewGCEFake()
53+
// Minimal provider config for the GCE fake
54+
pcForCloud := &providerconfig.ProviderConfig{
55+
ObjectMeta: metav1.ObjectMeta{Name: "pc-1"},
56+
Spec: providerconfig.ProviderConfigSpec{
57+
ProjectID: "test-project",
58+
ProjectNumber: 123,
59+
NetworkConfig: providerconfig.ProviderNetworkConfig{
60+
Network: "net-1",
61+
SubnetInfo: providerconfig.ProviderConfigSubnetInfo{Subnetwork: "sub-1"},
62+
},
63+
},
64+
}
65+
cloud, err := gceCreator.GCEForProviderConfig(pcForCloud, logger)
66+
if err != nil {
67+
t.Fatalf("create fake cloud: %v", err)
68+
}
69+
70+
// Stub newNEGController to capture the stopCh passed in and to construct a minimal controller
71+
// that can run without panics.
72+
var capturedStopCh <-chan struct{}
73+
orig := newNEGController
74+
newNEGController = func(kc kubernetes.Interface, sc svcnegclient.Interface, ec kubernetes.Interface, uid types.UID,
75+
ing cache.SharedIndexInformer, svc cache.SharedIndexInformer, pod cache.SharedIndexInformer, node cache.SharedIndexInformer,
76+
es cache.SharedIndexInformer, sn cache.SharedIndexInformer, netInf cache.SharedIndexInformer, gke cache.SharedIndexInformer, nt cache.SharedIndexInformer,
77+
synced func() bool, l4 namer.L4ResourcesNamer, defSP utils.ServicePort, cloud negtypes.NetworkEndpointGroupCloud, zg *zonegetter.ZoneGetter, nm negtypes.NetworkEndpointGroupNamer,
78+
resync time.Duration, gc time.Duration, workers int, enableRR bool, runL4 bool, nonGCP bool, dualStack bool, lp labels.PodLabelPropagationConfig,
79+
multiNetworking bool, ingressRegional bool, runNetLB bool, readOnly bool, enableNEGsForIngress bool,
80+
stopCh <-chan struct{}, l klog.Logger) (*neg.Controller, error) {
81+
capturedStopCh = stopCh
82+
return neg.NewController(kc, sc, ec, uid, ing, svc, pod, node, es, sn, netInf, gke, nt, synced, l4, defSP, cloud, zg, nm,
83+
resync, gc, workers, enableRR, runL4, nonGCP, dualStack, lp, multiNetworking, ingressRegional, runNetLB, readOnly, enableNEGsForIngress, stopCh, l)
84+
}
85+
t.Cleanup(func() { newNEGController = orig })
86+
87+
testCases := []struct {
88+
name string
89+
closeProvider bool
90+
}{
91+
{name: "provider-stop-closes-joined", closeProvider: true},
92+
{name: "global-stop-closes-joined", closeProvider: false},
93+
}
94+
95+
for _, tc := range testCases {
96+
tc := tc
97+
t.Run(tc.name, func(t *testing.T) {
98+
99+
var joinStop <-chan struct{}
100+
var providerStop chan<- struct{}
101+
102+
if tc.closeProvider {
103+
// Wire the join to the real globalStop for this subcase.
104+
joinStop = globalStop
105+
var err error
106+
providerStop, err = StartNEGController(informers, kubeClient, kubeClient, svcnegfake.NewSimpleClientset(), networkclient.Interface(nil), nodetopologyclient.Interface(nil), kubeSystemUID, rootNamer, l4Namer, lpCfg, cloud, joinStop, logger, pc)
107+
if err != nil {
108+
t.Fatalf("StartNEGController: %v", err)
109+
}
110+
close(providerStop)
111+
} else {
112+
// Use a dedicated join channel so informers keep running.
113+
js := make(chan struct{})
114+
joinStop = js
115+
var err error
116+
providerStop, err = StartNEGController(informers, kubeClient, kubeClient, svcnegfake.NewSimpleClientset(), networkclient.Interface(nil), nodetopologyclient.Interface(nil), kubeSystemUID, rootNamer, l4Namer, lpCfg, cloud, joinStop, logger, &providerconfig.ProviderConfig{ObjectMeta: metav1.ObjectMeta{Name: "pc-2"}})
117+
if err != nil {
118+
t.Fatalf("StartNEGController (2): %v", err)
119+
}
120+
close(js)
121+
defer close(providerStop) // safe if already closed
122+
}
123+
124+
if capturedStopCh == nil {
125+
t.Fatalf("capturedStopCh is nil; stub did not run")
126+
}
127+
select {
128+
case <-capturedStopCh:
129+
// ok
130+
case <-time.After(2 * time.Second):
131+
t.Fatalf("joined stopCh did not close for case %q", tc.name)
132+
}
133+
})
134+
}
135+
}
136+
137+
// TestStartNEGController_NilSvcNegClientErrors verifies StartNEGController returns an error
138+
// when the svcneg client is nil (which makes controller construction fail).
139+
func TestStartNEGController_NilSvcNegClientErrors(t *testing.T) {
140+
t.Parallel()
141+
142+
logger, _ := ktesting.NewTestContext(t)
143+
kubeClient := k8sfake.NewSimpleClientset()
144+
informers := multiprojectinformers.NewInformerSet(kubeClient, nil, networkclient.Interface(nil), nodetopologyclient.Interface(nil), metav1.Duration{})
145+
globalStop := make(chan struct{})
146+
t.Cleanup(func() { close(globalStop) })
147+
if err := informers.Start(globalStop, logger); err != nil {
148+
t.Fatalf("start informers: %v", err)
149+
}
150+
151+
pc := &providerconfig.ProviderConfig{ObjectMeta: metav1.ObjectMeta{Name: "pc-err"}}
152+
kubeSystemUID := types.UID("uid")
153+
rootNamer := namer.NewNamer("clusteruid", "", logger)
154+
l4Namer := namer.NewL4Namer(string(kubeSystemUID), rootNamer)
155+
lpCfg := labels.PodLabelPropagationConfig{}
156+
gceCreator := multiprojectgce.NewGCEFake()
157+
pcForCloud := &providerconfig.ProviderConfig{
158+
ObjectMeta: metav1.ObjectMeta{Name: "pc-err"},
159+
Spec: providerconfig.ProviderConfigSpec{
160+
ProjectID: "test-project",
161+
ProjectNumber: 123,
162+
NetworkConfig: providerconfig.ProviderNetworkConfig{
163+
Network: "net-1",
164+
SubnetInfo: providerconfig.ProviderConfigSubnetInfo{Subnetwork: "sub-1"},
165+
},
166+
},
167+
}
168+
cloud, err := gceCreator.GCEForProviderConfig(pcForCloud, logger)
169+
if err != nil {
170+
t.Fatalf("create fake cloud: %v", err)
171+
}
172+
173+
// newNEGController remains default (neg.NewController), which errors when svcNegClient is nil
174+
ch, err := StartNEGController(informers, kubeClient, kubeClient, nil /* svcneg */, networkclient.Interface(nil), nodetopologyclient.Interface(nil), kubeSystemUID, rootNamer, l4Namer, lpCfg, cloud, globalStop, logger, pc)
175+
if err == nil {
176+
t.Fatalf("expected error from StartNEGController when svcNegClient is nil, got nil and channel=%v", ch)
177+
}
178+
}

0 commit comments

Comments
 (0)