fix: enable faster db recovery (#1020)

diff --git a/internal/openstack_helm/barbican.go b/internal/openstack_helm/barbican.go
new file mode 100644
index 0000000..f246542
--- /dev/null
+++ b/internal/openstack_helm/barbican.go
@@ -0,0 +1,5 @@
+package openstack_helm
+
+type BarbicanConf struct {
+	Database *DatabaseConf `yaml:"database,omitempty"`
+}
diff --git a/internal/openstack_helm/cinder.go b/internal/openstack_helm/cinder.go
new file mode 100644
index 0000000..d9938a3
--- /dev/null
+++ b/internal/openstack_helm/cinder.go
@@ -0,0 +1,5 @@
+package openstack_helm
+
+type CinderConf struct {
+	Database *DatabaseConf `yaml:"database,omitempty"`
+}
diff --git a/internal/openstack_helm/database.go b/internal/openstack_helm/database.go
new file mode 100644
index 0000000..c5a52e7
--- /dev/null
+++ b/internal/openstack_helm/database.go
@@ -0,0 +1,7 @@
+package openstack_helm
+
+type DatabaseConf struct {
+	ConnectionRecycleTime int `yaml:"connection_recycle_time,omitempty"`
+	MaxPoolSize           int `yaml:"max_pool_size,omitempty"`
+	MaxRetries            int `yaml:"max_retries,omitempty"`
+}
diff --git a/internal/openstack_helm/designate.go b/internal/openstack_helm/designate.go
new file mode 100644
index 0000000..96bf595
--- /dev/null
+++ b/internal/openstack_helm/designate.go
@@ -0,0 +1,5 @@
+package openstack_helm
+
+type DesignateConf struct {
+	Database *DatabaseConf `yaml:"database,omitempty"`
+}
diff --git a/internal/openstack_helm/glance.go b/internal/openstack_helm/glance.go
new file mode 100644
index 0000000..ad1f7e8
--- /dev/null
+++ b/internal/openstack_helm/glance.go
@@ -0,0 +1,5 @@
+package openstack_helm
+
+type GlanceConf struct {
+	Database *DatabaseConf `yaml:"database,omitempty"`
+}
diff --git a/internal/openstack_helm/heat.go b/internal/openstack_helm/heat.go
new file mode 100644
index 0000000..3777c30
--- /dev/null
+++ b/internal/openstack_helm/heat.go
@@ -0,0 +1,5 @@
+package openstack_helm
+
+type HeatConf struct {
+	Database *DatabaseConf `yaml:"database,omitempty"`
+}
diff --git a/internal/openstack_helm/keystone.go b/internal/openstack_helm/keystone.go
new file mode 100644
index 0000000..3ac5d68
--- /dev/null
+++ b/internal/openstack_helm/keystone.go
@@ -0,0 +1,5 @@
+package openstack_helm
+
+type KeystoneConf struct {
+	Database *DatabaseConf `yaml:"database,omitempty"`
+}
diff --git a/internal/openstack_helm/magnum.go b/internal/openstack_helm/magnum.go
new file mode 100644
index 0000000..edf6e7b
--- /dev/null
+++ b/internal/openstack_helm/magnum.go
@@ -0,0 +1,5 @@
+package openstack_helm
+
+type MagnumConf struct {
+	Database *DatabaseConf `yaml:"database,omitempty"`
+}
diff --git a/internal/openstack_helm/manila.go b/internal/openstack_helm/manila.go
new file mode 100644
index 0000000..872049e
--- /dev/null
+++ b/internal/openstack_helm/manila.go
@@ -0,0 +1,5 @@
+package openstack_helm
+
+type ManilaConf struct {
+	Database *DatabaseConf `yaml:"database,omitempty"`
+}
diff --git a/internal/openstack_helm/neutron.go b/internal/openstack_helm/neutron.go
new file mode 100644
index 0000000..1c39ac8
--- /dev/null
+++ b/internal/openstack_helm/neutron.go
@@ -0,0 +1,5 @@
+package openstack_helm
+
+type NeutronConf struct {
+	Database *DatabaseConf `yaml:"database,omitempty"`
+}
diff --git a/internal/openstack_helm/nova.go b/internal/openstack_helm/nova.go
new file mode 100644
index 0000000..5f5618b
--- /dev/null
+++ b/internal/openstack_helm/nova.go
@@ -0,0 +1,5 @@
+package openstack_helm
+
+type NovaConf struct {
+	Database *DatabaseConf `yaml:"database,omitempty"`
+}
diff --git a/internal/openstack_helm/octavia.go b/internal/openstack_helm/octavia.go
new file mode 100644
index 0000000..881c026
--- /dev/null
+++ b/internal/openstack_helm/octavia.go
@@ -0,0 +1,5 @@
+package openstack_helm
+
+type OctaviaConf struct {
+	Database *DatabaseConf `yaml:"database,omitempty"`
+}
diff --git a/internal/openstack_helm/openstack_helm.go b/internal/openstack_helm/openstack_helm.go
new file mode 100644
index 0000000..e66c335
--- /dev/null
+++ b/internal/openstack_helm/openstack_helm.go
@@ -0,0 +1,79 @@
+package openstack_helm
+
+import (
+	"gopkg.in/yaml.v2"
+	"helm.sh/helm/v3/pkg/chart/loader"
+	"helm.sh/helm/v3/pkg/chartutil"
+)
+
+type HelmValues struct {
+	Conf `yaml:"conf"`
+}
+
+type Conf struct {
+	Barbican  *BarbicanConf  `yaml:"barbican,omitempty"`
+	Cinder    *CinderConf    `yaml:"cinder,omitempty"`
+	Designate *DesignateConf `yaml:"designate,omitempty"`
+	Glance    *GlanceConf    `yaml:"glance,omitempty"`
+	Heat      *HeatConf      `yaml:"heat,omitempty"`
+	Keystone  *KeystoneConf  `yaml:"keystone,omitempty"`
+	Magnum    *MagnumConf    `yaml:"magnum,omitempty"`
+	Manila    *ManilaConf    `yaml:"manila,omitempty"`
+	Neutron   *NeutronConf   `yaml:"neutron,omitempty"`
+	Nova      *NovaConf      `yaml:"nova,omitempty"`
+	Octavia   *OctaviaConf   `yaml:"octavia,omitempty"`
+	Placement *PlacementConf `yaml:"placement,omitempty"`
+	Senlin    *SenlinConf    `yaml:"senlin,omitempty"`
+	Staffeln  *StaffelnConf  `yaml:"staffeln,omitempty"`
+}
+
+func (h *HelmValues) YAML() ([]byte, error) {
+	return yaml.Marshal(h)
+}
+
+func (h *HelmValues) NativeHelmValues() (chartutil.Values, error) {
+	yamlData, err := h.YAML()
+	if err != nil {
+		return nil, err
+	}
+
+	return chartutil.ReadValues(yamlData)
+}
+
+func FromYAML(yamlData []byte) (*HelmValues, error) {
+	var helmValues HelmValues
+	err := yaml.Unmarshal(yamlData, &helmValues)
+	if err != nil {
+		return nil, err
+	}
+
+	return &helmValues, nil
+}
+
+func FromYAMLString(yamlString string) (*HelmValues, error) {
+	return FromYAML([]byte(yamlString))
+}
+
+func CoalescedHelmValues(name string, values *HelmValues) (*HelmValues, error) {
+	chart, err := loader.Load(name)
+	if err != nil {
+		return nil, err
+	}
+
+	releaseValues, err := values.NativeHelmValues()
+	if err != nil {
+		return nil, err
+	}
+
+	mergedValues, err := chartutil.CoalesceValues(chart, releaseValues)
+	if err != nil {
+		return nil, err
+	}
+
+	yamlData, err := mergedValues.YAML()
+	if err != nil {
+		return nil, err
+	}
+
+	return FromYAMLString(yamlData)
+}
diff --git a/internal/openstack_helm/placement.go b/internal/openstack_helm/placement.go
new file mode 100644
index 0000000..dbba720
--- /dev/null
+++ b/internal/openstack_helm/placement.go
@@ -0,0 +1,5 @@
+package openstack_helm
+
+type PlacementConf struct {
+	Database *DatabaseConf `yaml:"database,omitempty"`
+}
diff --git a/internal/openstack_helm/senlin.go b/internal/openstack_helm/senlin.go
new file mode 100644
index 0000000..5cb3e40
--- /dev/null
+++ b/internal/openstack_helm/senlin.go
@@ -0,0 +1,10 @@
+package openstack_helm
+
+type SenlinConf struct {
+	API      SenlinAPIConf `yaml:"senlin_api"`
+	Database *DatabaseConf `yaml:"database,omitempty"`
+}
+
+type SenlinAPIConf struct {
+	Workers int32 `yaml:"workers"`
+}
diff --git a/internal/openstack_helm/staffeln.go b/internal/openstack_helm/staffeln.go
new file mode 100644
index 0000000..7cf0c9f
--- /dev/null
+++ b/internal/openstack_helm/staffeln.go
@@ -0,0 +1,5 @@
+package openstack_helm
+
+type StaffelnConf struct {
+	Database *DatabaseConf `yaml:"database,omitempty"`
+}
diff --git a/internal/testutils/oslo_db.go b/internal/testutils/oslo_db.go
new file mode 100644
index 0000000..7d9feae
--- /dev/null
+++ b/internal/testutils/oslo_db.go
@@ -0,0 +1,15 @@
+package testutils
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/vexxhost/atmosphere/internal/openstack_helm"
+)
+
+func TestDatabaseConf(t *testing.T, config *openstack_helm.DatabaseConf) {
+	assert.Equal(t, 10, config.ConnectionRecycleTime)
+	assert.Equal(t, 1, config.MaxPoolSize)
+	assert.Equal(t, -1, config.MaxRetries)
+}
diff --git a/roles/barbican/vars/main.yml b/roles/barbican/vars/main.yml
index 7eb2c78..701df96 100644
--- a/roles/barbican/vars/main.yml
+++ b/roles/barbican/vars/main.yml
@@ -23,6 +23,9 @@
     barbican:
       DEFAULT:
         log_config_append: null
+      database:
+        connection_recycle_time: 10
+        max_pool_size: 1
       oslo_messaging_notifications:
         driver: noop
       simple_crypto_plugin:
diff --git a/roles/barbican/vars_test.go b/roles/barbican/vars_test.go
new file mode 100644
index 0000000..8a0dccc
--- /dev/null
+++ b/roles/barbican/vars_test.go
@@ -0,0 +1,39 @@
+package barbican
+
+import (
+	_ "embed"
+	"os"
+	"testing"
+
+	"github.com/goccy/go-yaml"
+	"github.com/stretchr/testify/require"
+
+	"github.com/vexxhost/atmosphere/internal/openstack_helm"
+	"github.com/vexxhost/atmosphere/internal/testutils"
+)
+
+var (
+	//go:embed vars/main.yml
+	varsFile []byte
+	vars     Vars
+)
+
+type Vars struct {
+	openstack_helm.HelmValues `yaml:"_barbican_helm_values"`
+}
+
+func TestMain(m *testing.M) {
+	t := &testing.T{}
+	err := yaml.UnmarshalWithOptions(varsFile, &vars)
+	require.NoError(t, err)
+
+	code := m.Run()
+	os.Exit(code)
+}
+
+func TestHelmValues(t *testing.T) {
+	vals, err := openstack_helm.CoalescedHelmValues("../../charts/barbican", &vars.HelmValues)
+	require.NoError(t, err)
+
+	testutils.TestDatabaseConf(t, vals.Conf.Barbican.Database)
+}
diff --git a/roles/cinder/vars/main.yml b/roles/cinder/vars/main.yml
index f0d1630..179f34e 100644
--- a/roles/cinder/vars/main.yml
+++ b/roles/cinder/vars/main.yml
@@ -36,6 +36,9 @@
         barbican_endpoint_type: internal
       cors:
         allowed_origins: "*"
+      database:
+        connection_recycle_time: 10
+        max_pool_size: 1
       oslo_messaging_notifications:
         driver: noop
   manifests:
diff --git a/roles/cinder/vars_test.go b/roles/cinder/vars_test.go
new file mode 100644
index 0000000..c501802
--- /dev/null
+++ b/roles/cinder/vars_test.go
@@ -0,0 +1,39 @@
+package cinder
+
+import (
+	_ "embed"
+	"os"
+	"testing"
+
+	"github.com/goccy/go-yaml"
+	"github.com/stretchr/testify/require"
+
+	"github.com/vexxhost/atmosphere/internal/openstack_helm"
+	"github.com/vexxhost/atmosphere/internal/testutils"
+)
+
+var (
+	//go:embed vars/main.yml
+	varsFile []byte
+	vars     Vars
+)
+
+type Vars struct {
+	openstack_helm.HelmValues `yaml:"__cinder_helm_values"`
+}
+
+func TestMain(m *testing.M) {
+	t := &testing.T{}
+	err := yaml.UnmarshalWithOptions(varsFile, &vars)
+	require.NoError(t, err)
+
+	code := m.Run()
+	os.Exit(code)
+}
+
+func TestHelmValues(t *testing.T) {
+	vals, err := openstack_helm.CoalescedHelmValues("../../charts/cinder", &vars.HelmValues)
+	require.NoError(t, err)
+
+	testutils.TestDatabaseConf(t, vals.Conf.Cinder.Database)
+}
diff --git a/roles/designate/vars/main.yml b/roles/designate/vars/main.yml
index 09ac03c..5e4e174 100644
--- a/roles/designate/vars/main.yml
+++ b/roles/designate/vars/main.yml
@@ -18,6 +18,9 @@
     tags: "{{ atmosphere_images | vexxhost.atmosphere.openstack_helm_image_tags('designate') }}"
   conf:
     designate:
+      database:
+        connection_recycle_time: 10
+        max_pool_size: 1
       service:central:
         managed_resource_tenant_id: "{{ _designate_project_info.openstack_projects[0].id }}"
     pools: "{{ designate_pools | to_yaml }}"
diff --git a/roles/designate/vars_test.go b/roles/designate/vars_test.go
new file mode 100644
index 0000000..e3ec382
--- /dev/null
+++ b/roles/designate/vars_test.go
@@ -0,0 +1,39 @@
+package designate
+
+import (
+	_ "embed"
+	"os"
+	"testing"
+
+	"github.com/goccy/go-yaml"
+	"github.com/stretchr/testify/require"
+
+	"github.com/vexxhost/atmosphere/internal/openstack_helm"
+	"github.com/vexxhost/atmosphere/internal/testutils"
+)
+
+var (
+	//go:embed vars/main.yml
+	varsFile []byte
+	vars     Vars
+)
+
+type Vars struct {
+	openstack_helm.HelmValues `yaml:"_designate_helm_values"`
+}
+
+func TestMain(m *testing.M) {
+	t := &testing.T{}
+	err := yaml.UnmarshalWithOptions(varsFile, &vars)
+	require.NoError(t, err)
+
+	code := m.Run()
+	os.Exit(code)
+}
+
+func TestHelmValues(t *testing.T) {
+	vals, err := openstack_helm.CoalescedHelmValues("../../charts/designate", &vars.HelmValues)
+	require.NoError(t, err)
+
+	testutils.TestDatabaseConf(t, vals.Conf.Designate.Database)
+}
diff --git a/roles/glance/vars/main.yml b/roles/glance/vars/main.yml
index 9f62885..7774bb6 100644
--- a/roles/glance/vars/main.yml
+++ b/roles/glance/vars/main.yml
@@ -43,6 +43,9 @@
         workers: 8
       cors:
         allowed_origins: "*"
+      database:
+        connection_recycle_time: 10
+        max_pool_size: 1
       image_format:
         disk_formats: "qcow2,raw"
       oslo_messaging_notifications:
diff --git a/roles/glance/vars_test.go b/roles/glance/vars_test.go
new file mode 100644
index 0000000..aa0455c
--- /dev/null
+++ b/roles/glance/vars_test.go
@@ -0,0 +1,39 @@
+package glance
+
+import (
+	_ "embed"
+	"os"
+	"testing"
+
+	"github.com/goccy/go-yaml"
+	"github.com/stretchr/testify/require"
+
+	"github.com/vexxhost/atmosphere/internal/openstack_helm"
+	"github.com/vexxhost/atmosphere/internal/testutils"
+)
+
+var (
+	//go:embed vars/main.yml
+	varsFile []byte
+	vars     Vars
+)
+
+type Vars struct {
+	openstack_helm.HelmValues `yaml:"_glance_helm_values"`
+}
+
+func TestMain(m *testing.M) {
+	t := &testing.T{}
+	err := yaml.UnmarshalWithOptions(varsFile, &vars)
+	require.NoError(t, err)
+
+	code := m.Run()
+	os.Exit(code)
+}
+
+func TestHelmValues(t *testing.T) {
+	vals, err := openstack_helm.CoalescedHelmValues("../../charts/glance", &vars.HelmValues)
+	require.NoError(t, err)
+
+	testutils.TestDatabaseConf(t, vals.Conf.Glance.Database)
+}
diff --git a/roles/heat/vars/main.yml b/roles/heat/vars/main.yml
index 3104161..10202f3 100644
--- a/roles/heat/vars/main.yml
+++ b/roles/heat/vars/main.yml
@@ -35,6 +35,9 @@
         server_keystone_endpoint_type: public
       clients_keystone:
         endpoint_type: publicURL
+      database:
+        connection_recycle_time: 10
+        max_pool_size: 1
       ec2authtoken:
         auth_uri: http://keystone-api.openstack.svc.cluster.local:5000
       heat_api:
diff --git a/roles/heat/vars_test.go b/roles/heat/vars_test.go
new file mode 100644
index 0000000..a87f90c
--- /dev/null
+++ b/roles/heat/vars_test.go
@@ -0,0 +1,39 @@
+package heat
+
+import (
+	_ "embed"
+	"os"
+	"testing"
+
+	"github.com/goccy/go-yaml"
+	"github.com/stretchr/testify/require"
+
+	"github.com/vexxhost/atmosphere/internal/openstack_helm"
+	"github.com/vexxhost/atmosphere/internal/testutils"
+)
+
+var (
+	//go:embed vars/main.yml
+	varsFile []byte
+	vars     Vars
+)
+
+type Vars struct {
+	openstack_helm.HelmValues `yaml:"_heat_helm_values"`
+}
+
+func TestMain(m *testing.M) {
+	t := &testing.T{}
+	err := yaml.UnmarshalWithOptions(varsFile, &vars)
+	require.NoError(t, err)
+
+	code := m.Run()
+	os.Exit(code)
+}
+
+func TestHelmValues(t *testing.T) {
+	vals, err := openstack_helm.CoalescedHelmValues("../../charts/heat", &vars.HelmValues)
+	require.NoError(t, err)
+
+	testutils.TestDatabaseConf(t, vals.Conf.Heat.Database)
+}
diff --git a/roles/keystone/vars/main.yml b/roles/keystone/vars/main.yml
index 4ed3565..625ff2f 100644
--- a/roles/keystone/vars/main.yml
+++ b/roles/keystone/vars/main.yml
@@ -35,6 +35,9 @@
         methods: password,token,openid,application_credential
       cors:
         allowed_origins: "*"
+      database:
+        connection_recycle_time: 10
+        max_pool_size: 1
       openid:
         remote_id_attribute: HTTP_OIDC_ISS
       federation:
diff --git a/roles/keystone/vars_test.go b/roles/keystone/vars_test.go
new file mode 100644
index 0000000..c5bfe19
--- /dev/null
+++ b/roles/keystone/vars_test.go
@@ -0,0 +1,32 @@
+package keystone
+
+import (
+	_ "embed"
+	"testing"
+
+	"github.com/goccy/go-yaml"
+	"github.com/stretchr/testify/require"
+
+	"github.com/vexxhost/atmosphere/internal/openstack_helm"
+	"github.com/vexxhost/atmosphere/internal/testutils"
+)
+
+var (
+	//go:embed vars/main.yml
+	varsFile []byte
+	vars     Vars
+)
+
+type Vars struct {
+	openstack_helm.HelmValues `yaml:"_keystone_helm_values"`
+}
+
+func TestHelmValues(t *testing.T) {
+	err := yaml.UnmarshalWithOptions(varsFile, &vars)
+	require.NoError(t, err)
+
+	vals, err := openstack_helm.CoalescedHelmValues("../../charts/keystone", &vars.HelmValues)
+	require.NoError(t, err)
+
+	testutils.TestDatabaseConf(t, vals.Conf.Keystone.Database)
+}
diff --git a/roles/magnum/vars/main.yml b/roles/magnum/vars/main.yml
index 8bc7fa3..eeee431 100644
--- a/roles/magnum/vars/main.yml
+++ b/roles/magnum/vars/main.yml
@@ -33,6 +33,9 @@
         kubernetes_default_network_driver: calico
       conductor:
         workers: 4
+      database:
+        connection_recycle_time: 10
+        max_pool_size: 1
       drivers:
         verify_ca: false
       glance_client:
diff --git a/roles/magnum/vars_test.go b/roles/magnum/vars_test.go
new file mode 100644
index 0000000..21e8fd6
--- /dev/null
+++ b/roles/magnum/vars_test.go
@@ -0,0 +1,39 @@
+package magnum
+
+import (
+	_ "embed"
+	"os"
+	"testing"
+
+	"github.com/goccy/go-yaml"
+	"github.com/stretchr/testify/require"
+
+	"github.com/vexxhost/atmosphere/internal/openstack_helm"
+	"github.com/vexxhost/atmosphere/internal/testutils"
+)
+
+var (
+	//go:embed vars/main.yml
+	varsFile []byte
+	vars     Vars
+)
+
+type Vars struct {
+	openstack_helm.HelmValues `yaml:"_magnum_helm_values"`
+}
+
+func TestMain(m *testing.M) {
+	t := &testing.T{}
+	err := yaml.UnmarshalWithOptions(varsFile, &vars)
+	require.NoError(t, err)
+
+	code := m.Run()
+	os.Exit(code)
+}
+
+func TestHelmValues(t *testing.T) {
+	vals, err := openstack_helm.CoalescedHelmValues("../../charts/magnum", &vars.HelmValues)
+	require.NoError(t, err)
+
+	testutils.TestDatabaseConf(t, vals.Conf.Magnum.Database)
+}
diff --git a/roles/manila/vars/main.yml b/roles/manila/vars/main.yml
index 01b8541..40e738f 100644
--- a/roles/manila/vars/main.yml
+++ b/roles/manila/vars/main.yml
@@ -47,6 +47,9 @@
       DEFAULT:
         host: manila-share-worker
         osapi_share_workers: 4
+      database:
+        connection_recycle_time: 10
+        max_pool_size: 1
       generic:
         connect_share_server_to_tenant_network: true
         limit_ssh_access: true
diff --git a/roles/manila/vars_test.go b/roles/manila/vars_test.go
new file mode 100644
index 0000000..627920b
--- /dev/null
+++ b/roles/manila/vars_test.go
@@ -0,0 +1,39 @@
+package manila
+
+import (
+	_ "embed"
+	"os"
+	"testing"
+
+	"github.com/goccy/go-yaml"
+	"github.com/stretchr/testify/require"
+
+	"github.com/vexxhost/atmosphere/internal/openstack_helm"
+	"github.com/vexxhost/atmosphere/internal/testutils"
+)
+
+var (
+	//go:embed vars/main.yml
+	varsFile []byte
+	vars     Vars
+)
+
+type Vars struct {
+	openstack_helm.HelmValues `yaml:"_manila_helm_values"`
+}
+
+func TestMain(m *testing.M) {
+	t := &testing.T{}
+	err := yaml.UnmarshalWithOptions(varsFile, &vars)
+	require.NoError(t, err)
+
+	code := m.Run()
+	os.Exit(code)
+}
+
+func TestHelmValues(t *testing.T) {
+	vals, err := openstack_helm.CoalescedHelmValues("../../charts/manila", &vars.HelmValues)
+	require.NoError(t, err)
+
+	testutils.TestDatabaseConf(t, vals.Conf.Manila.Database)
+}
diff --git a/roles/neutron/vars/main.yml b/roles/neutron/vars/main.yml
index 3ccd02e..151363d 100644
--- a/roles/neutron/vars/main.yml
+++ b/roles/neutron/vars/main.yml
@@ -33,6 +33,9 @@
         external_dns_driver: "{{ _neutron_external_dns_driver | default(omit) }}"
       cors:
         allowed_origin: "*"
+      database:
+        connection_recycle_time: 10
+        max_pool_size: 1
       nova:
         live_migration_events: true
       placement:
diff --git a/roles/neutron/vars_test.go b/roles/neutron/vars_test.go
new file mode 100644
index 0000000..8358366
--- /dev/null
+++ b/roles/neutron/vars_test.go
@@ -0,0 +1,39 @@
+package neutron
+
+import (
+	_ "embed"
+	"os"
+	"testing"
+
+	"github.com/goccy/go-yaml"
+	"github.com/stretchr/testify/require"
+
+	"github.com/vexxhost/atmosphere/internal/openstack_helm"
+	"github.com/vexxhost/atmosphere/internal/testutils"
+)
+
+var (
+	//go:embed vars/main.yml
+	varsFile []byte
+	vars     Vars
+)
+
+type Vars struct {
+	openstack_helm.HelmValues `yaml:"__neutron_helm_values"`
+}
+
+func TestMain(m *testing.M) {
+	t := &testing.T{}
+	err := yaml.UnmarshalWithOptions(varsFile, &vars)
+	require.NoError(t, err)
+
+	code := m.Run()
+	os.Exit(code)
+}
+
+func TestHelmValues(t *testing.T) {
+	vals, err := openstack_helm.CoalescedHelmValues("../../charts/neutron", &vars.HelmValues)
+	require.NoError(t, err)
+
+	testutils.TestDatabaseConf(t, vals.Conf.Neutron.Database)
+}
diff --git a/roles/nova/vars/main.yml b/roles/nova/vars/main.yml
index 1b9c802..b7a93c2 100644
--- a/roles/nova/vars/main.yml
+++ b/roles/nova/vars/main.yml
@@ -72,6 +72,9 @@
       cors:
         allowed_origin: "*"
         allow_headers: "X-Auth-Token,X-OpenStack-Nova-API-Version"
+      database:
+        connection_recycle_time: 10
+        max_pool_size: 1
       filter_scheduler:
         enabled_filters:
           AvailabilityZoneFilter,
diff --git a/roles/nova/vars_test.go b/roles/nova/vars_test.go
new file mode 100644
index 0000000..716b946
--- /dev/null
+++ b/roles/nova/vars_test.go
@@ -0,0 +1,39 @@
+package nova
+
+import (
+	_ "embed"
+	"os"
+	"testing"
+
+	"github.com/goccy/go-yaml"
+	"github.com/stretchr/testify/require"
+
+	"github.com/vexxhost/atmosphere/internal/openstack_helm"
+	"github.com/vexxhost/atmosphere/internal/testutils"
+)
+
+var (
+	//go:embed vars/main.yml
+	varsFile []byte
+	vars     Vars
+)
+
+type Vars struct {
+	openstack_helm.HelmValues `yaml:"_nova_helm_values"`
+}
+
+func TestMain(m *testing.M) {
+	t := &testing.T{}
+	err := yaml.UnmarshalWithOptions(varsFile, &vars)
+	require.NoError(t, err)
+
+	code := m.Run()
+	os.Exit(code)
+}
+
+func TestHelmValues(t *testing.T) {
+	vals, err := openstack_helm.CoalescedHelmValues("../../charts/nova", &vars.HelmValues)
+	require.NoError(t, err)
+
+	testutils.TestDatabaseConf(t, vals.Conf.Nova.Database)
+}
diff --git a/roles/octavia/vars/main.yml b/roles/octavia/vars/main.yml
index f83ae85..5cb3f1d 100644
--- a/roles/octavia/vars/main.yml
+++ b/roles/octavia/vars/main.yml
@@ -105,6 +105,9 @@
         client_ca: /etc/octavia/certs/client/ca.crt
         volume_driver: volume_cinder_driver
         workers: 4
+      database:
+        connection_recycle_time: 10
+        max_pool_size: 1
       glance:
         endpoint_type: internalURL
       haproxy_amphora:
diff --git a/roles/octavia/vars_test.go b/roles/octavia/vars_test.go
new file mode 100644
index 0000000..8c99a14
--- /dev/null
+++ b/roles/octavia/vars_test.go
@@ -0,0 +1,39 @@
+package octavia
+
+import (
+	_ "embed"
+	"os"
+	"testing"
+
+	"github.com/goccy/go-yaml"
+	"github.com/stretchr/testify/require"
+
+	"github.com/vexxhost/atmosphere/internal/openstack_helm"
+	"github.com/vexxhost/atmosphere/internal/testutils"
+)
+
+var (
+	//go:embed vars/main.yml
+	varsFile []byte
+	vars     Vars
+)
+
+type Vars struct {
+	openstack_helm.HelmValues `yaml:"_octavia_helm_values"`
+}
+
+func TestMain(m *testing.M) {
+	t := &testing.T{}
+	err := yaml.UnmarshalWithOptions(varsFile, &vars)
+	require.NoError(t, err)
+
+	code := m.Run()
+	os.Exit(code)
+}
+
+func TestHelmValues(t *testing.T) {
+	vals, err := openstack_helm.CoalescedHelmValues("../../charts/octavia", &vars.HelmValues)
+	require.NoError(t, err)
+
+	testutils.TestDatabaseConf(t, vals.Conf.Octavia.Database)
+}
diff --git a/roles/placement/vars/main.yml b/roles/placement/vars/main.yml
index 322a0ba..58082d8 100644
--- a/roles/placement/vars/main.yml
+++ b/roles/placement/vars/main.yml
@@ -23,6 +23,10 @@
     placement:
       DEFAULT:
         log_config_append: null
+      database:
+        connection_recycle_time: 10
+        max_pool_size: 1
+        max_retries: -1
       oslo_messaging_notifications:
         driver: noop
   manifests:
diff --git a/roles/placement/vars_test.go b/roles/placement/vars_test.go
new file mode 100644
index 0000000..fa3fac3
--- /dev/null
+++ b/roles/placement/vars_test.go
@@ -0,0 +1,39 @@
+package placement
+
+import (
+	_ "embed"
+	"os"
+	"testing"
+
+	"github.com/goccy/go-yaml"
+	"github.com/stretchr/testify/require"
+
+	"github.com/vexxhost/atmosphere/internal/openstack_helm"
+	"github.com/vexxhost/atmosphere/internal/testutils"
+)
+
+var (
+	//go:embed vars/main.yml
+	varsFile []byte
+	vars     Vars
+)
+
+type Vars struct {
+	openstack_helm.HelmValues `yaml:"_placement_helm_values"`
+}
+
+func TestMain(m *testing.M) {
+	t := &testing.T{}
+	err := yaml.UnmarshalWithOptions(varsFile, &vars)
+	require.NoError(t, err)
+
+	code := m.Run()
+	os.Exit(code)
+}
+
+func TestHelmValues(t *testing.T) {
+	vals, err := openstack_helm.CoalescedHelmValues("../../charts/placement", &vars.HelmValues)
+	require.NoError(t, err)
+
+	testutils.TestDatabaseConf(t, vals.Conf.Placement.Database)
+}
diff --git a/roles/senlin/vars/main.yml b/roles/senlin/vars/main.yml
index e0b9887..d1fcbdc 100644
--- a/roles/senlin/vars/main.yml
+++ b/roles/senlin/vars/main.yml
@@ -26,6 +26,9 @@
     senlin:
       DEFAULT:
         log_config_append: null
+      database:
+        connection_recycle_time: 10
+        max_pool_size: 1
       oslo_messaging_notifications:
         driver: noop
       senlin_api:
diff --git a/roles/senlin/vars_test.go b/roles/senlin/vars_test.go
index d872627..f399407 100644
--- a/roles/senlin/vars_test.go
+++ b/roles/senlin/vars_test.go
@@ -8,6 +8,9 @@
 	"github.com/goccy/go-yaml"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+
+	"github.com/vexxhost/atmosphere/internal/openstack_helm"
+	"github.com/vexxhost/atmosphere/internal/testutils"
 )
 
 var (
@@ -17,23 +20,7 @@
 )
 
 type Vars struct {
-	SenlinHelmValues `yaml:"_senlin_helm_values"`
-}
-
-type SenlinHelmValues struct {
-	Conf `yaml:"conf"`
-}
-
-type Conf struct {
-	Senlin SenlinConf `yaml:"senlin"`
-}
-
-type SenlinConf struct {
-	API SenlinAPIConf `yaml:"senlin_api"`
-}
-
-type SenlinAPIConf struct {
-	Workers int32 `yaml:"workers"`
+	openstack_helm.HelmValues `yaml:"_senlin_helm_values"`
 }
 
 func TestMain(m *testing.M) {
@@ -45,6 +32,11 @@
 	os.Exit(code)
 }
 
-func TestSenlinHelmValues(t *testing.T) {
-	assert.Equal(t, int32(2), vars.SenlinHelmValues.Conf.Senlin.API.Workers)
+func TestHelmValues(t *testing.T) {
+	vals, err := openstack_helm.CoalescedHelmValues("../../charts/senlin", &vars.HelmValues)
+	require.NoError(t, err)
+
+	assert.Equal(t, int32(2), vals.Conf.Senlin.API.Workers)
+
+	testutils.TestDatabaseConf(t, vals.Conf.Senlin.Database)
 }
diff --git a/roles/staffeln/vars/main.yml b/roles/staffeln/vars/main.yml
index c0be4b3..d23c9b1 100644
--- a/roles/staffeln/vars/main.yml
+++ b/roles/staffeln/vars/main.yml
@@ -25,6 +25,9 @@
       conductor:
         backup_metadata_key: "{{ staffeln_backup_metadata_key }}"
         retention_metadata_key: "{{ staffeln_retention_metadata_key }}"
+      database:
+        connection_recycle_time: 10
+        max_pool_size: 1
   manifests:
     ingress_api: false
     service_ingress_api: false
diff --git a/roles/staffeln/vars_test.go b/roles/staffeln/vars_test.go
new file mode 100644
index 0000000..07ec9ab
--- /dev/null
+++ b/roles/staffeln/vars_test.go
@@ -0,0 +1,39 @@
+package staffeln
+
+import (
+	_ "embed"
+	"os"
+	"testing"
+
+	"github.com/goccy/go-yaml"
+	"github.com/stretchr/testify/require"
+
+	"github.com/vexxhost/atmosphere/internal/openstack_helm"
+	"github.com/vexxhost/atmosphere/internal/testutils"
+)
+
+var (
+	//go:embed vars/main.yml
+	varsFile []byte
+	vars     Vars
+)
+
+type Vars struct {
+	openstack_helm.HelmValues `yaml:"_staffeln_helm_values"`
+}
+
+func TestMain(m *testing.M) {
+	t := &testing.T{}
+	err := yaml.UnmarshalWithOptions(varsFile, &vars)
+	require.NoError(t, err)
+
+	code := m.Run()
+	os.Exit(code)
+}
+
+func TestHelmValues(t *testing.T) {
+	vals, err := openstack_helm.CoalescedHelmValues("../../charts/staffeln", &vars.HelmValues)
+	require.NoError(t, err)
+
+	testutils.TestDatabaseConf(t, vals.Conf.Staffeln.Database)
+}