Skip to content

Commit 2f200cf

Browse files
committed
OPTIM: Enable dynamic update of HAProxy map files
Dynamic update of map files was dropped in 7953394 due to issues inherent to HAProxy 2.3 (details provided in said commit). In HAProxy 2.4+ those issues were fixed thus dynamic map update is enabled again. Updating the map file is done via the following steps: 1. Prepare the map file via `prepare map` command: this is going to create a new version of the map file. 2. Pushing content via `add map @<newVersion> <map> <payload>` command: where payload should not be bigger than haproxy tune.bufsize (default is 16384B), if map content is bigger than bufsize then this command should be called multiple times (content/payload times). 3. Loading the new version of the map file via `commit map`:
1 parent d7cb8ae commit 2f200cf

File tree

5 files changed

+68
-29
lines changed

5 files changed

+68
-29
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.17
55
require (
66
github.com/go-test/deep v1.0.7
77
github.com/google/renameio v1.0.1
8-
github.com/haproxytech/client-native/v3 v3.1.1-0.20220510114028-57f50c20a1bf
8+
github.com/haproxytech/client-native/v3 v3.1.1-0.20220520145135-9afaaf96bc7f
99
github.com/haproxytech/config-parser/v4 v4.0.0-rc2.0.20220428133329-7d0ec01198d4
1010
github.com/jessevdk/go-flags v1.4.0
1111
github.com/pires/go-proxyproto v0.6.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,8 @@ github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9
268268
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
269269
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
270270
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
271-
github.com/haproxytech/client-native/v3 v3.1.1-0.20220510114028-57f50c20a1bf h1:MotDEEbRdSuRIi7M4y8MvUEhLWO5IelS2CGySl4iPpM=
272-
github.com/haproxytech/client-native/v3 v3.1.1-0.20220510114028-57f50c20a1bf/go.mod h1:W3WGOnzwpgXp483BCs8Xj40gejn4rNfhRgfQrJXL/sc=
271+
github.com/haproxytech/client-native/v3 v3.1.1-0.20220520145135-9afaaf96bc7f h1:jV+f9lmAOqSJF3IBgrEFq5qrLBt73zuMRimdy0iJVis=
272+
github.com/haproxytech/client-native/v3 v3.1.1-0.20220520145135-9afaaf96bc7f/go.mod h1:W3WGOnzwpgXp483BCs8Xj40gejn4rNfhRgfQrJXL/sc=
273273
github.com/haproxytech/config-parser/v4 v4.0.0-rc2.0.20220428133329-7d0ec01198d4 h1:aTSSDkGwoDwGv0hRfKsaWxVejDW8iROlPPBhboI3ONk=
274274
github.com/haproxytech/config-parser/v4 v4.0.0-rc2.0.20220428133329-7d0ec01198d4/go.mod h1:pEuHx+aFhn0lIdvAg1OaawQfeRkpq1I8HzjtZN4/PLI=
275275
github.com/haproxytech/go-logger v1.0.1-0.20211022075555-178f1cdf4d84 h1:rSLHjJ4VGvMZcGAGQ9GaXuhvdswu1iLVXTThLX6OKN8=

pkg/haproxy/api/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ type HAProxyClient interface {
6060
GlobalCfgSnippet(snippet []string) error
6161
GetMap(mapFile string) (*models.Map, error)
6262
RefreshBackends() (deleted []string, err error)
63-
SetMapContent(mapFile string, payload string) error
63+
SetMapContent(mapFile string, payload []string) error
6464
SetServerAddr(backendName string, serverName string, ip string, port int) error
6565
SetServerState(backendName string, serverName string, state string) error
6666
ServerGet(serverName, backendNa string) (models.Server, error)

pkg/haproxy/api/runtime.go

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package api
22

33
import (
4+
"fmt"
5+
"strings"
6+
47
"github.com/haproxytech/client-native/v3/models"
58

69
"github.com/haproxytech/kubernetes-ingress/pkg/store"
710
"github.com/haproxytech/kubernetes-ingress/pkg/utils"
811
)
912

13+
var ErrMapNotFound = fmt.Errorf("map not found")
14+
1015
func (c *clientNative) ExecuteRaw(command string) (result []string, err error) {
1116
runtime, err := c.nativeAPI.Runtime()
1217
if err != nil {
@@ -31,16 +36,37 @@ func (c *clientNative) SetServerState(backendName string, serverName string, sta
3136
return runtime.SetServerState(backendName, serverName, state)
3237
}
3338

34-
func (c *clientNative) SetMapContent(mapFile string, payload string) error {
39+
func (c *clientNative) SetMapContent(mapFile string, payload []string) error {
40+
var mapVer, mapPath string
3541
runtime, err := c.nativeAPI.Runtime()
3642
if err != nil {
3743
return err
3844
}
39-
err = runtime.ClearMap(mapFile, false)
45+
mapVer, err = runtime.PrepareMap(mapFile)
46+
if err != nil {
47+
if strings.HasPrefix(err.Error(), "maps dir doesn't exists") {
48+
err = ErrMapNotFound
49+
}
50+
err = fmt.Errorf("error preparing map file: %w", err)
51+
return err
52+
}
53+
mapPath, err = runtime.GetMapsPath(mapFile)
4054
if err != nil {
55+
err = fmt.Errorf("error getting map path: %w", err)
4156
return err
4257
}
43-
return runtime.AddMapPayload(mapFile, payload)
58+
for i := 0; i < len(payload); i++ {
59+
_, err = runtime.ExecuteRaw(fmt.Sprintf("add map @%s %s <<\n%s\n", mapVer, mapPath, payload[i]))
60+
if err != nil {
61+
err = fmt.Errorf("error loading map payload: %w", err)
62+
return err
63+
}
64+
}
65+
err = runtime.CommitMap(mapVer, mapFile)
66+
if err != nil {
67+
err = fmt.Errorf("error committing map file: %w", err)
68+
}
69+
return err
4470
}
4571

4672
func (c *clientNative) GetMap(mapFile string) (*models.Map, error) {

pkg/haproxy/maps/main.go

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package maps
1616

1717
import (
18+
"errors"
1819
"fmt"
1920
"hash/fnv"
2021
"os"
@@ -48,6 +49,10 @@ var logger = utils.GetLogger()
4849

4950
var mapDir string
5051

52+
// bufsize is the default value of HAproxy tune.bufsize.
53+
// Map payload cannot be bigger than tune.bufsize
54+
const bufSize = 16000
55+
5156
type mapFile struct {
5257
rows []string
5358
hash uint64
@@ -56,17 +61,25 @@ type mapFile struct {
5661
// because it is always referenced in a haproxy rule.
5762
}
5863

59-
func (mf *mapFile) getContent() (string, uint64) {
60-
var b strings.Builder
64+
// getContent returns the content of a haproxy map file in a list of chunks
65+
// where each chunk is <= bufsie. It also returns a hash of the map content
66+
func (mf *mapFile) getContent() (result []string, hash uint64) {
67+
var chunk strings.Builder
6168
sort.Strings(mf.rows)
69+
h := fnv.New64a()
6270
for _, r := range mf.rows {
63-
b.WriteString(r)
64-
b.WriteRune('\n')
71+
if chunk.Len()+len(r) >= bufSize {
72+
result = append(result, chunk.String())
73+
chunk.Reset()
74+
}
75+
chunk.WriteString(r)
76+
chunk.WriteRune('\n')
77+
_, _ = h.Write([]byte(r))
6578
}
66-
content := b.String()
67-
h := fnv.New64a()
68-
_, _ = h.Write([]byte(content))
69-
return content, h.Sum64()
79+
if chunk.Len() > 0 {
80+
result = append(result, chunk.String())
81+
}
82+
return result, h.Sum64()
7083
}
7184

7285
func New(dir string, persistentMaps []Name) (Maps, error) {
@@ -111,7 +124,7 @@ func (m mapFiles) RefreshMaps(client api.HAProxyClient) (reload bool) {
111124
var f *os.File
112125
var err error
113126
filename := GetPath(name)
114-
if content == "" && !mapFile.persistent {
127+
if len(content) == 0 && !mapFile.persistent {
115128
logger.Error(os.Remove(string(filename)))
116129
delete(m, name)
117130
continue
@@ -120,21 +133,21 @@ func (m mapFiles) RefreshMaps(client api.HAProxyClient) (reload bool) {
120133
continue
121134
}
122135
defer f.Close()
123-
if _, err = f.WriteString(content); err != nil {
124-
logger.Error(err)
125-
continue
136+
for _, d := range content {
137+
if _, err = f.WriteString(d); err != nil {
138+
logger.Error(err)
139+
return
140+
}
126141
}
127142
logger.Error(f.Sync())
128-
reload = true
129-
logger.Debugf("Map file '%s' updated, reload required", name)
130-
// if err = client.SetMapContent(name, content); err != nil {
131-
// if strings.HasPrefix(err.Error(), "maps dir doesn't exists") {
132-
// logger.Debugf("creating Map file %s", name)
133-
// } else {
134-
// logger.Warningf("dynamic update of '%s' Map file failed: %s", name, err.Error()[:200])
135-
// }
136-
// reload = true
137-
// }
143+
if err = client.SetMapContent(string(name), content); err != nil {
144+
if errors.Is(err, api.ErrMapNotFound) {
145+
logger.Debugf("Map file %s created, reload required", name)
146+
} else {
147+
logger.Debugf("Runtime update of map file '%s' failed, reload required: %s", name, err)
148+
}
149+
reload = true
150+
}
138151
}
139152
return reload
140153
}

0 commit comments

Comments
 (0)