Changes CustomHTTPErrors annotation to use custom default backend
Updates e2e test Removes focus from e2e test Fixes renamed function Adds tests for new template funcs Addresses gofmt Updates e2e test, fixes custom-default-backend test by creating service Updates docs
This commit is contained in:
parent
7b2495047f
commit
3865e30a00
8 changed files with 240 additions and 86 deletions
|
|
@ -581,24 +581,25 @@ func (n *NGINXController) getBackendServers(ingresses []*ingress.Ingress) ([]*in
|
|||
isHTTPSfrom := []*ingress.Server{}
|
||||
for _, server := range servers {
|
||||
for _, location := range server.Locations {
|
||||
if upstream.Name == location.Backend {
|
||||
if len(upstream.Endpoints) == 0 {
|
||||
klog.V(3).Infof("Upstream %q has no active Endpoint", upstream.Name)
|
||||
// check if the location contains endpoints and a custom default backend
|
||||
if location.DefaultBackend != nil {
|
||||
sp := location.DefaultBackend.Spec.Ports[0]
|
||||
endps := getEndpoints(location.DefaultBackend, &sp, apiv1.ProtocolTCP, n.store.GetServiceEndpoints)
|
||||
if len(endps) > 0 {
|
||||
klog.V(3).Infof("Using custom default backend for location %q in server %q (Service \"%v/%v\")",
|
||||
location.Path, server.Hostname, location.DefaultBackend.Namespace, location.DefaultBackend.Name)
|
||||
if shouldCreateUpstreamForLocationDefaultBackend(upstream, location) {
|
||||
sp := location.DefaultBackend.Spec.Ports[0]
|
||||
endps := getEndpoints(location.DefaultBackend, &sp, apiv1.ProtocolTCP, n.store.GetServiceEndpoints)
|
||||
if len(endps) > 0 {
|
||||
|
||||
nb := upstream.DeepCopy()
|
||||
name := fmt.Sprintf("custom-default-backend-%v", upstream.Name)
|
||||
nb.Name = name
|
||||
nb.Endpoints = endps
|
||||
aUpstreams = append(aUpstreams, nb)
|
||||
location.Backend = name
|
||||
}
|
||||
name := fmt.Sprintf("custom-default-backend-%v", location.DefaultBackend.GetName())
|
||||
klog.V(3).Infof("Creating \"%v\" upstream based on default backend annotation", name)
|
||||
|
||||
nb := upstream.DeepCopy()
|
||||
nb.Name = name
|
||||
nb.Endpoints = endps
|
||||
aUpstreams = append(aUpstreams, nb)
|
||||
location.DefaultBackendUpstreamName = name
|
||||
|
||||
if len(upstream.Endpoints) == 0 {
|
||||
klog.V(3).Infof("Upstream %q has no active Endpoint, so using custom default backend for location %q in server %q (Service \"%v/%v\")",
|
||||
upstream.Name, location.Path, server.Hostname, location.DefaultBackend.Namespace, location.DefaultBackend.Name)
|
||||
|
||||
location.Backend = name
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -611,6 +612,8 @@ func (n *NGINXController) getBackendServers(ingresses []*ingress.Ingress) ([]*in
|
|||
isHTTPSfrom = append(isHTTPSfrom, server)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
location.DefaultBackendUpstreamName = "upstream-default-backend"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1347,3 +1350,10 @@ func getRemovedIngresses(rucfg, newcfg *ingress.Configuration) []string {
|
|||
|
||||
return oldIngresses.Difference(newIngresses).List()
|
||||
}
|
||||
|
||||
// checks conditions for whether or not an upstream should be created for a custom default backend
|
||||
func shouldCreateUpstreamForLocationDefaultBackend(upstream *ingress.Backend, location *ingress.Location) bool {
|
||||
return (upstream.Name == location.Backend) &&
|
||||
(len(upstream.Endpoints) == 0 || len(location.CustomHTTPErrors) != 0) &&
|
||||
location.DefaultBackend != nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import (
|
|||
"os/exec"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
text_template "text/template"
|
||||
"time"
|
||||
|
|
@ -150,17 +151,17 @@ var (
|
|||
"serverConfig": func(all config.TemplateConfig, server *ingress.Server) interface{} {
|
||||
return struct{ First, Second interface{} }{all, server}
|
||||
},
|
||||
"isValidByteSize": isValidByteSize,
|
||||
"buildForwardedFor": buildForwardedFor,
|
||||
"buildAuthSignURL": buildAuthSignURL,
|
||||
"buildOpentracing": buildOpentracing,
|
||||
"proxySetHeader": proxySetHeader,
|
||||
"buildInfluxDB": buildInfluxDB,
|
||||
"enforceRegexModifier": enforceRegexModifier,
|
||||
"stripLocationModifer": stripLocationModifer,
|
||||
"buildCustomErrorDeps": buildCustomErrorDeps,
|
||||
"collectCustomErrorsPerServer": collectCustomErrorsPerServer,
|
||||
"opentracingPropagateContext": opentracingPropagateContext,
|
||||
"isValidByteSize": isValidByteSize,
|
||||
"buildForwardedFor": buildForwardedFor,
|
||||
"buildAuthSignURL": buildAuthSignURL,
|
||||
"buildOpentracing": buildOpentracing,
|
||||
"proxySetHeader": proxySetHeader,
|
||||
"buildInfluxDB": buildInfluxDB,
|
||||
"enforceRegexModifier": enforceRegexModifier,
|
||||
"stripLocationModifer": stripLocationModifer,
|
||||
"buildCustomErrorDeps": buildCustomErrorDeps,
|
||||
"opentracingPropagateContext": opentracingPropagateContext,
|
||||
"buildCustomErrorLocationsPerServer": buildCustomErrorLocationsPerServer,
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -884,41 +885,72 @@ func proxySetHeader(loc interface{}) string {
|
|||
|
||||
// buildCustomErrorDeps is a utility function returning a struct wrapper with
|
||||
// the data required to build the 'CUSTOM_ERRORS' template
|
||||
func buildCustomErrorDeps(proxySetHeaders map[string]string, errorCodes []int, enableMetrics bool) interface{} {
|
||||
func buildCustomErrorDeps(upstreamName string, errorCodes []int, enableMetrics bool) interface{} {
|
||||
return struct {
|
||||
ProxySetHeaders map[string]string
|
||||
ErrorCodes []int
|
||||
EnableMetrics bool
|
||||
UpstreamName string
|
||||
ErrorCodes []int
|
||||
EnableMetrics bool
|
||||
}{
|
||||
ProxySetHeaders: proxySetHeaders,
|
||||
ErrorCodes: errorCodes,
|
||||
EnableMetrics: enableMetrics,
|
||||
UpstreamName: upstreamName,
|
||||
ErrorCodes: errorCodes,
|
||||
EnableMetrics: enableMetrics,
|
||||
}
|
||||
}
|
||||
|
||||
// collectCustomErrorsPerServer is a utility function which will collect all
|
||||
type errorLocation struct {
|
||||
UpstreamName string
|
||||
Codes []int
|
||||
}
|
||||
|
||||
// buildCustomErrorLocationsPerServer is a utility function which will collect all
|
||||
// custom error codes for all locations of a server block, deduplicates them,
|
||||
// and returns a unique set (for the template to create @custom_xxx locations)
|
||||
func collectCustomErrorsPerServer(input interface{}) []int {
|
||||
// and returns a set which is unique by default-upstream and error code. It returns an array
|
||||
// of errorLocations, each of which contain the upstream name and a list of
|
||||
// error codes for that given upstream, so that sufficiently unique
|
||||
// @custom error location blocks can be created in the template
|
||||
func buildCustomErrorLocationsPerServer(input interface{}) interface{} {
|
||||
server, ok := input.(*ingress.Server)
|
||||
if !ok {
|
||||
klog.Errorf("expected a '*ingress.Server' type but %T was returned", input)
|
||||
return nil
|
||||
}
|
||||
|
||||
codesMap := make(map[int]bool)
|
||||
codesMap := make(map[string]map[int]bool)
|
||||
for _, loc := range server.Locations {
|
||||
for _, code := range loc.CustomHTTPErrors {
|
||||
codesMap[code] = true
|
||||
backendUpstream := loc.DefaultBackendUpstreamName
|
||||
|
||||
var dedupedCodes map[int]bool
|
||||
if existingMap, ok := codesMap[backendUpstream]; ok {
|
||||
dedupedCodes = existingMap
|
||||
} else {
|
||||
dedupedCodes = make(map[int]bool)
|
||||
}
|
||||
|
||||
for _, code := range loc.CustomHTTPErrors {
|
||||
dedupedCodes[code] = true
|
||||
}
|
||||
codesMap[backendUpstream] = dedupedCodes
|
||||
}
|
||||
|
||||
uniqueCodes := make([]int, 0, len(codesMap))
|
||||
for key := range codesMap {
|
||||
uniqueCodes = append(uniqueCodes, key)
|
||||
errorLocations := []errorLocation{}
|
||||
|
||||
for upstream, dedupedCodes := range codesMap {
|
||||
codesForUpstream := []int{}
|
||||
for code := range dedupedCodes {
|
||||
codesForUpstream = append(codesForUpstream, code)
|
||||
}
|
||||
sort.Ints(codesForUpstream)
|
||||
errorLocations = append(errorLocations, errorLocation{
|
||||
UpstreamName: upstream,
|
||||
Codes: codesForUpstream,
|
||||
})
|
||||
}
|
||||
|
||||
return uniqueCodes
|
||||
sort.Slice(errorLocations, func(i, j int) bool {
|
||||
return errorLocations[i].UpstreamName < errorLocations[j].UpstreamName
|
||||
})
|
||||
|
||||
return errorLocations
|
||||
}
|
||||
|
||||
func opentracingPropagateContext(loc interface{}) string {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ import (
|
|||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
|
@ -903,26 +902,106 @@ func TestGetIngressInformation(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCollectCustomErrorsPerServer(t *testing.T) {
|
||||
invalidType := &ingress.Ingress{}
|
||||
customErrors := collectCustomErrorsPerServer(invalidType)
|
||||
if customErrors != nil {
|
||||
t.Errorf("Expected %v but returned %v", nil, customErrors)
|
||||
}
|
||||
|
||||
server := &ingress.Server{
|
||||
Locations: []*ingress.Location{
|
||||
{CustomHTTPErrors: []int{404, 405}},
|
||||
{CustomHTTPErrors: []int{404, 500}},
|
||||
func TestBuildCustomErrorLocationsPerServer(t *testing.T) {
|
||||
testCases := []struct {
|
||||
server interface{}
|
||||
expectedResults []errorLocation
|
||||
}{
|
||||
{ // Single ingress
|
||||
&ingress.Server{Locations: []*ingress.Location{
|
||||
{
|
||||
DefaultBackendUpstreamName: "custom-default-backend-test-backend",
|
||||
CustomHTTPErrors: []int{401, 402},
|
||||
},
|
||||
}},
|
||||
[]errorLocation{
|
||||
{
|
||||
UpstreamName: "custom-default-backend-test-backend",
|
||||
Codes: []int{401, 402},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ // Two ingresses, overlapping error codes, same backend
|
||||
&ingress.Server{Locations: []*ingress.Location{
|
||||
{
|
||||
DefaultBackendUpstreamName: "custom-default-backend-test-backend",
|
||||
CustomHTTPErrors: []int{401, 402},
|
||||
},
|
||||
{
|
||||
DefaultBackendUpstreamName: "custom-default-backend-test-backend",
|
||||
CustomHTTPErrors: []int{402, 403},
|
||||
},
|
||||
}},
|
||||
[]errorLocation{
|
||||
{
|
||||
UpstreamName: "custom-default-backend-test-backend",
|
||||
Codes: []int{401, 402, 403},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ // Two ingresses, overlapping error codes, different backends
|
||||
&ingress.Server{Locations: []*ingress.Location{
|
||||
{
|
||||
DefaultBackendUpstreamName: "custom-default-backend-test-one",
|
||||
CustomHTTPErrors: []int{401, 402},
|
||||
},
|
||||
{
|
||||
DefaultBackendUpstreamName: "custom-default-backend-test-two",
|
||||
CustomHTTPErrors: []int{402, 403},
|
||||
},
|
||||
}},
|
||||
[]errorLocation{
|
||||
{
|
||||
UpstreamName: "custom-default-backend-test-one",
|
||||
Codes: []int{401, 402},
|
||||
},
|
||||
{
|
||||
UpstreamName: "custom-default-backend-test-two",
|
||||
Codes: []int{402, 403},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ // Many ingresses, overlapping error codes, different backends
|
||||
&ingress.Server{Locations: []*ingress.Location{
|
||||
{
|
||||
DefaultBackendUpstreamName: "custom-default-backend-test-one",
|
||||
CustomHTTPErrors: []int{401, 402},
|
||||
},
|
||||
{
|
||||
DefaultBackendUpstreamName: "custom-default-backend-test-one",
|
||||
CustomHTTPErrors: []int{501, 502},
|
||||
},
|
||||
{
|
||||
DefaultBackendUpstreamName: "custom-default-backend-test-two",
|
||||
CustomHTTPErrors: []int{409, 410},
|
||||
},
|
||||
{
|
||||
DefaultBackendUpstreamName: "custom-default-backend-test-two",
|
||||
CustomHTTPErrors: []int{504, 505},
|
||||
},
|
||||
}},
|
||||
[]errorLocation{
|
||||
{
|
||||
UpstreamName: "custom-default-backend-test-one",
|
||||
Codes: []int{401, 402, 501, 502},
|
||||
},
|
||||
{
|
||||
UpstreamName: "custom-default-backend-test-two",
|
||||
Codes: []int{409, 410, 504, 505},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := []int{404, 405, 500}
|
||||
uniqueCodes := collectCustomErrorsPerServer(server)
|
||||
sort.Ints(uniqueCodes)
|
||||
|
||||
if !reflect.DeepEqual(expected, uniqueCodes) {
|
||||
t.Errorf("Expected '%v' but got '%v'", expected, uniqueCodes)
|
||||
for _, c := range testCases {
|
||||
response := buildCustomErrorLocationsPerServer(c.server)
|
||||
if results, ok := response.([]errorLocation); ok {
|
||||
if !reflect.DeepEqual(c.expectedResults, results) {
|
||||
t.Errorf("Expected %+v but got %+v", c.expectedResults, results)
|
||||
}
|
||||
} else {
|
||||
t.Error("Unable to convert to []errorLocation")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -287,6 +287,9 @@ type Location struct {
|
|||
// DefaultBackend allows the use of a custom default backend for this location.
|
||||
// +optional
|
||||
DefaultBackend *apiv1.Service `json:"defaultBackend,omitempty"`
|
||||
// DefaultBackendUpstreamName is the upstream-formatted string for the name of
|
||||
// this location's custom default backend
|
||||
DefaultBackendUpstreamName string `json:"defaultBackendUpstreamName,omitempty"`
|
||||
// XForwardedPrefix allows to add a header X-Forwarded-Prefix to the request with the
|
||||
// original location.
|
||||
// +optional
|
||||
|
|
|
|||
|
|
@ -479,6 +479,10 @@ func (l1 *Location) Equal(l2 *Location) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
if l1.DefaultBackendUpstreamName != l2.DefaultBackendUpstreamName {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue