Skip to content

Commit

Permalink
Created mock tests for ongoing Passive Health Check feature (#2938)
Browse files Browse the repository at this point in the history
There are three tests with the similar idea:
1) send a lot of requests
2) check for failure rate to be as expected
3) let endpointregistry update stats related to PHC
4) send a lot of requests
5) check for failure rate to be as expected after stats update

The cases are:
+ single healthy endpoint
+ multiple healthy endpoints
+ multiple healthy + single unhealthy endpoint

Signed-off-by: Roman Zavodskikh <[email protected]>
Co-authored-by: Roman Zavodskikh <[email protected]>
  • Loading branch information
RomanZavodskikh and Roman Zavodskikh authored Feb 15, 2024
1 parent ecb8c0b commit 885c8a5
Showing 1 changed file with 227 additions and 0 deletions.
227 changes: 227 additions & 0 deletions proxy/healthy_endpoints_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
package proxy

import (
"fmt"
"math/rand"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/zalando/skipper/loadbalancer"
"github.com/zalando/skipper/routing"
)

const (
nRequests = 10_000
rtFailureProbability = 0.8
period = 1 * time.Second
)

func defaultEndpointRegistry() *routing.EndpointRegistry {
return routing.NewEndpointRegistry(routing.RegistryOptions{})
}

func TestPHCForSingleHealthyEndpoint(t *testing.T) {
service := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
defer service.Close()
endpointRegistry := defaultEndpointRegistry()

doc := fmt.Sprintf(`* -> "%s"`, service.URL)
tp, err := newTestProxyWithParams(doc, Params{
EndpointRegistry: endpointRegistry,
})
if err != nil {
t.Fatal(err)
}
defer tp.close()

ps := httptest.NewServer(tp.proxy)
defer ps.Close()

failedReqs := 0
for i := 0; i < nRequests; i++ {
rsp, err := ps.Client().Get(ps.URL)
if err != nil {
t.Fatal(err)
}

if rsp.StatusCode != http.StatusOK {
failedReqs++
}
rsp.Body.Close()
}
assert.Equal(t, 0, failedReqs)

// Let endpointregistry update all stats
time.Sleep(period + time.Millisecond)
dummy := fmt.Sprintf(`Header("Foo", "Bar") -> "%s"`, service.URL)
tp.dc.UpdateDoc(dummy, nil)
tp.dc.UpdateDoc(doc, nil)
time.Sleep(10 * time.Millisecond)

failedReqs = 0
for i := 0; i < nRequests; i++ {
rsp, err := ps.Client().Get(ps.URL)
if err != nil {
t.Fatal(err)
}

if rsp.StatusCode != http.StatusOK {
failedReqs++
}
rsp.Body.Close()
}
assert.Equal(t, 0, failedReqs)
}

func TestPHCForMultipleHealthyEndpoints(t *testing.T) {
services := []*httptest.Server{}
for i := 0; i < 3; i++ {
service := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
services = append(services, service)
defer service.Close()
}
endpointRegistry := defaultEndpointRegistry()

doc := fmt.Sprintf(`* -> <random, "%s", "%s", "%s">`, services[0].URL, services[1].URL, services[2].URL)
tp, err := newTestProxyWithParams(doc, Params{
EndpointRegistry: endpointRegistry,
})
if err != nil {
t.Fatal(err)
}
defer tp.close()

ps := httptest.NewServer(tp.proxy)
defer ps.Close()

failedReqs := 0
for i := 0; i < nRequests; i++ {
rsp, err := ps.Client().Get(ps.URL)
if err != nil {
t.Fatal(err)
}

if rsp.StatusCode != http.StatusOK {
failedReqs++
}
rsp.Body.Close()
}
assert.Equal(t, 0, failedReqs)

// Let endpointregistry update all stats
time.Sleep(period + time.Millisecond)
dummy := fmt.Sprintf(`* -> <random, "%s", "%s">`, services[0].URL, services[1].URL)
tp.dc.UpdateDoc(dummy, nil)
tp.dc.UpdateDoc(doc, nil)
time.Sleep(10 * time.Millisecond)

failedReqs = 0
for i := 0; i < nRequests; i++ {
rsp, err := ps.Client().Get(ps.URL)
if err != nil {
t.Fatal(err)
}

if rsp.StatusCode != http.StatusOK {
failedReqs++
}
rsp.Body.Close()
}
assert.Equal(t, 0, failedReqs)
}

type roundTripperUnhealthyHost struct {
inner http.RoundTripper
host string
probability float64
rnd *rand.Rand
}

type RoundTripperUnhealthyHostParams struct {
Host string
Probability float64
}

func (rt *roundTripperUnhealthyHost) RoundTrip(r *http.Request) (*http.Response, error) {
p := rt.rnd.Float64()
if p < rt.probability && r.URL.Host == rt.host {
return nil, fmt.Errorf("roundTrip fail injected")
}

return rt.inner.RoundTrip(r)
}

func newRoundTripperUnhealthyHost(o *RoundTripperUnhealthyHostParams) func(r http.RoundTripper) http.RoundTripper {
return func(r http.RoundTripper) http.RoundTripper {
return &roundTripperUnhealthyHost{inner: r, rnd: rand.New(loadbalancer.NewLockedSource()), host: o.Host, probability: o.Probability}
}
}

func TestPHCForMultipleHealthyAndOneUnhealthyEndpoints(t *testing.T) {
services := []*httptest.Server{}
for i := 0; i < 3; i++ {
service := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))
services = append(services, service)
defer service.Close()
}
endpointRegistry := defaultEndpointRegistry()

doc := fmt.Sprintf(`* -> <random, "%s", "%s", "%s">`, services[0].URL, services[1].URL, services[2].URL)
tp, err := newTestProxyWithParams(doc, Params{
EndpointRegistry: endpointRegistry,
CustomHttpRoundTripperWrap: newRoundTripperUnhealthyHost(&RoundTripperUnhealthyHostParams{Host: services[0].URL[7:], Probability: rtFailureProbability}),
})
if err != nil {
t.Fatal(err)
}
defer tp.close()

ps := httptest.NewServer(tp.proxy)
defer ps.Close()

failedReqs := 0
for i := 0; i < nRequests; i++ {
rsp, err := ps.Client().Get(ps.URL)
if err != nil {
t.Fatal(err)
}

if rsp.StatusCode != http.StatusOK {
failedReqs++
}
rsp.Body.Close()
}
assert.InDelta(t, 0.33*rtFailureProbability*nRequests, failedReqs, 0.05*nRequests)

// Let endpointregistry update all stats
time.Sleep(period + time.Millisecond)
dummy := fmt.Sprintf(`* -> <random, "%s", "%s">`, services[0].URL, services[1].URL)
tp.dc.UpdateDoc(dummy, nil)
tp.dc.UpdateDoc(doc, nil)
time.Sleep(10 * time.Millisecond)

failedReqs = 0
for i := 0; i < nRequests; i++ {
rsp, err := ps.Client().Get(ps.URL)
if err != nil {
t.Fatal(err)
}

if rsp.StatusCode != http.StatusOK {
failedReqs++
}
rsp.Body.Close()
}
assert.InDelta(t, 0.33*rtFailureProbability*nRequests, failedReqs, 0.05*nRequests)
// After PHC is implemented, I expect failed requests to decrease like this:
// assert.InDelta(t, 0.33*rtFailureProbability*(1.0-rtFailureProbability)*nRequests, failedReqs, 0.05*nRequests)
}

0 comments on commit 885c8a5

Please sign in to comment.