Skip to content

Commit 54d6e3f

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

1 file changed

Lines changed: 132 additions & 65 deletions

File tree

test/e2e/pkg/cert_rotation_test.go

Lines changed: 132 additions & 65 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,57 +57,22 @@ var _ = Describe("Certificate Rotation", Ordered, Label("e2e-tests-cert-rotation
5757
} else if !errors.IsNotFound(err) {
5858
Expect(err).ShouldNot(HaveOccurred())
5959
}
60-
})
6160

62-
AfterAll(func() {
63-
By("restoring original MQTT certificate")
64-
if len(originalMQTTCerts) > 0 {
65-
secret, err := agentTestOpts.kubeClientSet.CoreV1().Secrets(agentTestOpts.agentNamespace).Get(ctx, "maestro-agent-certs", metav1.GetOptions{})
66-
Expect(err).ShouldNot(HaveOccurred())
67-
secret.Data = originalMQTTCerts
68-
_, err = agentTestOpts.kubeClientSet.CoreV1().Secrets(agentTestOpts.agentNamespace).Update(ctx, secret, metav1.UpdateOptions{})
69-
Expect(err).ShouldNot(HaveOccurred())
70-
}
71-
72-
By("restoring original gRPC broker certificate")
73-
if len(originalGRPCBrokerCerts) > 0 {
74-
secret, err := agentTestOpts.kubeClientSet.CoreV1().Secrets(agentTestOpts.agentNamespace).Get(ctx, "maestro-grpc-broker-cert", metav1.GetOptions{})
75-
Expect(err).ShouldNot(HaveOccurred())
76-
secret.Data = originalGRPCBrokerCerts
77-
_, err = agentTestOpts.kubeClientSet.CoreV1().Secrets(agentTestOpts.agentNamespace).Update(ctx, secret, metav1.UpdateOptions{})
78-
Expect(err).ShouldNot(HaveOccurred())
79-
}
80-
81-
// wait for certificate reload and agent reconnection
82-
time.Sleep(30 * time.Second)
83-
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{})
87-
Eventually(func() error {
88-
return AssertWorkNotFound(workName)
89-
}, 3*time.Minute, 3*time.Second).ShouldNot(HaveOccurred())
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")
9065

91-
// ensure the deployment is deleted from agent
92-
Eventually(func() error {
93-
_, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{})
94-
if err != nil {
95-
if errors.IsNotFound(err) {
96-
return nil
97-
}
98-
return err
99-
}
100-
return fmt.Errorf("nginx deployment still exists")
101-
}, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred())
102-
})
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())
10369

104-
It("should verify agent works with current certificates", func() {
105-
By("creating a test work with deployment (1 replica) to verify agent connectivity")
70+
By("creating a test work with deployment (1 replica)")
10671
work := helper.NewManifestWork(workName, deployName, "default", 1)
107-
_, err := sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Create(ctx, work, metav1.CreateOptions{})
72+
_, err = sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Create(ctx, work, metav1.CreateOptions{})
10873
Expect(err).ShouldNot(HaveOccurred())
10974

110-
By("verifying the deployment is applied on the agent cluster")
75+
By("verifying the deployment is created on the agent cluster")
11176
Eventually(func() error {
11277
deployment, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{})
11378
if err != nil {
@@ -135,28 +100,56 @@ var _ = Describe("Certificate Rotation", Ordered, Label("e2e-tests-cert-rotation
135100
}, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred())
136101
})
137102

138-
It("should rotate client certificate with short expiration for maestro agent", func() {
139-
rotated, err := rotateCertificates(ctx, 60*time.Second)
140-
Expect(err).ShouldNot(HaveOccurred())
141-
Expect(rotated).To(BeTrue(), "no CA secrets found; certificate rotation did not run")
103+
AfterAll(func() {
104+
By("restoring original MQTT certificate")
105+
if len(originalMQTTCerts) > 0 {
106+
secret, err := agentTestOpts.kubeClientSet.CoreV1().Secrets(agentTestOpts.agentNamespace).Get(ctx, "maestro-agent-certs", metav1.GetOptions{})
107+
Expect(err).ShouldNot(HaveOccurred())
108+
secret.Data = originalMQTTCerts
109+
_, err = agentTestOpts.kubeClientSet.CoreV1().Secrets(agentTestOpts.agentNamespace).Update(ctx, secret, metav1.UpdateOptions{})
110+
Expect(err).ShouldNot(HaveOccurred())
111+
}
142112

143-
By("waiting for certificate refresh...")
144-
time.Sleep(10 * time.Second)
145-
})
113+
By("restoring original gRPC broker certificate")
114+
if len(originalGRPCBrokerCerts) > 0 {
115+
secret, err := agentTestOpts.kubeClientSet.CoreV1().Secrets(agentTestOpts.agentNamespace).Get(ctx, "maestro-grpc-broker-cert", metav1.GetOptions{})
116+
Expect(err).ShouldNot(HaveOccurred())
117+
secret.Data = originalGRPCBrokerCerts
118+
_, err = agentTestOpts.kubeClientSet.CoreV1().Secrets(agentTestOpts.agentNamespace).Update(ctx, secret, metav1.UpdateOptions{})
119+
Expect(err).ShouldNot(HaveOccurred())
120+
}
121+
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())
146125

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-
})
126+
By(fmt.Sprintf("deleting test work %s", workName))
127+
err = sourceWorkClient.ManifestWorks(agentTestOpts.consumerName).Delete(ctx, workName, metav1.DeleteOptions{})
152128
Expect(err).ShouldNot(HaveOccurred())
153-
Expect(len(pods.Items)).Should(BeNumerically(">", 0))
154129

155-
// check pod is in Running state
156-
pod := pods.Items[0]
157-
Expect(pod.Status.Phase).Should(Equal(corev1.PodRunning))
130+
By("ensuring the work is deleted")
131+
Eventually(func() error {
132+
return AssertWorkNotFound(workName)
133+
}, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred())
134+
135+
By("ensuring the deployment is deleted from agent cluster")
136+
Eventually(func() error {
137+
_, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{})
138+
if err != nil {
139+
if errors.IsNotFound(err) {
140+
return nil
141+
}
142+
return err
143+
}
144+
return fmt.Errorf("deployment %s still exists", deployName)
145+
}, 1*time.Minute, 1*time.Second).ShouldNot(HaveOccurred())
146+
})
147+
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)
158151

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

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

171-
By("verifying the deployment is updated to 2 replicas before agent certificate expiration...")
164+
// By("consistently verifying the deployment replicas is NOT updated to 2 (certificate expired)")
165+
// Consistently(func() error {
166+
// deployment, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{})
167+
// if err != nil {
168+
// return err
169+
// }
170+
// if deployment.Spec.Replicas != nil && *deployment.Spec.Replicas == 2 {
171+
// return fmt.Errorf("deployment should not be updated to 2 replicas with expired cert, but got %d", *deployment.Spec.Replicas)
172+
// }
173+
// // expect it to still be 1
174+
// if deployment.Spec.Replicas != nil && *deployment.Spec.Replicas != 1 {
175+
// return fmt.Errorf("expected deployment to remain at 1 replica, got %d", *deployment.Spec.Replicas)
176+
// }
177+
// return nil
178+
// }, 30*time.Second, 2*time.Second).ShouldNot(HaveOccurred())
179+
180+
By("rotating certificate with long expiration (1 hour)")
181+
rotated, err := rotateCertificates(ctx, 1*time.Hour)
182+
Expect(err).ShouldNot(HaveOccurred())
183+
Expect(rotated).To(BeTrue(), "certificate rotation did not run")
184+
185+
By("waiting 10 seconds for certificate reload")
186+
time.Sleep(10 * time.Second)
187+
188+
By("eventually verifying the deployment replicas is updated to 2 (certificate refreshed)")
172189
Eventually(func() error {
173190
deployment, err := agentTestOpts.kubeClientSet.AppsV1().Deployments("default").Get(ctx, deployName, metav1.GetOptions{})
174191
if err != nil {
175192
return err
176193
}
177-
if deployment.Spec.Replicas != nil && *deployment.Spec.Replicas != 2 {
194+
if deployment.Spec.Replicas == nil {
195+
return fmt.Errorf("deployment replicas is nil")
196+
}
197+
if *deployment.Spec.Replicas != 2 {
178198
return fmt.Errorf("expected 2 replicas, got %d", *deployment.Spec.Replicas)
179199
}
180200
return nil
@@ -352,3 +372,50 @@ func signClientCertificate(caCert *x509.Certificate, caKey *rsa.PrivateKey, dura
352372

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

0 commit comments

Comments
 (0)