Skip to content

Commit dcb6ca2

Browse files
committed
Add sorting template helper code to sort haproxy map files so that wildcard
routes don't take precedence over routes that start with a number. Based on discussions in PR openshift#19076 to fix issue openshift#16724
1 parent ce742c8 commit dcb6ca2

File tree

6 files changed

+392
-5
lines changed

6 files changed

+392
-5
lines changed

images/router/haproxy/conf/haproxy-config.template

+15
Original file line numberDiff line numberDiff line change
@@ -580,3 +580,18 @@ backend be_tcp:{{$cfgIdx}}
580580
{{ end -}}
581581
{{ end -}}
582582
{{ end }}{{/* end cert_config map template */}}
583+
584+
585+
{{/*
586+
Sort the haproxy map files (based on map paths and group wildcards at end).
587+
Note: We don't sort the cert_config map `cert_config.map` as it doesn't matter.
588+
*/}}
589+
{{ define "/tmp/sort-haproxy-maps" -}}
590+
{{sortMap "/var/lib/haproxy/conf/os_wildcard_domain.map"}}
591+
{{sortMap "/var/lib/haproxy/conf/os_http_be.map"}}
592+
{{sortMap "/var/lib/haproxy/conf/os_edge_reencrypt_be.map"}}
593+
{{sortMap "/var/lib/haproxy/conf/os_route_http_redirect.map"}}
594+
{{sortMap "/var/lib/haproxy/conf/os_tcp_be.map"}}
595+
{{sortMap "/var/lib/haproxy/conf/os_sni_passthrough.map"}}
596+
{{ end -}}{{/* end sort maps */}}
597+

images/router/haproxy/reload-haproxy

-5
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,6 @@ function haproxyHealthCheck() {
7272
retries=20
7373

7474

75-
# sort the path based map files for the haproxy map_beg function
76-
for mapfile in "$haproxy_conf_dir"/*.map; do
77-
sort -r "$mapfile" -o "$mapfile"
78-
done
79-
8075
old_pids=$(ps -A -opid,args | grep haproxy | egrep -v -e 'grep|reload-haproxy' | awk '{print $1}' | tr '\n' ' ')
8176

8277
reload_status=0

pkg/router/template/template_helper.go

+22
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package templaterouter
22

33
import (
44
"fmt"
5+
"io/ioutil"
56
"math/rand"
67
"os"
78
"regexp"
@@ -12,6 +13,8 @@ import (
1213
"github.com/golang/glog"
1314

1415
routeapi "github.com/openshift/origin/pkg/route/apis/route"
16+
templateutil "github.com/openshift/origin/pkg/router/template/util"
17+
fileutil "github.com/openshift/origin/pkg/util/file"
1518
)
1619

1720
func isTrue(s string) bool {
@@ -170,6 +173,24 @@ func endpointsForAlias(alias ServiceAliasConfig, svc ServiceUnit) []Endpoint {
170173
return endpoints
171174
}
172175

176+
// sortMap sorts a map file based on longest path first (a reverse sort)
177+
// with the sorted wildcard paths added at the end.
178+
func sortMap(name string) bool {
179+
lines, err := fileutil.ReadLines(name)
180+
if err != nil {
181+
glog.Errorf("Error reading map file %s: %v", name, err)
182+
return false
183+
}
184+
185+
data := templateutil.SortMapPaths(lines, `^[^\.]*\.`)
186+
if err := ioutil.WriteFile(name, []byte(strings.Join(data, "\n")), 0664); err != nil {
187+
glog.Errorf("Error writing map file %s: %v", name, err)
188+
return false
189+
}
190+
191+
return true
192+
}
193+
173194
var helperFunctions = template.FuncMap{
174195
"endpointsForAlias": endpointsForAlias, //returns the list of valid endpoints
175196
"processEndpointsForAlias": processEndpointsForAlias, //returns the list of valid endpoints after processing them
@@ -184,4 +205,5 @@ var helperFunctions = template.FuncMap{
184205

185206
"isTrue": isTrue, //determines if a given variable is a true value
186207
"firstMatch": firstMatch, //anchors provided regular expression and evaluates against given strings, returns the first matched string or ""
208+
"sortMap": sortMap, //sorts a map file based on the longest path first and sorted wildcard paths at the end
187209
}

pkg/router/template/template_helper_test.go

+64
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package templaterouter
22

33
import (
4+
"io/ioutil"
45
"regexp"
6+
"strings"
57
"testing"
8+
9+
fileutil "github.com/openshift/origin/pkg/util/file"
610
)
711

812
func TestFirstMatch(t *testing.T) {
@@ -297,3 +301,63 @@ func TestMatchPattern(t *testing.T) {
297301
}
298302
}
299303
}
304+
305+
func TestSortMap(t *testing.T) {
306+
testData := []string{
307+
`^api-stg\.127\.0\.0\.1\.nip\.io(:[0-9]+)?(/.*)?$ stg:api-route`,
308+
`^api-prod\.127\.0\.0\.1\.nip\.io(:[0-9]+)?(/.*)?$ prod:api-route`,
309+
`^[^\.]*\.127\.0\.0\.1\.nip\.io(:[0-9]+)?(/.*)?$ prod:wildcard-route`,
310+
`^3dev\.127\.0\.0\.1\.nip\.io(:[0-9]+)?(/.*)?$ dev:api-route`,
311+
`^api-prod\.127\.0\.0\.1\.nip\.io(:[0-9]+)?/x/y/z(/.*)?$ prod:api-path-route`,
312+
`^3app-admin\.127\.0\.0\.1\.nip\.io(:[0-9]+)?(/.*)?$ dev:admin-route`,
313+
`^[^\.]*\.foo\.127\.0\.0\.1\.nip\.io(:[0-9]+)?(/.*)?$ devel2:foo-wildcard-route`,
314+
`^zzz-production\.wildcard\.test(:[0-9]+)?/x/y/z(/.*)?$ test:api-route`,
315+
`^backend-app\.127\.0\.0\.1\.nip\.io(:[0-9]+)?(/.*)?$ prod:backend-route`,
316+
`^[^\.]*\.foo\.wildcard\.test(:[0-9]+)?(/.*)?$ devel2:foo-wildcard-test`,
317+
}
318+
319+
expectedOrder := []string{
320+
"test:api-route",
321+
"prod:backend-route",
322+
"stg:api-route",
323+
"prod:api-path-route",
324+
"prod:api-route",
325+
"dev:api-route",
326+
"dev:admin-route",
327+
"devel2:foo-wildcard-test",
328+
"devel2:foo-wildcard-route",
329+
"prod:wildcard-route",
330+
}
331+
332+
fileName := ""
333+
tempFile, err := ioutil.TempFile("", "sort-map-test")
334+
if err == nil {
335+
fileName = tempFile.Name()
336+
err = tempFile.Close()
337+
}
338+
if err != nil || len(fileName) == 0 {
339+
t.Errorf("TestSortMap unexpected error creating a temp file: %s", fileName)
340+
}
341+
if err := ioutil.WriteFile(fileName, []byte(strings.Join(testData, "\n")), 0664); err != nil {
342+
t.Errorf("TestSortMap unexpected error writing temp file %s: %v", fileName, err)
343+
}
344+
345+
if !sortMap(fileName) {
346+
t.Errorf("TestSortMap temp file %s sort failed", fileName)
347+
}
348+
349+
lines, err := fileutil.ReadLines(fileName)
350+
if err != nil {
351+
t.Errorf("TestSortMap error reading sorted temp file %s: %v", fileName, err)
352+
}
353+
if len(lines) != len(expectedOrder) {
354+
t.Errorf("TestSortMap sorted data length %d did not match expected length %d",
355+
len(lines), len(expectedOrder))
356+
}
357+
for idx, suffix := range expectedOrder {
358+
if !strings.HasSuffix(lines[idx], suffix) {
359+
t.Errorf("TestSortMap sorted data %s at index %d did not match expectation %s",
360+
lines[idx], idx, suffix)
361+
}
362+
}
363+
}

pkg/router/template/util/map_paths.go

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package util
2+
3+
import (
4+
"sort"
5+
"strings"
6+
)
7+
8+
// sorterFunc is the type of "Less" function that defines map path order.
9+
type sorterFunc func(s1, s2 string) bool
10+
11+
// mapPathSorter sorts a slice of map paths using the sort function.
12+
type mapPathSorter struct {
13+
data []string
14+
fn sorterFunc // closure used in the Less method.
15+
}
16+
17+
// Len returns the length of the data and is part of sort.Interface
18+
func (s *mapPathSorter) Len() int {
19+
return len(s.data)
20+
}
21+
22+
// Swap swaps the entries for the given indexes and (part of sort.Interface)
23+
func (s *mapPathSorter) Swap(i, j int) {
24+
s.data[i], s.data[j] = s.data[j], s.data[i]
25+
}
26+
27+
// Less compares two map paths using a closure and is part of sort.Interface
28+
func (s *mapPathSorter) Less(i, j int) bool {
29+
return s.fn(s.data[i], s.data[j])
30+
}
31+
32+
// sortByGroup sorts the data with any matching prefixes sorted last.
33+
// `reverse` indicates whether or not the data in each group (with and
34+
// without prefixes) needs to be reverse sorted.
35+
// Note that the matching prefixes are sorted based on length.
36+
func sortByGroup(data []string, prefix string, reverse bool) []string {
37+
patternsAtEnd := func(s1, s2 string) bool {
38+
if len(prefix) > 0 {
39+
if strings.HasPrefix(s1, prefix) {
40+
if !strings.HasPrefix(s2, prefix) {
41+
return false
42+
}
43+
} else if strings.HasPrefix(s2, prefix) {
44+
return true
45+
}
46+
}
47+
48+
// No prefix or both or neither strings have the prefix.
49+
if reverse {
50+
return s1 > s2
51+
}
52+
53+
return s1 < s2
54+
}
55+
56+
mps := &mapPathSorter{data: data, fn: patternsAtEnd}
57+
sort.Sort(mps)
58+
return mps.data
59+
}
60+
61+
// SortMapPaths sorts the data by groups with any matching prefixes sorted at
62+
// the end. The data in each group is reverse sorted.
63+
func SortMapPaths(data []string, prefix string) []string {
64+
return sortByGroup(data, prefix, true)
65+
}

0 commit comments

Comments
 (0)