Skip to content

Commit 1a51302

Browse files
committed
test(clusterlink-proxy): add controller unit test
1. add unit test for proxy resourceCache controller 2.change the scope of ResourceCache CRD to Cluster Signed-off-by: JimDevil <[email protected]>
1 parent 6e9f40c commit 1a51302

File tree

8 files changed

+927
-58
lines changed

8 files changed

+927
-58
lines changed

cmd/clusterlink/proxy/app/clusterlink-proxy.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,5 +78,10 @@ func run(ctx context.Context, opts *options.Options) error {
7878
return nil
7979
})
8080

81+
server.GenericAPIServer.AddPreShutdownHookOrDie("stop-karmada-proxy-controller", func() error {
82+
config.ExtraConfig.ProxyController.Stop()
83+
return nil
84+
})
85+
8186
return server.GenericAPIServer.PrepareRun().Run(ctx.Done())
8287
}

deploy/crds/kosmos.io_resourcecaches.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1
33
kind: CustomResourceDefinition
44
metadata:
55
annotations:
6-
controller-gen.kubebuilder.io/version: v0.14.0
6+
controller-gen.kubebuilder.io/version: v0.11.0
77
name: resourcecaches.kosmos.io
88
spec:
99
group: kosmos.io
@@ -12,7 +12,7 @@ spec:
1212
listKind: ResourceCacheList
1313
plural: resourcecaches
1414
singular: resourcecache
15-
scope: Namespaced
15+
scope: Cluster
1616
versions:
1717
- name: v1alpha1
1818
schema:

pkg/clusterlink/proxy/controller/controller.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,9 @@ func (rc *ResourceCacheController) getGroupVersionResource(restMapper meta.RESTM
177177
return restMapping.Resource, nil
178178
}
179179

180+
func (rc *ResourceCacheController) Stop() {
181+
rc.store.Stop()
182+
}
180183
func (rc *ResourceCacheController) Run(stopCh <-chan struct{}, workers int) {
181184
defer utilruntime.HandleCrash()
182185
defer rc.queue.ShutDown()

pkg/clusterlink/proxy/controller/controller_test.go

Lines changed: 225 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,259 @@
11
package controller
22

33
import (
4-
"strings"
4+
"context"
5+
"net/http"
6+
"net/http/httptest"
7+
"reflect"
58
"testing"
9+
"time"
610

11+
"github.com/google/go-cmp/cmp"
712
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8-
dynfake "k8s.io/client-go/dynamic"
13+
"k8s.io/apimachinery/pkg/runtime"
14+
"k8s.io/apimachinery/pkg/runtime/schema"
15+
dyfake "k8s.io/client-go/dynamic/fake"
16+
"k8s.io/client-go/kubernetes/scheme"
917
"k8s.io/client-go/rest"
10-
"k8s.io/client-go/restmapper"
1118

19+
v1alpha1 "github.com/kosmos.io/kosmos/pkg/apis/kosmos/v1alpha1"
20+
"github.com/kosmos.io/kosmos/pkg/clusterlink/proxy/delegate"
21+
proxytest "github.com/kosmos.io/kosmos/pkg/clusterlink/proxy/testing"
1222
fakekosmosclient "github.com/kosmos.io/kosmos/pkg/generated/clientset/versioned/fake"
13-
informerfactory "github.com/kosmos.io/kosmos/pkg/generated/informers/externalversions"
23+
kosmosInformer "github.com/kosmos.io/kosmos/pkg/generated/informers/externalversions"
24+
"github.com/kosmos.io/kosmos/pkg/utils"
1425
)
1526

16-
var apiGroupResources = []*restmapper.APIGroupResources{
17-
{
18-
Group: metav1.APIGroup{
19-
Name: "apps",
20-
Versions: []metav1.GroupVersionForDiscovery{
21-
{GroupVersion: "apps/v1", Version: "v1"},
22-
},
23-
PreferredVersion: metav1.GroupVersionForDiscovery{
24-
GroupVersion: "apps/v1", Version: "v1",
25-
},
26-
},
27-
VersionedResources: map[string][]metav1.APIResource{
28-
"v1": {
29-
{Name: "deployments", SingularName: "deployment", Namespaced: true, Kind: "Deployment"},
27+
func TestNewResourceCacheController(t *testing.T) {
28+
restConfig := &rest.Config{
29+
Host: "https://localhost:6443",
30+
}
31+
rc := &v1alpha1.ResourceCache{
32+
ObjectMeta: metav1.ObjectMeta{Name: "rc"},
33+
Spec: v1alpha1.ResourceCacheSpec{
34+
ResourceCacheSelectors: []v1alpha1.ResourceCacheSelector{
35+
proxytest.PodResourceCacheSelector,
3036
},
3137
},
32-
},
33-
{
34-
Group: metav1.APIGroup{
35-
Name: "",
36-
Versions: []metav1.GroupVersionForDiscovery{
37-
{GroupVersion: "v1", Version: "v1"},
38+
}
39+
kosmosFactory := kosmosInformer.NewSharedInformerFactory(fakekosmosclient.NewSimpleClientset(rc), 0)
40+
o := NewControllerOption{
41+
DynamicClient: dyfake.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), map[schema.GroupVersionResource]string{
42+
proxytest.PodGVR: "PodList",
43+
}),
44+
KosmosFactory: kosmosFactory,
45+
RestConfig: restConfig,
46+
RestMapper: proxytest.RestMapper,
47+
}
48+
proxyCtl, err := NewResourceCacheController(o)
49+
if err != nil {
50+
t.Error(err)
51+
return
52+
}
53+
if proxyCtl == nil {
54+
t.Error("proxyCtl is nil")
55+
return
56+
}
57+
stopCh := make(chan struct{})
58+
defer close(stopCh)
59+
kosmosFactory.Start(stopCh)
60+
// start proxyctl
61+
go func() {
62+
proxyCtl.Run(stopCh, 1)
63+
defer proxyCtl.Stop()
64+
}()
65+
kosmosFactory.WaitForCacheSync(stopCh)
66+
time.Sleep(time.Second)
67+
hasPod := proxyCtl.store.HasResource(proxytest.PodGVR)
68+
if !hasPod {
69+
t.Error("has no pod resource cached")
70+
return
71+
}
72+
}
73+
74+
func TestResourceCacheController_syncResourceCache(t *testing.T) {
75+
newMultiNs := func(namespaces ...string) *utils.MultiNamespace {
76+
multiNs := utils.NewMultiNamespace()
77+
if len(namespaces) == 0 {
78+
multiNs.Add(metav1.NamespaceAll)
79+
return multiNs
80+
}
81+
for _, ns := range namespaces {
82+
multiNs.Add(ns)
83+
}
84+
return multiNs
85+
}
86+
87+
tests := []struct {
88+
name string
89+
input []runtime.Object
90+
want map[string]*utils.MultiNamespace
91+
}{
92+
{
93+
name: "cache pod resource with two namespace",
94+
input: []runtime.Object{
95+
&v1alpha1.ResourceCache{
96+
ObjectMeta: metav1.ObjectMeta{Name: "rc1"},
97+
Spec: v1alpha1.ResourceCacheSpec{
98+
ResourceCacheSelectors: []v1alpha1.ResourceCacheSelector{
99+
proxytest.PodResourceCacheSelector,
100+
},
101+
},
102+
},
38103
},
39-
PreferredVersion: metav1.GroupVersionForDiscovery{
40-
GroupVersion: "v1", Version: "v1",
104+
want: map[string]*utils.MultiNamespace{
105+
"pods": newMultiNs("ns1", "ns2"),
41106
},
42107
},
43-
VersionedResources: map[string][]metav1.APIResource{
44-
"v1": {
45-
{Name: "pods", SingularName: "pod", Namespaced: true, Kind: "Pod"},
108+
{
109+
name: "cache pod twice in two ResourceCache with different namespace",
110+
input: []runtime.Object{
111+
&v1alpha1.ResourceCache{
112+
ObjectMeta: metav1.ObjectMeta{Name: "rc1"},
113+
Spec: v1alpha1.ResourceCacheSpec{
114+
ResourceCacheSelectors: []v1alpha1.ResourceCacheSelector{
115+
proxytest.PodSelectorWithNS1,
116+
},
117+
},
118+
},
119+
&v1alpha1.ResourceCache{
120+
ObjectMeta: metav1.ObjectMeta{Name: "rc2"},
121+
Spec: v1alpha1.ResourceCacheSpec{
122+
ResourceCacheSelectors: []v1alpha1.ResourceCacheSelector{
123+
proxytest.PodSelectorWithNS2,
124+
},
125+
},
126+
},
127+
},
128+
want: map[string]*utils.MultiNamespace{
129+
"pods": newMultiNs("ns1", "ns2"),
46130
},
47131
},
48-
},
132+
}
133+
for _, tt := range tests {
134+
t.Run(tt.name, func(t *testing.T) {
135+
actual := map[string]*utils.MultiNamespace{}
136+
kosmosClientSet := fakekosmosclient.NewSimpleClientset(tt.input...)
137+
kosmosFactory := kosmosInformer.NewSharedInformerFactory(kosmosClientSet, 0)
138+
ctl := &ResourceCacheController{
139+
restMapper: proxytest.RestMapper,
140+
resourceCacheLister: kosmosFactory.Kosmos().V1alpha1().ResourceCaches().Lister(),
141+
store: &proxytest.MockStore{
142+
UpdateCacheFunc: func(resources map[schema.GroupVersionResource]*utils.MultiNamespace) error {
143+
for k, v := range resources {
144+
actual[k.Resource] = v
145+
}
146+
return nil
147+
},
148+
},
149+
}
150+
stopCh := make(chan struct{})
151+
kosmosFactory.Start(stopCh)
152+
kosmosFactory.WaitForCacheSync(stopCh)
153+
err := ctl.syncResourceCache("test")
154+
if err != nil {
155+
t.Error(err)
156+
return
157+
}
158+
if !reflect.DeepEqual(actual, tt.want) {
159+
t.Errorf("diff: %v", cmp.Diff(actual, tt.want))
160+
}
161+
})
162+
}
49163
}
50164

51-
func TestNewResourceCacheController(t *testing.T) {
52-
type args struct {
53-
option NewControllerOption
54-
}
55-
dyClient, _ := dynfake.NewForConfig(&rest.Config{})
56-
o := NewControllerOption{
57-
DynamicClient: dyClient,
58-
KosmosFactory: informerfactory.NewSharedInformerFactory(fakekosmosclient.NewSimpleClientset(), 0),
59-
RestConfig: &rest.Config{},
60-
RestMapper: restmapper.NewDiscoveryRESTMapper(apiGroupResources),
165+
func TestResourceCacheController_Connect(t *testing.T) {
166+
store := &proxytest.MockStore{
167+
HasResourceFunc: func(gvr schema.GroupVersionResource) bool { return gvr == proxytest.PodGVR },
61168
}
62169
tests := []struct {
63-
name string
64-
args args
65-
want *ResourceCacheController
66-
wantErr bool
67-
errMsg string
170+
name string
171+
plugins []*proxytest.MockDelegate
172+
wantErr bool
173+
wantCalled []bool
68174
}{
69175
{
70-
name: "NewResourceCacheController",
71-
args: args{
72-
option: o,
176+
name: "call first",
177+
plugins: []*proxytest.MockDelegate{
178+
{
179+
MockOrder: 0,
180+
IsSupportRequest: true,
181+
},
182+
{
183+
MockOrder: 1,
184+
IsSupportRequest: true,
185+
},
186+
},
187+
wantErr: false,
188+
wantCalled: []bool{true, false},
189+
},
190+
{
191+
name: "call second",
192+
plugins: []*proxytest.MockDelegate{
193+
{
194+
MockOrder: 0,
195+
IsSupportRequest: false,
196+
},
197+
{
198+
MockOrder: 1,
199+
IsSupportRequest: true,
200+
},
73201
},
74-
wantErr: false,
202+
wantErr: false,
203+
wantCalled: []bool{false, true},
204+
},
205+
{
206+
name: "call fail",
207+
plugins: []*proxytest.MockDelegate{
208+
{
209+
MockOrder: 0,
210+
IsSupportRequest: false,
211+
},
212+
{
213+
MockOrder: 1,
214+
IsSupportRequest: false,
215+
},
216+
},
217+
wantErr: true,
218+
wantCalled: []bool{false, false},
75219
},
76220
}
77221
for _, tt := range tests {
78222
t.Run(tt.name, func(t *testing.T) {
79-
_, err := NewResourceCacheController(tt.args.option)
80-
if err == nil && tt.wantErr {
81-
t.Fatal("expected an error, but got none")
223+
ctl := &ResourceCacheController{
224+
delegate: delegate.NewDelegateChain(proxytest.ConvertPluginSlice(tt.plugins)),
225+
negotiatedSerializer: scheme.Codecs.WithoutConversion(),
226+
store: store,
227+
}
228+
229+
conn, err := ctl.Connect(context.TODO(), "/api/v1/pods", nil)
230+
if err != nil {
231+
t.Fatal(err)
82232
}
83-
if err != nil && !tt.wantErr {
84-
t.Errorf("unexpected error, got: %v", err)
233+
234+
req, err := http.NewRequest(http.MethodGet, "/prefix/api/v1/pods", nil)
235+
if err != nil {
236+
t.Fatal(err)
85237
}
86-
if err != nil && tt.wantErr && !strings.Contains(err.Error(), tt.errMsg) {
87-
t.Errorf("expected error message %s to be in %s", tt.errMsg, err.Error())
238+
239+
recorder := httptest.NewRecorder()
240+
conn.ServeHTTP(recorder, req)
241+
242+
response := recorder.Result()
243+
244+
if (response.StatusCode != 200) != tt.wantErr {
245+
t.Errorf("http request returned status code = %v, want error = %v",
246+
response.StatusCode, tt.wantErr)
247+
}
248+
249+
if len(tt.plugins) != len(tt.wantCalled) {
250+
panic("len(tt.plugins) != len(tt.wantCalled), please fix test cases")
251+
}
252+
253+
for i, n := 0, len(tt.plugins); i < n; i++ {
254+
if tt.plugins[i].Called != tt.wantCalled[i] {
255+
t.Errorf("plugin[%v].Called = %v, want = %v", i, tt.plugins[i].Called, tt.wantCalled[i])
256+
}
88257
}
89258
})
90259
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package testing
2+
3+
import (
4+
corev1 "k8s.io/api/core/v1"
5+
"k8s.io/apimachinery/pkg/api/meta"
6+
"k8s.io/apimachinery/pkg/runtime/schema"
7+
8+
v1alpha1 "github.com/kosmos.io/kosmos/pkg/apis/kosmos/v1alpha1"
9+
)
10+
11+
var (
12+
PodGVK = corev1.SchemeGroupVersion.WithKind("Pod")
13+
SecretGVR = corev1.SchemeGroupVersion.WithKind("Secret")
14+
RestMapper *meta.DefaultRESTMapper
15+
16+
PodGVR = corev1.SchemeGroupVersion.WithResource("pods")
17+
18+
PodSelectorWithNS1 = v1alpha1.ResourceCacheSelector{APIVersion: PodGVK.GroupVersion().String(), Kind: PodGVK.Kind, Namespace: []string{"ns1"}}
19+
20+
PodSelectorWithNS2 = v1alpha1.ResourceCacheSelector{APIVersion: PodGVK.GroupVersion().String(), Kind: PodGVK.Kind, Namespace: []string{"ns2"}}
21+
22+
PodResourceCacheSelector = v1alpha1.ResourceCacheSelector{APIVersion: PodGVK.GroupVersion().String(), Kind: PodGVK.Kind, Namespace: []string{"ns1", "ns2"}}
23+
)
24+
25+
func init() {
26+
RestMapper = meta.NewDefaultRESTMapper([]schema.GroupVersion{corev1.SchemeGroupVersion})
27+
RestMapper.Add(PodGVK, meta.RESTScopeNamespace)
28+
RestMapper.Add(SecretGVR, meta.RESTScopeNamespace)
29+
}

0 commit comments

Comments
 (0)