|
6 | 6 | "context" |
7 | 7 | "crypto/x509" |
8 | 8 | "crypto/x509/pkix" |
| 9 | + "encoding/json" |
9 | 10 | "fmt" |
10 | 11 | "io" |
11 | 12 | "net" |
@@ -1985,6 +1986,97 @@ func EnsureGlobalPullSecret(t *testing.T, ctx context.Context, mgmtClient crclie |
1985 | 1986 | nodeCount := *np.Spec.Replicas |
1986 | 1987 | t.Logf("NodePool replicas: %d", nodeCount) |
1987 | 1988 |
|
| 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 | + |
1988 | 2080 | // Verify that nodes from Replace NodePools have the globalPS label applied via CAPI propagation. |
1989 | 2081 | // This label is set on the MachineDeployment template so it flows to Nodes at creation time. |
1990 | 2082 | 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 |
2028 | 2120 | }, 30*time.Second, 5*time.Second).Should(Succeed(), "global-pull-secret secret is not present") |
2029 | 2121 | }) |
2030 | 2122 |
|
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 | | - |
2043 | 2123 | t.Run("Wait for critical DaemonSets to be ready - first check", func(t *testing.T) { |
2044 | 2124 | g.Expect(waitForDaemonSetReady(t, ctx, guestClient, "ovnkube-node", "openshift-ovn-kubernetes", nodeCount)).To(Succeed()) |
2045 | 2125 | g.Expect(waitForDaemonSetReady(t, ctx, guestClient, hccomanifests.GlobalPullSecretDSName, hccomanifests.GlobalPullSecretNamespace, nodeCount)).To(Succeed()) |
|
0 commit comments