Skip to content

Commit 8900180

Browse files
vgramermjuraga
authored andcommitted
BUG/MAJOR: reload runtime client if the configuration is changed with global endpoint
Signed-off-by: Vincent Gramer <[email protected]>
1 parent 7a50817 commit 8900180

File tree

9 files changed

+217
-74
lines changed

9 files changed

+217
-74
lines changed

client-native/cn.go

+67
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import (
55
"fmt"
66
"strconv"
77
"sync"
8+
"time"
9+
10+
clientnative "github.com/haproxytech/client-native/v5"
11+
"github.com/haproxytech/client-native/v5/models"
812

913
"github.com/haproxytech/client-native/v5/configuration"
1014
configuration_options "github.com/haproxytech/client-native/v5/configuration/options"
@@ -155,3 +159,66 @@ func ConfigureRuntimeClient(ctx context.Context, confClient configuration.Config
155159
log.Warning("Cannot read runtime API configuration, not using it")
156160
return runtimeClient
157161
}
162+
163+
// ReconfigureRuntime check if runtime client need be reconfigured by comparing the current configuration with the old
164+
// one (i.e. runtimeAPIsOld) and returns a callback that ReloadAgent can use to reconfigure the runtime client.
165+
func ReconfigureRuntime(client clientnative.HAProxyClient, runtimeAPIsOld []*models.RuntimeAPI) (callbackNeeded bool, callback func(), err error) {
166+
cfg, err := client.Configuration()
167+
if err != nil {
168+
return false, nil, err
169+
}
170+
_, globalConf, err := cfg.GetGlobalConfiguration("")
171+
if err != nil {
172+
return false, nil, err
173+
}
174+
runtimeAPIsNew := globalConf.RuntimeAPIs
175+
reconfigureRuntime := false
176+
if len(runtimeAPIsOld) != len(runtimeAPIsNew) {
177+
reconfigureRuntime = true
178+
} else {
179+
for _, runtimeOld := range runtimeAPIsOld {
180+
if runtimeOld.Address == nil {
181+
continue
182+
}
183+
found := false
184+
for _, runtimeNew := range runtimeAPIsNew {
185+
if runtimeNew.Address == nil {
186+
continue
187+
}
188+
if *runtimeNew.Address == *runtimeOld.Address {
189+
found = true
190+
break
191+
}
192+
}
193+
if !found {
194+
reconfigureRuntime = true
195+
break
196+
}
197+
}
198+
}
199+
200+
if reconfigureRuntime {
201+
dpapiCfg := dataplaneapi_config.Get()
202+
haproxyOptions := dpapiCfg.HAProxy
203+
return true, func() {
204+
var rnt runtime_api.Runtime
205+
i := 1
206+
for i < 10 {
207+
rnt = ConfigureRuntimeClient(context.Background(), cfg, haproxyOptions)
208+
if rnt != nil {
209+
break
210+
}
211+
time.Sleep(time.Duration(i) * time.Second)
212+
i += i // exponential backoof
213+
}
214+
client.ReplaceRuntime(rnt)
215+
if rnt == nil {
216+
log.Debugf("reload callback completed, no runtime API")
217+
} else {
218+
log.Debugf("reload callback completed, runtime API reconfigured")
219+
}
220+
}, nil
221+
}
222+
223+
return false, nil, nil
224+
}

e2e/tests/global/data/put_socket.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"runtime_apis": [
3+
{
4+
"address": "/var/lib/haproxy/stats-new",
5+
"level": "admin"
6+
}
7+
],
8+
"daemon": "enabled",
9+
"master-worker": true,
10+
"maxconn": 5000,
11+
"pidfile": "/var/run/haproxy.pid"
12+
}

e2e/tests/global/replace.bats

+50
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,53 @@ load 'utils/_helpers'
3535
assert_equal "$(get_json_path "$BODY" '.data.runtime_apis[0].address')" "/var/lib/haproxy/stats"
3636
assert_equal "$(get_json_path "$BODY" '.data.runtime_apis[0].level')" "admin"
3737
}
38+
39+
40+
@test "global: Replace a global configuration with socket path changed" {
41+
resource_put "$_GLOBAL_BASE_PATH" "data/put_socket.json" ""
42+
assert_equal "$SC" 202
43+
44+
resource_get "$_GLOBAL_BASE_PATH" ""
45+
assert_equal "$SC" 200
46+
assert_equal "$(get_json_path "$BODY" '.data.maxconn')" "5000"
47+
assert_equal "$(get_json_path "$BODY" '.data.daemon')" "enabled"
48+
assert_equal "$(get_json_path "$BODY" '.data.pidfile')" "/var/run/haproxy.pid"
49+
assert_equal "$(get_json_path "$BODY" '.data.runtime_apis[0].address')" "/var/lib/haproxy/stats-new"
50+
assert_equal "$(get_json_path "$BODY" '.data.runtime_apis[0].level')" "admin"
51+
52+
# check that runtime client has been reconfigured with the new socket
53+
sleep 5
54+
resource_get "$_RUNTIME_MAP_FILES_BASE_PATH" ""
55+
assert_equal "$SC" 200
56+
}
57+
58+
@test "global: Replace a global configuration with socket path changed (using transaction)" {
59+
# create transaction
60+
resource_post "$_TRANSACTIONS_BASE_PATH" ""
61+
assert_equal "$SC" 201
62+
local transaction_id; transaction_id=$(get_json_path "${BODY}" ".id")
63+
64+
# PUT new configuration
65+
run dpa_curl PUT "${_GLOBAL_BASE_PATH}?transaction_id=${transaction_id}" "data/put_socket.json"
66+
assert_success
67+
dpa_curl_status_body '$output'
68+
assert_equal "$SC" 202
69+
70+
# commit transaction
71+
resource_put "$_TRANSACTIONS_BASE_PATH/$transaction_id" ""
72+
assert_equal "$SC" 202
73+
74+
# check configuration has been applied
75+
resource_get "$_GLOBAL_BASE_PATH" ""
76+
assert_equal "$SC" 200
77+
assert_equal "$(get_json_path "$BODY" '.data.maxconn')" "5000"
78+
assert_equal "$(get_json_path "$BODY" '.data.daemon')" "enabled"
79+
assert_equal "$(get_json_path "$BODY" '.data.pidfile')" "/var/run/haproxy.pid"
80+
assert_equal "$(get_json_path "$BODY" '.data.runtime_apis[0].address')" "/var/lib/haproxy/stats-new"
81+
assert_equal "$(get_json_path "$BODY" '.data.runtime_apis[0].level')" "admin"
82+
83+
# check that runtime client has been reconfigured with the new socket
84+
sleep 5
85+
resource_get "$_RUNTIME_MAP_FILES_BASE_PATH" ""
86+
assert_equal "$SC" 200
87+
}

e2e/tests/global/utils/_helpers.bash

+3
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@
1616
#
1717

1818
_GLOBAL_BASE_PATH="/services/haproxy/configuration/global"
19+
_RUNTIME_MAP_FILES_BASE_PATH="/services/haproxy/runtime/maps"
20+
_TRANSACTIONS_BASE_PATH="/services/haproxy/transactions"
21+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"global\n chroot /var/lib/haproxy\n user haproxy\n group haproxy\n maxconn 4000\n pidfile /var/run/haproxy.pid\n stats socket /var/lib/haproxy/stats-new level admin\n log 127.0.0.1 local2\n\ndefaults\n mode http\n maxconn 3000\n log global\n option httplog\n option redispatch\n option dontlognull\n option http-server-close\n option forwardfor except 127.0.0.0/8\n timeout http-request 10s\n timeout check 10s\n timeout connect 10s\n timeout client 1m\n timeout queue 1m\n timeout server 1m\n timeout http-keep-alive 10s\n retries 3\n\nbackend test_backend\n mode http\n balance roundrobin\n option forwardfor\n server server_01 10.1.1.1:8080 check weight 80\n server server_02 10.1.1.2:8080 check weight 80\n server server_03 10.1.1.2:8080 check weight 80\n"

e2e/tests/raw/post.bats

+23
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,26 @@ load 'utils/_helpers'
3434
assert_equal "$SC" 400
3535
assert_equal "$(get_json_path "$BODY" '.message')" "invalid configuration: no newline character found"
3636
}
37+
38+
39+
@test "raw: Post new configuration with socket path changed" {
40+
resource_post "$_RAW_BASE_PATH" 'data/haproxy_socket.cfg.json'
41+
assert_equal "$SC" 202
42+
43+
resource_get "$_RAW_BASE_PATH" ""
44+
assert_equal "$SC" 200
45+
46+
local socket; socket='stats socket /var/lib/haproxy/stats-new'
47+
if [[ "$BODY" != *"$socket"* ]]; then
48+
batslib_print_kv_single_or_multi 8 \
49+
'configuration' "$BODY" \
50+
'expected socket' "$socket" \
51+
| batslib_decorate 'configuration does not contains the new socket' \
52+
| fail
53+
fi
54+
55+
# check that runtime client has been reconfigured with the new socket
56+
sleep 5
57+
resource_get "$_RUNTIME_MAP_FILES_BASE_PATH" ""
58+
assert_equal "$SC" 200
59+
}

handlers/global.go

+29-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import (
1919
"github.com/go-openapi/runtime/middleware"
2020
client_native "github.com/haproxytech/client-native/v5"
2121
"github.com/haproxytech/client-native/v5/models"
22-
22+
cn "github.com/haproxytech/dataplaneapi/client-native"
2323
"github.com/haproxytech/dataplaneapi/haproxy"
2424
"github.com/haproxytech/dataplaneapi/misc"
2525
"github.com/haproxytech/dataplaneapi/operations/global"
@@ -84,22 +84,48 @@ func (h *ReplaceGlobalHandlerImpl) Handle(params global.ReplaceGlobalParams, pri
8484
return global.NewReplaceGlobalDefault(int(*e.Code)).WithPayload(e)
8585
}
8686

87+
// save old runtime configuration to know if the runtime client must be configured after the new configuration is
88+
// reloaded by HAProxy. Logic is done by cn.ReconfigureRuntime() function.
89+
_, globalConf, err := configuration.GetGlobalConfiguration("")
90+
if err != nil {
91+
e := misc.HandleError(err)
92+
return global.NewReplaceGlobalDefault(int(*e.Code)).WithPayload(e)
93+
}
94+
runtimeAPIsOld := globalConf.RuntimeAPIs
95+
8796
err = configuration.PushGlobalConfiguration(params.Data, t, v)
8897
if err != nil {
8998
e := misc.HandleError(err)
9099
return global.NewReplaceGlobalDefault(int(*e.Code)).WithPayload(e)
91100
}
92101

93102
if params.TransactionID == nil {
103+
callbackNeeded, reconfigureFunc, err := cn.ReconfigureRuntime(h.Client, runtimeAPIsOld)
104+
if err != nil {
105+
e := misc.HandleError(err)
106+
return global.NewReplaceGlobalDefault(int(*e.Code)).WithPayload(e)
107+
}
108+
94109
if *params.ForceReload {
95-
err := h.ReloadAgent.ForceReload()
110+
if callbackNeeded {
111+
err = h.ReloadAgent.ForceReloadWithCallback(reconfigureFunc)
112+
} else {
113+
err = h.ReloadAgent.ForceReload()
114+
}
115+
96116
if err != nil {
97117
e := misc.HandleError(err)
98118
return global.NewReplaceGlobalDefault(int(*e.Code)).WithPayload(e)
99119
}
100120
return global.NewReplaceGlobalOK().WithPayload(params.Data)
101121
}
102-
rID := h.ReloadAgent.Reload()
122+
123+
var rID string
124+
if callbackNeeded {
125+
rID = h.ReloadAgent.ReloadWithCallback(reconfigureFunc)
126+
} else {
127+
rID = h.ReloadAgent.Reload()
128+
}
103129
return global.NewReplaceGlobalAccepted().WithReloadID(rID).WithPayload(params.Data)
104130
}
105131
return global.NewReplaceGlobalAccepted().WithPayload(params.Data)

handlers/raw.go

+2-69
Original file line numberDiff line numberDiff line change
@@ -16,21 +16,15 @@
1616
package handlers
1717

1818
import (
19-
"context"
2019
"fmt"
2120
"strconv"
2221
"strings"
23-
"time"
2422

2523
"github.com/go-openapi/runtime/middleware"
2624
client_native "github.com/haproxytech/client-native/v5"
2725
"github.com/haproxytech/client-native/v5/models"
28-
"github.com/haproxytech/client-native/v5/runtime"
29-
3026
cn "github.com/haproxytech/dataplaneapi/client-native"
31-
dataplaneapi_config "github.com/haproxytech/dataplaneapi/configuration"
3227
"github.com/haproxytech/dataplaneapi/haproxy"
33-
"github.com/haproxytech/dataplaneapi/log"
3428
"github.com/haproxytech/dataplaneapi/misc"
3529
"github.com/haproxytech/dataplaneapi/operations/configuration"
3630
)
@@ -137,7 +131,7 @@ func (h *PostRawConfigurationHandlerImpl) Handle(params configuration.PostHAProx
137131
if forceReload {
138132
var callbackNeeded bool
139133
var reconfigureFunc func()
140-
callbackNeeded, reconfigureFunc, err = h.reconfigureRuntime(runtimeAPIsOld)
134+
callbackNeeded, reconfigureFunc, err = cn.ReconfigureRuntime(h.Client, runtimeAPIsOld)
141135
if err != nil {
142136
e := misc.HandleError(err)
143137
return configuration.NewPostHAProxyConfigurationDefault(int(*e.Code)).WithPayload(e)
@@ -153,7 +147,7 @@ func (h *PostRawConfigurationHandlerImpl) Handle(params configuration.PostHAProx
153147
}
154148
return configuration.NewPostHAProxyConfigurationCreated().WithPayload(params.Data)
155149
}
156-
callbackNeeded, reconfigureFunc, err := h.reconfigureRuntime(runtimeAPIsOld)
150+
callbackNeeded, reconfigureFunc, err := cn.ReconfigureRuntime(h.Client, runtimeAPIsOld)
157151
if err != nil {
158152
e := misc.HandleError(err)
159153
return configuration.NewPostHAProxyConfigurationDefault(int(*e.Code)).WithPayload(e)
@@ -169,67 +163,6 @@ func (h *PostRawConfigurationHandlerImpl) Handle(params configuration.PostHAProx
169163
return configuration.NewPostHAProxyConfigurationAccepted().WithReloadID(rID).WithPayload(params.Data)
170164
}
171165

172-
func (h *PostRawConfigurationHandlerImpl) reconfigureRuntime(runtimeAPIsOld []*models.RuntimeAPI) (callbackNeeded bool, callback func(), err error) {
173-
cfg, err := h.Client.Configuration()
174-
if err != nil {
175-
return false, nil, err
176-
}
177-
_, globalConf, err := cfg.GetGlobalConfiguration("")
178-
if err != nil {
179-
return false, nil, err
180-
}
181-
runtimeAPIsNew := globalConf.RuntimeAPIs
182-
reconfigureRuntime := false
183-
if len(runtimeAPIsOld) != len(runtimeAPIsNew) {
184-
reconfigureRuntime = true
185-
} else {
186-
for _, runtimeOld := range runtimeAPIsOld {
187-
if runtimeOld.Address == nil {
188-
continue
189-
}
190-
found := false
191-
for _, runtimeNew := range runtimeAPIsNew {
192-
if runtimeNew.Address == nil {
193-
continue
194-
}
195-
if *runtimeNew.Address == *runtimeOld.Address {
196-
found = true
197-
break
198-
}
199-
}
200-
if !found {
201-
reconfigureRuntime = true
202-
break
203-
}
204-
}
205-
}
206-
207-
if reconfigureRuntime {
208-
dpapiCfg := dataplaneapi_config.Get()
209-
haproxyOptions := dpapiCfg.HAProxy
210-
return true, func() {
211-
var rnt runtime.Runtime
212-
i := 1
213-
for i < 10 {
214-
rnt = cn.ConfigureRuntimeClient(context.Background(), cfg, haproxyOptions)
215-
if rnt != nil {
216-
break
217-
}
218-
time.Sleep(time.Duration(i) * time.Second)
219-
i += i // exponential backoof
220-
}
221-
h.Client.ReplaceRuntime(rnt)
222-
if rnt == nil {
223-
log.Debugf("reload callback completed, no runtime API")
224-
} else {
225-
log.Debugf("reload callback completed, runtime API reconfigured")
226-
}
227-
}, nil
228-
}
229-
230-
return false, nil, nil
231-
}
232-
233166
func executeRuntimeActions(actionsStr string, client client_native.HAProxyClient) error {
234167
runtime, err := client.Runtime()
235168
if err != nil {

0 commit comments

Comments
 (0)