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