Skip to content

Commit a144a13

Browse files
committed
Unit test
Signed-off-by: lubronzhan <[email protected]>
1 parent 43bd8e8 commit a144a13

File tree

3 files changed

+201
-6
lines changed

3 files changed

+201
-6
lines changed

go.mod

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ require (
1111
k8s.io/cloud-provider v0.29.1
1212
k8s.io/component-base v0.29.1
1313
k8s.io/klog v1.0.0
14+
k8s.io/klog/v2 v2.110.1
15+
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
1416
)
1517

1618
require (
@@ -97,10 +99,8 @@ require (
9799
k8s.io/apiserver v0.29.1 // indirect
98100
k8s.io/component-helpers v0.29.1 // indirect
99101
k8s.io/controller-manager v0.29.1 // indirect
100-
k8s.io/klog/v2 v2.110.1 // indirect
101102
k8s.io/kms v0.29.1 // indirect
102103
k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect
103-
k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect
104104
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.28.0 // indirect
105105
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
106106
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect

pkg/provider/loadbalancerclass.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"time"
77

88
corev1 "k8s.io/api/core/v1"
9-
v1 "k8s.io/api/core/v1"
109
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
1110
"k8s.io/apimachinery/pkg/util/wait"
1211
"k8s.io/client-go/informers"
@@ -168,9 +167,10 @@ func (c *loadbalancerClassServiceController) syncService(key string) error {
168167
case err != nil:
169168
utilruntime.HandleError(fmt.Errorf("unable to retrieve service %v from store: %v", key, err))
170169
case loadbalancerClassMatch(svc):
171-
klog.V(4).Infof("Skip reoconciling service %s/%s, since loadbalancerClass doesn't match", svc.Namespace, svc.Name)
172-
default:
170+
klog.V(4).Infof("Reconcile service %s/%s, since loadbalancerClass match", svc.Namespace, svc.Name)
173171
err = c.processServiceCreateOrUpdate(svc)
172+
default:
173+
klog.V(4).Infof("Skip reoconciling service %s/%s, since loadbalancerClass doesn't match", svc.Namespace, svc.Name)
174174
}
175175

176176
return err
@@ -179,7 +179,7 @@ func (c *loadbalancerClassServiceController) syncService(key string) error {
179179
func (c *loadbalancerClassServiceController) processServiceCreateOrUpdate(svc *corev1.Service) error {
180180
// ctx := context.Background()
181181
_, err := syncLoadBalancer(context.Background(), c.kubeClient, svc, c.cmName, c.cmNamespace)
182-
c.recorder.Eventf(svc, v1.EventTypeWarning, "syncLoadBalancer", "Error syncing load balancer: %v", err)
182+
c.recorder.Eventf(svc, corev1.EventTypeWarning, "syncLoadBalancer", "Error syncing load balancer: %v", err)
183183
return err
184184
}
185185

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/*
2+
Copyright 2021 The Kubernetes Authors.
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
http://www.apache.org/licenses/LICENSE-2.0
7+
Unless required by applicable law or agreed to in writing, software
8+
distributed under the License is distributed on an "AS IS" BASIS,
9+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10+
See the License for the specific language governing permissions and
11+
limitations under the License.
12+
*/
13+
14+
package provider
15+
16+
import (
17+
"context"
18+
"testing"
19+
20+
corev1 "k8s.io/api/core/v1"
21+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
22+
"k8s.io/apimachinery/pkg/labels"
23+
"k8s.io/client-go/informers"
24+
"k8s.io/client-go/kubernetes/fake"
25+
corelisters "k8s.io/client-go/listers/core/v1"
26+
"k8s.io/client-go/tools/record"
27+
"k8s.io/client-go/util/workqueue"
28+
"k8s.io/utils/ptr"
29+
30+
klog "k8s.io/klog/v2"
31+
)
32+
33+
func alwaysReady() bool { return true }
34+
35+
func newController(kubeClient *fake.Clientset) *loadbalancerClassServiceController {
36+
eventBroadcaster := record.NewBroadcaster()
37+
eventBroadcaster.StartLogging(klog.Infof)
38+
informerFactory := informers.NewSharedInformerFactory(kubeClient, 0)
39+
serviceInformer := informerFactory.Core().V1().Services()
40+
41+
c := &loadbalancerClassServiceController{
42+
serviceInformer: serviceInformer.Informer(),
43+
serviceLister: serviceInformer.Lister(),
44+
serviceListerSynced: alwaysReady,
45+
kubeClient: kubeClient,
46+
cmName: KubeVipClientConfig,
47+
cmNamespace: KubeVipClientConfigNamespace,
48+
49+
recorder: record.NewFakeRecorder(100),
50+
workqueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "Nodes"),
51+
}
52+
kubeClient.ClearActions()
53+
return c
54+
}
55+
56+
func TestProcessServiceCreateOrUpdate(t *testing.T) {
57+
testCases := []struct {
58+
desc string
59+
svcs []*corev1.Service
60+
svcUpdate []*corev1.Service
61+
expectedNumPatches int
62+
expectIPAllocated bool
63+
}{
64+
{
65+
desc: "create service with correct lbclass, reconcile",
66+
svcs: []*corev1.Service{service("t1s1", ptr.To(LoadbalancerClass)), service("t1s2", ptr.To(LoadbalancerClass))},
67+
expectedNumPatches: 2,
68+
expectIPAllocated: true,
69+
},
70+
{
71+
desc: "create service with incorrect lbclass, skip reconciling",
72+
svcs: []*corev1.Service{service("t2s1", ptr.To("wrong")), service("t2s2", ptr.To("wrong"))},
73+
expectedNumPatches: 0,
74+
expectIPAllocated: true,
75+
},
76+
}
77+
78+
// create ip pool for service to use
79+
client := fake.NewSimpleClientset()
80+
ctx := context.Background()
81+
cm := poolConfigMap()
82+
if _, err := client.CoreV1().ConfigMaps(cm.Namespace).Create(ctx, cm, metav1.CreateOptions{}); err != nil {
83+
t.Errorf("Failed to prepare configmap %s for testing: %v", cm.Name, err)
84+
}
85+
86+
for _, tc := range testCases {
87+
t.Run(tc.desc, func(t *testing.T) {
88+
c := newController(client)
89+
c.serviceLister = newFakeServiceLister(nil, tc.svcs...)
90+
// create service
91+
for _, svc := range tc.svcs {
92+
if _, err := client.CoreV1().Services(svc.Namespace).Create(ctx, svc, metav1.CreateOptions{}); err != nil {
93+
t.Errorf("Failed to prepare service %s for testing: %v", svc.Name, err)
94+
}
95+
}
96+
client.ClearActions()
97+
98+
// run process processServiceCreateOrUpdate
99+
for _, svc := range tc.svcs {
100+
if err := c.syncService(svc.Name); err != nil {
101+
t.Errorf("failed to update service %s: %v", svc.Name, err)
102+
}
103+
}
104+
105+
// verify the number of patch request to ippool to update spec section
106+
actions := client.Actions()
107+
numPatches := 0
108+
for _, a := range actions {
109+
if a.Matches("update", "services") {
110+
numPatches++
111+
}
112+
}
113+
if tc.expectedNumPatches != numPatches {
114+
t.Errorf("expectedPatch %d doesn't match number of patches %d", tc.expectedNumPatches, numPatches)
115+
}
116+
})
117+
}
118+
}
119+
120+
func service(name string, lbc *string) *corev1.Service {
121+
return &corev1.Service{
122+
ObjectMeta: metav1.ObjectMeta{
123+
Name: name,
124+
},
125+
Spec: corev1.ServiceSpec{
126+
LoadBalancerClass: lbc,
127+
},
128+
}
129+
}
130+
131+
func poolConfigMap() *corev1.ConfigMap {
132+
return &corev1.ConfigMap{
133+
ObjectMeta: metav1.ObjectMeta{
134+
Name: KubeVipClientConfig,
135+
Namespace: KubeVipClientConfigNamespace,
136+
},
137+
Data: map[string]string{
138+
"cidr-global": "10.0.0.1/24",
139+
},
140+
}
141+
}
142+
143+
type fakeServiceLister struct {
144+
cache []*corev1.Service
145+
err error
146+
}
147+
148+
func newFakeServiceLister(err error, svcs ...*corev1.Service) *fakeServiceLister {
149+
ret := &fakeServiceLister{}
150+
ret.cache = svcs
151+
ret.err = err
152+
return ret
153+
}
154+
155+
// List lists all Services in the indexer.
156+
// Objects returned here must be treated as read-only.
157+
func (l *fakeServiceLister) List(selector labels.Selector) (ret []*corev1.Service, err error) {
158+
return l.cache, l.err
159+
}
160+
161+
// Services retrieves the ServiceNamespaceLister for a namespace.
162+
// Objects returned here must be treated as read-only.
163+
func (l *fakeServiceLister) Services(namespace string) corelisters.ServiceNamespaceLister {
164+
res := &fakeServiceNamespaceLister{
165+
cache: []*corev1.Service{},
166+
}
167+
for _, svc := range l.cache {
168+
if svc.Namespace == namespace {
169+
res.cache = append(res.cache, svc)
170+
}
171+
}
172+
return res
173+
}
174+
175+
type fakeServiceNamespaceLister struct {
176+
cache []*corev1.Service
177+
err error
178+
}
179+
180+
// List lists all Services in the indexer.
181+
// Objects returned here must be treated as read-only.
182+
func (l fakeServiceNamespaceLister) List(selector labels.Selector) (ret []*corev1.Service, err error) {
183+
return l.cache, l.err
184+
}
185+
186+
// Get retrieves the Service from the index for a given name.
187+
// Objects returned here must be treated as read-only.
188+
func (l fakeServiceNamespaceLister) Get(name string) (*corev1.Service, error) {
189+
for _, svc := range l.cache {
190+
if svc.Name == name {
191+
return svc, nil
192+
}
193+
}
194+
return nil, nil
195+
}

0 commit comments

Comments
 (0)