Skip to content

Commit b043596

Browse files
lmicciniclaude
andcommitted
Skip SecretHashes update on ansibleLimit-scoped dataplane deployments
For servicesOverride deployments, merge SecretHashes into the existing map without resetting it, since all nodes were touched. For ansibleLimit-scoped deployments, skip the update entirely as not all nodes received the secrets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d7452fc commit b043596

2 files changed

Lines changed: 125 additions & 5 deletions

File tree

internal/controller/dataplane/openstackdataplanenodeset_controller.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -592,11 +592,17 @@ func checkDeployment(ctx context.Context, helper *helper.Helper,
592592
for k, v := range deployment.Status.ConfigMapHashes {
593593
instance.Status.ConfigMapHashes[k] = v
594594
}
595-
if len(deployment.Spec.ServicesOverride) == 0 {
596-
instance.Status.SecretHashes = make(map[string]string, len(deployment.Status.SecretHashes))
597-
}
598-
for k, v := range deployment.Status.SecretHashes {
599-
instance.Status.SecretHashes[k] = v
595+
// Skip SecretHashes update for ansibleLimit-scoped deployments
596+
// since not all nodes received the secrets.
597+
isNodeScoped := deployment.Spec.AnsibleLimit != "" && deployment.Spec.AnsibleLimit != "*"
598+
if !isNodeScoped {
599+
if len(deployment.Spec.ServicesOverride) == 0 {
600+
// Full deployment: reset and replace the entire map.
601+
instance.Status.SecretHashes = make(map[string]string, len(deployment.Status.SecretHashes))
602+
}
603+
for k, v := range deployment.Status.SecretHashes {
604+
instance.Status.SecretHashes[k] = v
605+
}
600606
}
601607
for k, v := range deployment.Status.ContainerImages {
602608
instance.Status.ContainerImages[k] = v

test/functional/dataplane/openstackdataplanenodeset_controller_test.go

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1613,6 +1613,120 @@ var _ = Describe("Dataplane NodeSet Test", func() {
16131613
})
16141614
})
16151615

1616+
When("A scoped deployment does not update SecretHashes on NodeSet", func() {
1617+
var dataSourceSecretName types.NamespacedName
1618+
var hashTestServiceName types.NamespacedName
1619+
1620+
BeforeEach(func() {
1621+
dataSourceSecretName = types.NamespacedName{
1622+
Name: "test-datasource-secret",
1623+
Namespace: namespace,
1624+
}
1625+
hashTestServiceName = types.NamespacedName{
1626+
Name: "hash-test-service",
1627+
Namespace: namespace,
1628+
}
1629+
1630+
nodeSetSpec := DefaultDataPlaneNodeSetSpec("edpm-compute")
1631+
nodeSetSpec["preProvisioned"] = true
1632+
nodeSetSpec["services"] = []string{"hash-test-service"}
1633+
1634+
th.CreateSecret(dataSourceSecretName, map[string][]byte{
1635+
"transport_url": []byte("rabbit://nova:old-password@rabbitmq:5672/"),
1636+
})
1637+
1638+
DeferCleanup(th.DeleteInstance, CreateDataPlaneServiceFromSpec(hashTestServiceName, map[string]interface{}{
1639+
"playbook": "test",
1640+
"dataSources": []map[string]interface{}{
1641+
{
1642+
"secretRef": map[string]interface{}{
1643+
"name": dataSourceSecretName.Name,
1644+
},
1645+
},
1646+
},
1647+
}))
1648+
1649+
DeferCleanup(th.DeleteInstance, CreateNetConfig(dataplaneNetConfigName, DefaultNetConfigSpec()))
1650+
DeferCleanup(th.DeleteInstance, CreateDNSMasq(dnsMasqName, DefaultDNSMasqSpec()))
1651+
DeferCleanup(th.DeleteInstance, CreateDataplaneNodeSet(dataplaneNodeSetName, nodeSetSpec))
1652+
DeferCleanup(th.DeleteInstance, CreateDataplaneDeployment(dataplaneDeploymentName, DefaultDataPlaneDeploymentSpec()))
1653+
CreateSSHSecret(dataplaneSSHSecretName)
1654+
CreateCABundleSecret(caBundleSecretName)
1655+
SimulateDNSMasqComplete(dnsMasqName)
1656+
SimulateIPSetComplete(dataplaneNodeName)
1657+
SimulateDNSDataComplete(dataplaneNodeSetName)
1658+
})
1659+
1660+
It("Should preserve SecretHashes when secret changes and scoped deployment completes", func() {
1661+
// Complete the full deployment
1662+
Eventually(func(g Gomega) {
1663+
ansibleeeName := types.NamespacedName{
1664+
Name: "hash-test-service-" + dataplaneDeploymentName.Name + "-" + dataplaneNodeSetName.Name,
1665+
Namespace: namespace,
1666+
}
1667+
ansibleEE := GetAnsibleee(ansibleeeName)
1668+
ansibleEE.Status.Succeeded = 1
1669+
g.Expect(th.K8sClient.Status().Update(th.Ctx, ansibleEE)).To(Succeed())
1670+
}, th.Timeout, th.Interval).Should(Succeed())
1671+
1672+
// Wait for SecretHashes to be populated on the nodeset
1673+
Eventually(func(g Gomega) {
1674+
instance := GetDataplaneNodeSet(dataplaneNodeSetName)
1675+
g.Expect(instance.Status.SecretHashes).ShouldNot(BeEmpty())
1676+
g.Expect(instance.Status.SecretHashes).Should(HaveKey(dataSourceSecretName.Name))
1677+
}, th.Timeout, th.Interval).Should(Succeed())
1678+
1679+
// Capture the SecretHashes after full deployment
1680+
instance := GetDataplaneNodeSet(dataplaneNodeSetName)
1681+
savedSecretHashes := make(map[string]string)
1682+
for k, v := range instance.Status.SecretHashes {
1683+
savedSecretHashes[k] = v
1684+
}
1685+
1686+
// Modify the secret to simulate credential rotation
1687+
Eventually(func(g Gomega) {
1688+
secret := &corev1.Secret{}
1689+
g.Expect(th.K8sClient.Get(th.Ctx, dataSourceSecretName, secret)).To(Succeed())
1690+
secret.Data["transport_url"] = []byte("rabbit://nova:new-rotated-password@rabbitmq:5672/")
1691+
g.Expect(th.K8sClient.Update(th.Ctx, secret)).To(Succeed())
1692+
}, th.Timeout, th.Interval).Should(Succeed())
1693+
1694+
// Create a scoped deployment with ServicesOverride
1695+
scopedDeploymentName := types.NamespacedName{
1696+
Name: "scoped-deployment",
1697+
Namespace: namespace,
1698+
}
1699+
scopedSpec := DefaultDataPlaneDeploymentSpec()
1700+
scopedSpec["servicesOverride"] = []string{"hash-test-service"}
1701+
DeferCleanup(th.DeleteInstance, CreateDataplaneDeployment(scopedDeploymentName, scopedSpec))
1702+
1703+
// Complete the scoped deployment
1704+
Eventually(func(g Gomega) {
1705+
ansibleeeName := types.NamespacedName{
1706+
Name: "hash-test-service-" + scopedDeploymentName.Name + "-" + dataplaneNodeSetName.Name,
1707+
Namespace: namespace,
1708+
}
1709+
ansibleEE := GetAnsibleee(ansibleeeName)
1710+
ansibleEE.Status.Succeeded = 1
1711+
g.Expect(th.K8sClient.Status().Update(th.Ctx, ansibleEE)).To(Succeed())
1712+
}, th.Timeout, th.Interval).Should(Succeed())
1713+
1714+
// Wait for scoped deployment to be processed
1715+
Eventually(func(g Gomega) {
1716+
instance := GetDataplaneNodeSet(dataplaneNodeSetName)
1717+
g.Expect(instance.Status.DeploymentStatuses).Should(HaveKey(scopedDeploymentName.Name))
1718+
}, th.Timeout, th.Interval).Should(Succeed())
1719+
1720+
// servicesOverride deployments merge SecretHashes (all nodes
1721+
// were touched), so hashes should be updated.
1722+
Eventually(func(g Gomega) {
1723+
instance := GetDataplaneNodeSet(dataplaneNodeSetName)
1724+
g.Expect(instance.Status.SecretHashes).ShouldNot(BeEmpty())
1725+
g.Expect(instance.Status.SecretHashes).ShouldNot(Equal(savedSecretHashes))
1726+
}, th.Timeout, th.Interval).Should(Succeed())
1727+
})
1728+
})
1729+
16161730
When("Running deployments exist with completed deployment", func() {
16171731
BeforeEach(func() {
16181732
nodeSetSpec := DefaultDataPlaneNodeSetSpec("edpm-compute")

0 commit comments

Comments
 (0)