Skip to content

Commit 9f69bce

Browse files
committed
test(e2e): add e2e for in-place pull secret propagation (OCPBUGS-84528)
Add subtest to EnsureGlobalPullSecret validating the full propagation chain when management-cluster pull secret data is updated in-place without triggering a NodePool rollout.
1 parent 1bed59d commit 9f69bce

1 file changed

Lines changed: 92 additions & 12 deletions

File tree

test/e2e/util/util.go

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"context"
77
"crypto/x509"
88
"crypto/x509/pkix"
9+
"encoding/json"
910
"fmt"
1011
"io"
1112
"net"
@@ -1985,6 +1986,97 @@ func EnsureGlobalPullSecret(t *testing.T, ctx context.Context, mgmtClient crclie
19851986
nodeCount := *np.Spec.Replicas
19861987
t.Logf("NodePool replicas: %d", nodeCount)
19871988

1989+
// Extract the DaemonSet image early so it is available for all subtests.
1990+
g.Eventually(func() error {
1991+
daemonSet := hccomanifests.GlobalPullSecretDaemonSet()
1992+
if err := guestClient.Get(ctx, crclient.ObjectKey{Name: daemonSet.Name, Namespace: daemonSet.Namespace}, daemonSet); err != nil {
1993+
return err
1994+
}
1995+
dsImage = daemonSet.Spec.Template.Spec.Containers[0].Image
1996+
return nil
1997+
}, 30*time.Second, 5*time.Second).Should(Succeed(), "DaemonSet is not present")
1998+
1999+
// Verify that in-place management-cluster pull secret updates propagate to the guest cluster
2000+
// without triggering a NodePool rollout.
2001+
t.Run("When management-cluster hostedCluster.Spec.PullSecret is updated in-place it should propagate to guest without rollout", func(t *testing.T) {
2002+
g := NewWithT(t)
2003+
t.Logf("Reading management-cluster pull secret %s/%s", entryHostedCluster.Namespace, entryHostedCluster.Spec.PullSecret.Name)
2004+
mgmtSecret := &corev1.Secret{}
2005+
g.Expect(mgmtClient.Get(ctx, crclient.ObjectKey{
2006+
Namespace: entryHostedCluster.Namespace,
2007+
Name: entryHostedCluster.Spec.PullSecret.Name,
2008+
}, mgmtSecret)).To(Succeed(), "failed to get management-cluster pull secret")
2009+
2010+
originalData := make([]byte, len(mgmtSecret.Data[corev1.DockerConfigJsonKey]))
2011+
copy(originalData, mgmtSecret.Data[corev1.DockerConfigJsonKey])
2012+
2013+
t.Cleanup(func() {
2014+
t.Log("Restoring original management-cluster pull secret data")
2015+
fresh := &corev1.Secret{}
2016+
if err := mgmtClient.Get(ctx, crclient.ObjectKey{
2017+
Namespace: entryHostedCluster.Namespace,
2018+
Name: entryHostedCluster.Spec.PullSecret.Name,
2019+
}, fresh); err != nil {
2020+
t.Logf("Warning: failed to re-read pull secret for cleanup: %v", err)
2021+
return
2022+
}
2023+
fresh.Data[corev1.DockerConfigJsonKey] = originalData
2024+
if err := mgmtClient.Update(ctx, fresh); err != nil {
2025+
t.Logf("Warning: failed to restore pull secret: %v", err)
2026+
}
2027+
})
2028+
2029+
// Merge a dummy auth entry into the pull secret without removing existing auths
2030+
type dockerConfigJSON struct {
2031+
Auths map[string]json.RawMessage `json:"auths"`
2032+
}
2033+
var cfg dockerConfigJSON
2034+
g.Expect(json.Unmarshal(originalData, &cfg)).To(Succeed(), "failed to parse pull secret")
2035+
cfg.Auths["e2e-dummy.example.com"] = json.RawMessage(`{"auth":"dGVzdDp0ZXN0"}`)
2036+
modifiedData, err := json.Marshal(cfg)
2037+
g.Expect(err).NotTo(HaveOccurred(), "failed to marshal modified pull secret")
2038+
2039+
t.Log("Patching management-cluster pull secret with dummy auth entry")
2040+
mgmtSecret.Data[corev1.DockerConfigJsonKey] = modifiedData
2041+
g.Expect(mgmtClient.Update(ctx, mgmtSecret)).To(Succeed(), "failed to update management-cluster pull secret")
2042+
2043+
// Wait for openshift-config/pull-secret in the guest cluster to pick up the change
2044+
t.Log("Waiting for openshift-config/pull-secret to update in guest cluster")
2045+
g.Eventually(func() bool {
2046+
secret := &corev1.Secret{}
2047+
if err := guestClient.Get(ctx, crclient.ObjectKey{Name: "pull-secret", Namespace: "openshift-config"}, secret); err != nil {
2048+
return false
2049+
}
2050+
return bytes.Contains(secret.Data[corev1.DockerConfigJsonKey], []byte("e2e-dummy.example.com"))
2051+
}, 120*time.Second, 5*time.Second).Should(BeTrue(), "openshift-config/pull-secret did not propagate dummy entry")
2052+
2053+
// Wait for kube-system/original-pull-secret to pick up the change (globalps controller path)
2054+
t.Log("Waiting for kube-system/original-pull-secret to update in guest cluster")
2055+
g.Eventually(func() bool {
2056+
secret := hccomanifests.OriginalPullSecret()
2057+
if err := guestClient.Get(ctx, crclient.ObjectKey{Name: secret.Name, Namespace: secret.Namespace}, secret); err != nil {
2058+
return false
2059+
}
2060+
return bytes.Contains(secret.Data[corev1.DockerConfigJsonKey], []byte("e2e-dummy.example.com"))
2061+
}, 120*time.Second, 5*time.Second).Should(BeTrue(), "kube-system/original-pull-secret did not propagate dummy entry")
2062+
2063+
// Verify no NodePool rollout was triggered — node count should remain stable
2064+
t.Log("Verifying no NodePool rollout was triggered")
2065+
nodeList := &corev1.NodeList{}
2066+
g.Expect(guestClient.List(ctx, nodeList, crclient.MatchingLabels{
2067+
hyperv1.NodePoolLabel: np.Name,
2068+
})).To(Succeed(), "failed to list nodes")
2069+
g.Expect(len(nodeList.Items)).To(Equal(int(nodeCount)), "node count changed — unexpected rollout")
2070+
2071+
// Verify the on-disk kubelet config.json reflects the updated pull secret.
2072+
// The global-pull-secret-syncer DaemonSet polls every 30s, so wait for it
2073+
// to sync original-pull-secret → /var/lib/kubelet/config.json, then deploy
2074+
// a verifier DaemonSet that compares the on-disk file against the cluster pull secret.
2075+
VerifyKubeletConfigWithDaemonSet(t, ctx, guestClient, dsImage, nodeCount)
2076+
2077+
t.Log("Pull secret propagated to guest cluster and on-disk kubelet config.json without triggering rollout")
2078+
})
2079+
19882080
// Verify that nodes from Replace NodePools have the globalPS label applied via CAPI propagation.
19892081
// This label is set on the MachineDeployment template so it flows to Nodes at creation time.
19902082
t.Run("Check Replace nodes have globalPS label from CAPI propagation", func(t *testing.T) {
@@ -2028,18 +2120,6 @@ func EnsureGlobalPullSecret(t *testing.T, ctx context.Context, mgmtClient crclie
20282120
}, 30*time.Second, 5*time.Second).Should(Succeed(), "global-pull-secret secret is not present")
20292121
})
20302122

2031-
// Check if the DaemonSet is present in the DataPlane
2032-
t.Run("Check if the DaemonSet is present in the DataPlane", func(t *testing.T) {
2033-
g.Eventually(func() error {
2034-
daemonSet := hccomanifests.GlobalPullSecretDaemonSet()
2035-
if err := guestClient.Get(ctx, crclient.ObjectKey{Name: daemonSet.Name, Namespace: daemonSet.Namespace}, daemonSet); err != nil {
2036-
return err
2037-
}
2038-
dsImage = daemonSet.Spec.Template.Spec.Containers[0].Image
2039-
return nil
2040-
}, 30*time.Second, 5*time.Second).Should(Succeed(), "DaemonSet is not present")
2041-
})
2042-
20432123
t.Run("Wait for critical DaemonSets to be ready - first check", func(t *testing.T) {
20442124
g.Expect(waitForDaemonSetReady(t, ctx, guestClient, "ovnkube-node", "openshift-ovn-kubernetes", nodeCount)).To(Succeed())
20452125
g.Expect(waitForDaemonSetReady(t, ctx, guestClient, hccomanifests.GlobalPullSecretDSName, hccomanifests.GlobalPullSecretNamespace, nodeCount)).To(Succeed())

0 commit comments

Comments
 (0)