Skip to content

Commit 656ad86

Browse files
committed
add aga crd status updates utils
1 parent b1e26b3 commit 656ad86

File tree

2 files changed

+1191
-0
lines changed

2 files changed

+1191
-0
lines changed

pkg/status/aga/status_updater.go

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
package aga
2+
3+
import (
4+
"context"
5+
"reflect"
6+
7+
"github.com/go-logr/logr"
8+
"github.com/pkg/errors"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"sigs.k8s.io/aws-load-balancer-controller/apis/aga/v1beta1"
11+
"sigs.k8s.io/aws-load-balancer-controller/pkg/k8s"
12+
agamodel "sigs.k8s.io/aws-load-balancer-controller/pkg/model/aga"
13+
"sigs.k8s.io/controller-runtime/pkg/client"
14+
)
15+
16+
const (
17+
// Condition type constants
18+
ConditionTypeReady = "Ready"
19+
ConditionTypeAcceleratorDisabling = "AcceleratorDisabling"
20+
21+
// Reason constants
22+
ReasonAcceleratorReady = "AcceleratorReady"
23+
ReasonAcceleratorProvisioning = "AcceleratorProvisioning"
24+
ReasonAcceleratorDisabling = "AcceleratorDisabling"
25+
ReasonAcceleratorDeleting = "AcceleratorDeleting"
26+
27+
// Status constants
28+
StatusDeployed = "DEPLOYED"
29+
StatusInProgress = "IN_PROGRESS"
30+
StatusDeleting = "DELETING"
31+
)
32+
33+
// StatusUpdater handles GlobalAccelerator resource status updates
34+
type StatusUpdater interface {
35+
// UpdateStatusSuccess updates the GlobalAccelerator status after successful deployment
36+
UpdateStatusSuccess(ctx context.Context, ga *v1beta1.GlobalAccelerator, accelerator *agamodel.Accelerator) (bool, error)
37+
38+
// UpdateStatusFailure updates the GlobalAccelerator status when deployment fails
39+
UpdateStatusFailure(ctx context.Context, ga *v1beta1.GlobalAccelerator, reason, message string) error
40+
41+
// UpdateStatusDeletion updates the GlobalAccelerator status during deletion process
42+
UpdateStatusDeletion(ctx context.Context, ga *v1beta1.GlobalAccelerator) error
43+
}
44+
45+
// NewStatusUpdater creates a new StatusUpdater
46+
func NewStatusUpdater(k8sClient client.Client, logger logr.Logger) StatusUpdater {
47+
return &defaultStatusUpdater{
48+
k8sClient: k8sClient,
49+
logger: logger.WithName("aga-status-updater"),
50+
}
51+
}
52+
53+
// defaultStatusUpdater is the default implementation of StatusUpdater
54+
type defaultStatusUpdater struct {
55+
k8sClient client.Client
56+
logger logr.Logger
57+
}
58+
59+
// UpdateStatusSuccess updates the GlobalAccelerator status after successful deployment
60+
// Returns true if requeue is needed for status polling
61+
func (u *defaultStatusUpdater) UpdateStatusSuccess(ctx context.Context, ga *v1beta1.GlobalAccelerator,
62+
accelerator *agamodel.Accelerator) (bool, error) {
63+
64+
// Accelerator status should always be set after deployment, if it's not, prevent NPE
65+
if accelerator.Status == nil {
66+
u.logger.Info("Unable to update GlobalAccelerator Status due to null accelerator status",
67+
"globalAccelerator", k8s.NamespacedName(ga))
68+
return false, nil
69+
}
70+
71+
gaOld := ga.DeepCopy()
72+
var needPatch bool
73+
var requeueNeeded bool
74+
75+
// Check if accelerator is fully deployed
76+
isDeployed := u.isAcceleratorDeployed(*accelerator.Status)
77+
78+
// Update observed generation
79+
if ga.Status.ObservedGeneration == nil || *ga.Status.ObservedGeneration != ga.Generation {
80+
ga.Status.ObservedGeneration = &ga.Generation
81+
needPatch = true
82+
}
83+
84+
// Update accelerator ARN
85+
if ga.Status.AcceleratorARN == nil || *ga.Status.AcceleratorARN != accelerator.Status.AcceleratorARN {
86+
ga.Status.AcceleratorARN = &accelerator.Status.AcceleratorARN
87+
needPatch = true
88+
}
89+
90+
// Update DNS name
91+
if ga.Status.DNSName == nil || *ga.Status.DNSName != accelerator.Status.DNSName {
92+
ga.Status.DNSName = &accelerator.Status.DNSName
93+
needPatch = true
94+
}
95+
96+
// Update dual stack DNS name
97+
if accelerator.Status.DualStackDNSName != "" {
98+
if ga.Status.DualStackDnsName == nil || *ga.Status.DualStackDnsName != accelerator.Status.DualStackDNSName {
99+
ga.Status.DualStackDnsName = &accelerator.Status.DualStackDNSName
100+
needPatch = true
101+
}
102+
} else if ga.Status.DualStackDnsName != nil {
103+
// Clear the field when DualStackDNSName is no longer available
104+
ga.Status.DualStackDnsName = nil
105+
needPatch = true
106+
}
107+
108+
// Update IP sets
109+
if len(accelerator.Status.IPSets) > 0 {
110+
newIPSets := make([]v1beta1.IPSet, len(accelerator.Status.IPSets))
111+
for i, ipSet := range accelerator.Status.IPSets {
112+
newIPSets[i] = v1beta1.IPSet{
113+
IpAddresses: &ipSet.IpAddresses,
114+
IpAddressFamily: &ipSet.IpAddressFamily,
115+
}
116+
}
117+
if !u.areIPSetsEqual(ga.Status.IPSets, newIPSets) {
118+
ga.Status.IPSets = newIPSets
119+
needPatch = true
120+
}
121+
}
122+
123+
// Update status
124+
if ga.Status.Status == nil || *ga.Status.Status != accelerator.Status.Status {
125+
ga.Status.Status = &accelerator.Status.Status
126+
needPatch = true
127+
}
128+
129+
// Update conditions based on deployment status
130+
var readyCondition metav1.Condition
131+
if isDeployed {
132+
readyCondition = metav1.Condition{
133+
Type: ConditionTypeReady,
134+
Status: metav1.ConditionTrue,
135+
LastTransitionTime: metav1.Now(),
136+
Reason: ReasonAcceleratorReady,
137+
Message: "GlobalAccelerator is ready and available",
138+
}
139+
} else {
140+
// Set Ready to Unknown while accelerator is provisioning
141+
readyCondition = metav1.Condition{
142+
Type: ConditionTypeReady,
143+
Status: metav1.ConditionUnknown,
144+
LastTransitionTime: metav1.Now(),
145+
Reason: ReasonAcceleratorProvisioning,
146+
Message: "GlobalAccelerator is being provisioned",
147+
}
148+
requeueNeeded = true
149+
}
150+
151+
conditionUpdated := u.updateCondition(&ga.Status.Conditions, readyCondition)
152+
if conditionUpdated {
153+
needPatch = true
154+
}
155+
156+
// Skip status update if observed generation already matches and nothing else changed
157+
if ga.Status.ObservedGeneration != nil && *ga.Status.ObservedGeneration == ga.Generation && !needPatch {
158+
u.logger.V(1).Info("Skipping status update - no changes needed", "globalAccelerator", k8s.NamespacedName(ga))
159+
return requeueNeeded, nil
160+
}
161+
162+
if needPatch {
163+
if err := u.k8sClient.Status().Patch(ctx, ga, client.MergeFrom(gaOld)); err != nil {
164+
return requeueNeeded, errors.Wrapf(err, "failed to update GlobalAccelerator status: %v", k8s.NamespacedName(ga))
165+
}
166+
u.logger.Info("Successfully updated GlobalAccelerator status", "globalAccelerator", k8s.NamespacedName(ga))
167+
}
168+
169+
return requeueNeeded, nil
170+
}
171+
172+
// UpdateStatusFailure updates the GlobalAccelerator status when deployment fails
173+
func (u *defaultStatusUpdater) UpdateStatusFailure(ctx context.Context, ga *v1beta1.GlobalAccelerator,
174+
reason, message string) error {
175+
176+
gaOld := ga.DeepCopy()
177+
var needPatch bool
178+
179+
// Update observed generation
180+
if ga.Status.ObservedGeneration == nil || *ga.Status.ObservedGeneration != ga.Generation {
181+
ga.Status.ObservedGeneration = &ga.Generation
182+
needPatch = true
183+
}
184+
185+
// Set Ready condition to False with failure reason
186+
failureCondition := metav1.Condition{
187+
Type: ConditionTypeReady,
188+
Status: metav1.ConditionFalse,
189+
LastTransitionTime: metav1.Now(),
190+
Reason: reason,
191+
Message: message,
192+
}
193+
194+
conditionUpdated := u.updateCondition(&ga.Status.Conditions, failureCondition)
195+
if conditionUpdated {
196+
needPatch = true
197+
}
198+
199+
// Skip status update if observed generation already matches and nothing else changed
200+
if ga.Status.ObservedGeneration != nil && *ga.Status.ObservedGeneration == ga.Generation && !needPatch {
201+
u.logger.V(1).Info("Skipping status update - no changes needed", "globalAccelerator", k8s.NamespacedName(ga))
202+
return nil
203+
}
204+
205+
if needPatch {
206+
if err := u.k8sClient.Status().Patch(ctx, ga, client.MergeFrom(gaOld)); err != nil {
207+
return errors.Wrapf(err, "failed to update GlobalAccelerator status: %v", k8s.NamespacedName(ga))
208+
}
209+
u.logger.Info("Successfully updated GlobalAccelerator status with failure",
210+
"globalAccelerator", k8s.NamespacedName(ga),
211+
"reason", reason)
212+
}
213+
214+
return nil
215+
}
216+
217+
// UpdateStatusDeletion updates the GlobalAccelerator status during deletion process
218+
func (u *defaultStatusUpdater) UpdateStatusDeletion(ctx context.Context, ga *v1beta1.GlobalAccelerator) error {
219+
gaOld := ga.DeepCopy()
220+
var needPatch bool
221+
222+
// Update observed generation
223+
if ga.Status.ObservedGeneration == nil || *ga.Status.ObservedGeneration != ga.Generation {
224+
ga.Status.ObservedGeneration = &ga.Generation
225+
needPatch = true
226+
}
227+
228+
// Set status to "Deleting" to indicate it's in the process of being deleted
229+
if ga.Status.Status == nil || *ga.Status.Status != StatusDeleting {
230+
deletingStatus := StatusDeleting
231+
ga.Status.Status = &deletingStatus
232+
needPatch = true
233+
}
234+
235+
// Add a condition to indicate we're waiting for the accelerator to be disabled
236+
waitingCondition := metav1.Condition{
237+
Type: ConditionTypeAcceleratorDisabling,
238+
Status: metav1.ConditionTrue,
239+
LastTransitionTime: metav1.Now(),
240+
Reason: ReasonAcceleratorDisabling,
241+
Message: "Waiting for accelerator to be disabled before deletion",
242+
}
243+
244+
// Set Ready condition to False during deletion
245+
readyCondition := metav1.Condition{
246+
Type: ConditionTypeReady,
247+
Status: metav1.ConditionFalse,
248+
LastTransitionTime: metav1.Now(),
249+
Reason: ReasonAcceleratorDeleting,
250+
Message: "GlobalAccelerator is being deleted",
251+
}
252+
253+
// Update both conditions
254+
conditionUpdated1 := u.updateCondition(&ga.Status.Conditions, waitingCondition)
255+
conditionUpdated2 := u.updateCondition(&ga.Status.Conditions, readyCondition)
256+
if conditionUpdated1 || conditionUpdated2 {
257+
needPatch = true
258+
}
259+
260+
// Skip status update if nothing changed
261+
if !needPatch {
262+
return nil
263+
}
264+
265+
if err := u.k8sClient.Status().Patch(ctx, ga, client.MergeFrom(gaOld)); err != nil {
266+
return errors.Wrapf(err, "failed to update GlobalAccelerator status: %v", k8s.NamespacedName(ga))
267+
}
268+
269+
u.logger.Info("Updated GlobalAccelerator status for deletion",
270+
"globalAccelerator", k8s.NamespacedName(ga))
271+
272+
return nil
273+
}
274+
275+
// Helper methods
276+
277+
// isAcceleratorDeployed checks if the accelerator is fully deployed and ready
278+
func (u *defaultStatusUpdater) isAcceleratorDeployed(acceleratorStatus agamodel.AcceleratorStatus) bool {
279+
// Check if the accelerator status indicates it's deployed
280+
// GlobalAccelerator status can be: IN_PROGRESS or DEPLOYED
281+
return acceleratorStatus.Status == StatusDeployed
282+
}
283+
284+
// updateCondition updates or adds a condition to the conditions slice
285+
func (u *defaultStatusUpdater) updateCondition(conditions *[]metav1.Condition, newCondition metav1.Condition) bool {
286+
if conditions == nil {
287+
*conditions = []metav1.Condition{newCondition}
288+
return true
289+
}
290+
291+
for i, condition := range *conditions {
292+
if condition.Type == newCondition.Type {
293+
if condition.Status != newCondition.Status ||
294+
condition.Reason != newCondition.Reason ||
295+
condition.Message != newCondition.Message {
296+
(*conditions)[i] = newCondition
297+
return true
298+
}
299+
return false
300+
}
301+
}
302+
303+
// Condition not found, add it
304+
*conditions = append(*conditions, newCondition)
305+
return true
306+
}
307+
308+
// areIPSetsEqual compares two slices of IPSets for equality
309+
func (u *defaultStatusUpdater) areIPSetsEqual(existing []v1beta1.IPSet, new []v1beta1.IPSet) bool {
310+
return reflect.DeepEqual(existing, new)
311+
}

0 commit comments

Comments
 (0)