From f219b5ba6ee659ca6a7fc0c8292048f63fcfd651 Mon Sep 17 00:00:00 2001 From: Helene Durand Date: Mon, 18 Nov 2024 17:05:34 +0100 Subject: [PATCH] BUG/MEDIUM: fix haproxy version with multiple runtime clients As haproxy version was a field of the client struct and the version set in a sync.Once function, the version was nil for all runtime clients except the first one. It could also happend that the first call was done with DoNotCheckRuntimeOnInit option and then, it could also happen that even the first one was nil. --- .gitignore | 1 + go.mod | 2 +- runtime/runtime_client.go | 42 +++++++++++++++++++++++---------------- 3 files changed, 27 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index 762ab035..cecb79ec 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ .idea/ bin/golangci-lint bin/swagger +.envrc diff --git a/go.mod b/go.mod index 9b2f1923..971c3dae 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 github.com/sirkon/dst v0.26.4 github.com/stretchr/testify v1.9.0 + golang.org/x/sync v0.8.0 golang.org/x/text v0.19.0 golang.org/x/tools v0.26.0 gopkg.in/yaml.v3 v3.0.1 @@ -39,6 +40,5 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect go.mongodb.org/mongo-driver v1.17.1 // indirect golang.org/x/mod v0.21.0 // indirect - golang.org/x/sync v0.8.0 // indirect golang.org/x/sys v0.26.0 // indirect ) diff --git a/runtime/runtime_client.go b/runtime/runtime_client.go index c0b86d32..75db3523 100644 --- a/runtime/runtime_client.go +++ b/runtime/runtime_client.go @@ -23,19 +23,18 @@ import ( "os" "path/filepath" "strings" - "sync" native_errors "github.com/haproxytech/client-native/v6/errors" "github.com/haproxytech/client-native/v6/misc" "github.com/haproxytech/client-native/v6/models" "github.com/haproxytech/client-native/v6/runtime/options" + "golang.org/x/sync/singleflight" ) // Client handles multiple HAProxy clients type client struct { - haproxyVersion *HAProxyVersion - options options.RuntimeOptions - runtime *SingleRuntime + options options.RuntimeOptions + runtime *SingleRuntime } const ( @@ -90,50 +89,59 @@ func (c *client) GetInfo() (models.ProcessInfo, error) { return result, nil } -var versionSync sync.Once //nolint:gochecknoglobals +var ( + haproxyVersion *HAProxyVersion //nolint:gochecknoglobals + versionKey = "version" //nolint:gochecknoglobals + versionSfg = singleflight.Group{} //nolint:gochecknoglobals +) // GetVersion returns info from the socket func (c *client) GetVersion() (HAProxyVersion, error) { var err error - versionSync.Do(func() { + if haproxyVersion != nil { + return *haproxyVersion, nil + } + _, err, _ = versionSfg.Do(versionKey, func() (interface{}, error) { version := &HAProxyVersion{} var response string response, err = c.runtime.ExecuteRaw("show info") if err != nil { - return + return HAProxyVersion{}, err } for _, line := range strings.Split(response, "\n") { if strings.HasPrefix(line, "Version: ") { err = version.ParseHAProxyVersion(strings.TrimPrefix(line, "Version: ")) if err != nil { - return + return HAProxyVersion{}, err } - c.haproxyVersion = version - return + haproxyVersion = version + return HAProxyVersion{}, err } // Starting with HAProxy 3.0, there is no more "Version:" prefix. if len(line) > 0 && line[0] >= '3' && line[0] <= '9' { err = version.ParseHAProxyVersion(line) if err == nil { - c.haproxyVersion = version + haproxyVersion = version } - return + return HAProxyVersion{}, err } } err = errors.New("version data not found") + return HAProxyVersion{}, err // it's dereferenced in IsVersionBiggerOrEqual }) if err != nil { return HAProxyVersion{}, err } - if c.haproxyVersion == nil { + if haproxyVersion == nil { return HAProxyVersion{}, errors.New("version data not found") } - return *c.haproxyVersion, err + + return *haproxyVersion, err } func (c *client) IsVersionBiggerOrEqual(minimumVersion *HAProxyVersion) bool { - return IsBiggerOrEqual(minimumVersion, c.haproxyVersion) + return IsBiggerOrEqual(minimumVersion, haproxyVersion) } // Reloads HAProxy's configuration file. Similar to SIGUSR2. Returns the startup logs. @@ -144,7 +152,7 @@ func (c *client) Reload() (string, error) { return "", errors.New("cannot reload: not connected to a master socket") } if !c.IsVersionBiggerOrEqual(&HAProxyVersion{Major: 2, Minor: 7}) { - return "", errors.New("cannot reload: requires HAProxy 2.7 or later") + return "", fmt.Errorf("cannot reload: requires HAProxy 2.7 or later but current version is %v", haproxyVersion) } output, err := c.runtime.ExecuteMaster("reload") @@ -218,7 +226,7 @@ func (c *client) AddServer(backend, name, attributes string) error { return errors.New("no valid runtime found") } if !c.IsVersionBiggerOrEqual(&HAProxyVersion{Major: 2, Minor: 6}) { - return errors.New("this operation requires HAProxy 2.6 or later") + return fmt.Errorf("this operation requires HAProxy 2.6 or later but current version is %v", haproxyVersion) } err := c.runtime.AddServer(backend, name, attributes) if err != nil {