Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions api/core/v1beta1/openstackcontrolplane_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -990,9 +990,8 @@ func (r *OpenStackControlPlane) DefaultServices() {
if template.StorageClass == "" {
template.StorageClass = r.Spec.StorageClass
}
if template.Secret == "" {
template.Secret = r.Spec.Secret
}
// Don't default Secret here - it's handled conditionally in reconciliation
// to support both default (osp-secret) and auto-generated (blank) passwords
template.Default()
// By-value copy, need to update
(*r.Spec.Galera.Templates)[key] = template
Expand Down
67 changes: 67 additions & 0 deletions api/core/v1beta1/openstackcontrolplane_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
ironicv1 "github.com/openstack-k8s-operators/ironic-operator/api/v1beta1"
keystonev1 "github.com/openstack-k8s-operators/keystone-operator/api/v1beta1"
manilav1 "github.com/openstack-k8s-operators/manila-operator/api/v1beta1"
mariadbv1 "github.com/openstack-k8s-operators/mariadb-operator/api/v1beta1"
neutronv1 "github.com/openstack-k8s-operators/neutron-operator/api/v1beta1"
novav1 "github.com/openstack-k8s-operators/nova-operator/api/v1beta1"
octaviav1 "github.com/openstack-k8s-operators/octavia-operator/api/v1beta1"
Expand All @@ -19,6 +20,7 @@ import (
watcherv1 "github.com/openstack-k8s-operators/watcher-operator/api/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
)

var _ = Describe("OpenStackControlPlane Webhook", func() {
Expand Down Expand Up @@ -942,4 +944,69 @@ var _ = Describe("OpenStackControlPlane Webhook", func() {
})
})
})

Context("Galera Secret field defaulting behavior", func() {
var instance *OpenStackControlPlane

BeforeEach(func() {
instance = &OpenStackControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "test-namespace",
},
Spec: OpenStackControlPlaneSpec{
Secret: "osp-secret",
StorageClass: "local-storage",
Galera: GaleraSection{
Enabled: true,
},
},
}
})

It("should not default template Secret when omitted in webhook", func() {
instance.Spec.Galera.Templates = ptr.To(map[string]mariadbv1.GaleraSpecCore{
"openstack": {
StorageRequest: "500M",
// Secret field is omitted/empty
},
})

instance.DefaultServices()

template := (*instance.Spec.Galera.Templates)["openstack"]
Expect(template.Secret).To(Equal(""))
})

It("should preserve explicitly set Secret value", func() {
// Create a Galera template with explicit Secret
instance.Spec.Galera.Templates = ptr.To(map[string]mariadbv1.GaleraSpecCore{
"openstack": {
StorageRequest: "500M",
Secret: "custom-secret",
},
})

instance.DefaultServices()

template := (*instance.Spec.Galera.Templates)["openstack"]
Expect(template.Secret).To(Equal("custom-secret"))
})

It("should preserve explicitly blank Secret for auto-generation", func() {
// Create a Galera template with explicitly blank Secret
instance.Spec.Galera.Templates = ptr.To(map[string]mariadbv1.GaleraSpecCore{
"openstack": {
StorageRequest: "500M",
Secret: "", // Explicitly blank for auto-generation
},
})

instance.DefaultServices()

template := (*instance.Spec.Galera.Templates)["openstack"]
// Should remain blank to allow mariadb-operator auto-generation
Expect(template.Secret).To(Equal(""))
})
})
})
162 changes: 162 additions & 0 deletions test/functional/ctlplane/openstackoperator_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3083,6 +3083,168 @@ var _ = Describe("OpenStackOperator controller", func() {
// to nil does not clear template-level NotificationsBus configuration.
// Template-level takes precedence over top-level.
})

//
// Galera Secret field behavior tests
//
When("A OpenStackControlPlane with blank Galera secret is created", func() {
BeforeEach(func() {
spec := GetDefaultOpenStackControlPlaneSpec()
spec["tls"] = GetTLSPublicSpec()

// Modify galera template to have blank secret for auto-generation
galeraTemplate := spec["galera"].(map[string]interface{})
templates := galeraTemplate["templates"].(map[string]interface{})
dbTemplate := templates[names.DBName.Name].(map[string]interface{})
dbTemplate["secret"] = "" // Explicitly blank for auto-generated password

DeferCleanup(
th.DeleteInstance,
CreateOpenStackControlPlane(names.OpenStackControlplaneName, spec),
)
})

It("should create Galera CR with blank secret allowing auto-generation", func() {
OSCtlplane := GetOpenStackControlPlane(names.OpenStackControlplaneName)
Expect(OSCtlplane.Spec.Galera.Enabled).Should(BeTrue())

Eventually(func(g Gomega) {
db := mariadb.GetGalera(names.DBName)
g.Expect(db).Should(Not(BeNil()))
// When created fresh with blank secret, it should remain blank
// (allowing mariadb-operator to auto-generate the root password)
g.Expect(db.Spec.Secret).To(Equal(""))
}, timeout, interval).Should(Succeed())
})
})

When("A OpenStackControlPlane with omitted Galera secret is created", func() {
BeforeEach(func() {
spec := GetDefaultOpenStackControlPlaneSpec()
spec["tls"] = GetTLSPublicSpec()

// Modify galera template to omit secret entirely (not set)
galeraTemplate := spec["galera"].(map[string]interface{})
templates := galeraTemplate["templates"].(map[string]interface{})
dbTemplate := templates[names.DBName.Name].(map[string]interface{})
delete(dbTemplate, "secret") // Omit the field entirely

DeferCleanup(th.DeleteInstance, CreateOpenStackControlPlane(names.OpenStackControlplaneName, spec))
})

It("should create Galera CR with blank secret for auto-generation", func() {
Eventually(func(g Gomega) {
db := mariadb.GetGalera(names.DBName)
g.Expect(db).ShouldNot(BeNil())
// When omitted (not explicitly set), should remain blank
// allowing mariadb-operator to auto-generate the password
g.Expect(db.Spec.Secret).To(Equal(""))
}, timeout, interval).Should(Succeed())
})
})

When("A OpenStackControlPlane with explicit custom Galera secret is created", func() {
BeforeEach(func() {
spec := GetDefaultOpenStackControlPlaneSpec()
spec["tls"] = GetTLSPublicSpec()

// Modify galera template to use custom secret
galeraTemplate := spec["galera"].(map[string]interface{})
templates := galeraTemplate["templates"].(map[string]interface{})
dbTemplate := templates[names.DBName.Name].(map[string]interface{})
dbTemplate["secret"] = "custom-galera-secret"

DeferCleanup(th.DeleteInstance, CreateOpenStackControlPlane(names.OpenStackControlplaneName, spec))
})

It("should create Galera CR with the custom secret", func() {
Eventually(func(g Gomega) {
db := mariadb.GetGalera(names.DBName)
g.Expect(db).ShouldNot(BeNil())
g.Expect(db.Spec.Secret).To(Equal("custom-galera-secret"))
}, timeout, interval).Should(Succeed())
})
})

When("Multiple Galera templates with different secret configurations are created", func() {
BeforeEach(func() {
spec := GetDefaultOpenStackControlPlaneSpec()
spec["tls"] = GetTLSPublicSpec()

// Modify galera templates to have different secret configurations
galeraTemplate := spec["galera"].(map[string]interface{})
templates := map[string]interface{}{
names.DBName.Name: map[string]interface{}{
"storageRequest": "500M",
// secret is omitted
},
names.DBCell1Name.Name: map[string]interface{}{
"storageRequest": "500M",
"secret": "cell1-secret", // Explicit secret
},
}
galeraTemplate["templates"] = templates

DeferCleanup(th.DeleteInstance, CreateOpenStackControlPlane(names.OpenStackControlplaneName, spec))
})

It("should create each Galera CR with its respective secret configuration", func() {
// Verify main DB with blank secret
Eventually(func(g Gomega) {
db := mariadb.GetGalera(names.DBName)
g.Expect(db).ShouldNot(BeNil())
g.Expect(db.Spec.Secret).To(Equal(""))
}, timeout, interval).Should(Succeed())

// Verify cell1 DB with explicit secret
Eventually(func(g Gomega) {
db := mariadb.GetGalera(names.DBCell1Name)
g.Expect(db).ShouldNot(BeNil())
g.Expect(db.Spec.Secret).To(Equal("cell1-secret"))
}, timeout, interval).Should(Succeed())
})
})

// Test that we can change from explicit secret to auto-generated
When("An OpenStackControlPlane Galera secret starts as osp-secret", func() {
BeforeEach(func() {
spec := GetDefaultOpenStackControlPlaneSpec()
spec["tls"] = GetTLSPublicSpec()

// Start with an EXPLICIT secret (old deployment style)
galeraTemplate := spec["galera"].(map[string]interface{})
templates := galeraTemplate["templates"].(map[string]interface{})
dbTemplate := templates[names.DBName.Name].(map[string]interface{})
dbTemplate["secret"] = "osp-secret" // Explicit secret, as in pre-FR6 versions

DeferCleanup(th.DeleteInstance, CreateOpenStackControlPlane(names.OpenStackControlplaneName, spec))
})

It("should allow changing to blank secret for auto-generation", func() {
Eventually(func(g Gomega) {
db := mariadb.GetGalera(names.DBName)
g.Expect(db).ShouldNot(BeNil())
g.Expect(db.Spec.Secret).To(Equal("osp-secret"))
}, timeout, interval).Should(Succeed())

Eventually(func(g Gomega) {
oscp := GetOpenStackControlPlane(names.OpenStackControlplaneName)
templates := *oscp.Spec.Galera.Templates
t := templates[names.DBName.Name]
t.Secret = "" // User removes secret to enable auto-gen
templates[names.DBName.Name] = t
oscp.Spec.Galera.Templates = &templates
g.Expect(k8sClient.Update(ctx, oscp)).Should(Succeed())
}, timeout, interval).Should(Succeed())

Eventually(func(g Gomega) {
db := mariadb.GetGalera(names.DBName)
g.Expect(db).ShouldNot(BeNil())
g.Expect(db.Spec.Secret).To(Equal(""))
}, timeout, interval).Should(Succeed())
})

})
})

var _ = Describe("OpenStackOperator Webhook", func() {
Expand Down
Loading