mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-20 18:31:15 +00:00
Merge pull request #93030 from swetharepakula/endpoint-tracker
Requeue Service after Endpoint Deletion
This commit is contained in:
commit
15a3d46db1
@ -110,6 +110,9 @@ func NewEndpointController(podInformer coreinformers.PodInformer, serviceInforme
|
|||||||
e.podLister = podInformer.Lister()
|
e.podLister = podInformer.Lister()
|
||||||
e.podsSynced = podInformer.Informer().HasSynced
|
e.podsSynced = podInformer.Informer().HasSynced
|
||||||
|
|
||||||
|
endpointsInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||||
|
DeleteFunc: e.onEndpointsDelete,
|
||||||
|
})
|
||||||
e.endpointsLister = endpointsInformer.Lister()
|
e.endpointsLister = endpointsInformer.Lister()
|
||||||
e.endpointsSynced = endpointsInformer.Informer().HasSynced
|
e.endpointsSynced = endpointsInformer.Informer().HasSynced
|
||||||
|
|
||||||
@ -287,6 +290,15 @@ func (e *EndpointController) onServiceDelete(obj interface{}) {
|
|||||||
e.queue.Add(key)
|
e.queue.Add(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *EndpointController) onEndpointsDelete(obj interface{}) {
|
||||||
|
key, err := controller.KeyFunc(obj)
|
||||||
|
if err != nil {
|
||||||
|
utilruntime.HandleError(fmt.Errorf("Couldn't get key for object %+v: %v", obj, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.queue.Add(key)
|
||||||
|
}
|
||||||
|
|
||||||
// worker runs a worker thread that just dequeues items, processes them, and
|
// worker runs a worker thread that just dequeues items, processes them, and
|
||||||
// marks them done. You may run as many of these in parallel as you wish; the
|
// marks them done. You may run as many of these in parallel as you wish; the
|
||||||
// workqueue guarantees that they will not end up processing the same service
|
// workqueue guarantees that they will not end up processing the same service
|
||||||
|
@ -159,6 +159,49 @@ func makeTestServer(t *testing.T, namespace string) (*httptest.Server, *utiltest
|
|||||||
return httptest.NewServer(mux), &fakeEndpointsHandler
|
return httptest.NewServer(mux), &fakeEndpointsHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// makeBlockingEndpointDeleteTestServer will signal the blockNextAction channel on endpoint "POST" & "DELETE" requests. All
|
||||||
|
// block endpoint "DELETE" requestsi will wait on a blockDelete signal to delete endpoint. If controller is nil, a error will
|
||||||
|
// be sent in the response.
|
||||||
|
func makeBlockingEndpointDeleteTestServer(t *testing.T, controller *endpointController, endpoint *v1.Endpoints, blockDelete, blockNextAction chan struct{}, namespace string) *httptest.Server {
|
||||||
|
|
||||||
|
handlerFunc := func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
if controller == nil {
|
||||||
|
res.WriteHeader(http.StatusInternalServerError)
|
||||||
|
res.Write([]byte("controller has not been set yet"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Method == "POST" {
|
||||||
|
controller.endpointsStore.Add(endpoint)
|
||||||
|
blockNextAction <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Method == "DELETE" {
|
||||||
|
go func() {
|
||||||
|
// Delay the deletion of endoints to make endpoint cache out of sync
|
||||||
|
<-blockDelete
|
||||||
|
controller.endpointsStore.Delete(endpoint)
|
||||||
|
controller.onEndpointsDelete(endpoint)
|
||||||
|
}()
|
||||||
|
blockNextAction <- struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.WriteHeader(http.StatusOK)
|
||||||
|
res.Write([]byte(runtime.EncodeOrDie(clientscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion), &v1.Endpoints{})))
|
||||||
|
}
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/api/v1/namespaces/"+namespace+"/endpoints", handlerFunc)
|
||||||
|
mux.HandleFunc("/api/v1/namespaces/"+namespace+"/endpoints/", handlerFunc)
|
||||||
|
mux.HandleFunc("/api/v1/namespaces/"+namespace+"/events", func(res http.ResponseWriter, req *http.Request) {})
|
||||||
|
mux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
|
||||||
|
t.Errorf("unexpected request: %v", req.RequestURI)
|
||||||
|
http.Error(res, "", http.StatusNotFound)
|
||||||
|
})
|
||||||
|
return httptest.NewServer(mux)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
type endpointController struct {
|
type endpointController struct {
|
||||||
*EndpointController
|
*EndpointController
|
||||||
podStore cache.Store
|
podStore cache.Store
|
||||||
@ -1954,9 +1997,105 @@ func TestEndpointPortFromServicePort(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestMultipleServiceChanges tests that endpoints that are not created because of an out of sync endpoints cache are eventually recreated
|
||||||
|
// A service will be created. After the endpoints exist, the service will be deleted and the endpoints will not be deleted from the cache immediately.
|
||||||
|
// After the service is recreated, the endpoints will be deleted replicating an out of sync cache. Expect that eventually the endpoints will be recreated.
|
||||||
|
func TestMultipleServiceChanges(t *testing.T) {
|
||||||
|
ns := metav1.NamespaceDefault
|
||||||
|
expectedSubsets := []v1.EndpointSubset{{
|
||||||
|
Addresses: []v1.EndpointAddress{
|
||||||
|
{IP: "1.2.3.4", NodeName: &emptyNodeName, TargetRef: &v1.ObjectReference{Kind: "Pod", Name: "pod0", Namespace: ns}},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
endpoint := &v1.Endpoints{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns, ResourceVersion: "1"},
|
||||||
|
Subsets: expectedSubsets,
|
||||||
|
}
|
||||||
|
|
||||||
|
controller := &endpointController{}
|
||||||
|
blockDelete := make(chan struct{})
|
||||||
|
blockNextAction := make(chan struct{})
|
||||||
|
stopChan := make(chan struct{})
|
||||||
|
testServer := makeBlockingEndpointDeleteTestServer(t, controller, endpoint, blockDelete, blockNextAction, ns)
|
||||||
|
defer testServer.Close()
|
||||||
|
|
||||||
|
*controller = *newController(testServer.URL, 0*time.Second)
|
||||||
|
addPods(controller.podStore, ns, 1, 1, 0, ipv4only)
|
||||||
|
|
||||||
|
go func() { controller.Run(1, stopChan) }()
|
||||||
|
|
||||||
|
svc := &v1.Service{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{Name: "foo", Namespace: ns},
|
||||||
|
Spec: v1.ServiceSpec{
|
||||||
|
Selector: map[string]string{"foo": "bar"},
|
||||||
|
ClusterIP: "None",
|
||||||
|
Ports: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
controller.serviceStore.Add(svc)
|
||||||
|
controller.onServiceUpdate(svc)
|
||||||
|
// blockNextAction should eventually unblock once server gets endpoint request.
|
||||||
|
waitForChanReceive(t, 1*time.Second, blockNextAction, "Service Add should have caused a request to be sent to the test server")
|
||||||
|
|
||||||
|
controller.serviceStore.Delete(svc)
|
||||||
|
controller.onServiceDelete(svc)
|
||||||
|
waitForChanReceive(t, 1*time.Second, blockNextAction, "Service Delete should have caused a request to be sent to the test server")
|
||||||
|
|
||||||
|
// If endpoints cache has not updated before service update is registered
|
||||||
|
// Services add will not trigger a Create endpoint request.
|
||||||
|
controller.serviceStore.Add(svc)
|
||||||
|
controller.onServiceUpdate(svc)
|
||||||
|
|
||||||
|
// Ensure the work queue has been processed by looping for up to a second to prevent flakes.
|
||||||
|
wait.PollImmediate(50*time.Millisecond, 1*time.Second, func() (bool, error) {
|
||||||
|
return controller.queue.Len() == 0, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Cause test server to delete endpoints
|
||||||
|
close(blockDelete)
|
||||||
|
waitForChanReceive(t, 1*time.Second, blockNextAction, "Endpoint should have been recreated")
|
||||||
|
|
||||||
|
close(blockNextAction)
|
||||||
|
close(stopChan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEndpointsDeletionEvents(t *testing.T) {
|
||||||
|
ns := metav1.NamespaceDefault
|
||||||
|
testServer, _ := makeTestServer(t, ns)
|
||||||
|
defer testServer.Close()
|
||||||
|
controller := newController(testServer.URL, 0)
|
||||||
|
store := controller.endpointsStore
|
||||||
|
ep1 := &v1.Endpoints{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: ns,
|
||||||
|
ResourceVersion: "rv1",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Unexpected and Expected Deletes
|
||||||
|
store.Delete(ep1)
|
||||||
|
controller.onEndpointsDelete(ep1)
|
||||||
|
|
||||||
|
if controller.queue.Len() != 1 {
|
||||||
|
t.Errorf("Expected one service to be in the queue, found %d", controller.queue.Len())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func stringVal(str *string) string {
|
func stringVal(str *string) string {
|
||||||
if str == nil {
|
if str == nil {
|
||||||
return "nil"
|
return "nil"
|
||||||
}
|
}
|
||||||
return *str
|
return *str
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// waitForChanReceive blocks up to the timeout waiting for the receivingChan to receive
|
||||||
|
func waitForChanReceive(t *testing.T, timeout time.Duration, receivingChan chan struct{}, errorMsg string) {
|
||||||
|
timer := time.NewTimer(timeout)
|
||||||
|
select {
|
||||||
|
case <-timer.C:
|
||||||
|
t.Errorf(errorMsg)
|
||||||
|
case <-receivingChan:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user