@@ -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