...

Source file src/k8s.io/kube-aggregator/pkg/controllers/status/available_controller_test.go

Documentation: k8s.io/kube-aggregator/pkg/controllers/status

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    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  			// We want a fairly tight requeue time.  The controller listens to the API, but because it relies on the routability of the
   131  			// service network, it is possible for an external, non-watchable factor to affect availability.  This keeps
   132  			// the maximum disruption time to a minimum, but it does prevent hot loops.
   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  	// model 1 APIService pointing at a given service, and 30 pointing at local group/versions
   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  	// model one service backing an API service, and 100 unrelated services
   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  			// ought to have one action writing status
   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  			// ought to have one action writing status
   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