Skip to content

Commit 918325c

Browse files
Kumm-KaiRAPSNX
andauthored
feat(LB): improve immutable changes error message (#253)
* feat: add annotation to `resultImmutableChanged` struct and include it in the error message if set * fix: also set extra labels for existing LBs * fix lint * Update pkg/ccm/loadbalancer.go Co-authored-by: Raphael Grömmer <38243306+RAPSNX@users.noreply.github.com> --------- Co-authored-by: Raphael Grömmer <38243306+RAPSNX@users.noreply.github.com>
1 parent fac900f commit 918325c

File tree

4 files changed

+89
-77
lines changed

4 files changed

+89
-77
lines changed

pkg/ccm/loadbalancer.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ func (l *LoadBalancer) GetLoadBalancerName(_ context.Context, _ string, service
106106
// load balancer is not ready yet (e.g., it is still being provisioned) and
107107
// polling at a fixed rate is preferred over backing off exponentially in
108108
// order to minimize latency.
109-
func (l *LoadBalancer) EnsureLoadBalancer(
109+
func (l *LoadBalancer) EnsureLoadBalancer( //nolint:gocyclo // not really complex
110110
ctx context.Context,
111111
clusterName string,
112112
service *corev1.Service,
@@ -126,7 +126,7 @@ func (l *LoadBalancer) EnsureLoadBalancer(
126126
return nil, fmt.Errorf("reconcile metricsRemoteWrite: %w", err)
127127
}
128128

129-
spec, events, err := lbSpecFromService(service, nodes, l.networkID, observabilityOptions)
129+
spec, events, err := lbSpecFromService(service, nodes, l.networkID, l.extraLabels, observabilityOptions)
130130
if err != nil {
131131
return nil, fmt.Errorf("invalid load balancer specification: %w", err)
132132
}
@@ -137,7 +137,11 @@ func (l *LoadBalancer) EnsureLoadBalancer(
137137

138138
fulfills, immutableChanged := compareLBwithSpec(lb, spec)
139139
if immutableChanged != nil {
140-
return nil, fmt.Errorf("updated to load balancer cannot be fulfilled. Load balancer API doesn't support changing %q", immutableChanged.field)
140+
changeStr := fmt.Sprintf("%q", immutableChanged.field)
141+
if immutableChanged.annotation != "" {
142+
changeStr += fmt.Sprintf(" (%q)", immutableChanged.annotation)
143+
}
144+
return nil, fmt.Errorf("update to load balancer cannot be fulfilled: API doesn't support changing %s", changeStr)
141145
}
142146
if !fulfills {
143147
credentialsRefBeforeUpdate := getMetricsRemoteWriteRef(lb)
@@ -202,7 +206,7 @@ func (l *LoadBalancer) createLoadBalancer(ctx context.Context, clusterName strin
202206
return nil, fmt.Errorf("reconcile metricsRemoteWrite: %w", err)
203207
}
204208

205-
spec, events, err := lbSpecFromService(service, nodes, l.networkID, metricsRemoteWrite)
209+
spec, events, err := lbSpecFromService(service, nodes, l.networkID, l.extraLabels, metricsRemoteWrite)
206210
if err != nil {
207211
return nil, fmt.Errorf("invalid load balancer specification: %w", err)
208212
}
@@ -234,7 +238,7 @@ func (l *LoadBalancer) createLoadBalancer(ctx context.Context, clusterName strin
234238
// It is not called on controller start-up. EnsureLoadBalancer must also ensure to update targets.
235239
func (l *LoadBalancer) UpdateLoadBalancer(ctx context.Context, clusterName string, service *corev1.Service, nodes []*corev1.Node) error {
236240
// only TargetPools are used from spec
237-
spec, events, err := lbSpecFromService(service, nodes, l.networkID, nil)
241+
spec, events, err := lbSpecFromService(service, nodes, l.networkID, l.extraLabels, nil)
238242
if err != nil {
239243
return fmt.Errorf("invalid service: %w", err)
240244
}

pkg/ccm/loadbalancer_spec.go

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/stackitcloud/stackit-sdk-go/core/utils"
1111
"github.com/stackitcloud/stackit-sdk-go/services/loadbalancer"
1212
corev1 "k8s.io/api/core/v1"
13+
"k8s.io/utils/ptr"
1314

1415
"github.com/stackitcloud/cloud-provider-stackit/pkg/cmp"
1516
)
@@ -244,6 +245,7 @@ func lbSpecFromService( //nolint:funlen,gocyclo // It is long but not complex.
244245
service *corev1.Service,
245246
nodes []*corev1.Node,
246247
networkID string,
248+
extraLabels map[string]string,
247249
observability *loadbalancer.LoadbalancerOptionObservability,
248250
) (*loadbalancer.CreateLoadBalancerPayload, []Event, error) {
249251
lb := &loadbalancer.CreateLoadBalancerPayload{
@@ -275,6 +277,14 @@ func lbSpecFromService( //nolint:funlen,gocyclo // It is long but not complex.
275277
}
276278
}
277279

280+
// Add extraLabels if set
281+
if extraLabels != nil {
282+
lb.Labels = ptr.To(extraLabels)
283+
}
284+
285+
// Add metric metricsRemoteWrite settings
286+
lb.Options.Observability = observability
287+
278288
events := make([]Event, 0)
279289

280290
// Parse private network from annotations.
@@ -342,9 +352,6 @@ func lbSpecFromService( //nolint:funlen,gocyclo // It is long but not complex.
342352
lb.ExternalAddress = &externalIP
343353
}
344354

345-
// Add metric metricsRemoteWrite settings
346-
lb.Options.Observability = observability
347-
348355
// Parse TCP idle timeout from annotations.
349356
// TODO: Split into separate function.
350357
tcpIdleTimeout := defaultTCPIdleTimeout
@@ -576,7 +583,8 @@ func checkUnsupportedAnnotations(service *corev1.Service) *Event {
576583
// resultImmutableChanged denotes that at least one property that cannot be changed did change.
577584
// Attempting an update will fail.
578585
type resultImmutableChanged struct {
579-
field string
586+
field string
587+
annotation string
580588
}
581589

582590
// compareLBwithSpec checks whether the load balancer fulfills the specification.
@@ -587,7 +595,7 @@ func compareLBwithSpec(lb *loadbalancer.LoadBalancer, spec *loadbalancer.CreateL
587595
fulfills = true
588596

589597
if cmp.UnpackPtr(cmp.UnpackPtr(lb.Options).PrivateNetworkOnly) != cmp.UnpackPtr(cmp.UnpackPtr(spec.Options).PrivateNetworkOnly) {
590-
return false, &resultImmutableChanged{field: ".options.privateNetworkOnly"}
598+
return false, &resultImmutableChanged{field: ".options.privateNetworkOnly", annotation: internalLBAnnotation}
591599
}
592600

593601
if !cmp.PtrValEqualFn(
@@ -620,7 +628,7 @@ func compareLBwithSpec(lb *loadbalancer.LoadBalancer, spec *loadbalancer.CreateL
620628
// lb.ExternalAddress is set to the ephemeral IP if the load balancer is ephemeral, while spec will never contain an ephemeral IP.
621629
// So we only compare them if the spec has a static IP.
622630
if !cmp.PtrValEqual(lb.ExternalAddress, spec.ExternalAddress) {
623-
return false, &resultImmutableChanged{field: ".externalAddress"}
631+
return false, &resultImmutableChanged{field: ".externalAddress", annotation: externalIPAnnotation}
624632
}
625633
if cmp.UnpackPtr(cmp.UnpackPtr(lb.Options).EphemeralAddress) {
626634
// Promote an ephemeral IP to a static IP.
@@ -629,7 +637,7 @@ func compareLBwithSpec(lb *loadbalancer.LoadBalancer, spec *loadbalancer.CreateL
629637
} else if !cmp.UnpackPtr(cmp.UnpackPtr(lb.Options).PrivateNetworkOnly) &&
630638
!cmp.UnpackPtr(cmp.UnpackPtr(lb.Options).EphemeralAddress) {
631639
// Demotion is not allowed by the load balancer API.
632-
return false, &resultImmutableChanged{field: ".options.ephemeralAddress"}
640+
return false, &resultImmutableChanged{field: ".options.ephemeralAddress", annotation: externalIPAnnotation}
633641
}
634642

635643
if cmp.LenSlicePtr(lb.Listeners) != cmp.LenSlicePtr(spec.Listeners) {
@@ -664,16 +672,16 @@ func compareLBwithSpec(lb *loadbalancer.LoadBalancer, spec *loadbalancer.CreateL
664672
}
665673

666674
if cmp.LenSlicePtr(lb.Networks) != cmp.LenSlicePtr(spec.Networks) {
667-
return false, &resultImmutableChanged{field: "len(.networks)"}
675+
return false, &resultImmutableChanged{field: "len(.networks)", annotation: listenerNetworkAnnotation}
668676
}
669677
if cmp.LenSlicePtr(lb.Networks) > 0 {
670678
for i, x := range *lb.Networks {
671679
y := (*spec.Networks)[i]
672680
if !cmp.PtrValEqual(x.NetworkId, y.NetworkId) {
673-
return false, &resultImmutableChanged{field: fmt.Sprintf(".networks[%d].networkId", i)}
681+
return false, &resultImmutableChanged{field: fmt.Sprintf(".networks[%d].networkId", i), annotation: listenerNetworkAnnotation}
674682
}
675683
if !cmp.PtrValEqual(x.Role, y.Role) {
676-
return false, &resultImmutableChanged{field: fmt.Sprintf(".networks[%d].role", i)}
684+
return false, &resultImmutableChanged{field: fmt.Sprintf(".networks[%d].role", i), annotation: listenerNetworkAnnotation}
677685
}
678686
}
679687
}

0 commit comments

Comments
 (0)