Skip to content

Commit

Permalink
Merge pull request #17155 from knobunc/bug/bz1507664-suppress-health-…
Browse files Browse the repository at this point in the history
…checks

Automatic merge from submit-queue.

Bug/bz1507664 suppress health checks

Fix the "supress health checks when only one backing service" logic

This reverts commit 9835dca because it is wrong, then the second commit implements the correct change.
    
Previously we had attempted to suppress the health checks, but it was looking at the endpoints of the service, not the servers that back the route. The problem is that if we just look at endpoints, then if a route has two backing services, each with one endpoint, we would incorrectly suppress the health checks.
    
This is the third attempt.  I had missed a case in the last one, so now the logic is moved to be computed and recorded by the router controller, but used in the template.
    
Fixes bug 1507664 (https://bugzilla.redhat.com/show_bug.cgi?id=1507664)
  • Loading branch information
openshift-merge-robot authored Nov 3, 2017
2 parents b65ef46 + 8cb7b95 commit fc8725b
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 200 deletions.
6 changes: 2 additions & 4 deletions images/router/haproxy/conf/haproxy-config.template
Original file line number Diff line number Diff line change
Expand Up @@ -410,10 +410,8 @@ backend be_secure:{{$cfgIdx}}
{{- else if or (eq $cfg.TLSTermination "") (eq $cfg.TLSTermination "edge") }}
{{- end }}{{/* end type specific options*/}}

{{- if not $endpoint.NoHealthCheck }} check inter {{firstMatch $timeSpecPattern (index $cfg.Annotations "router.openshift.io/haproxy.health.check.interval") (env "ROUTER_BACKEND_CHECK_INTERVAL") "5000ms"}}
{{- if and (not $endpoint.NoHealthCheck) (gt $cfg.ActiveEndpoints 1) }} check inter {{firstMatch $timeSpecPattern (index $cfg.Annotations "router.openshift.io/haproxy.health.check.interval") (env "ROUTER_BACKEND_CHECK_INTERVAL") "5000ms"}}
{{- end }}{{/* end else no health check */}}


{{- end }}{{/* end if cg.TLSTermination */}}
{{- end }}{{/* end range processEndpointsForAlias */}}
{{- end }}{{/* end get serviceUnit from its name */}}
Expand Down Expand Up @@ -464,7 +462,7 @@ backend be_tcp:{{$cfgIdx}}
{{- with $serviceUnit := index $.ServiceUnits $serviceUnitName }}
{{- range $idx, $endpoint := processEndpointsForAlias $cfg $serviceUnit (env "ROUTER_BACKEND_PROCESS_ENDPOINTS" "") }}
server {{$endpoint.ID}} {{$endpoint.IP}}:{{$endpoint.Port}} weight {{$weight}}
{{- if not $endpoint.NoHealthCheck }} check inter {{firstMatch $timeSpecPattern (index $cfg.Annotations "router.openshift.io/haproxy.health.check.interval") (env "ROUTER_BACKEND_CHECK_INTERVAL") "5000ms"}}
{{- if and (not $endpoint.NoHealthCheck) (gt $cfg.ActiveEndpoints 1) }} check inter {{firstMatch $timeSpecPattern (index $cfg.Annotations "router.openshift.io/haproxy.health.check.interval") (env "ROUTER_BACKEND_CHECK_INTERVAL") "5000ms"}}
{{- end }}{{/* end else no health check */}}
{{- end }}{{/* end range processEndpointsForAlias */}}
{{- end }}{{/* end get ServiceUnit from serviceUnitName */}}
Expand Down
52 changes: 38 additions & 14 deletions pkg/router/template/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,9 @@ func (r *templateRouter) writeConfig() error {
// called here to make sure we have the actual number of endpoints.
cfg.ServiceUnitNames = r.calculateServiceWeights(cfg.ServiceUnits)

// Calculate the number of active endpoints for the route.
cfg.ActiveEndpoints = r.getActiveEndpoints(cfg.ServiceUnits)

cfg.Status = ServiceAliasConfigStatusSaved
r.state[k] = cfg
}
Expand Down Expand Up @@ -877,28 +880,49 @@ func getServiceUnits(route *routeapi.Route) map[string]int32 {

// get the weight and number of endpoints for each service
key := endpointsKeyFromParts(route.Namespace, route.Spec.To.Name)
serviceUnits[key] = 0
if route.Spec.To.Weight != nil {
serviceUnits[key] = int32(*route.Spec.To.Weight)
}
if serviceUnits[key] < 0 || serviceUnits[key] > 256 {
serviceUnits[key] = 0
}
serviceUnits[key] = getServiceUnitWeight(route.Spec.To.Weight)

for _, svc := range route.Spec.AlternateBackends {
key = endpointsKeyFromParts(route.Namespace, svc.Name)
serviceUnits[key] = 0
if svc.Weight != nil {
serviceUnits[key] = int32(*svc.Weight)
}
if serviceUnits[key] < 0 || serviceUnits[key] > 256 {
serviceUnits[key] = 0
}
serviceUnits[key] = getServiceUnitWeight(svc.Weight)
}

return serviceUnits
}

// getServiceUnitWeight takes a reference to a weight and returns its value or the default.
// It also checks that it is in the correct range.
func getServiceUnitWeight(weightRef *int32) int32 {
// Default to 1 if there is no weight
var weight int32 = 1
if weightRef != nil {
weight = *weightRef
}

// Do a bounds check
if weight < 0 {
weight = 0
} else if weight > 256 {
weight = 256
}

return weight
}

// getActiveEndpoints calculates the number of endpoints that are not associated
// with service units with a zero weight and returns the count.
func (r *templateRouter) getActiveEndpoints(serviceUnits map[string]int32) int {
var activeEndpoints int32 = 0

for key, weight := range serviceUnits {
if weight > 0 {
activeEndpoints += r.numberOfEndpoints(key)
}
}

return int(activeEndpoints)
}

// calculateServiceWeights returns a map of service keys to their weights.
// Each service gets (weight/sum_of_weights) fraction of the requests.
// For each service, the requests are distributed among the endpoints.
Expand Down
20 changes: 4 additions & 16 deletions pkg/router/template/template_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,28 +157,16 @@ func processEndpointsForAlias(alias ServiceAliasConfig, svc ServiceUnit, action
}

func endpointsForAlias(alias ServiceAliasConfig, svc ServiceUnit) []Endpoint {
if len(alias.PreferPort) == 0 && len(svc.EndpointTable) != 1 {
// We can skip the work. They are selecting everything, and since they don't have one endpoint,
// we can't disable the health checks. So we can just use their list.
if len(alias.PreferPort) == 0 {
return svc.EndpointTable
}

endpoints := make([]Endpoint, 0, len(svc.EndpointTable))
for _, endpoint := range svc.EndpointTable {
// Filter the list:
// - If PreferPort length is 0, match everything
// - Otherwise, if the PortName or Port matches PreferPort, then we have a match
if len(alias.PreferPort) == 0 || endpoint.PortName == alias.PreferPort || endpoint.Port == alias.PreferPort {
for i := range svc.EndpointTable {
endpoint := svc.EndpointTable[i]
if endpoint.PortName == alias.PreferPort || endpoint.Port == alias.PreferPort {
endpoints = append(endpoints, endpoint)
}
}

// We want to disable endpoint checks if there is only one endpoint since there's no point in
// testing and removing it from the set ahead of time since there are no other eps to replace it.
if len(endpoints) == 1 {
endpoints[0].NoHealthCheck = true
}

return endpoints
}

Expand Down
166 changes: 0 additions & 166 deletions pkg/router/template/template_helper_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package templaterouter

import (
"reflect"
"regexp"
"testing"
)
Expand Down Expand Up @@ -298,168 +297,3 @@ func TestMatchPattern(t *testing.T) {
}
}
}

func TestEndpointsForAlias(t *testing.T) {
testEndpoints := []struct {
name string
preferPort string
eps []Endpoint
expected []Endpoint
}{
{
name: "Test no preference with a single endpoint",
preferPort: "",
eps: []Endpoint{
{
ID: "ep1",
Port: "80",
PortName: "http",
},
},
expected: []Endpoint{
{
ID: "ep1",
Port: "80",
PortName: "http",
NoHealthCheck: true,
},
},
},
{
name: "Test no preference with multiple",
preferPort: "",
eps: []Endpoint{
{
ID: "ep1",
Port: "80",
PortName: "http",
},
{
ID: "ep1",
Port: "443",
PortName: "https",
},
{
ID: "ep2",
Port: "80",
PortName: "http",
},
{
ID: "ep3",
Port: "80",
PortName: "http",
},
},
expected: []Endpoint{
{
ID: "ep1",
Port: "80",
PortName: "http",
},
{
ID: "ep1",
Port: "443",
PortName: "https",
},
{
ID: "ep2",
Port: "80",
PortName: "http",
},
{
ID: "ep3",
Port: "80",
PortName: "http",
},
},
},
{
name: "Test a preference resulting in multiple",
preferPort: "http",
eps: []Endpoint{
{
ID: "ep1",
Port: "80",
PortName: "http",
},
{
ID: "ep1",
Port: "443",
PortName: "https",
},
{
ID: "ep2",
Port: "80",
PortName: "http",
},
{
ID: "ep3",
Port: "80",
PortName: "http",
},
},
expected: []Endpoint{
{
ID: "ep1",
Port: "80",
PortName: "http",
},
{
ID: "ep2",
Port: "80",
PortName: "http",
},
{
ID: "ep3",
Port: "80",
PortName: "http",
},
},
},
{
name: "Test a preference resulting in one",
preferPort: "https",
eps: []Endpoint{
{
ID: "ep1",
Port: "80",
PortName: "http",
},
{
ID: "ep1",
Port: "443",
PortName: "https",
},
{
ID: "ep2",
Port: "80",
PortName: "http",
},
{
ID: "ep3",
Port: "80",
PortName: "http",
},
},
expected: []Endpoint{
{
ID: "ep1",
Port: "443",
PortName: "https",
NoHealthCheck: true,
},
},
},
}

for _, te := range testEndpoints {
eps := endpointsForAlias(
ServiceAliasConfig{PreferPort: te.preferPort},
ServiceUnit{EndpointTable: te.eps},
)

if !reflect.DeepEqual(te.expected, eps) {
t.Errorf("%s: expected result: %#v, got: %#v", te.name, te.expected, eps)
}
}
}
3 changes: 3 additions & 0 deletions pkg/router/template/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ type ServiceAliasConfig struct {

// ActiveServiceUnits is a count of the service units with a non-zero weight
ActiveServiceUnits int

// ActiveEndpoints is a count of the route endpoints that are part of a service unit with a non-zero weight
ActiveEndpoints int
}

type ServiceAliasConfigStatus string
Expand Down

0 comments on commit fc8725b

Please sign in to comment.