Skip to content

Commit 644a5ad

Browse files
committed
add aga crd status updates utils
1 parent 1b7cc23 commit 644a5ad

File tree

2 files changed

+1187
-0
lines changed

2 files changed

+1187
-0
lines changed

pkg/status/aga/status_updater.go

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
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 if available
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+
}
103+
104+
// Update IP sets
105+
if len(accelerator.Status.IPSets) > 0 {
106+
newIPSets := make([]v1beta1.IPSet, len(accelerator.Status.IPSets))
107+
for i, ipSet := range accelerator.Status.IPSets {
108+
newIPSets[i] = v1beta1.IPSet{
109+
IpAddresses: &ipSet.IpAddresses,
110+
IpAddressFamily: &ipSet.IpAddressFamily,
111+
}
112+
}
113+
if !u.areIPSetsEqual(ga.Status.IPSets, newIPSets) {
114+
ga.Status.IPSets = newIPSets
115+
needPatch = true
116+
}
117+
}
118+
119+
// Update status
120+
if ga.Status.Status == nil || *ga.Status.Status != accelerator.Status.Status {
121+
ga.Status.Status = &accelerator.Status.Status
122+
needPatch = true
123+
}
124+
125+
// Update conditions based on deployment status
126+
var readyCondition metav1.Condition
127+
if isDeployed {
128+
readyCondition = metav1.Condition{
129+
Type: ConditionTypeReady,
130+
Status: metav1.ConditionTrue,
131+
LastTransitionTime: metav1.Now(),
132+
Reason: ReasonAcceleratorReady,
133+
Message: "GlobalAccelerator is ready and available",
134+
}
135+
} else {
136+
// Set Ready to Unknown while accelerator is provisioning
137+
readyCondition = metav1.Condition{
138+
Type: ConditionTypeReady,
139+
Status: metav1.ConditionUnknown,
140+
LastTransitionTime: metav1.Now(),
141+
Reason: ReasonAcceleratorProvisioning,
142+
Message: "GlobalAccelerator is being provisioned",
143+
}
144+
requeueNeeded = true
145+
}
146+
147+
conditionUpdated := u.updateCondition(&ga.Status.Conditions, readyCondition)
148+
if conditionUpdated {
149+
needPatch = true
150+
}
151+
152+
// Skip status update if observed generation already matches and nothing else changed
153+
if ga.Status.ObservedGeneration != nil && *ga.Status.ObservedGeneration == ga.Generation && !needPatch {
154+
u.logger.V(1).Info("Skipping status update - no changes needed", "globalAccelerator", k8s.NamespacedName(ga))
155+
return requeueNeeded, nil
156+
}
157+
158+
if needPatch {
159+
if err := u.k8sClient.Status().Patch(ctx, ga, client.MergeFrom(gaOld)); err != nil {
160+
return requeueNeeded, errors.Wrapf(err, "failed to update GlobalAccelerator status: %v", k8s.NamespacedName(ga))
161+
}
162+
u.logger.Info("Successfully updated GlobalAccelerator status", "globalAccelerator", k8s.NamespacedName(ga))
163+
}
164+
165+
return requeueNeeded, nil
166+
}
167+
168+
// UpdateStatusFailure updates the GlobalAccelerator status when deployment fails
169+
func (u *defaultStatusUpdater) UpdateStatusFailure(ctx context.Context, ga *v1beta1.GlobalAccelerator,
170+
reason, message string) error {
171+
172+
gaOld := ga.DeepCopy()
173+
var needPatch bool
174+
175+
// Update observed generation
176+
if ga.Status.ObservedGeneration == nil || *ga.Status.ObservedGeneration != ga.Generation {
177+
ga.Status.ObservedGeneration = &ga.Generation
178+
needPatch = true
179+
}
180+
181+
// Set Ready condition to False with failure reason
182+
failureCondition := metav1.Condition{
183+
Type: ConditionTypeReady,
184+
Status: metav1.ConditionFalse,
185+
LastTransitionTime: metav1.Now(),
186+
Reason: reason,
187+
Message: message,
188+
}
189+
190+
conditionUpdated := u.updateCondition(&ga.Status.Conditions, failureCondition)
191+
if conditionUpdated {
192+
needPatch = true
193+
}
194+
195+
// Skip status update if observed generation already matches and nothing else changed
196+
if ga.Status.ObservedGeneration != nil && *ga.Status.ObservedGeneration == ga.Generation && !needPatch {
197+
u.logger.V(1).Info("Skipping status update - no changes needed", "globalAccelerator", k8s.NamespacedName(ga))
198+
return nil
199+
}
200+
201+
if needPatch {
202+
if err := u.k8sClient.Status().Patch(ctx, ga, client.MergeFrom(gaOld)); err != nil {
203+
return errors.Wrapf(err, "failed to update GlobalAccelerator status: %v", k8s.NamespacedName(ga))
204+
}
205+
u.logger.Info("Successfully updated GlobalAccelerator status with failure",
206+
"globalAccelerator", k8s.NamespacedName(ga),
207+
"reason", reason)
208+
}
209+
210+
return nil
211+
}
212+
213+
// UpdateStatusDeletion updates the GlobalAccelerator status during deletion process
214+
func (u *defaultStatusUpdater) UpdateStatusDeletion(ctx context.Context, ga *v1beta1.GlobalAccelerator) error {
215+
gaOld := ga.DeepCopy()
216+
var needPatch bool
217+
218+
// Update observed generation
219+
if ga.Status.ObservedGeneration == nil || *ga.Status.ObservedGeneration != ga.Generation {
220+
ga.Status.ObservedGeneration = &ga.Generation
221+
needPatch = true
222+
}
223+
224+
// Set status to "Deleting" to indicate it's in the process of being deleted
225+
if ga.Status.Status == nil || *ga.Status.Status != StatusDeleting {
226+
deletingStatus := StatusDeleting
227+
ga.Status.Status = &deletingStatus
228+
needPatch = true
229+
}
230+
231+
// Add a condition to indicate we're waiting for the accelerator to be disabled
232+
waitingCondition := metav1.Condition{
233+
Type: ConditionTypeAcceleratorDisabling,
234+
Status: metav1.ConditionTrue,
235+
LastTransitionTime: metav1.Now(),
236+
Reason: ReasonAcceleratorDisabling,
237+
Message: "Waiting for accelerator to be disabled before deletion",
238+
}
239+
240+
// Set Ready condition to False during deletion
241+
readyCondition := metav1.Condition{
242+
Type: ConditionTypeReady,
243+
Status: metav1.ConditionFalse,
244+
LastTransitionTime: metav1.Now(),
245+
Reason: ReasonAcceleratorDeleting,
246+
Message: "GlobalAccelerator is being deleted",
247+
}
248+
249+
// Update both conditions
250+
conditionUpdated1 := u.updateCondition(&ga.Status.Conditions, waitingCondition)
251+
conditionUpdated2 := u.updateCondition(&ga.Status.Conditions, readyCondition)
252+
if conditionUpdated1 || conditionUpdated2 {
253+
needPatch = true
254+
}
255+
256+
// Skip status update if nothing changed
257+
if !needPatch {
258+
return nil
259+
}
260+
261+
if err := u.k8sClient.Status().Patch(ctx, ga, client.MergeFrom(gaOld)); err != nil {
262+
return errors.Wrapf(err, "failed to update GlobalAccelerator status: %v", k8s.NamespacedName(ga))
263+
}
264+
265+
u.logger.Info("Updated GlobalAccelerator status for deletion",
266+
"globalAccelerator", k8s.NamespacedName(ga))
267+
268+
return nil
269+
}
270+
271+
// Helper methods
272+
273+
// isAcceleratorDeployed checks if the accelerator is fully deployed and ready
274+
func (u *defaultStatusUpdater) isAcceleratorDeployed(acceleratorStatus agamodel.AcceleratorStatus) bool {
275+
// Check if the accelerator status indicates it's deployed
276+
// GlobalAccelerator status can be: IN_PROGRESS or DEPLOYED
277+
return acceleratorStatus.Status == StatusDeployed
278+
}
279+
280+
// updateCondition updates or adds a condition to the conditions slice
281+
func (u *defaultStatusUpdater) updateCondition(conditions *[]metav1.Condition, newCondition metav1.Condition) bool {
282+
if conditions == nil {
283+
*conditions = []metav1.Condition{newCondition}
284+
return true
285+
}
286+
287+
for i, condition := range *conditions {
288+
if condition.Type == newCondition.Type {
289+
if condition.Status != newCondition.Status ||
290+
condition.Reason != newCondition.Reason ||
291+
condition.Message != newCondition.Message {
292+
(*conditions)[i] = newCondition
293+
return true
294+
}
295+
return false
296+
}
297+
}
298+
299+
// Condition not found, add it
300+
*conditions = append(*conditions, newCondition)
301+
return true
302+
}
303+
304+
// areIPSetsEqual compares two slices of IPSets for equality
305+
func (u *defaultStatusUpdater) areIPSetsEqual(existing []v1beta1.IPSet, new []v1beta1.IPSet) bool {
306+
return reflect.DeepEqual(existing, new)
307+
}

0 commit comments

Comments
 (0)