Skip to content

Commit cef4150

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 6ed19e3 commit cef4150

1 file changed

Lines changed: 103 additions & 12 deletions

File tree

test/e2e/util/util.go

Lines changed: 103 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"
@@ -1986,6 +1987,108 @@ func EnsureGlobalPullSecret(t *testing.T, ctx context.Context, mgmtClient crclie
19861987
nodeCount := *np.Spec.Replicas
19871988
t.Logf("NodePool replicas: %d", nodeCount)
19881989

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

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

0 commit comments

Comments
 (0)