Skip to content

Commit 1b4ab2d

Browse files
author
Morven Cao
committed
rewrite certificate roration test.
Signed-off-by: Morven Cao <lcao@redhat.com>
1 parent aa955db commit 1b4ab2d

1 file changed

Lines changed: 118 additions & 62 deletions

File tree

test/e2e/pkg/cert_rotation_test.go

Lines changed: 118 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import (
1414
. "github.com/onsi/ginkgo/v2"
1515
. "github.com/onsi/gomega"
1616
"github.com/openshift-online/maestro/pkg/client/cloudevents/grpcsource"
17-
corev1 "k8s.io/api/core/v1"
1817
"k8s.io/apimachinery/pkg/api/errors"
1918
"k8s.io/apimachinery/pkg/api/meta"
2019
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2120
"k8s.io/apimachinery/pkg/types"
2221
k8srand "k8s.io/apimachinery/pkg/util/rand"
22+
"k8s.io/client-go/kubernetes"
2323
workv1 "open-cluster-management.io/api/work/v1"
2424
)
2525

@@ -57,6 +57,47 @@ var _ = Describe("Certificate Rotation", Ordered, Label("e2e-tests-cert-rotation
5757
} else if !errors.IsNotFound(err) {
5858
Expect(err).ShouldNot(HaveOccurred())
5959
}
60+
61+
By("rotating certificate with 30 seconds expiration")
62+
rotated, err := rotateCertificates(ctx, 30*time.Second)
63+
Expect(err).ShouldNot(HaveOccurred())
64+
Expect(rotated).To(BeTrue(), "no CA secrets found; certificate rotation did not run")
65+
66+
By("restarting maestro-agent to quickly pick up new certificate")
67+
err = restartDeployment(ctx, agentTestOpts.kubeClientSet, "maestro-agent", agentTestOpts.agentNamespace)
68+
Expect(err).ShouldNot(HaveOccurred())
69+
70+
By("creating a test work with deployment (1 replica)")
71+
work := helper.NewManifestWork(workName, deployName, "default", 1)
72+
_, err = sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Create(ctx, work, metav1.CreateOptions{})
73+
Expect(err).ShouldNot(HaveOccurred())
74+
75+
By("verifying the deployment is created on the agent cluster")
76+
Eventually(func() error {
77+
deployment, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{})
78+
if err != nil {
79+
return err
80+
}
81+
if deployment.Spec.Replicas != nil && *deployment.Spec.Replicas != 1 {
82+
return fmt.Errorf("expected 1 replica, got %d", *deployment.Spec.Replicas)
83+
}
84+
return nil
85+
}, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred())
86+
87+
By("verifying work status is reported back")
88+
Eventually(func() error {
89+
work, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Get(ctx, workName, metav1.GetOptions{})
90+
if err != nil {
91+
return err
92+
}
93+
if !meta.IsStatusConditionTrue(work.Status.Conditions, "Applied") {
94+
return fmt.Errorf("work not applied yet")
95+
}
96+
if !meta.IsStatusConditionTrue(work.Status.Conditions, "Available") {
97+
return fmt.Errorf("work not available yet")
98+
}
99+
return nil
100+
}, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred())
60101
})
61102

62103
AfterAll(func() {
@@ -78,17 +119,20 @@ var _ = Describe("Certificate Rotation", Ordered, Label("e2e-tests-cert-rotation
78119
Expect(err).ShouldNot(HaveOccurred())
79120
}
80121

81-
// wait for certificate reload and agent reconnection
82-
time.Sleep(30 * time.Second)
122+
By("restarting maestro-agent to quickly pick up restored certificate")
123+
err := restartDeployment(ctx, agentTestOpts.kubeClientSet, "maestro-agent", agentTestOpts.agentNamespace)
124+
Expect(err).ShouldNot(HaveOccurred())
83125

84-
// clean up the test resource
85-
By(fmt.Sprintf("cleaning up test work %s", workName))
86-
_ = sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Delete(ctx, workName, metav1.DeleteOptions{})
126+
By(fmt.Sprintf("deleting test work %s", workName))
127+
err = sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Delete(ctx, workName, metav1.DeleteOptions{})
128+
Expect(err).ShouldNot(HaveOccurred())
129+
130+
By("ensuring the work is deleted")
87131
Eventually(func() error {
88132
return AssertWorkNotFound(workName)
89-
}, 3*time.Minute, 3*time.Second).ShouldNot(HaveOccurred())
133+
}, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred())
90134

91-
// ensure the deployment is deleted from agent
135+
By("ensuring the deployment is deleted from agent cluster")
92136
Eventually(func() error {
93137
_, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{})
94138
if err != nil {
@@ -97,66 +141,23 @@ var _ = Describe("Certificate Rotation", Ordered, Label("e2e-tests-cert-rotation
97141
}
98142
return err
99143
}
100-
return fmt.Errorf("nginx deployment still exists")
144+
return fmt.Errorf("deployment %s still exists", deployName)
101145
}, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred())
102146
})
103147

104-
It("should verify agent works with current certificates", func() {
105-
By("creating a test work with deployment (1 replica) to verify agent connectivity")
106-
work := helper.NewManifestWork(workName, deployName, "default", 1)
107-
_, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Create(ctx, work, metav1.CreateOptions{})
108-
Expect(err).ShouldNot(HaveOccurred())
109-
110-
By("verifying the deployment is applied on the agent cluster")
111-
Eventually(func() error {
112-
deployment, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{})
113-
if err != nil {
114-
return err
115-
}
116-
if deployment.Spec.Replicas != nil && *deployment.Spec.Replicas != 1 {
117-
return fmt.Errorf("expected 1 replica, got %d", *deployment.Spec.Replicas)
118-
}
119-
return nil
120-
}, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred())
121-
122-
By("verifying work status is reported back")
123-
Eventually(func() error {
124-
work, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Get(ctx, workName, metav1.GetOptions{})
125-
if err != nil {
126-
return err
127-
}
128-
if !meta.IsStatusConditionTrue(work.Status.Conditions, "Applied") {
129-
return fmt.Errorf("work not applied yet")
130-
}
131-
if !meta.IsStatusConditionTrue(work.Status.Conditions, "Available") {
132-
return fmt.Errorf("work not available yet")
133-
}
134-
return nil
135-
}, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred())
136-
})
148+
It("should update work when certificate expires and succeed after rotation", func() {
149+
By("waiting for the certificate to expire (30 seconds)")
150+
time.Sleep(30 * time.Second)
137151

138-
It("should rotate client certificate with short expiration for maestro agent", func() {
139-
rotated, err := rotateCertificates(ctx, 60*time.Second)
152+
By("rotating certificate with long expiration (1 hour)")
153+
rotated, err := rotateCertificates(ctx, 1*time.Hour)
140154
Expect(err).ShouldNot(HaveOccurred())
141-
Expect(rotated).To(BeTrue(), "no CA secrets found; certificate rotation did not run")
155+
Expect(rotated).To(BeTrue(), "certificate rotation did not run")
142156

143-
By("waiting for certificate refresh...")
157+
By("waiting 10 seconds for certificate reload")
144158
time.Sleep(10 * time.Second)
145-
})
146-
147-
It("should verify agent can process updates with new certificate", func() {
148-
By("checking agent pod is still running")
149-
pods, err := agentTestOpts.kubeClientSet.CoreV1().Pods(agentTestOpts.agentNamespace).List(ctx, metav1.ListOptions{
150-
LabelSelector: "app=maestro-agent",
151-
})
152-
Expect(err).ShouldNot(HaveOccurred())
153-
Expect(len(pods.Items)).Should(BeNumerically(">", 0))
154-
155-
// check pod is in Running state
156-
pod := pods.Items[0]
157-
Expect(pod.Status.Phase).Should(Equal(corev1.PodRunning))
158159

159-
By("updating the deployment to 2 replicas to trigger agent communication")
160+
By("updating the work to change replicas to 2")
160161
work, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Get(ctx, workName, metav1.GetOptions{})
161162
Expect(err).ShouldNot(HaveOccurred())
162163

@@ -168,13 +169,16 @@ var _ = Describe("Certificate Rotation", Ordered, Label("e2e-tests-cert-rotation
168169
_, err = sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Patch(ctx, workName, types.MergePatchType, patchData, metav1.PatchOptions{})
169170
Expect(err).ShouldNot(HaveOccurred())
170171

171-
By("verifying the deployment is updated to 2 replicas before agent certificate expiration...")
172+
By("eventually verifying the deployment replicas is updated to 2 (certificate refreshed)")
172173
Eventually(func() error {
173174
deployment, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{})
174175
if err != nil {
175176
return err
176177
}
177-
if deployment.Spec.Replicas != nil && *deployment.Spec.Replicas != 2 {
178+
if deployment.Spec.Replicas == nil {
179+
return fmt.Errorf("deployment replicas is nil")
180+
}
181+
if *deployment.Spec.Replicas != 2 {
178182
return fmt.Errorf("expected 2 replicas, got %d", *deployment.Spec.Replicas)
179183
}
180184
return nil
@@ -352,3 +356,55 @@ func signClientCertificate(caCert *x509.Certificate, caKey *rsa.PrivateKey, dura
352356

353357
return certPEM, keyPEM, nil
354358
}
359+
360+
// restartDeployment restarts a deployment by adding the "kubectl.kubernetes.io/restartedAt" annotation
361+
func restartDeployment(ctx context.Context, kubeClient kubernetes.Interface, deploymentName, namespace string) error {
362+
// Get the deployment
363+
deployment, err := kubeClient.AppsV1().Deployments(namespace).Get(ctx, deploymentName, metav1.GetOptions{})
364+
if err != nil {
365+
return fmt.Errorf("failed to get deployment %s/%s: %w", namespace, deploymentName, err)
366+
}
367+
368+
// Add restart annotation to trigger rollout restart
369+
if deployment.Spec.Template.Annotations == nil {
370+
deployment.Spec.Template.Annotations = make(map[string]string)
371+
}
372+
deployment.Spec.Template.Annotations["kubectl.kubernetes.io/restartedAt"] = time.Now().Format(time.RFC3339)
373+
374+
// Update the deployment
375+
_, err = kubeClient.AppsV1().Deployments(namespace).Update(ctx, deployment, metav1.UpdateOptions{})
376+
if err != nil {
377+
return fmt.Errorf("failed to update deployment %s/%s: %w", namespace, deploymentName, err)
378+
}
379+
380+
// Wait for the rollout to complete
381+
Eventually(func() error {
382+
deploy, err := kubeClient.AppsV1().Deployments(namespace).Get(ctx, deploymentName, metav1.GetOptions{})
383+
if err != nil {
384+
return err
385+
}
386+
387+
if deploy.Spec.Replicas == nil {
388+
return fmt.Errorf("deployment %s/%s has nil .spec.replicas", namespace, deploymentName)
389+
}
390+
desired := *deploy.Spec.Replicas
391+
392+
// Check if the deployment is ready
393+
if deploy.Status.UpdatedReplicas != desired {
394+
return fmt.Errorf("waiting for rollout: updated replicas %d/%d", deploy.Status.UpdatedReplicas, desired)
395+
}
396+
if deploy.Status.ReadyReplicas != desired {
397+
return fmt.Errorf("waiting for rollout: ready replicas %d/%d", deploy.Status.ReadyReplicas, desired)
398+
}
399+
if deploy.Status.AvailableReplicas != desired {
400+
return fmt.Errorf("waiting for rollout: available replicas %d/%d", deploy.Status.AvailableReplicas, desired)
401+
}
402+
403+
return nil
404+
}, 2*time.Minute, 2*time.Second).ShouldNot(HaveOccurred())
405+
406+
// Give the deployment a moment to establish connections
407+
time.Sleep(5 * time.Second)
408+
409+
return nil
410+
}

0 commit comments

Comments
 (0)