1
16
17 package apiserver
18
19 import (
20 "fmt"
21 "net/http"
22 "net/http/httptest"
23 "net/url"
24 "strings"
25 "testing"
26 "time"
27
28 "k8s.io/utils/pointer"
29
30 v1 "k8s.io/api/core/v1"
31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32 "k8s.io/apimachinery/pkg/util/dump"
33 v1listers "k8s.io/client-go/listers/core/v1"
34 clienttesting "k8s.io/client-go/testing"
35 "k8s.io/client-go/tools/cache"
36 "k8s.io/client-go/util/workqueue"
37 apiregistration "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
38 "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/fake"
39 apiregistrationclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1"
40 listers "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1"
41 )
42
43 const (
44 testServicePort = 1234
45 testServicePortName = "testPort"
46 )
47
48 func newEndpoints(namespace, name string) *v1.Endpoints {
49 return &v1.Endpoints{
50 ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name},
51 }
52 }
53
54 func newEndpointsWithAddress(namespace, name string, port int32, portName string) *v1.Endpoints {
55 return &v1.Endpoints{
56 ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name},
57 Subsets: []v1.EndpointSubset{
58 {
59 Addresses: []v1.EndpointAddress{
60 {
61 IP: "val",
62 },
63 },
64 Ports: []v1.EndpointPort{
65 {
66 Name: portName,
67 Port: port,
68 },
69 },
70 },
71 },
72 }
73 }
74
75 func newService(namespace, name string, port int32, portName string) *v1.Service {
76 return &v1.Service{
77 ObjectMeta: metav1.ObjectMeta{Namespace: namespace, Name: name},
78 Spec: v1.ServiceSpec{
79 Type: v1.ServiceTypeClusterIP,
80 Ports: []v1.ServicePort{
81 {Port: port, Name: portName},
82 },
83 },
84 }
85 }
86
87 func newLocalAPIService(name string) *apiregistration.APIService {
88 return &apiregistration.APIService{
89 ObjectMeta: metav1.ObjectMeta{Name: name},
90 }
91 }
92
93 func newRemoteAPIService(name string) *apiregistration.APIService {
94 return &apiregistration.APIService{
95 ObjectMeta: metav1.ObjectMeta{Name: name},
96 Spec: apiregistration.APIServiceSpec{
97 Group: strings.SplitN(name, ".", 2)[0],
98 Version: strings.SplitN(name, ".", 2)[1],
99 Service: &apiregistration.ServiceReference{
100 Namespace: "foo",
101 Name: "bar",
102 Port: pointer.Int32Ptr(testServicePort),
103 },
104 },
105 }
106 }
107
108 func setupAPIServices(apiServices []*apiregistration.APIService) (*AvailableConditionController, *fake.Clientset) {
109 fakeClient := fake.NewSimpleClientset()
110 apiServiceIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
111 serviceIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
112 endpointsIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
113
114 testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
115 w.WriteHeader(http.StatusOK)
116 }))
117 defer testServer.Close()
118
119 for _, o := range apiServices {
120 apiServiceIndexer.Add(o)
121 }
122
123 c := AvailableConditionController{
124 apiServiceClient: fakeClient.ApiregistrationV1(),
125 apiServiceLister: listers.NewAPIServiceLister(apiServiceIndexer),
126 serviceLister: v1listers.NewServiceLister(serviceIndexer),
127 endpointsLister: v1listers.NewEndpointsLister(endpointsIndexer),
128 serviceResolver: &fakeServiceResolver{url: testServer.URL},
129 queue: workqueue.NewNamedRateLimitingQueue(
130
131
132
133 workqueue.NewItemExponentialFailureRateLimiter(5*time.Millisecond, 30*time.Second),
134 "AvailableConditionController"),
135 metrics: newAvailabilityMetrics(),
136 }
137 for _, svc := range apiServices {
138 c.addAPIService(svc)
139 }
140 return &c, fakeClient
141 }
142
143 func BenchmarkBuildCache(b *testing.B) {
144 apiServiceName := "remote.group"
145
146 apiServices := []*apiregistration.APIService{newRemoteAPIService(apiServiceName)}
147 for i := 0; i < 30; i++ {
148 apiServices = append(apiServices, newLocalAPIService(fmt.Sprintf("local.group%d", i)))
149 }
150
151 services := []*v1.Service{newService("foo", "bar", testServicePort, testServicePortName)}
152 for i := 0; i < 100; i++ {
153 services = append(services, newService("foo", fmt.Sprintf("bar%d", i), testServicePort, testServicePortName))
154 }
155 c, _ := setupAPIServices(apiServices)
156 b.ReportAllocs()
157 b.ResetTimer()
158 for n := 1; n <= b.N; n++ {
159 for _, svc := range services {
160 c.addService(svc)
161 }
162 for _, svc := range services {
163 c.updateService(svc, svc)
164 }
165 for _, svc := range services {
166 c.deleteService(svc)
167 }
168 }
169 }
170
171 func TestBuildCache(t *testing.T) {
172 tests := []struct {
173 name string
174
175 apiServiceName string
176 apiServices []*apiregistration.APIService
177 services []*v1.Service
178 endpoints []*v1.Endpoints
179
180 expectedAvailability apiregistration.APIServiceCondition
181 }{
182 {
183 name: "api service",
184 apiServiceName: "remote.group",
185 apiServices: []*apiregistration.APIService{newRemoteAPIService("remote.group")},
186 services: []*v1.Service{newService("foo", "bar", testServicePort, testServicePortName)},
187 },
188 }
189 for _, tc := range tests {
190 t.Run(tc.name, func(t *testing.T) {
191 c, fakeClient := setupAPIServices(tc.apiServices)
192 for _, svc := range tc.services {
193 c.addService(svc)
194 }
195
196 c.sync(tc.apiServiceName)
197
198
199 if e, a := 1, len(fakeClient.Actions()); e != a {
200 t.Fatalf("%v expected %v, got %v", tc.name, e, fakeClient.Actions())
201 }
202 })
203 }
204 }
205
206 func TestSync(t *testing.T) {
207 tests := []struct {
208 name string
209
210 apiServiceName string
211 apiServices []*apiregistration.APIService
212 services []*v1.Service
213 endpoints []*v1.Endpoints
214 backendStatus int
215 backendLocation string
216
217 expectedAvailability apiregistration.APIServiceCondition
218 }{
219 {
220 name: "local",
221 apiServiceName: "local.group",
222 apiServices: []*apiregistration.APIService{newLocalAPIService("local.group")},
223 backendStatus: http.StatusOK,
224 expectedAvailability: apiregistration.APIServiceCondition{
225 Type: apiregistration.Available,
226 Status: apiregistration.ConditionTrue,
227 Reason: "Local",
228 Message: "Local APIServices are always available",
229 },
230 },
231 {
232 name: "no service",
233 apiServiceName: "remote.group",
234 apiServices: []*apiregistration.APIService{newRemoteAPIService("remote.group")},
235 services: []*v1.Service{newService("foo", "not-bar", testServicePort, testServicePortName)},
236 backendStatus: http.StatusOK,
237 expectedAvailability: apiregistration.APIServiceCondition{
238 Type: apiregistration.Available,
239 Status: apiregistration.ConditionFalse,
240 Reason: "ServiceNotFound",
241 Message: `service/bar in "foo" is not present`,
242 },
243 },
244 {
245 name: "service on bad port",
246 apiServiceName: "remote.group",
247 apiServices: []*apiregistration.APIService{newRemoteAPIService("remote.group")},
248 services: []*v1.Service{{
249 ObjectMeta: metav1.ObjectMeta{Namespace: "foo", Name: "bar"},
250 Spec: v1.ServiceSpec{
251 Type: v1.ServiceTypeClusterIP,
252 Ports: []v1.ServicePort{
253 {Port: 6443},
254 },
255 },
256 }},
257 endpoints: []*v1.Endpoints{newEndpointsWithAddress("foo", "bar", testServicePort, testServicePortName)},
258 backendStatus: http.StatusOK,
259 expectedAvailability: apiregistration.APIServiceCondition{
260 Type: apiregistration.Available,
261 Status: apiregistration.ConditionFalse,
262 Reason: "ServicePortError",
263 Message: fmt.Sprintf(`service/bar in "foo" is not listening on port %d`, testServicePort),
264 },
265 },
266 {
267 name: "no endpoints",
268 apiServiceName: "remote.group",
269 apiServices: []*apiregistration.APIService{newRemoteAPIService("remote.group")},
270 services: []*v1.Service{newService("foo", "bar", testServicePort, testServicePortName)},
271 backendStatus: http.StatusOK,
272 expectedAvailability: apiregistration.APIServiceCondition{
273 Type: apiregistration.Available,
274 Status: apiregistration.ConditionFalse,
275 Reason: "EndpointsNotFound",
276 Message: `cannot find endpoints for service/bar in "foo"`,
277 },
278 },
279 {
280 name: "missing endpoints",
281 apiServiceName: "remote.group",
282 apiServices: []*apiregistration.APIService{newRemoteAPIService("remote.group")},
283 services: []*v1.Service{newService("foo", "bar", testServicePort, testServicePortName)},
284 endpoints: []*v1.Endpoints{newEndpoints("foo", "bar")},
285 backendStatus: http.StatusOK,
286 expectedAvailability: apiregistration.APIServiceCondition{
287 Type: apiregistration.Available,
288 Status: apiregistration.ConditionFalse,
289 Reason: "MissingEndpoints",
290 Message: `endpoints for service/bar in "foo" have no addresses with port name "testPort"`,
291 },
292 },
293 {
294 name: "wrong endpoint port name",
295 apiServiceName: "remote.group",
296 apiServices: []*apiregistration.APIService{newRemoteAPIService("remote.group")},
297 services: []*v1.Service{newService("foo", "bar", testServicePort, testServicePortName)},
298 endpoints: []*v1.Endpoints{newEndpointsWithAddress("foo", "bar", testServicePort, "wrongName")},
299 backendStatus: http.StatusOK,
300 expectedAvailability: apiregistration.APIServiceCondition{
301 Type: apiregistration.Available,
302 Status: apiregistration.ConditionFalse,
303 Reason: "MissingEndpoints",
304 Message: fmt.Sprintf(`endpoints for service/bar in "foo" have no addresses with port name "%s"`, testServicePortName),
305 },
306 },
307 {
308 name: "remote",
309 apiServiceName: "remote.group",
310 apiServices: []*apiregistration.APIService{newRemoteAPIService("remote.group")},
311 services: []*v1.Service{newService("foo", "bar", testServicePort, testServicePortName)},
312 endpoints: []*v1.Endpoints{newEndpointsWithAddress("foo", "bar", testServicePort, testServicePortName)},
313 backendStatus: http.StatusOK,
314 expectedAvailability: apiregistration.APIServiceCondition{
315 Type: apiregistration.Available,
316 Status: apiregistration.ConditionTrue,
317 Reason: "Passed",
318 Message: `all checks passed`,
319 },
320 },
321 {
322 name: "remote-bad-return",
323 apiServiceName: "remote.group",
324 apiServices: []*apiregistration.APIService{newRemoteAPIService("remote.group")},
325 services: []*v1.Service{newService("foo", "bar", testServicePort, testServicePortName)},
326 endpoints: []*v1.Endpoints{newEndpointsWithAddress("foo", "bar", testServicePort, testServicePortName)},
327 backendStatus: http.StatusForbidden,
328 expectedAvailability: apiregistration.APIServiceCondition{
329 Type: apiregistration.Available,
330 Status: apiregistration.ConditionFalse,
331 Reason: "FailedDiscoveryCheck",
332 Message: `failing or missing response from`,
333 },
334 },
335 {
336 name: "remote-redirect",
337 apiServiceName: "remote.group",
338 apiServices: []*apiregistration.APIService{newRemoteAPIService("remote.group")},
339 services: []*v1.Service{newService("foo", "bar", testServicePort, testServicePortName)},
340 endpoints: []*v1.Endpoints{newEndpointsWithAddress("foo", "bar", testServicePort, testServicePortName)},
341 backendStatus: http.StatusFound,
342 backendLocation: "/test",
343 expectedAvailability: apiregistration.APIServiceCondition{
344 Type: apiregistration.Available,
345 Status: apiregistration.ConditionFalse,
346 Reason: "FailedDiscoveryCheck",
347 Message: `failing or missing response from`,
348 },
349 },
350 {
351 name: "remote-304",
352 apiServiceName: "remote.group",
353 apiServices: []*apiregistration.APIService{newRemoteAPIService("remote.group")},
354 services: []*v1.Service{newService("foo", "bar", testServicePort, testServicePortName)},
355 endpoints: []*v1.Endpoints{newEndpointsWithAddress("foo", "bar", testServicePort, testServicePortName)},
356 backendStatus: http.StatusNotModified,
357 expectedAvailability: apiregistration.APIServiceCondition{
358 Type: apiregistration.Available,
359 Status: apiregistration.ConditionFalse,
360 Reason: "FailedDiscoveryCheck",
361 Message: `failing or missing response from`,
362 },
363 },
364 }
365
366 for _, tc := range tests {
367 t.Run(tc.name, func(t *testing.T) {
368 fakeClient := fake.NewSimpleClientset()
369 apiServiceIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
370 serviceIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
371 endpointsIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc})
372 for _, obj := range tc.apiServices {
373 apiServiceIndexer.Add(obj)
374 }
375 for _, obj := range tc.services {
376 serviceIndexer.Add(obj)
377 }
378 for _, obj := range tc.endpoints {
379 endpointsIndexer.Add(obj)
380 }
381
382 testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
383 if tc.backendLocation != "" {
384 w.Header().Set("Location", tc.backendLocation)
385 }
386 w.WriteHeader(tc.backendStatus)
387 }))
388 defer testServer.Close()
389
390 c := AvailableConditionController{
391 apiServiceClient: fakeClient.ApiregistrationV1(),
392 apiServiceLister: listers.NewAPIServiceLister(apiServiceIndexer),
393 serviceLister: v1listers.NewServiceLister(serviceIndexer),
394 endpointsLister: v1listers.NewEndpointsLister(endpointsIndexer),
395 serviceResolver: &fakeServiceResolver{url: testServer.URL},
396 proxyCurrentCertKeyContent: func() ([]byte, []byte) { return emptyCert(), emptyCert() },
397 metrics: newAvailabilityMetrics(),
398 }
399 c.sync(tc.apiServiceName)
400
401
402 if e, a := 1, len(fakeClient.Actions()); e != a {
403 t.Fatalf("%v expected %v, got %v", tc.name, e, fakeClient.Actions())
404 }
405
406 action, ok := fakeClient.Actions()[0].(clienttesting.UpdateAction)
407 if !ok {
408 t.Fatalf("%v got %v", tc.name, ok)
409 }
410
411 if e, a := 1, len(action.GetObject().(*apiregistration.APIService).Status.Conditions); e != a {
412 t.Fatalf("%v expected %v, got %v", tc.name, e, action.GetObject())
413 }
414 condition := action.GetObject().(*apiregistration.APIService).Status.Conditions[0]
415 if e, a := tc.expectedAvailability.Type, condition.Type; e != a {
416 t.Errorf("%v expected %v, got %#v", tc.name, e, condition)
417 }
418 if e, a := tc.expectedAvailability.Status, condition.Status; e != a {
419 t.Errorf("%v expected %v, got %#v", tc.name, e, condition)
420 }
421 if e, a := tc.expectedAvailability.Reason, condition.Reason; e != a {
422 t.Errorf("%v expected %v, got %#v", tc.name, e, condition)
423 }
424 if e, a := tc.expectedAvailability.Message, condition.Message; !strings.HasPrefix(a, e) {
425 t.Errorf("%v expected %v, got %#v", tc.name, e, condition)
426 }
427 if condition.LastTransitionTime.IsZero() {
428 t.Error("expected lastTransitionTime to be non-zero")
429 }
430 })
431 }
432 }
433
434 type fakeServiceResolver struct {
435 url string
436 }
437
438 func (f *fakeServiceResolver) ResolveEndpoint(namespace, name string, port int32) (*url.URL, error) {
439 return url.Parse(f.url)
440 }
441
442 func TestUpdateAPIServiceStatus(t *testing.T) {
443 foo := &apiregistration.APIService{Status: apiregistration.APIServiceStatus{Conditions: []apiregistration.APIServiceCondition{{Type: "foo"}}}}
444 bar := &apiregistration.APIService{Status: apiregistration.APIServiceStatus{Conditions: []apiregistration.APIServiceCondition{{Type: "bar"}}}}
445
446 fakeClient := fake.NewSimpleClientset()
447 c := AvailableConditionController{
448 apiServiceClient: fakeClient.ApiregistrationV1().(apiregistrationclient.APIServicesGetter),
449 metrics: newAvailabilityMetrics(),
450 }
451
452 c.updateAPIServiceStatus(foo, foo)
453 if e, a := 0, len(fakeClient.Actions()); e != a {
454 t.Error(dump.Pretty(fakeClient.Actions()))
455 }
456
457 fakeClient.ClearActions()
458 c.updateAPIServiceStatus(foo, bar)
459 if e, a := 1, len(fakeClient.Actions()); e != a {
460 t.Error(dump.Pretty(fakeClient.Actions()))
461 }
462 }
463
464 func emptyCert() []byte {
465 return []byte{}
466 }
467
View as plain text