fix(pxc): raise haproxy conn limit (#732)

diff --git a/roles/defaults/helpers.go b/roles/defaults/helpers.go
new file mode 100644
index 0000000..a7bd833
--- /dev/null
+++ b/roles/defaults/helpers.go
@@ -0,0 +1,28 @@
+package defaults
+
+import (
+	"regexp"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+var (
+	r *regexp.Regexp
+)
+
+func init() {
+	r, _ = regexp.Compile(`{{ atmosphere_images\['(?P<ImageName>\w+)'] \| vexxhost.kubernetes.docker_image\('ref'\) }}`)
+}
+
+func AssertAtmosphereImage(t *testing.T, expected string, value string) {
+	matches := r.FindStringSubmatch(value)
+	require.Len(t, matches, 2)
+	imageName := matches[1]
+
+	image, err := GetImageByKey(imageName)
+	require.NoError(t, err)
+
+	assert.Equal(t, expected, image)
+}
diff --git a/roles/defaults/vars.go b/roles/defaults/vars.go
new file mode 100644
index 0000000..07b3dc0
--- /dev/null
+++ b/roles/defaults/vars.go
@@ -0,0 +1,27 @@
+package defaults
+
+import (
+	"bytes"
+	_ "embed"
+
+	"github.com/goccy/go-yaml"
+)
+
+var (
+	//go:embed vars/main.yml
+	vars_file []byte
+)
+
+func GetImageByKey(key string) (string, error) {
+	path, err := yaml.PathString("$._atmosphere_images." + key)
+	if err != nil {
+		return "", err
+	}
+
+	var image string
+	if err := path.Read(bytes.NewReader(vars_file), &image); err != nil {
+		return "", err
+	}
+
+	return image, nil
+}
diff --git a/roles/percona_xtradb_cluster/vars/main.yml b/roles/percona_xtradb_cluster/vars/main.yml
index fe7aff7..7836fdb 100644
--- a/roles/percona_xtradb_cluster/vars/main.yml
+++ b/roles/percona_xtradb_cluster/vars/main.yml
@@ -1,5 +1,5 @@
 _percona_xtradb_cluster_spec:
-  crVersion: "1.10.0"
+  crVersion: "1.12.0"
   secretsName: percona-xtradb
   pxc:
     size: 3
@@ -43,3 +43,48 @@
     image: "{{ atmosphere_images['percona_xtradb_cluster_haproxy'] | vexxhost.kubernetes.docker_image('ref') }}"
     nodeSelector:
       openstack-control-plane: enabled
+    configuration: |
+      global
+        maxconn 8192
+        external-check
+        insecure-fork-wanted
+        stats socket /etc/haproxy/pxc/haproxy.sock mode 600 expose-fd listeners level admin
+
+      defaults
+        default-server init-addr last,libc,none
+        log global
+        mode tcp
+        retries 10
+        timeout client 28800s
+        timeout connect 100500
+        timeout server 28800s
+
+      frontend galera-in
+        bind *:3309 accept-proxy
+        bind *:3306
+        mode tcp
+        option clitcpka
+        default_backend galera-nodes
+
+      frontend galera-admin-in
+        bind *:33062
+        mode tcp
+        option clitcpka
+        default_backend galera-admin-nodes
+
+      frontend galera-replica-in
+        bind *:3307
+        mode tcp
+        option clitcpka
+        default_backend galera-replica-nodes
+
+      frontend galera-mysqlx-in
+        bind *:33060
+        mode tcp
+        option clitcpka
+        default_backend galera-mysqlx-nodes
+
+      frontend stats
+        bind *:8404
+        mode http
+        http-request use-service prometheus-exporter if { path /metrics }
diff --git a/roles/percona_xtradb_cluster/vars_test.go b/roles/percona_xtradb_cluster/vars_test.go
new file mode 100644
index 0000000..0a73722
--- /dev/null
+++ b/roles/percona_xtradb_cluster/vars_test.go
@@ -0,0 +1,168 @@
+package percona_xtradb_cluster
+
+import (
+	_ "embed"
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"regexp"
+	"strings"
+	"testing"
+
+	"github.com/goccy/go-yaml"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	"github.com/vexxhost/atmosphere/roles/defaults"
+	"gopkg.in/ini.v1"
+	"helm.sh/helm/v3/pkg/chart/loader"
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
+
+	pxc_v1 "github.com/percona/percona-xtradb-cluster-operator/pkg/apis/pxc/v1"
+)
+
+var (
+	//go:embed vars/main.yml
+	vars_file []byte
+	vars      Vars
+)
+
+type Vars struct {
+	PerconaXtraDBClusterSpec pxc_v1.PerconaXtraDBClusterSpec `yaml:"_percona_xtradb_cluster_spec"`
+}
+
+func TestMain(m *testing.M) {
+	t := &testing.T{}
+
+	err := yaml.UnmarshalWithOptions(vars_file, &vars, yaml.Strict(), yaml.UseJSONUnmarshaler())
+	require.NoError(t, err)
+
+	code := m.Run()
+	os.Exit(code)
+}
+
+func TestPerconaXtraDBClusterSpec(t *testing.T) {
+	chart, err := loader.LoadDir("../../charts/pxc-operator")
+	require.NoError(t, err)
+
+	assert.Equal(t, chart.AppVersion(), vars.PerconaXtraDBClusterSpec.CRVersion)
+	assert.Equal(t, "percona-xtradb", vars.PerconaXtraDBClusterSpec.SecretsName)
+}
+
+func TestPerconaXtraDBClusterPXCSpec(t *testing.T) {
+	assert.Equal(t, int32(3), vars.PerconaXtraDBClusterSpec.PXC.Size)
+	assert.Equal(t, true, *vars.PerconaXtraDBClusterSpec.PXC.AutoRecovery)
+	defaults.AssertAtmosphereImage(t, "docker.io/percona/percona-xtradb-cluster:5.7.39-31.61", vars.PerconaXtraDBClusterSpec.PXC.Image)
+
+	assert.Equal(t, map[string]string{
+		"openstack-control-plane": "enabled",
+	}, vars.PerconaXtraDBClusterSpec.PXC.NodeSelector)
+
+	assert.Equal(t, &pxc_v1.VolumeSpec{
+		PersistentVolumeClaim: &v1.PersistentVolumeClaimSpec{
+			Resources: v1.ResourceRequirements{
+				Requests: v1.ResourceList{
+					"storage": resource.MustParse("160Gi"),
+				},
+			},
+		},
+	}, vars.PerconaXtraDBClusterSpec.PXC.VolumeSpec)
+}
+
+func parsePXCConfiguration(t *testing.T, cfg string) *ini.File {
+	parsed, err := ini.LoadSources(ini.LoadOptions{
+		AllowBooleanKeys: true,
+	}, []byte(cfg))
+	require.NoError(t, err)
+
+	return parsed
+}
+
+func TestPerconaXtraDBClusterPXCConfiguration(t *testing.T) {
+	cfg := parsePXCConfiguration(t, vars.PerconaXtraDBClusterSpec.PXC.Configuration)
+
+	section := cfg.Section("mysqld")
+	assert.Equal(t, 8192, section.Key("max_connections").MustInt())
+	assert.Equal(t, "4096M", section.Key("innodb_buffer_pool_size").String())
+	assert.Equal(t, "16M", section.Key("max_allowed_packet").String())
+	assert.Equal(t, true, section.Key("skip-name-resolve").MustBool())
+}
+
+func TestPerconaXtraDBClusterPXCSidecarSpec(t *testing.T) {
+	sidecar := vars.PerconaXtraDBClusterSpec.PXC.Sidecars[0]
+	assert.Equal(t, "exporter", sidecar.Name)
+	defaults.AssertAtmosphereImage(t, "quay.io/prometheus/mysqld-exporter:v0.14.0", sidecar.Image)
+
+	assert.Equal(t, v1.EnvVar{
+		Name: "MONITOR_PASSWORD",
+		ValueFrom: &v1.EnvVarSource{
+			SecretKeyRef: &v1.SecretKeySelector{
+				LocalObjectReference: v1.LocalObjectReference{
+					Name: vars.PerconaXtraDBClusterSpec.SecretsName,
+				},
+				Key: "monitor",
+			},
+		},
+	}, sidecar.Env[0])
+	assert.Equal(t, v1.EnvVar{
+		Name:  "DATA_SOURCE_NAME",
+		Value: "monitor:$(MONITOR_PASSWORD)@(localhost:3306)/",
+	}, sidecar.Env[1])
+
+	assert.Equal(t, v1.ContainerPort{
+		Name:          "metrics",
+		ContainerPort: 9104,
+	}, sidecar.Ports[0])
+}
+
+func TestPerconaXtraDBClusterHAProxySpec(t *testing.T) {
+	assert.Equal(t, true, vars.PerconaXtraDBClusterSpec.HAProxy.Enabled)
+	assert.Equal(t, int32(3), vars.PerconaXtraDBClusterSpec.HAProxy.Size)
+
+	chart, err := loader.LoadDir("../../charts/pxc-operator")
+	require.NoError(t, err)
+
+	defaults.AssertAtmosphereImage(t,
+		fmt.Sprintf("docker.io/percona/percona-xtradb-cluster-operator:%s-haproxy", chart.AppVersion()),
+		vars.PerconaXtraDBClusterSpec.HAProxy.Image,
+	)
+
+	assert.Equal(t, map[string]string{
+		"openstack-control-plane": "enabled",
+	}, vars.PerconaXtraDBClusterSpec.HAProxy.NodeSelector)
+}
+
+func TestPerconaXtraDBClusterHAProxyConfiguration(t *testing.T) {
+	chart, err := loader.LoadDir("../../charts/pxc-operator")
+	require.NoError(t, err)
+
+	pxcConfig := parsePXCConfiguration(t, vars.PerconaXtraDBClusterSpec.PXC.Configuration)
+	maxConnections := pxcConfig.Section("mysqld").Key("max_connections").MustInt()
+
+	// NOTE(mnaser): Since there is no way of overriding specific values, we pull
+	//               the file from the Docker image, replace the maxconn value and
+	//               then compare it.
+
+	// Get the default HAproxy configuration
+	configFileUrl := fmt.Sprintf("https://raw.githubusercontent.com/percona/percona-docker/pxc-operator-%s/haproxy/dockerdir/etc/haproxy/haproxy-global.cfg", chart.AppVersion())
+	resp, err := http.Get(configFileUrl)
+	require.NoError(t, err)
+	defer resp.Body.Close()
+	haproxyConfigData, err := io.ReadAll(resp.Body)
+	require.NoError(t, err)
+	haproxyConfig := string(haproxyConfigData)
+
+	// Replace the 4 spaces at the start of each line
+	regex := regexp.MustCompile("(?m)^    ")
+	haproxyConfig = regex.ReplaceAllString(haproxyConfig, "")
+
+	// Replace the maxconn value
+	haproxyConfig = strings.Replace(haproxyConfig, "maxconn 2048", fmt.Sprintf("maxconn %d", maxConnections), 1)
+	assert.Contains(t, haproxyConfig, fmt.Sprintf("maxconn %d", maxConnections))
+
+	assert.Equal(t,
+		haproxyConfig,
+		vars.PerconaXtraDBClusterSpec.HAProxy.Configuration,
+	)
+}